diff --git a/Aptos.Examples/Main.cs b/Aptos.Examples/Main.cs index 3071db8..cc34e16 100644 --- a/Aptos.Examples/Main.cs +++ b/Aptos.Examples/Main.cs @@ -7,7 +7,9 @@ public class RunExample { { "1", KeylessTransferExample.Run }, { "2", SimpleTransferExample.Run }, - { "3", PlaygroundExample.Run }, + { "3", SimpleTransferSingleKeyExample.Run }, + { "4", SimpleTransferMultiKeyExample.Run }, + { "5", PlaygroundExample.Run }, }; public static async Task Main() diff --git a/Aptos.Examples/SimpleTransferMultiKey.cs b/Aptos.Examples/SimpleTransferMultiKey.cs new file mode 100644 index 0000000..5713e2c --- /dev/null +++ b/Aptos.Examples/SimpleTransferMultiKey.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json; + +namespace Aptos.Examples; + +public class SimpleTransferMultiKeyExample +{ + public static async Task Run() + { + Console.WriteLine("=== Addresses ===\n"); + var aptos = new AptosClient(new AptosConfig(Networks.Devnet)); + var aliceSigner = Account.Generate(); + var bobSigner = SingleKeyAccount.Generate(); + var account = new MultiKeyAccount( + new([aliceSigner.PublicKey, bobSigner.PublicKey], 1), + [aliceSigner] + ); + Console.WriteLine($"Alice-Bob MultiKey (1/2): {account.Address}"); + + Console.WriteLine("\n=== Funding accounts ===\n"); + var aliceFundTxn = await aptos.Faucet.FundAccount(account.Address, 100_000_000); + Console.WriteLine($"Alice-Bob MultiKey (1/2)'s fund transaction: {aliceFundTxn.Hash}"); + + Console.WriteLine("\n=== Building transaction ===\n"); + + var txn = await aptos.Transaction.Build( + sender: account.Address, + data: new GenerateEntryFunctionPayloadData( + function: "0x1::coin::transfer", + typeArguments: ["0x1::aptos_coin::AptosCoin"], + functionArguments: [account.Address, "100000"] + ) + ); + Console.WriteLine($"{JsonConvert.SerializeObject(txn.RawTransaction)}"); + + Console.WriteLine("\n=== Signing and submitting transaction ===\n"); + + var pendingTxn = await aptos.Transaction.SignAndSubmitTransaction(account, txn); + Console.WriteLine($"Submitted transaction with hash: {pendingTxn.Hash}"); + + Console.WriteLine("Waiting for transaction..."); + var committedTxn = await aptos.Transaction.WaitForTransaction(pendingTxn.Hash.ToString()); + Console.WriteLine( + $"Transaction {committedTxn.Hash} is {(committedTxn.Success ? "success" : "failure")}" + ); + + Console.WriteLine("\n=== Account Balance ===\n"); + var balance = await aptos.Account.GetCoinBalance(account.Address, "0xa"); + Console.WriteLine($"Account {account.Address} has {balance?.Amount ?? 0} coin balances"); + } +} diff --git a/Aptos.Examples/SimpleTransferSingleKey.cs b/Aptos.Examples/SimpleTransferSingleKey.cs new file mode 100644 index 0000000..4bad861 --- /dev/null +++ b/Aptos.Examples/SimpleTransferSingleKey.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json; + +namespace Aptos.Examples; + +public class SimpleTransferSingleKeyExample +{ + public static async Task Run() + { + Console.WriteLine("=== Addresses ===\n"); + var aptos = new AptosClient(new AptosConfig(Networks.Devnet)); + var account = SingleKeyAccount.Generate(); + Console.WriteLine($"Alice: {account.Address}"); + + Console.WriteLine("\n=== Funding accounts ===\n"); + var aliceFundTxn = await aptos.Faucet.FundAccount(account.Address, 100_000_000); + Console.WriteLine($"Alice's fund transaction: {aliceFundTxn.Hash}"); + + Console.WriteLine("\n=== Building transaction ===\n"); + + var txn = await aptos.Transaction.Build( + sender: account.Address, + data: new GenerateEntryFunctionPayloadData( + function: "0x1::coin::transfer", + typeArguments: ["0x1::aptos_coin::AptosCoin"], + functionArguments: [account.Address, "100000"] + ) + ); + Console.WriteLine($"{JsonConvert.SerializeObject(txn.RawTransaction)}"); + + Console.WriteLine("\n=== Signing and submitting transaction ===\n"); + + var pendingTxn = await aptos.Transaction.SignAndSubmitTransaction(account, txn); + Console.WriteLine($"Submitted transaction with hash: {pendingTxn.Hash}"); + + Console.WriteLine("Waiting for transaction..."); + var committedTxn = await aptos.Transaction.WaitForTransaction(pendingTxn.Hash.ToString()); + Console.WriteLine( + $"Transaction {committedTxn.Hash} is {(committedTxn.Success ? "success" : "failure")}" + ); + + Console.WriteLine("\n=== Account Balance ===\n"); + var balance = await aptos.Account.GetCoinBalance(account.Address, "0xa"); + Console.WriteLine($"Account {account.Address} has {balance?.Amount ?? 0} coin balances"); + } +} diff --git a/Aptos.Tests/Aptos.Crypto/SingleKey.Tests.cs b/Aptos.Tests/Aptos.Crypto/SingleKey.Tests.cs new file mode 100644 index 0000000..1affe92 --- /dev/null +++ b/Aptos.Tests/Aptos.Crypto/SingleKey.Tests.cs @@ -0,0 +1,81 @@ +namespace Aptos.Tests.Core; + +using Xunit.Gherkin.Quick; + +[FeatureFile("../../../../features/single_key.feature")] +public sealed class SingleKeyFeatureTests : Feature +{ + private string? _publicKeyType; + + private dynamic? _inputValue; + + private dynamic? _output; + + [Given(@"(ed25519|secp256k1|keyless) (.*)")] + public void GivenValue(string type, string value) + { + PublicKey publicKey = type switch + { + "ed25519" => Ed25519PublicKey.Deserialize(new(value)), + "secp256k1" => Secp256k1PublicKey.Deserialize(new(value)), + "keyless" => KeylessPublicKey.Deserialize(new(value)), + _ => throw new ArgumentException("Invalid public key type"), + }; + _publicKeyType = type; + _inputValue = new SingleKey(publicKey); + } + + [When(@"I serialize")] + public void WhenISerialize() + { + if (_inputValue == null) + throw new ArgumentException("No input value"); + if (_inputValue is SingleKey singleKey) + _output = singleKey.BcsToHex(); + } + + [When(@"I derive authentication key")] + public void WhenIDeriveAuthenticationKey() + { + if (_inputValue == null) + throw new ArgumentException("No input value"); + if (_inputValue is SingleKey singleKey) + _output = singleKey.AuthKey().BcsToHex(); + } + + [When(@"I verify signature (.*) with message (.*)")] + public void WhenIVerifySignatureWithMessage(string signature, string message) + { + if (_inputValue == null) + throw new ArgumentException("No input value"); + + if (_inputValue is SingleKey singleKey) + { + PublicKeySignature publicKeySignature = _publicKeyType switch + { + "ed25519" => Ed25519Signature.Deserialize(new(signature)), + "secp256k1" => Secp256k1Signature.Deserialize(new(signature)), + "keyless" => KeylessSignature.Deserialize(new(signature)), + _ => throw new ArgumentException("Invalid signature public key type"), + }; + _output = singleKey.VerifySignature(message, publicKeySignature); + } + } + + [Then(@"the result should be (.*) (.*)")] + public void ThenTheResultShouldBeTypeValue(string type, string value) + { + if (_output == null) + throw new ArgumentException("No output value"); + + switch (type) + { + case "bool": + Assert.Equal(bool.Parse(value), _output); + break; + case "bcs": + Assert.Equal(Hex.FromHexString(value), _output); + break; + } + } +} diff --git a/Aptos/Aptos.Accounts/Account.cs b/Aptos/Aptos.Accounts/Account.cs index a342c4e..7c5f851 100644 --- a/Aptos/Aptos.Accounts/Account.cs +++ b/Aptos/Aptos.Accounts/Account.cs @@ -1,7 +1,7 @@ -namespace Aptos; - using Aptos.Schemes; +namespace Aptos; + /// /// Abstract class representing a signer account. /// @@ -10,7 +10,7 @@ public abstract class Account /// /// Gets the public key of the account. /// - public abstract AccountPublicKey PublicKey { get; } + public abstract IVerifyingKey VerifyingKey { get; } /// /// Gets the address of the account. @@ -25,6 +25,14 @@ public abstract class Account /// public Signature Sign(string message) => Sign(SigningMessage.Convert(message)); + /// + /// Signs a transaction using the account's private key. + /// + /// The transaction to sign. + /// The signed transaction. + public virtual Signature Sign(AnyRawTransaction transaction) => + Sign(SigningMessage.GenerateForTransaction(transaction)); + /// /// Signs a message with the using the signer. /// @@ -32,32 +40,24 @@ public abstract class Account /// The signed message. public abstract Signature Sign(byte[] message); - /// - /// Signs a transaction using the account's private key. - /// - /// The transaction to sign. - /// The signature of the transaction. - public abstract Signature SignTransaction(AnyRawTransaction transaction); - /// public AccountAuthenticator SignWithAuthenticator(string message) => SignWithAuthenticator(SigningMessage.Convert(message)); /// - /// Signs a message and returns an authenticator for the account. + /// Signs a transaction and returns an authenticator for the account. /// - /// The message to sign as a byte array. + /// The transaction to sign. /// The authenticator containing the signature. - public abstract AccountAuthenticator SignWithAuthenticator(byte[] message); + public virtual AccountAuthenticator SignWithAuthenticator(AnyRawTransaction transaction) => + SignWithAuthenticator(SigningMessage.GenerateForTransaction(transaction)); /// - /// Signs a transaction and returns an authenticator for the account. + /// Signs a message and returns an authenticator for the account. /// - /// The transaction to sign. + /// The message to sign as a byte array. /// The authenticator containing the signature. - public abstract AccountAuthenticator SignTransactionWithAuthenticator( - AnyRawTransaction transaction - ); + public abstract AccountAuthenticator SignWithAuthenticator(byte[] message); /// /// Generates a new Ed25519 account. diff --git a/Aptos/Aptos.Accounts/Ed25519Account.cs b/Aptos/Aptos.Accounts/Ed25519Account.cs index ed6964c..2490b76 100644 --- a/Aptos/Aptos.Accounts/Ed25519Account.cs +++ b/Aptos/Aptos.Accounts/Ed25519Account.cs @@ -12,12 +12,17 @@ public class Ed25519Account : Account /// public readonly Ed25519PrivateKey PrivateKey; - private readonly Ed25519PublicKey _publicKey; + private readonly Ed25519PublicKey _verifyingKey; /// /// Gets the Ed25519PublicKey for the account. /// - public override AccountPublicKey PublicKey => _publicKey; + public override IVerifyingKey VerifyingKey => _verifyingKey; + + /// + /// Gets the Ed25519PublicKey for the account. + /// + public PublicKey PublicKey => _verifyingKey; private readonly AccountAddress _address; @@ -50,8 +55,8 @@ public Ed25519Account(Ed25519PrivateKey privateKey, byte[]? address = null) /// The account address. public Ed25519Account(Ed25519PrivateKey privateKey, AccountAddress? address = null) { - _publicKey = (Ed25519PublicKey)privateKey.PublicKey(); - _address = address ?? PublicKey.AuthKey().DerivedAddress(); + _verifyingKey = (Ed25519PublicKey)privateKey.PublicKey(); + _address = address ?? _verifyingKey.AuthKey().DerivedAddress(); PrivateKey = privateKey; } @@ -62,15 +67,7 @@ public Ed25519Account(Ed25519PrivateKey privateKey, AccountAddress? address = nu /// The signed message to verify. /// True if the signature is valid; otherwise, false. public bool VerifySignature(byte[] message, Ed25519Signature signature) => - PublicKey.VerifySignature(message, signature); - - /// - /// Signs a transaction using the account's private key. - /// - /// The transaction to sign. - /// The transaction signature. - public override Signature SignTransaction(AnyRawTransaction transaction) => - Sign(SigningMessage.GenerateForTransaction(transaction)); + _verifyingKey.VerifySignature(message, signature); /// /// Signs a message with the using the account's private key. @@ -85,17 +82,7 @@ public override Signature SignTransaction(AnyRawTransaction transaction) => /// The message to sign as a byte array. /// The authenticator containing the signature. public override AccountAuthenticator SignWithAuthenticator(byte[] message) => - new AccountAuthenticatorEd25519(_publicKey, (Ed25519Signature)PrivateKey.Sign(message)); - - /// - /// Signs a transaction and returns an authenticator with the signature. - /// - /// The transaction to sign. - /// The authenticator containing the signature. - public override AccountAuthenticator SignTransactionWithAuthenticator( - AnyRawTransaction transaction - ) => - new AccountAuthenticatorEd25519(_publicKey, (Ed25519Signature)SignTransaction(transaction)); + new AccountAuthenticatorEd25519(_verifyingKey, (Ed25519Signature)Sign(message)); /// /// Generates a new Ed25519 account. diff --git a/Aptos/Aptos.Accounts/EphemeralKeyPair.cs b/Aptos/Aptos.Accounts/EphemeralKeyPair.cs index 06129e2..440e91a 100644 --- a/Aptos/Aptos.Accounts/EphemeralKeyPair.cs +++ b/Aptos/Aptos.Accounts/EphemeralKeyPair.cs @@ -46,14 +46,7 @@ public EphemeralKeyPair( ) { _privateKey = privateKey; - if (privateKey.PublicKey() is LegacyAccountPublicKey publicKey) - { - PublicKey = new EphemeralPublicKey(publicKey); - } - else - throw new ArgumentException( - "Invalid PrivateKey passed to EphemeralKeyPair. Expected LegacyAccountPublicKey." - ); + PublicKey = new EphemeralPublicKey(privateKey.PublicKey()); // By default, the expiry timestamp is 14 days from now. ExpiryTimestamp = @@ -78,13 +71,7 @@ public EphemeralSignature Sign(byte[] data) if (IsExpired()) throw new Exception("EphemeralKeyPair is expired"); var signature = _privateKey.Sign(data); - if (signature is LegacySignature legacySignature) - { - return new EphemeralSignature(legacySignature); - } - throw new ArgumentException( - "Invalid PrivateKey passed to EphemeralKeyPair. Expecting a legacy private key." - ); + return new EphemeralSignature(signature); } public override void Serialize(Serializer s) diff --git a/Aptos/Aptos.Accounts/KeylessAccount.cs b/Aptos/Aptos.Accounts/KeylessAccount.cs index 485b34a..d0f5ee3 100644 --- a/Aptos/Aptos.Accounts/KeylessAccount.cs +++ b/Aptos/Aptos.Accounts/KeylessAccount.cs @@ -10,12 +10,12 @@ public class KeylessAccount : Account public static readonly string DOMAIN_SEPARATOR = "APTOS::TransactionAndProof"; - private readonly KeylessPublicKey _publicKey; + private readonly SingleKey _verifyingKey; /// - /// Gets the KeylessPublicKey for the account. + /// Gets the KeylessPublicKey inside a SingleKey for the account. /// - public override AccountPublicKey PublicKey => _publicKey; + public override IVerifyingKey VerifyingKey => _verifyingKey; private readonly AccountAddress _address; @@ -52,8 +52,8 @@ public KeylessAccount( if (pepper.Length != PEPPER_LENGTH) throw new ArgumentException($"Pepper length in bytes should be {PEPPER_LENGTH}"); - _publicKey = KeylessPublicKey.FromJwt(jwt, pepper, uidKey); - _address = address ?? _publicKey.AuthKey().DerivedAddress(); + _verifyingKey = new SingleKey(KeylessPublicKey.FromJwt(jwt, pepper, uidKey)); + _address = address ?? _verifyingKey.AuthKey().DerivedAddress(); EphemeralKeyPair = ekp; Proof = proof; Pepper = pepper; @@ -73,7 +73,7 @@ public bool VerifySignature(byte[] message, KeylessSignature signature) return EphemeralKeyPair.PublicKey.VerifySignature(message, signature.EphemeralSignature); } - public override Signature SignTransaction(AnyRawTransaction transaction) + public override Signature Sign(AnyRawTransaction transaction) { RawTransaction rawTxn = transaction.RawTransaction; Serializer s = new(); @@ -101,16 +101,15 @@ public override Signature Sign(byte[] message) public override AccountAuthenticator SignWithAuthenticator(byte[] message) => new AccountAuthenticatorSingleKey( - new AnyPublicKey(_publicKey), - new AnySignature(Sign(message)) + _verifyingKey.PublicKey, + (PublicKeySignature)Sign(message) ); - public override AccountAuthenticator SignTransactionWithAuthenticator( - AnyRawTransaction transaction - ) => + public override AccountAuthenticator SignWithAuthenticator(AnyRawTransaction transaction) => new AccountAuthenticatorSingleKey( - new AnyPublicKey(_publicKey), - new AnySignature(SignTransaction(transaction)) + _verifyingKey.PublicKey, + // Have to declare explictly because the virtual method signs without proof + (PublicKeySignature)Sign(transaction) ); public void Serialize(Serializer s) diff --git a/Aptos/Aptos.Accounts/MultiKeyAccount.cs b/Aptos/Aptos.Accounts/MultiKeyAccount.cs index 6256af0..61ff908 100644 --- a/Aptos/Aptos.Accounts/MultiKeyAccount.cs +++ b/Aptos/Aptos.Accounts/MultiKeyAccount.cs @@ -10,17 +10,17 @@ namespace Aptos; /// public class MultiKeyAccount : Account { - private readonly MultiKey _publicKey; + private readonly MultiKey _verifyingKey; /// /// Gets the MultiKeyPublicKey for the account. /// - public override AccountPublicKey PublicKey => _publicKey; + public override IVerifyingKey VerifyingKey => _verifyingKey; /// /// Gets the address of the account. /// - public override AccountAddress Address => PublicKey.AuthKey().DerivedAddress(); + public override AccountAddress Address => _verifyingKey.AuthKey().DerivedAddress(); /// /// The signers used to sign messages. These signers should correspond to public keys in the @@ -52,20 +52,29 @@ public class MultiKeyAccount : Account /// If the signers do not correspond to public keys in the MultiKeyAccount's public key. public MultiKeyAccount(MultiKey multiKey, List signers) { - _publicKey = multiKey; + _verifyingKey = multiKey; // Get the index of each respective signer in the bitmpa List bitPositions = []; for (int i = 0; i < signers.Count; i++) { var signer = signers[i]; - bitPositions.Add(multiKey.GetIndex(signer.PublicKey)); + if (signer.VerifyingKey is SingleKey singleKey) + { + bitPositions.Add(multiKey.GetIndex(singleKey.PublicKey)); + } + else if (signer.VerifyingKey is Ed25519PublicKey ed25519PublicKey) + { + bitPositions.Add(multiKey.GetIndex(ed25519PublicKey)); + } + else + { + throw new ArgumentException( + "MultiKeyAccount cannot be used with unified verifying keys (e.g. MultiKeyPublicKey)" + ); + } } - if (signers.Any(s => s.PublicKey is UnifiedAccountPublicKey)) - throw new ArgumentException( - "MultiKeyAccount cannot be used with unified account public keys (e.g. AnyPublicKey or MultiKeyPublicKey)" - ); if (multiKey.SignaturesRequired > signers.Count) throw new ArgumentException( $"Signatures required must be less than or equal to the number of signers" @@ -101,7 +110,7 @@ public bool VerifySignature(byte[] message, MultiKeySignature signature) for (int i = 0; i < signature.Signatures.Count; i++) { Signature singleSignature = signature.Signatures[i]; - PublicKey singlePublicKey = _publicKey.PublicKeys[SignerIndicies[i]]; + PublicKey singlePublicKey = _verifyingKey.PublicKeys[SignerIndicies[i]]; if (!singlePublicKey.VerifySignature(message, singleSignature)) return false; } @@ -116,18 +125,15 @@ public bool VerifySignature(byte[] message, MultiKeySignature signature) /// The signed message. public override Signature Sign(byte[] message) => new MultiKeySignature( - Signers.Select(s => s.Sign(message)).ToList(), - MultiKey.CreateBitmap(SignerIndicies) - ); - - /// - /// Signs a transaction using the signer. - /// - /// The transaction to sign. - /// The transaction signature. - public override Signature SignTransaction(AnyRawTransaction transaction) => - new MultiKeySignature( - Signers.Select(s => s.SignTransaction(transaction)).ToList(), + Signers + .Select(s => + s.Sign(message) is PublicKeySignature publicKeySignature + ? publicKeySignature + : throw new Exception( + "MultiKeyAccount cannot be used with unified accounts (e.g. MultiKeyAccount)" + ) + ) + .ToList(), MultiKey.CreateBitmap(SignerIndicies) ); @@ -137,18 +143,5 @@ public override Signature SignTransaction(AnyRawTransaction transaction) => /// The message to sign as a byte array. /// The authenticator containing the signature. public override AccountAuthenticator SignWithAuthenticator(byte[] message) => - new AccountAuthenticatorMultiKey(_publicKey, (MultiKeySignature)Sign(message)); - - /// - /// Signs a message and returns an authenticator with the signature. - /// - /// The message to sign as a byte array. - /// The authenticator containing the signature. - public override AccountAuthenticator SignTransactionWithAuthenticator( - AnyRawTransaction transaction - ) => - new AccountAuthenticatorMultiKey( - _publicKey, - (MultiKeySignature)SignTransaction(transaction) - ); + new AccountAuthenticatorMultiKey(_verifyingKey, (MultiKeySignature)Sign(message)); } diff --git a/Aptos/Aptos.Accounts/SingleKeyAccount.cs b/Aptos/Aptos.Accounts/SingleKeyAccount.cs index dae6743..bb0146c 100644 --- a/Aptos/Aptos.Accounts/SingleKeyAccount.cs +++ b/Aptos/Aptos.Accounts/SingleKeyAccount.cs @@ -1,13 +1,15 @@ -namespace Aptos; - using Aptos.Schemes; +namespace Aptos; + public class SingleKeyAccount : Account { public readonly PrivateKey PrivateKey; - private readonly AnyPublicKey _publicKey; - public override AccountPublicKey PublicKey => _publicKey; + private readonly SingleKey _verifyingKey; + public override IVerifyingKey VerifyingKey => _verifyingKey; + + public PublicKey PublicKey => _verifyingKey.PublicKey; private readonly AccountAddress _address; public override AccountAddress Address => _address; @@ -25,32 +27,26 @@ public SingleKeyAccount(PrivateKey privateKey, string? address = null) public SingleKeyAccount(PrivateKey privateKey, AccountAddress? address = null) { - _publicKey = new AnyPublicKey(privateKey.PublicKey()); - _address = address ?? _publicKey.AuthKey().DerivedAddress(); + _verifyingKey = new SingleKey(privateKey.PublicKey()); + _address = address ?? _verifyingKey.AuthKey().DerivedAddress(); PrivateKey = privateKey; } - public bool VerifySignature(string message, AnySignature signature) => - PublicKey.VerifySignature(message, signature); - - public bool VerifySignature(byte[] message, AnySignature signature) => - PublicKey.VerifySignature(message, signature); + public bool VerifySignature(string message, Signature signature) => + _verifyingKey.VerifySignature(message, signature); - public override Signature SignTransaction(AnyRawTransaction transaction) => - Sign(SigningMessage.GenerateForTransaction(transaction)); + public bool VerifySignature(byte[] message, Signature signature) => + _verifyingKey.VerifySignature(message, signature); - public override Signature Sign(byte[] message) => new AnySignature(PrivateKey.Sign(message)); + public override Signature Sign(byte[] message) => PrivateKey.Sign(message); public override AccountAuthenticator SignWithAuthenticator(byte[] message) => - new AccountAuthenticatorSingleKey(_publicKey, (AnySignature)Sign(message)); - - public override AccountAuthenticator SignTransactionWithAuthenticator( - AnyRawTransaction transaction - ) => new AccountAuthenticatorSingleKey(_publicKey, (AnySignature)SignTransaction(transaction)); - - public static new SingleKeyAccount Generate() => Generate(PublicKeyVariant.Ed25519); + new AccountAuthenticatorSingleKey( + _verifyingKey.PublicKey, + (PublicKeySignature)Sign(message) + ); - public static SingleKeyAccount Generate(PublicKeyVariant scheme) + public static SingleKeyAccount Generate(PublicKeyVariant scheme = PublicKeyVariant.Ed25519) { PrivateKey privateKey = scheme switch { diff --git a/Aptos/Aptos.Api/AccountSignature.cs b/Aptos/Aptos.Api/AccountSignature.cs index 9b4f2ca..23ab12f 100644 --- a/Aptos/Aptos.Api/AccountSignature.cs +++ b/Aptos/Aptos.Api/AccountSignature.cs @@ -65,38 +65,36 @@ public class AccountEd25519Signature(Hex publicKey, Hex signature) public Hex Signature = signature; } -public class AccountSingleKeySignature(ILegacyPublicKey publicKey, LegacySignature signature) +public class AccountSingleKeySignature(PublicKey publicKey, PublicKeySignature signature) : AccountSignature(SigningScheme.SingleKey) { [JsonProperty("public_key")] - public ILegacyPublicKey PublicKey = publicKey; + public PublicKey PublicKey = publicKey; [JsonProperty("signature")] - public LegacySignature Signature = signature; + public PublicKeySignature Signature = signature; } public class AccountMultiKeySignature( - List publicKeys, - List signatures, + List publicKeys, + List signatures, byte signaturesRequired ) : AccountSignature(SigningScheme.MultiKey) { - public class IndexedAccountSignature(byte index, LegacySignature signature) + public class IndexedAccountSignature(byte index, PublicKeySignature signature) { [JsonProperty("index")] public byte Index = index; [JsonProperty("signature")] - public LegacySignature Signature = signature; + public PublicKeySignature Signature = signature; } [JsonProperty("public_keys")] - public List PublicKeys = publicKeys; + public List PublicKeys = publicKeys; [JsonProperty("signatures")] - public List Signatures = signatures - .Select((sig, i) => new IndexedAccountSignature((byte)i, sig)) - .ToList(); + public List Signatures = signatures; [JsonProperty("signatures_required")] public byte SignaturesRequired = signaturesRequired; diff --git a/Aptos/Aptos.Api/TransactionSignature.cs b/Aptos/Aptos.Api/TransactionSignature.cs index a9edcdc..dd5e5a0 100644 --- a/Aptos/Aptos.Api/TransactionSignature.cs +++ b/Aptos/Aptos.Api/TransactionSignature.cs @@ -92,6 +92,21 @@ JsonSerializer serializer ); } } + else if (jsonObject.ContainsKey("public_keys")) + { + var publicKeys = jsonObject["public_keys"]?.ToString(); + var signatures = jsonObject["signatures"]?.ToString(); + var signaturesRequired = jsonObject["signatures_required"]?.ToString(); + + // AccountSignature_MultiKeySignature + if (publicKeys != null && signatures != null && signaturesRequired != null) + { + jsonObject["type"] = "multi_key_signature"; + signature = JsonConvert.DeserializeObject( + jsonObject.ToString() + ); + } + } if (signature == null) throw new Exception("Invalid account signature"); diff --git a/Aptos/Aptos.Clients/KeylessClient.cs b/Aptos/Aptos.Clients/KeylessClient.cs index c562a3e..32f57bf 100644 --- a/Aptos/Aptos.Clients/KeylessClient.cs +++ b/Aptos/Aptos.Clients/KeylessClient.cs @@ -20,8 +20,9 @@ public async Task DeriveAccount( // Derive the keyless account from the JWT and EphemeralKeyPair var publicKey = KeylessPublicKey.FromJwt(jwt, pepper, uidKey); + var address = await _client.Account.LookupOriginalAccountAddress( - publicKey.AuthKey().DerivedAddress().ToString() + new SingleKey(publicKey).AuthKey().DerivedAddress().ToString() ); // Create and return the keyless account diff --git a/Aptos/Aptos.Clients/TransactionClient.cs b/Aptos/Aptos.Clients/TransactionClient.cs index 7509dbc..f514e49 100644 --- a/Aptos/Aptos.Clients/TransactionClient.cs +++ b/Aptos/Aptos.Clients/TransactionClient.cs @@ -16,7 +16,7 @@ public class TransactionClient(AptosClient client) /// The transaction to sign. /// The authenticator with the signed transaction and public key. public AccountAuthenticator SignTransaction(Account signer, AnyRawTransaction transaction) => - signer.SignTransactionWithAuthenticator(transaction); + signer.SignWithAuthenticator(transaction); /// /// Submits a transaction to the blockchain. diff --git a/Aptos/Aptos.Crypto/AccountPublicKey.cs b/Aptos/Aptos.Crypto/AccountPublicKey.cs deleted file mode 100644 index eea876a..0000000 --- a/Aptos/Aptos.Crypto/AccountPublicKey.cs +++ /dev/null @@ -1,106 +0,0 @@ -namespace Aptos; - -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; - -public abstract class AccountPublicKey : PublicKey -{ - public abstract AuthenticationKey AuthKey(); -} - -[JsonConverter(typeof(StringEnumConverter))] -public enum PublicKeyVariant : uint -{ - [EnumMember(Value = "ed25519")] - Ed25519, - - [EnumMember(Value = "secp256k1_ecdsa")] - Secp256k1Ecdsa, - - [EnumMember(Value = "secp256r1_ecdsa")] - Secp256r1Ecdsa, - - [EnumMember(Value = "keyless")] - Keyless, -} - -[JsonConverter(typeof(LegacyPublicKeyConverter))] -public interface ILegacyPublicKey -{ - [JsonProperty("type")] - public PublicKeyVariant Type { get; } - - [JsonProperty("value")] - public Hex Value { get; } -} - -public abstract class LegacyPublicKey(PublicKeyVariant type) : PublicKey, ILegacyPublicKey -{ - private readonly PublicKeyVariant _type = type; - public PublicKeyVariant Type => _type; - - public abstract Hex Value { get; } -} - -public class LegacyPublicKeyConverter : JsonConverter -{ - public override ILegacyPublicKey? ReadJson( - JsonReader reader, - Type objectType, - ILegacyPublicKey? existingValue, - bool hasExistingValue, - JsonSerializer serializer - ) - { - var jsonObject = JObject.Load(reader); - var type = jsonObject["type"]?.ToString(); - - AnyValue? anyValue = JsonConvert.DeserializeObject(jsonObject.ToString()); - if (anyValue == null) - throw new Exception("Invalid public key shape"); - - Deserializer deserializer = new(anyValue.Value); - deserializer.Uleb128AsU32(); - - return type switch - { - "ed25519" => new Ed25519PublicKey(anyValue.Value), - "secp256k1_ecdsa" => new Secp256k1PublicKey(anyValue.Value), - "keyless" => KeylessPublicKey.Deserialize(new Deserializer(anyValue.Value)), - _ => throw new Exception($"Unknown public key type: {type}"), - }; - } - - public override void WriteJson( - JsonWriter writer, - ILegacyPublicKey? value, - JsonSerializer serializer - ) - { - if (value == null) - writer.WriteNull(); - else - { - writer.WriteStartObject(); - writer.WritePropertyName("type"); - writer.WriteValue(JsonConvert.SerializeObject(value.Type).Replace("\"", "")); - writer.WritePropertyName("value"); - writer.WriteValue(value.Value.ToString()); - writer.WriteEndObject(); - } - } -} - -public abstract class UnifiedAccountPublicKey : AccountPublicKey { } - -public abstract class LegacyAccountPublicKey(PublicKeyVariant type) - : AccountPublicKey, - ILegacyPublicKey -{ - private readonly PublicKeyVariant _type = type; - public PublicKeyVariant Type => _type; - - public abstract Hex Value { get; } -} diff --git a/Aptos/Aptos.Crypto/Ed25519.cs b/Aptos/Aptos.Crypto/Ed25519.cs index 5a5286c..14ff7a9 100644 --- a/Aptos/Aptos.Crypto/Ed25519.cs +++ b/Aptos/Aptos.Crypto/Ed25519.cs @@ -62,7 +62,7 @@ public static bool IsCanonicalEd25519Signature(Signature signature) } } -public class Ed25519PublicKey : LegacyAccountPublicKey +public class Ed25519PublicKey : PublicKey, IVerifyingKey { static readonly int LENGTH = 32; @@ -83,7 +83,7 @@ public Ed25519PublicKey(byte[] publicKey) public override byte[] ToByteArray() => _key.ToByteArray(); - public override AuthenticationKey AuthKey() => + public AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.Ed25519, _key.ToByteArray()); public override bool VerifySignature(byte[] message, Signature signature) @@ -102,7 +102,7 @@ public override bool VerifySignature(byte[] message, Signature signature) public override void Serialize(Serializer s) => s.Bytes(_key.ToByteArray()); - public static Ed25519PublicKey Deserialize(Deserializer d) => new(d.Bytes()); + public static new Ed25519PublicKey Deserialize(Deserializer d) => new(d.Bytes()); } public class Ed25519PrivateKey : PrivateKey @@ -129,9 +129,7 @@ public override PublicKey PublicKey() => new Ed25519PrivateKeyParameters(_key.ToByteArray(), 0).GeneratePublicKey().GetEncoded() ); - public override Signature Sign(string message) => Sign(SigningMessage.Convert(message)); - - public override Signature Sign(byte[] message) + public override PublicKeySignature Sign(byte[] message) { Ed25519Signer signer = new(); signer.Init(true, new Ed25519PrivateKeyParameters(_key.ToByteArray(), 0)); @@ -174,7 +172,7 @@ public static Ed25519PrivateKey FromDerivationPath(string path, string mnemonic) public static Ed25519PrivateKey Deserialize(Deserializer d) => new(d.Bytes()); } -public class Ed25519Signature : LegacySignature +public class Ed25519Signature : PublicKeySignature { static readonly int LENGTH = 64; @@ -186,7 +184,7 @@ public Ed25519Signature(string signature) : this(Hex.FromHexInput(signature).ToByteArray()) { } public Ed25519Signature(byte[] signature) - : base(SignatureVariant.Ed25519) + : base(PublicKeySignatureVariant.Ed25519) { if (signature.Length != LENGTH) throw new KeyLengthMismatch("Ed25519Signature", LENGTH); @@ -197,5 +195,5 @@ public Ed25519Signature(byte[] signature) public override void Serialize(Serializer s) => s.Bytes(_value.ToByteArray()); - public static Ed25519Signature Deserialize(Deserializer d) => new(d.Bytes()); + public static new Ed25519Signature Deserialize(Deserializer d) => new(d.Bytes()); } diff --git a/Aptos/Aptos.Crypto/Ephemeral.cs b/Aptos/Aptos.Crypto/Ephemeral.cs index 4070b36..5226b2f 100644 --- a/Aptos/Aptos.Crypto/Ephemeral.cs +++ b/Aptos/Aptos.Crypto/Ephemeral.cs @@ -2,13 +2,13 @@ namespace Aptos; using Aptos.Exceptions; -public class EphemeralPublicKey : LegacyPublicKey +public class EphemeralPublicKey : PublicKey { - public readonly LegacyAccountPublicKey PublicKey; + public readonly PublicKey PublicKey; public override Hex Value => PublicKey.BcsToHex(); - public EphemeralPublicKey(LegacyAccountPublicKey publicKey) + public EphemeralPublicKey(PublicKey publicKey) : base(publicKey.Type) { PublicKey = publicKey; @@ -34,7 +34,7 @@ public override void Serialize(Serializer s) PublicKey.Serialize(s); } - public static EphemeralPublicKey Deserialize(Deserializer d) + public static new EphemeralPublicKey Deserialize(Deserializer d) { PublicKeyVariant variant = (PublicKeyVariant)d.Uleb128AsU32(); return variant switch @@ -45,18 +45,19 @@ public static EphemeralPublicKey Deserialize(Deserializer d) } } -public class EphemeralSignature : Signature +public class EphemeralSignature : PublicKeySignature { - public readonly Signature Signature; + public readonly PublicKeySignature Signature; - public readonly SignatureVariant Type; + public override Hex Value => Signature.Value; - public EphemeralSignature(LegacySignature signature) + public EphemeralSignature(PublicKeySignature signature) + : base(signature.Type) { Signature = signature; switch (signature.Type) { - case SignatureVariant.Ed25519: + case PublicKeySignatureVariant.Ed25519: break; default: throw new EphemeralSignatureVariantUnsupported(signature.Type); @@ -71,13 +72,107 @@ public override void Serialize(Serializer s) Signature.Serialize(s); } - public static EphemeralSignature Deserialize(Deserializer d) + public static new EphemeralSignature Deserialize(Deserializer d) { - SignatureVariant variant = (SignatureVariant)d.Uleb128AsU32(); + PublicKeySignatureVariant variant = (PublicKeySignatureVariant)d.Uleb128AsU32(); return variant switch { - SignatureVariant.Ed25519 => new EphemeralSignature(Ed25519Signature.Deserialize(d)), + PublicKeySignatureVariant.Ed25519 => new EphemeralSignature( + Ed25519Signature.Deserialize(d) + ), _ => throw new EphemeralSignatureVariantUnsupported(variant), }; } } + +public enum EphemeralSignatureVariant : uint +{ + ZkProof = 0, +} + +public abstract class CertificateSignature : Serializable +{ + public abstract byte[] ToByteArray(); + + public override string ToString() => Hex.FromHexInput(ToByteArray()).ToString(); +} + +public class EphemeralCertificate : Serializable +{ + public readonly CertificateSignature Signature; + + public readonly EphemeralSignatureVariant Variant; + + public EphemeralCertificate(CertificateSignature signature, EphemeralSignatureVariant variant) + { + Signature = signature; + Variant = variant; + } + + public byte[] ToByteArray() => Signature.ToByteArray(); + + public override void Serialize(Serializer s) + { + s.U32AsUleb128((uint)Variant); + Signature.Serialize(s); + } + + public static EphemeralCertificate Deserialize(Deserializer d) + { + EphemeralSignatureVariant variant = (EphemeralSignatureVariant)d.Uleb128AsU32(); + return variant switch + { + EphemeralSignatureVariant.ZkProof => new EphemeralCertificate( + ZeroKnowledgeSignature.Deserialize(d), + EphemeralSignatureVariant.ZkProof + ), + _ => throw new ArgumentException("Invalid signature variant"), + }; + } +} + +public class ZeroKnowledgeSignature( + ZkProof proof, + ulong expHorizonSecs, + string? extraField = null, + string? overrideAudVal = null, + EphemeralSignature? trainingWheelSignature = null +) : CertificateSignature +{ + public readonly ZkProof Proof = proof; + + public readonly ulong ExpHorizonSecs = expHorizonSecs; + + public readonly string? ExtraField = extraField; + + public readonly string? OverrideAudVal = overrideAudVal; + + public readonly EphemeralSignature? TrainingWheelSignature = trainingWheelSignature; + + public override byte[] ToByteArray() => BcsToBytes(); + + public override void Serialize(Serializer s) + { + Proof.Serialize(s); + s.U64(ExpHorizonSecs); + s.OptionString(ExtraField); + s.OptionString(OverrideAudVal); + s.Option(TrainingWheelSignature); + } + + public static ZeroKnowledgeSignature Deserialize(Deserializer d) + { + ZkProof proof = ZkProof.Deserialize(d); + ulong expHorizonSecs = d.U64(); + string? extraField = d.OptionString(); + string? overrideAudVal = d.OptionString(); + EphemeralSignature? trainingWheelSignature = d.Option(EphemeralSignature.Deserialize); + return new ZeroKnowledgeSignature( + proof, + expHorizonSecs, + extraField, + overrideAudVal, + trainingWheelSignature + ); + } +} diff --git a/Aptos/Aptos.Crypto/IVerifyingKey.cs b/Aptos/Aptos.Crypto/IVerifyingKey.cs new file mode 100644 index 0000000..42e9ce8 --- /dev/null +++ b/Aptos/Aptos.Crypto/IVerifyingKey.cs @@ -0,0 +1,15 @@ +namespace Aptos; + +/// +/// A verifying key represents a collection of public keys that can be used to verify signatures +/// or derive authentication keys. This interface is typically implemented to collect public keys +/// for Account Authenticators. +/// +public interface IVerifyingKey +{ + public AuthenticationKey AuthKey(); + + public bool VerifySignature(string message, Signature signature); + + public bool VerifySignature(byte[] message, Signature signature); +} diff --git a/Aptos/Aptos.Crypto/Keyless.cs b/Aptos/Aptos.Crypto/Keyless.cs index e53c4d2..c800ac8 100644 --- a/Aptos/Aptos.Crypto/Keyless.cs +++ b/Aptos/Aptos.Crypto/Keyless.cs @@ -2,7 +2,6 @@ namespace Aptos; using System.Numerics; using Aptos.Poseidon; -using Aptos.Schemes; using Microsoft.IdentityModel.JsonWebTokens; public static class Keyless @@ -54,9 +53,27 @@ byte[] pepper KeylessPublicKey.ID_COMMITMENT_LENGTH ); } + + public static KeylessSignature GetSimulationSignature() => + new( + ephemeralCertificate: new EphemeralCertificate( + new ZeroKnowledgeSignature( + proof: new ZkProof( + new Groth16Zkp(new byte[32], new byte[64], new byte[32]), + ZkpVariant.Groth16 + ), + expHorizonSecs: 0 + ), + variant: EphemeralSignatureVariant.ZkProof + ), + jwtHeader: "{}", + expiryDateSecs: 0, + ephemeralPublicKey: new EphemeralPublicKey(new Ed25519PublicKey(new byte[32])), + ephemeralSignature: new EphemeralSignature(new Ed25519Signature(new byte[64])) + ); } -public class KeylessPublicKey : LegacyAccountPublicKey +public class KeylessPublicKey : PublicKey { public static readonly int ID_COMMITMENT_LENGTH = 32; @@ -84,16 +101,12 @@ public KeylessPublicKey(string iss, byte[] idCommitment) IdCommitment = idCommitment; } - public override AuthenticationKey AuthKey() - { - Serializer s = new(); - s.U32AsUleb128((uint)Type); - s.FixedBytes(BcsToBytes()); - return AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.SingleKey, s.ToBytes()); - } - public override bool VerifySignature(byte[] message, Signature signature) => - throw new NotImplementedException(); + signature is KeylessSignature keylessSignature + && keylessSignature.EphemeralPublicKey.VerifySignature( + message, + keylessSignature.EphemeralSignature.Signature + ); public override byte[] ToByteArray() => BcsToBytes(); @@ -103,7 +116,7 @@ public override void Serialize(Serializer s) s.Bytes(IdCommitment); } - public static KeylessPublicKey Deserialize(Deserializer d) + public static new KeylessPublicKey Deserialize(Deserializer d) { string iss = d.String(); byte[] idCommitment = d.Bytes(); @@ -117,7 +130,7 @@ public static KeylessPublicKey FromJwt(string jwt, byte[] pepper, string uidKey new(new JsonWebToken(jwt).Issuer, Keyless.ComputeIdCommitment(jwt, pepper, uidKey)); } -public class KeylessSignature : LegacySignature +public class KeylessSignature : PublicKeySignature { public readonly EphemeralCertificate EphemeralCertificate; @@ -138,7 +151,7 @@ public KeylessSignature( EphemeralPublicKey ephemeralPublicKey, EphemeralSignature ephemeralSignature ) - : base(SignatureVariant.Keyless) + : base(PublicKeySignatureVariant.Keyless) { EphemeralCertificate = ephemeralCertificate; JwtHeader = jwtHeader; @@ -158,7 +171,7 @@ public override void Serialize(Serializer s) EphemeralSignature.Serialize(s); } - public static KeylessSignature Deserialize(Deserializer d) + public static new KeylessSignature Deserialize(Deserializer d) { EphemeralCertificate ephemeralCertificate = EphemeralCertificate.Deserialize(d); string jwtHeader = d.String(); @@ -174,88 +187,3 @@ public static KeylessSignature Deserialize(Deserializer d) ); } } - -public enum EphemeralSignatureVariant : uint -{ - ZkProof = 0, -} - -public class EphemeralCertificate : Signature -{ - public readonly Signature Signature; - - public readonly EphemeralSignatureVariant Variant; - - public EphemeralCertificate(Signature signature, EphemeralSignatureVariant variant) - { - Signature = signature; - Variant = variant; - } - - public override byte[] ToByteArray() => Signature.ToByteArray(); - - public override void Serialize(Serializer s) - { - s.U32AsUleb128((uint)Variant); - Signature.Serialize(s); - } - - public static EphemeralCertificate Deserialize(Deserializer d) - { - EphemeralSignatureVariant variant = (EphemeralSignatureVariant)d.Uleb128AsU32(); - return variant switch - { - EphemeralSignatureVariant.ZkProof => new EphemeralCertificate( - ZeroKnowledgeSignature.Deserialize(d), - EphemeralSignatureVariant.ZkProof - ), - _ => throw new ArgumentException("Invalid signature variant"), - }; - } -} - -public class ZeroKnowledgeSignature( - ZkProof proof, - ulong expHorizonSecs, - string? extraField, - string? overrideAudVal, - EphemeralSignature? trainingWheelSignature -) : Signature -{ - public readonly ZkProof Proof = proof; - - public readonly ulong ExpHorizonSecs = expHorizonSecs; - - public readonly string? ExtraField = extraField; - - public readonly string? OverrideAudVal = overrideAudVal; - - public readonly EphemeralSignature? TrainingWheelSignature = trainingWheelSignature; - - public override byte[] ToByteArray() => BcsToBytes(); - - public override void Serialize(Serializer s) - { - Proof.Serialize(s); - s.U64(ExpHorizonSecs); - s.OptionString(ExtraField); - s.OptionString(OverrideAudVal); - s.Option(TrainingWheelSignature); - } - - public static ZeroKnowledgeSignature Deserialize(Deserializer d) - { - ZkProof proof = ZkProof.Deserialize(d); - ulong expHorizonSecs = d.U64(); - string? extraField = d.OptionString(); - string? overrideAudVal = d.OptionString(); - EphemeralSignature? trainingWheelSignature = d.Option(EphemeralSignature.Deserialize); - return new ZeroKnowledgeSignature( - proof, - expHorizonSecs, - extraField, - overrideAudVal, - trainingWheelSignature - ); - } -} diff --git a/Aptos/Aptos.Crypto/MultiKey.cs b/Aptos/Aptos.Crypto/MultiKey.cs index 3064bf8..cefa41b 100644 --- a/Aptos/Aptos.Crypto/MultiKey.cs +++ b/Aptos/Aptos.Crypto/MultiKey.cs @@ -42,9 +42,9 @@ public static int BitCount(byte b) } } -public partial class MultiKey : UnifiedAccountPublicKey +public partial class MultiKey : Serializable, IVerifyingKey { - public readonly List PublicKeys; + public readonly List PublicKeys; public readonly byte SignaturesRequired; @@ -58,68 +58,67 @@ public MultiKey(List publicKeys, byte signaturesRequired) ); // Make sure that all public keys are normalized to the SingleKey authentication scheme - PublicKeys = publicKeys - .Select(p => p is AnyPublicKey anyPublicKey ? anyPublicKey : new AnyPublicKey(p)) - .ToList(); + PublicKeys = publicKeys; SignaturesRequired = signaturesRequired; } + public AuthenticationKey AuthKey() => + AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.MultiKey, BcsToBytes()); + public int GetIndex(PublicKey publicKey) { - AnyPublicKey pubkey = publicKey is AnyPublicKey anyPublicKey - ? anyPublicKey - : new AnyPublicKey(publicKey); - int index = PublicKeys.FindIndex((pk) => pk.ToString().Equals(pubkey.ToString())); + int index = PublicKeys.FindIndex((pk) => pk.ToString().Equals(publicKey.ToString())); if (index == -1) throw new ArgumentException("Public key not found"); return index; } - public override byte[] ToByteArray() => BcsToBytes(); + public bool VerifySignature(string message, Signature signature) => + VerifySignature(SigningMessage.Convert(message), signature); - public override AuthenticationKey AuthKey() => - AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.MultiKey, BcsToBytes()); - - public override bool VerifySignature(byte[] message, Signature signature) => + public bool VerifySignature(byte[] message, Signature signature) => throw new NotImplementedException(); public override void Serialize(Serializer s) { - s.Vector(PublicKeys); + s.U32AsUleb128((uint)PublicKeys.Count); + PublicKeys.ForEach(e => + { + s.U32AsUleb128((uint)e.Type); + e.Serialize(s); + }); s.U8(SignaturesRequired); } public static MultiKey Deserialize(Deserializer d) { - List publicKeys = d.Vector(AnyPublicKey.Deserialize).Cast().ToList(); + List publicKeys = [.. d.Vector(PublicKey.Deserialize)]; byte signaturesRequired = d.U8(); return new MultiKey(publicKeys, signaturesRequired); } } -public class MultiKeySignature : UnifiedSignature +public class MultiKeySignature : Signature { public static readonly int BITMAP_LEN = 4; public static readonly int MAX_SIGNATURES_SUPPORTED = BITMAP_LEN * 8; - public readonly List Signatures; + public readonly List Signatures; public readonly byte[] Bitmap; - public MultiKeySignature(List signatures, int[] bitmap) + public MultiKeySignature(List signatures, int[] bitmap) : this(signatures, MultiKey.CreateBitmap(bitmap)) { } - public MultiKeySignature(List signatures, byte[] bitmap) + public MultiKeySignature(List signatures, byte[] bitmap) { // Make sure that all signatures are normalized to the SingleKey authentication scheme if (signatures.Count > MAX_SIGNATURES_SUPPORTED) throw new ArgumentException( $"Signatures count should be less than or equal to {MAX_SIGNATURES_SUPPORTED}" ); - Signatures = signatures - .Select(s => s is AnySignature anySignature ? anySignature : new AnySignature(s)) - .ToList(); + Signatures = signatures; // Make sure that the bitmap is the correct length if (bitmap.Length != BITMAP_LEN) @@ -137,13 +136,18 @@ public MultiKeySignature(List signatures, byte[] bitmap) public override void Serialize(Serializer s) { - s.Vector(Signatures); + s.U32AsUleb128((uint)Signatures.Count); + Signatures.ForEach(e => + { + s.U32AsUleb128((uint)e.Type); + e.Serialize(s); + }); s.Bytes(Bitmap); } public static MultiKeySignature Deserialize(Deserializer d) { - List signatures = d.Vector(AnySignature.Deserialize).Cast().ToList(); + List signatures = [.. d.Vector(PublicKeySignature.Deserialize)]; byte[] bitmap = d.Bytes(); return new MultiKeySignature(signatures, bitmap); } diff --git a/Aptos/Aptos.Crypto/PrivateKey.cs b/Aptos/Aptos.Crypto/PrivateKey.cs index 15eec92..425dd78 100644 --- a/Aptos/Aptos.Crypto/PrivateKey.cs +++ b/Aptos/Aptos.Crypto/PrivateKey.cs @@ -2,9 +2,9 @@ namespace Aptos; public abstract class PrivateKey : Serializable { - public abstract Signature Sign(byte[] message); + public virtual PublicKeySignature Sign(string message) => Sign(SigningMessage.Convert(message)); - public abstract Signature Sign(string message); + public abstract PublicKeySignature Sign(byte[] message); public abstract PublicKey PublicKey(); diff --git a/Aptos/Aptos.Crypto/PublicKey.cs b/Aptos/Aptos.Crypto/PublicKey.cs index 94e5482..67b7d69 100644 --- a/Aptos/Aptos.Crypto/PublicKey.cs +++ b/Aptos/Aptos.Crypto/PublicKey.cs @@ -1,7 +1,35 @@ namespace Aptos; -public abstract class PublicKey : Serializable +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; + +[JsonConverter(typeof(StringEnumConverter))] +public enum PublicKeyVariant : uint { + [EnumMember(Value = "ed25519")] + Ed25519, + + [EnumMember(Value = "secp256k1_ecdsa")] + Secp256k1Ecdsa, + + [EnumMember(Value = "secp256r1_ecdsa")] + Secp256r1Ecdsa, + + [EnumMember(Value = "keyless")] + Keyless, +} + +[JsonConverter(typeof(PublicKeyConverter))] +public abstract class PublicKey(PublicKeyVariant type) : Serializable +{ + [JsonProperty("type")] + public readonly PublicKeyVariant Type = type; + + [JsonProperty("value")] + public abstract Hex Value { get; } + public bool VerifySignature(string message, Signature signature) => VerifySignature(SigningMessage.Convert(message), signature); @@ -9,5 +37,62 @@ public bool VerifySignature(string message, Signature signature) => public abstract byte[] ToByteArray(); + public static PublicKey Deserialize(Deserializer d) + { + PublicKeyVariant variant = (PublicKeyVariant)d.Uleb128AsU32(); + return variant switch + { + PublicKeyVariant.Ed25519 => new Ed25519PublicKey(d.Bytes()), + PublicKeyVariant.Secp256k1Ecdsa => new Secp256k1PublicKey(d.Bytes()), + PublicKeyVariant.Keyless => KeylessPublicKey.Deserialize(d), + _ => throw new ArgumentException("Invalid public key variant"), + }; + } + public override string ToString() => Hex.FromHexInput(ToByteArray()).ToString(); } + +public class PublicKeyConverter : JsonConverter +{ + public override PublicKey? ReadJson( + JsonReader reader, + Type objectType, + PublicKey? existingValue, + bool hasExistingValue, + JsonSerializer serializer + ) + { + var jsonObject = JObject.Load(reader); + var type = jsonObject["type"]?.ToString(); + + AnyValue? anyValue = JsonConvert.DeserializeObject(jsonObject.ToString()); + if (anyValue == null) + throw new Exception("Invalid public key shape"); + + Deserializer deserializer = new(anyValue.Value); + deserializer.Uleb128AsU32(); + + return type switch + { + "ed25519" => new Ed25519PublicKey(anyValue.Value), + "secp256k1_ecdsa" => new Secp256k1PublicKey(anyValue.Value), + "keyless" => KeylessPublicKey.Deserialize(new Deserializer(anyValue.Value)), + _ => throw new Exception($"Unknown public key type: {type}"), + }; + } + + public override void WriteJson(JsonWriter writer, PublicKey? value, JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + { + writer.WriteStartObject(); + writer.WritePropertyName("type"); + writer.WriteValue(JsonConvert.SerializeObject(value.Type).Replace("\"", "")); + writer.WritePropertyName("value"); + writer.WriteValue(value.Value.ToString()); + writer.WriteEndObject(); + } + } +} diff --git a/Aptos/Aptos.Crypto/PublicKeySignature.cs b/Aptos/Aptos.Crypto/PublicKeySignature.cs new file mode 100644 index 0000000..c709791 --- /dev/null +++ b/Aptos/Aptos.Crypto/PublicKeySignature.cs @@ -0,0 +1,92 @@ +namespace Aptos; + +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; + +[JsonConverter(typeof(StringEnumConverter))] +public enum PublicKeySignatureVariant +{ + [EnumMember(Value = "ed25519")] + Ed25519 = 0, + + [EnumMember(Value = "secp256k1_ecdsa")] + Secp256k1Ecdsa = 1, + + [EnumMember(Value = "keyless")] + Keyless = 3, +} + +/// +/// Cryptographic signatures typically using a private key to sign a message or transaction. +/// +[JsonConverter(typeof(PublicKeySignatureConverter))] +public abstract class PublicKeySignature(PublicKeySignatureVariant type) : Signature +{ + [JsonProperty("type")] + public PublicKeySignatureVariant Type = type; + + [JsonProperty("value")] + public abstract Hex Value { get; } + + public abstract override byte[] ToByteArray(); + + public static PublicKeySignature Deserialize(Deserializer d) + { + PublicKeySignatureVariant variant = (PublicKeySignatureVariant)d.Uleb128AsU32(); + return variant switch + { + PublicKeySignatureVariant.Ed25519 => Ed25519Signature.Deserialize(d), + PublicKeySignatureVariant.Secp256k1Ecdsa => Secp256k1Signature.Deserialize(d), + PublicKeySignatureVariant.Keyless => KeylessSignature.Deserialize(d), + _ => throw new ArgumentException("Invalid signature variant"), + }; + } +} + +public class PublicKeySignatureConverter : JsonConverter +{ + public override PublicKeySignature? ReadJson( + JsonReader reader, + Type objectType, + PublicKeySignature? existingValue, + bool hasExistingValue, + JsonSerializer serializer + ) + { + var jsonObject = JObject.Load(reader); + var type = jsonObject["type"]?.ToString(); + + AnyValue? anyValue = JsonConvert.DeserializeObject(jsonObject.ToString()); + if (anyValue == null) + throw new Exception("Invalid Signature shape"); + + return type switch + { + "ed25519" => new Ed25519Signature(anyValue.Value), + "secp256k1_ecdsa" => new Secp256k1Signature(anyValue.Value), + "keyless" => KeylessSignature.Deserialize(new Deserializer(anyValue.Value)), + _ => throw new Exception($"Unknown signature type: {type}"), + }; + } + + public override void WriteJson( + JsonWriter writer, + PublicKeySignature? value, + JsonSerializer serializer + ) + { + if (value == null) + writer.WriteNull(); + else + { + writer.WriteStartObject(); + writer.WritePropertyName("type"); + writer.WriteValue(JsonConvert.SerializeObject(value.Type).Replace("\"", "")); + writer.WritePropertyName("value"); + writer.WriteValue(value.Value.ToString()); + writer.WriteEndObject(); + } + } +} diff --git a/Aptos/Aptos.Crypto/Secp256k1.cs b/Aptos/Aptos.Crypto/Secp256k1.cs index 670b808..d4fb882 100644 --- a/Aptos/Aptos.Crypto/Secp256k1.cs +++ b/Aptos/Aptos.Crypto/Secp256k1.cs @@ -23,7 +23,7 @@ public static class Secp256k1 .Curve.Order.ShiftRight(1); } -public class Secp256k1PublicKey : LegacyPublicKey +public class Secp256k1PublicKey : PublicKey { static readonly int LENGTH = 65; @@ -65,7 +65,7 @@ public override bool VerifySignature(byte[] message, Signature signature) return signer.VerifySignature(hash, r, s); } - public static Secp256k1PublicKey Deserialize(Deserializer d) => new(d.Bytes()); + public static new Secp256k1PublicKey Deserialize(Deserializer d) => new(d.Bytes()); } public class Secp256k1PrivateKey : PrivateKey @@ -101,9 +101,7 @@ public override PublicKey PublicKey() return new Secp256k1PublicKey(publicKey.Q.GetEncoded(false)); } - public override Signature Sign(string message) => Sign(SigningMessage.Convert(message)); - - public override Signature Sign(byte[] message) + public override PublicKeySignature Sign(byte[] message) { // Hash the message byte[] hash = DigestUtilities.CalculateDigest("SHA3-256", message); @@ -158,7 +156,7 @@ public static Secp256k1PrivateKey FromDerivationPath(string path, string mnemoni public static Secp256k1PrivateKey Deserialize(Deserializer d) => new(d.Bytes()); } -public class Secp256k1Signature : LegacySignature +public class Secp256k1Signature : PublicKeySignature { static readonly int LENGTH = 64; @@ -170,7 +168,7 @@ public Secp256k1Signature(string signature) : this(Hex.FromHexInput(signature).ToByteArray()) { } public Secp256k1Signature(byte[] signature) - : base(SignatureVariant.Secp256k1Ecdsa) + : base(PublicKeySignatureVariant.Secp256k1Ecdsa) { if (signature.Length != LENGTH) throw new KeyLengthMismatch("Secp256k1Signature", LENGTH); @@ -181,5 +179,5 @@ public Secp256k1Signature(byte[] signature) public override void Serialize(Serializer s) => s.Bytes(_value.ToByteArray()); - public static Secp256k1Signature Deserialize(Deserializer d) => new(d.Bytes()); + public static new Secp256k1Signature Deserialize(Deserializer d) => new(d.Bytes()); } diff --git a/Aptos/Aptos.Crypto/Signature.cs b/Aptos/Aptos.Crypto/Signature.cs index d84450d..291fe2d 100644 --- a/Aptos/Aptos.Crypto/Signature.cs +++ b/Aptos/Aptos.Crypto/Signature.cs @@ -1,10 +1,5 @@ namespace Aptos; -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; - /// /// Base signatures for anything signed (not specific to signing transactions/messages). This may include all signatures needed for ZK proofs, Certificates, etc. /// @@ -14,80 +9,3 @@ public abstract class Signature : Serializable public override string ToString() => Hex.FromHexInput(ToByteArray()).ToString(); } - -[JsonConverter(typeof(StringEnumConverter))] -public enum SignatureVariant -{ - [EnumMember(Value = "ed25519")] - Ed25519 = 0, - - [EnumMember(Value = "secp256k1_ecdsa")] - Secp256k1Ecdsa = 1, - - [EnumMember(Value = "keyless")] - Keyless = 3, -} - -/// -/// Signature for results of signing transactions/messages using a authentication scheme (e.g. Ed25519, Keyless, etc.) -/// -/// -/// The type of the signature (e.g. Ed25519, Keyless, etc.) -/// -[JsonConverter(typeof(LegacySignatureConverter))] -public abstract class LegacySignature(SignatureVariant type) : Signature -{ - [JsonProperty("type")] - public SignatureVariant Type = type; - - [JsonProperty("value")] - public abstract Hex Value { get; } -} - -public abstract class UnifiedSignature : Signature { } - -public class LegacySignatureConverter : JsonConverter -{ - public override LegacySignature? ReadJson( - JsonReader reader, - Type objectType, - LegacySignature? existingValue, - bool hasExistingValue, - JsonSerializer serializer - ) - { - var jsonObject = JObject.Load(reader); - var type = jsonObject["type"]?.ToString(); - - AnyValue? anyValue = JsonConvert.DeserializeObject(jsonObject.ToString()); - if (anyValue == null) - throw new Exception("Invalid LegacySignature shape"); - - return type switch - { - "ed25519" => new Ed25519Signature(anyValue.Value), - "secp256k1_ecdsa" => new Secp256k1Signature(anyValue.Value), - "keyless" => KeylessSignature.Deserialize(new Deserializer(anyValue.Value)), - _ => throw new Exception($"Unknown signature type: {type}"), - }; - } - - public override void WriteJson( - JsonWriter writer, - LegacySignature? value, - JsonSerializer serializer - ) - { - if (value == null) - writer.WriteNull(); - else - { - writer.WriteStartObject(); - writer.WritePropertyName("type"); - writer.WriteValue(JsonConvert.SerializeObject(value.Type).Replace("\"", "")); - writer.WritePropertyName("value"); - writer.WriteValue(value.Value.ToString()); - writer.WriteEndObject(); - } - } -} diff --git a/Aptos/Aptos.Crypto/SingleKey.cs b/Aptos/Aptos.Crypto/SingleKey.cs index fc279ff..7950ed5 100644 --- a/Aptos/Aptos.Crypto/SingleKey.cs +++ b/Aptos/Aptos.Crypto/SingleKey.cs @@ -1,91 +1,25 @@ -namespace Aptos; - using Aptos.Schemes; -public class AnyPublicKey : UnifiedAccountPublicKey -{ - public readonly PublicKey PublicKey; - - public readonly PublicKeyVariant Type; - - public AnyPublicKey(PublicKey publicKey) - { - if (publicKey is LegacyPublicKey legacyPublicKey) - { - PublicKey = legacyPublicKey; - Type = legacyPublicKey.Type; - } - else if (publicKey is LegacyAccountPublicKey legacyAccountPublicKey) - { - PublicKey = legacyAccountPublicKey; - Type = legacyAccountPublicKey.Type; - } - else - { - throw new ArgumentException("Invalid public key type"); - } - } - - public override bool VerifySignature(byte[] message, Signature signature) => - PublicKey.VerifySignature( - message, - signature is AnySignature anySignature ? anySignature.Signature : signature - ); - - public override AuthenticationKey AuthKey() => - AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.SingleKey, BcsToBytes()); - - public override byte[] ToByteArray() => PublicKey.ToByteArray(); - - public override void Serialize(Serializer s) - { - s.U32AsUleb128((uint)Type); - PublicKey.Serialize(s); - } - - public static AnyPublicKey Deserialize(Deserializer d) - { - PublicKeyVariant variant = (PublicKeyVariant)d.Uleb128AsU32(); - return variant switch - { - PublicKeyVariant.Ed25519 => new AnyPublicKey(Ed25519PublicKey.Deserialize(d)), - PublicKeyVariant.Secp256k1Ecdsa => new AnyPublicKey(Secp256k1PublicKey.Deserialize(d)), - PublicKeyVariant.Keyless => new AnyPublicKey(KeylessPublicKey.Deserialize(d)), - _ => throw new ArgumentException("Invalid public key variant"), - }; - } -} +namespace Aptos; -public class AnySignature(LegacySignature signature) : UnifiedSignature +public partial class SingleKey(PublicKey publicKey) : Serializable, IVerifyingKey { - public readonly Signature Signature = signature; + public readonly PublicKey PublicKey = publicKey; - public readonly SignatureVariant Type = signature.Type; + public AuthenticationKey AuthKey() => + AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.SingleKey, BcsToBytes()); - public AnySignature(Signature signature) - : this( - signature is LegacySignature accountSignature - ? accountSignature - : throw new ArgumentException("Invalid signature type") - ) { } + public bool VerifySignature(string message, Signature signature) => + VerifySignature(SigningMessage.Convert(message), signature); - public override byte[] ToByteArray() => Signature.ToByteArray(); + public bool VerifySignature(byte[] message, Signature signature) => + PublicKey.VerifySignature(message, signature); public override void Serialize(Serializer s) { - s.U32AsUleb128((uint)Type); - Signature.Serialize(s); + s.U32AsUleb128((uint)PublicKey.Type); + PublicKey.Serialize(s); } - public static AnySignature Deserialize(Deserializer d) - { - SignatureVariant variant = (SignatureVariant)d.Uleb128AsU32(); - return variant switch - { - SignatureVariant.Ed25519 => new AnySignature(Ed25519Signature.Deserialize(d)), - SignatureVariant.Secp256k1Ecdsa => new AnySignature(Secp256k1Signature.Deserialize(d)), - SignatureVariant.Keyless => new AnySignature(KeylessSignature.Deserialize(d)), - _ => throw new ArgumentException("Invalid signature variant"), - }; - } + public static SingleKey Deserialize(Deserializer d) => new(PublicKey.Deserialize(d)); } diff --git a/Aptos/Aptos.Exceptions/Crypto.cs b/Aptos/Aptos.Exceptions/Crypto.cs index b011c70..1de6efb 100644 --- a/Aptos/Aptos.Exceptions/Crypto.cs +++ b/Aptos/Aptos.Exceptions/Crypto.cs @@ -1,6 +1,6 @@ namespace Aptos.Exceptions { - public class EphemeralSignatureVariantUnsupported(SignatureVariant variant) + public class EphemeralSignatureVariantUnsupported(PublicKeySignatureVariant variant) : BaseException($"Ephemeral signature variant {variant} is not supported") { } public class EphemeralKeyVariantUnsupported(PublicKeyVariant variant) diff --git a/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs b/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs index b3e703f..c8dc753 100644 --- a/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs +++ b/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs @@ -1,23 +1,27 @@ namespace Aptos; -public class AccountAuthenticatorSingleKey(AnyPublicKey publicKey, AnySignature signature) +public class AccountAuthenticatorSingleKey(PublicKey publicKey, PublicKeySignature signature) : AccountAuthenticator { - public readonly AnyPublicKey PublicKey = publicKey; + public readonly PublicKey PublicKey = publicKey; - public readonly AnySignature Signature = signature; + public readonly PublicKeySignature Signature = signature; public override void Serialize(Serializer s) { s.U32AsUleb128((uint)AccountAuthenticatorVariant.SingleKey); + + s.U32AsUleb128((uint)PublicKey.Type); PublicKey.Serialize(s); + + s.U32AsUleb128((uint)Signature.Type); Signature.Serialize(s); } public static new AccountAuthenticatorSingleKey Deserialize(Deserializer d) { - AnyPublicKey publicKey = AnyPublicKey.Deserialize(d); - AnySignature signature = AnySignature.Deserialize(d); + PublicKey publicKey = PublicKey.Deserialize(d); + PublicKeySignature signature = PublicKeySignature.Deserialize(d); return new AccountAuthenticatorSingleKey(publicKey, signature); } } diff --git a/Aptos/Aptos.Transactions/TransactionBuilder.cs b/Aptos/Aptos.Transactions/TransactionBuilder.cs index 392d10f..310ad94 100644 --- a/Aptos/Aptos.Transactions/TransactionBuilder.cs +++ b/Aptos/Aptos.Transactions/TransactionBuilder.cs @@ -16,18 +16,15 @@ public static AccountAuthenticator GetAuthenticatorForSimulation(PublicKey publi return new AccountAuthenticatorEd25519(ed25519PublicKey, invalidSignature); } - if (publicKey is AnyPublicKey anyPublicKey) + if (publicKey is KeylessPublicKey keylessPublicKey) { return new AccountAuthenticatorSingleKey( - anyPublicKey, - new AnySignature(invalidSignature) + keylessPublicKey, + Keyless.GetSimulationSignature() ); } - throw new InvalidPublicKey( - publicKey, - "Authenticator for simulation cannot be derived for this PublicKey." - ); + return new AccountAuthenticatorSingleKey(publicKey, invalidSignature); } #endregion @@ -50,10 +47,7 @@ public static SignedTransaction GeneratedSignedTransaction(SubmitTransactionData (data.Transaction.FeePayerAddress, data.FeePayerAuthenticator) ); } - else if ( - data.Transaction.SecondarySignerAddresses != null - || data.SenderAuthenticator is AccountAuthenticatorMultiKey - ) + else if (data.Transaction.SecondarySignerAddresses != null) { // Make sure that there are enough additional authenticators for the secondary signers addresses if ( @@ -82,6 +76,12 @@ public static SignedTransaction GeneratedSignedTransaction(SubmitTransactionData singleKeyAuthenticator ); } + else if (data.SenderAuthenticator is AccountAuthenticatorMultiKey multiKeyAuthenticator) + { + transactionAuthenticator = new TransactionAuthenticatorSingleSender( + multiKeyAuthenticator + ); + } if (transactionAuthenticator == null) throw new ArgumentException("Invalid authentication scheme"); diff --git a/features/single_key.feature b/features/single_key.feature new file mode 100644 index 0000000..72cd992 --- /dev/null +++ b/features/single_key.feature @@ -0,0 +1,42 @@ +Feature: SingleKey +""" +Single is a verification key used to authenticate a public key. This specification aims to covers SingleKey signatures, serialization, and authentication keys. + +All key values are represented in BCS (Binary Canonical Serialization) format as a Hex string. It's important to use a Deserializer ensure compatibility. +""" + + Scenario Outline: SingleKey serialization + Given + When I serialize + Then the result should be bcs + + Examples: + | type | key | value | + | ed25519 | 0x2066e6dde4a271c5fcc68e9bda668ac41ff67049ea75e4db103c7927934b1e4ace | 0x002066e6dde4a271c5fcc68e9bda668ac41ff67049ea75e4db103c7927934b1e4ace | + | secp256k1 | 0x4104ffaf07a5268f92f86aa8be6a8047aaf2cceb33c3ac69628d9dde4e2ffdbe2d21551ecb9f69a6a79a509a7328a6641d81b4bb1fb39d4955549dad93d44ccc9d50 | 0x014104ffaf07a5268f92f86aa8be6a8047aaf2cceb33c3ac69628d9dde4e2ffdbe2d21551ecb9f69a6a79a509a7328a6641d81b4bb1fb39d4955549dad93d44ccc9d50 | + | keyless | 0x1b68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d20efbfb94b89da565468e11b1a7d29eb3be1032637ec8d21b61f5220511d668814 | 0x031b68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d20efbfb94b89da565468e11b1a7d29eb3be1032637ec8d21b61f5220511d668814 | + + Scenario Outline: SingleKey authentication key + Given + When I derive authentication key + Then the result should be bcs + + Examples: + | type | key | value | + | ed25519 | 0x2066e6dde4a271c5fcc68e9bda668ac41ff67049ea75e4db103c7927934b1e4ace | 0x73312812b63e652652f5a2aecfcb42ec051923c3456329d9500eb0e919e3903d | + | secp256k1 | 0x4104ffaf07a5268f92f86aa8be6a8047aaf2cceb33c3ac69628d9dde4e2ffdbe2d21551ecb9f69a6a79a509a7328a6641d81b4bb1fb39d4955549dad93d44ccc9d50 | 0xe6e936c0c9c41d8c1f0490de291789677d64467ef38f1fff7111cad9546eee44 | + | keyless | 0x1b68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d20efbfb94b89da565468e11b1a7d29eb3be1032637ec8d21b61f5220511d668814 | 0x3c38e3250bf5e446947a9fd4bdfd41e76c3812e8061abf40702febde99b68d51 | + + Scenario Outline: Verify signatures using SingleKey + Given + When I verify signature with message + Then the result should be bool + + Examples: + | type | key | signature | message | value | + | ed25519 | 0x2066e6dde4a271c5fcc68e9bda668ac41ff67049ea75e4db103c7927934b1e4ace | 0x40400461806f41581b75459e5db432e9d4da0881ac0ef0b7f8bb1c4b8e108d4ec2dd88abf5ff1e00616b2c338c375e245470e15d75f69aec465fa090001df19505 | hello world | true | + | ed25519 | 0x2066e6dde4a271c5fcc68e9bda668ac41ff67049ea75e4db103c7927934b1e4ace | 0x40400461806f41581b75459e5db432e9d4da0881ac0ef0b7f8bb1c4b8e108d4ec2dd88abf5ff1e00616b2c338c375e245470e15d75f69aec465fa090001df19505 | N/A | false | + | secp256k1 | 0x4104ffaf07a5268f92f86aa8be6a8047aaf2cceb33c3ac69628d9dde4e2ffdbe2d21551ecb9f69a6a79a509a7328a6641d81b4bb1fb39d4955549dad93d44ccc9d50 | 0x40882b4ddbc14df439c222300c051d1039a62d36026ecd5789fd777224fef0ab7e07bb0891c5b411ec0938456a4b250067e8ff6c996d31f0186019521fc8f6fb25 | hello world | true | + | secp256k1 | 0x4104ffaf07a5268f92f86aa8be6a8047aaf2cceb33c3ac69628d9dde4e2ffdbe2d21551ecb9f69a6a79a509a7328a6641d81b4bb1fb39d4955549dad93d44ccc9d50 | 0x40882b4ddbc14df439c222300c051d1039a62d36026ecd5789fd777224fef0ab7e07bb0891c5b411ec0938456a4b250067e8ff6c996d31f0186019521fc8f6fb25 | N/A | false | + | keyless | 0x1b68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d20efbfb94b89da565468e11b1a7d29eb3be1032637ec8d21b61f5220511d668814 | 0x000016e4ab03e4a1f199b26b32ecc1ad3f7d8676ed332eef87f29f1198582366af88a91fcb2cfd0ac6599bd720e8815de8a288cabd5c21706a7d675e80365e2e6f1056c753e59d1d597b41ac34e5db0334982bfd2fc1c8cc167edb866b9e78c0eda96155be3d53755fdc7922028d789546b3929b3c50cfdd290d86b3eae5b45c070f809698000000000000000100405f4891933c8ac11391f82a2ef6c51131da95c3cc56e48daca75e2153c13faa48050f893fb97d72719ef1a7872c0c5f164a670c0b8325b5608d3cbcca7d2ed20d4c7b22616c67223a225253323536222c226b6964223a2262323632306435653766313332623532616665383837356364663337373663303634323439643034222c22747970223a224a5754227dc03d04670000000000206c2adb07d4bdf1462fa4090578805f654c00d5316b600f2ac7b7a92f727016f6004063b9bd27870d78845daa78effc11fc0acbadaf377a5df2791fe45f4ea0d9f10ea267b73692e7870fce3d37288631b691baf920bef64cba2996259cbabf419600 | hello world | true | + | keyless | 0x1b68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d20efbfb94b89da565468e11b1a7d29eb3be1032637ec8d21b61f5220511d668814 | 0x000016e4ab03e4a1f199b26b32ecc1ad3f7d8676ed332eef87f29f1198582366af88a91fcb2cfd0ac6599bd720e8815de8a288cabd5c21706a7d675e80365e2e6f1056c753e59d1d597b41ac34e5db0334982bfd2fc1c8cc167edb866b9e78c0eda96155be3d53755fdc7922028d789546b3929b3c50cfdd290d86b3eae5b45c070f809698000000000000000100405f4891933c8ac11391f82a2ef6c51131da95c3cc56e48daca75e2153c13faa48050f893fb97d72719ef1a7872c0c5f164a670c0b8325b5608d3cbcca7d2ed20d4c7b22616c67223a225253323536222c226b6964223a2262323632306435653766313332623532616665383837356364663337373663303634323439643034222c22747970223a224a5754227dc03d04670000000000206c2adb07d4bdf1462fa4090578805f654c00d5316b600f2ac7b7a92f727016f6004063b9bd27870d78845daa78effc11fc0acbadaf377a5df2791fe45f4ea0d9f10ea267b73692e7870fce3d37288631b691baf920bef64cba2996259cbabf419600 | N/A | false |