diff --git a/Aptos.Examples/Main.cs b/Aptos.Examples/Main.cs index 6bca93b..7c56f16 100644 --- a/Aptos.Examples/Main.cs +++ b/Aptos.Examples/Main.cs @@ -6,7 +6,9 @@ public class RunExample { { "1", KeylessTransferExample.Run }, { "2", SimpleTransferExample.Run }, - { "3", PlaygroundExample.Run } + { "3", PlaygroundExample.Run }, + { "4", SimpleTransferSingleKeyExample.Run }, + { "5", SimpleTransferMultiKeyExample.Run }, }; public static async Task Main() diff --git a/Aptos.Examples/SimpleTransfer.cs b/Aptos.Examples/SimpleTransfer.cs index 8b417a0..c832c6f 100644 --- a/Aptos.Examples/SimpleTransfer.cs +++ b/Aptos.Examples/SimpleTransfer.cs @@ -8,8 +8,8 @@ public class SimpleTransferExample public static async Task Run() { Console.WriteLine("=== Addresses ===\n"); - var aptos = new AptosClient(new AptosConfig(Networks.Testnet)); - var account = Ed25519Account.Generate(); + var aptos = new AptosClient(new AptosConfig(Networks.Devnet)); + var account = Account.Generate(); Console.WriteLine($"Alice: {account.Address}"); Console.WriteLine("\n=== Funding accounts ===\n"); diff --git a/Aptos.Examples/SimpleTransferMultiKey.cs b/Aptos.Examples/SimpleTransferMultiKey.cs new file mode 100644 index 0000000..91f3f42 --- /dev/null +++ b/Aptos.Examples/SimpleTransferMultiKey.cs @@ -0,0 +1,51 @@ +using Newtonsoft.Json; +using Org.BouncyCastle.Asn1.Esf; + +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 = Account.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..3001973 --- /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/Aptos.Accounts/Account.cs b/Aptos/Aptos.Accounts/Account.cs index 43e9424..5411ed9 100644 --- a/Aptos/Aptos.Accounts/Account.cs +++ b/Aptos/Aptos.Accounts/Account.cs @@ -10,7 +10,7 @@ public abstract class Account /// /// Gets the public key of the account. /// - public abstract AccountPublicKey PublicKey { get; } + public abstract PublicKey PublicKey { get; } /// /// Gets the address of the account. @@ -22,6 +22,12 @@ public abstract class Account /// public abstract SigningScheme SigningScheme { get; } + /// + /// Gets the authentication key for the account. + /// + /// The authentication key for the account. + public abstract AuthenticationKey AuthKey(); + /// public Signature Sign(string message) => Sign(SigningMessage.Convert(message)); diff --git a/Aptos/Aptos.Accounts/Ed25519Account.cs b/Aptos/Aptos.Accounts/Ed25519Account.cs index c1889ac..2f6cebe 100644 --- a/Aptos/Aptos.Accounts/Ed25519Account.cs +++ b/Aptos/Aptos.Accounts/Ed25519Account.cs @@ -17,7 +17,7 @@ public class Ed25519Account : Account /// /// Gets the Ed25519PublicKey for the account. /// - public override AccountPublicKey PublicKey => _publicKey; + public override PublicKey PublicKey => _publicKey; private readonly AccountAddress _address; /// @@ -30,6 +30,13 @@ public class Ed25519Account : Account /// public override SigningScheme SigningScheme => SigningScheme.Ed25519; + /// + /// The authentication key for the account. + public override AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.Ed25519, _publicKey.ToByteArray()); + + /// public Ed25519Account(Ed25519PrivateKey privateKey) : this(privateKey, (AccountAddress?)null) { } @@ -47,7 +54,7 @@ public Ed25519Account(Ed25519PrivateKey privateKey, byte[]? address = null) : th public Ed25519Account(Ed25519PrivateKey privateKey, AccountAddress? address = null) { _publicKey = (Ed25519PublicKey)privateKey.PublicKey(); - _address = address ?? PublicKey.AuthKey().DerivedAddress(); + _address = address ?? AuthKey().DerivedAddress(); PrivateKey = privateKey; } diff --git a/Aptos/Aptos.Accounts/EphemeralKeyPair.cs b/Aptos/Aptos.Accounts/EphemeralKeyPair.cs index 90f57fc..3f77feb 100644 --- a/Aptos/Aptos.Accounts/EphemeralKeyPair.cs +++ b/Aptos/Aptos.Accounts/EphemeralKeyPair.cs @@ -42,11 +42,7 @@ public class EphemeralKeyPair : Serializable public EphemeralKeyPair(PrivateKey privateKey, ulong? expiryTimestamp = null, byte[]? blinder = null) { _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 = expiryTimestamp ?? (ulong)Utilities.FloorToWholeHour(DateTime.Now.ToUnixTimestamp() + DEFAULT_EXPIRY_DURATION); @@ -65,11 +61,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 5427756..3be2f4f 100644 --- a/Aptos/Aptos.Accounts/KeylessAccount.cs +++ b/Aptos/Aptos.Accounts/KeylessAccount.cs @@ -14,7 +14,7 @@ public class KeylessAccount : Account /// /// Gets the KeylessPublicKey for the account. /// - public override AccountPublicKey PublicKey => _publicKey; + public override PublicKey PublicKey => _publicKey; private readonly AccountAddress _address; /// @@ -24,6 +24,14 @@ public class KeylessAccount : Account public override SigningScheme SigningScheme => SigningScheme.SingleKey; + public override AuthenticationKey AuthKey() + { + Serializer s = new(); + s.U32AsUleb128((uint)_publicKey.Type); + s.FixedBytes(_publicKey.BcsToBytes()); + return AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.SingleKey, s.ToBytes()); + } + public readonly EphemeralKeyPair EphemeralKeyPair; public readonly string UidKey; @@ -43,7 +51,7 @@ public KeylessAccount(string jwt, EphemeralKeyPair ekp, ZeroKnowledgeSignature p 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(); + _address = address ?? AuthKey().DerivedAddress(); EphemeralKeyPair = ekp; Proof = proof; Pepper = pepper; @@ -84,9 +92,9 @@ public override Signature Sign(byte[] message) ); } - public override AccountAuthenticator SignWithAuthenticator(byte[] message) => new AccountAuthenticatorSingleKey(new AnyPublicKey(_publicKey), new AnySignature(Sign(message))); + public override AccountAuthenticator SignWithAuthenticator(byte[] message) => new AccountAuthenticatorSingleKey(_publicKey, Sign(message)); - public override AccountAuthenticator SignTransactionWithAuthenticator(AnyRawTransaction transaction) => new AccountAuthenticatorSingleKey(new AnyPublicKey(_publicKey), new AnySignature(SignTransaction(transaction))); + public override AccountAuthenticator SignTransactionWithAuthenticator(AnyRawTransaction transaction) => new AccountAuthenticatorSingleKey(_publicKey, SignTransaction(transaction)); public void Serialize(Serializer s) { diff --git a/Aptos/Aptos.Accounts/MultiKeyAccount.cs b/Aptos/Aptos.Accounts/MultiKeyAccount.cs index 36a46a1..b81f822 100644 --- a/Aptos/Aptos.Accounts/MultiKeyAccount.cs +++ b/Aptos/Aptos.Accounts/MultiKeyAccount.cs @@ -15,12 +15,12 @@ public class MultiKeyAccount : Account /// /// Gets the MultiKeyPublicKey for the account. /// - public override AccountPublicKey PublicKey => _publicKey; + public override PublicKey PublicKey => _publicKey; /// /// Gets the address of the account. /// - public override AccountAddress Address => PublicKey.AuthKey().DerivedAddress(); + public override AccountAddress Address => AuthKey().DerivedAddress(); /// /// The signers used to sign messages. These signers should correspond to public keys in the @@ -42,6 +42,8 @@ public class MultiKeyAccount : Account /// public override SigningScheme SigningScheme => SigningScheme.MultiKey; + public override AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.MultiKey, _publicKey.BcsToBytes()); + /// /// Initializes a new instance of the MultiKeyAccount class with a MultiKey and a list of signers. /// @@ -62,7 +64,7 @@ public MultiKeyAccount(MultiKey multiKey, List signers) bitPositions.Add(multiKey.GetIndex(signer.PublicKey)); } - 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 (signers.Any(s => !PublicKey.IsSigningKey(s.PublicKey))) throw new ArgumentException("MultiKeyAccount cannot be used with non-signing public keys (e.g. MultiKeyPublicKey)"); if (multiKey.SignaturesRequired > signers.Count) throw new ArgumentException($"Signatures required must be less than or equal to the number of signers"); // Zip signers and bit positions and sort signers by bit positions in order diff --git a/Aptos/Aptos.Accounts/SingleKeyAccount.cs b/Aptos/Aptos.Accounts/SingleKeyAccount.cs index ff9f73d..047bcd3 100644 --- a/Aptos/Aptos.Accounts/SingleKeyAccount.cs +++ b/Aptos/Aptos.Accounts/SingleKeyAccount.cs @@ -7,34 +7,43 @@ public class SingleKeyAccount : Account public readonly PrivateKey PrivateKey; - private readonly AnyPublicKey _publicKey; - public override AccountPublicKey PublicKey => _publicKey; + private readonly PublicKey _publicKey; + public override PublicKey PublicKey => _publicKey; private readonly AccountAddress _address; public override AccountAddress Address => _address; public override SigningScheme SigningScheme => SigningScheme.SingleKey; + public override AuthenticationKey AuthKey() + { + Serializer s = new(); + s.U32AsUleb128((uint)_publicKey.Type); + PublicKey.Serialize(s); + return AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.SingleKey, s.ToBytes()); + } + public SingleKeyAccount(PrivateKey privateKey) : this(privateKey, (AccountAddress?)null) { } public SingleKeyAccount(PrivateKey privateKey, byte[]? address = null) : this(privateKey, address != null ? AccountAddress.From(address) : null) { } public SingleKeyAccount(PrivateKey privateKey, string? address = null) : this(privateKey, address != null ? AccountAddress.From(address) : null) { } public SingleKeyAccount(PrivateKey privateKey, AccountAddress? address = null) { - _publicKey = new AnyPublicKey(privateKey.PublicKey()); - _address = address ?? _publicKey.AuthKey().DerivedAddress(); + if (!PublicKey.IsSigningKey(privateKey.PublicKey())) throw new ArgumentException("SingleKeyAccount only supports signing keys (e.g. Ed25519, Keyless, Secp256k1)"); + _publicKey = privateKey.PublicKey(); + _address = address ?? 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) => PublicKey.VerifySignature(message, signature); + public bool VerifySignature(byte[] message, Signature signature) => PublicKey.VerifySignature(message, signature); public override Signature SignTransaction(AnyRawTransaction transaction) => Sign(SigningMessage.GenerateForTransaction(transaction)); - 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 SignWithAuthenticator(byte[] message) => new AccountAuthenticatorSingleKey(_publicKey, Sign(message)); - public override AccountAuthenticator SignTransactionWithAuthenticator(AnyRawTransaction transaction) => new AccountAuthenticatorSingleKey(_publicKey, (AnySignature)SignTransaction(transaction)); + public override AccountAuthenticator SignTransactionWithAuthenticator(AnyRawTransaction transaction) => new AccountAuthenticatorSingleKey(_publicKey, SignTransaction(transaction)); public new static SingleKeyAccount Generate() => Generate(PublicKeyVariant.Ed25519); public static SingleKeyAccount Generate(PublicKeyVariant scheme) diff --git a/Aptos/Aptos.Api/AccountSignature.cs b/Aptos/Aptos.Api/AccountSignature.cs index 2c35e22..fb7ea49 100644 --- a/Aptos/Aptos.Api/AccountSignature.cs +++ b/Aptos/Aptos.Api/AccountSignature.cs @@ -42,32 +42,32 @@ public class AccountEd25519Signature(Hex publicKey, Hex signature) : AccountSign public Hex Signature = signature; } -public class AccountSingleKeySignature(ILegacyPublicKey publicKey, LegacySignature signature) : AccountSignature(SigningScheme.SingleKey) +public class AccountSingleKeySignature(PublicKey publicKey, Signature signature) : AccountSignature(SigningScheme.SingleKey) { [JsonProperty("public_key")] - public ILegacyPublicKey PublicKey = publicKey; + public PublicKey PublicKey = publicKey; [JsonProperty("signature")] - public LegacySignature Signature = signature; + public Signature Signature = signature; } -public class AccountMultiKeySignature(List publicKeys, List signatures, byte signaturesRequired) : AccountSignature(SigningScheme.MultiKey) +public class AccountMultiKeySignature(List publicKeys, List signatures, byte signaturesRequired) : AccountSignature(SigningScheme.MultiKey) { - public class IndexedAccountSignature(byte index, LegacySignature signature) + public class IndexedAccountSignature(byte index, Signature signature) { [JsonProperty("index")] public byte Index = index; [JsonProperty("signature")] - public LegacySignature Signature = signature; + public Signature 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 348c964..7a57eb2 100644 --- a/Aptos/Aptos.Api/TransactionSignature.cs +++ b/Aptos/Aptos.Api/TransactionSignature.cs @@ -67,6 +67,19 @@ public class TransactionSignatureConverter : JsonConverter signature = JsonConvert.DeserializeObject(jsonObject.ToString()); } } + 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 c1cc8b1..333b4bf 100644 --- a/Aptos/Aptos.Clients/KeylessClient.cs +++ b/Aptos/Aptos.Clients/KeylessClient.cs @@ -1,6 +1,7 @@ namespace Aptos; using Aptos.Core; +using Aptos.Schemes; using Newtonsoft.Json; public class KeylessClient(AptosClient client) @@ -14,7 +15,14 @@ public async Task DeriveAccount(string jwt, EphemeralKeyPair ekp // 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()); + + // Derive the address from the public key + Serializer s = new(); + s.U32AsUleb128((uint)publicKey.Type); + s.FixedBytes(publicKey.BcsToBytes()); + var address = await _client.Account.LookupOriginalAccountAddress(AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.SingleKey, s.ToBytes()) + .DerivedAddress() + .ToString()); // Create and return the keyless account return new KeylessAccount(jwt, ekp, proof, pepper, uidKey, address); diff --git a/Aptos/Aptos.Crypto/AccountPublicKey.cs b/Aptos/Aptos.Crypto/AccountPublicKey.cs deleted file mode 100644 index eadc67a..0000000 --- a/Aptos/Aptos.Crypto/AccountPublicKey.cs +++ /dev/null @@ -1,92 +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; } - -} \ No newline at end of file diff --git a/Aptos/Aptos.Crypto/AuthenticationKey.cs b/Aptos/Aptos.Crypto/AuthenticationKey.cs index be39efe..1a362f3 100644 --- a/Aptos/Aptos.Crypto/AuthenticationKey.cs +++ b/Aptos/Aptos.Crypto/AuthenticationKey.cs @@ -27,6 +27,8 @@ public AuthenticationKey(byte[] data) public static AuthenticationKey Deserialize(Deserializer d) => new(d.FixedBytes(LENGTH)); public static AuthenticationKey FromSchemeAndBytes(AuthenticationKeyScheme scheme, string bytes) => FromSchemeAndBytes(scheme, Hex.FromHexString(bytes).ToByteArray()); + public static AuthenticationKey FromSchemeAndBytes(SigningScheme scheme, string bytes) => FromSchemeAndBytes(scheme, Hex.FromHexString(bytes).ToByteArray()); + public static AuthenticationKey FromSchemeAndBytes(SigningScheme scheme, byte[] bytes) => FromSchemeAndBytes((AuthenticationKeyScheme)scheme, bytes); public static AuthenticationKey FromSchemeAndBytes(AuthenticationKeyScheme scheme, byte[] bytes) { // Create a new array combining input bytes and the scheme byte diff --git a/Aptos/Aptos.Crypto/Ed25519.cs b/Aptos/Aptos.Crypto/Ed25519.cs index cc94a80..be02335 100644 --- a/Aptos/Aptos.Crypto/Ed25519.cs +++ b/Aptos/Aptos.Crypto/Ed25519.cs @@ -1,7 +1,6 @@ namespace Aptos; using Aptos.Exceptions; -using Aptos.Schemes; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; @@ -25,7 +24,7 @@ public static bool IsCanonicalEd25519Signature(Signature signature) } } -public class Ed25519PublicKey : LegacyAccountPublicKey +public class Ed25519PublicKey : PublicKey { static readonly int LENGTH = 32; @@ -42,8 +41,6 @@ public Ed25519PublicKey(byte[] publicKey) : base(PublicKeyVariant.Ed25519) public override byte[] ToByteArray() => _key.ToByteArray(); - public override AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.Ed25519, _key.ToByteArray()); - public override bool VerifySignature(byte[] message, Signature signature) { if (!Ed25519.IsCanonicalEd25519Signature(signature)) return false; @@ -59,7 +56,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()); } @@ -121,7 +118,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 : Signature { static readonly int LENGTH = 64; @@ -141,6 +138,6 @@ public Ed25519Signature(byte[] signature) : base(SignatureVariant.Ed25519) 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()); } \ No newline at end of file diff --git a/Aptos/Aptos.Crypto/Ephemeral.cs b/Aptos/Aptos.Crypto/Ephemeral.cs index b96dbeb..646a8a8 100644 --- a/Aptos/Aptos.Crypto/Ephemeral.cs +++ b/Aptos/Aptos.Crypto/Ephemeral.cs @@ -2,14 +2,14 @@ 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) : base(publicKey.Type) + public EphemeralPublicKey(PublicKey publicKey) : base(publicKey.Type) { PublicKey = publicKey; switch (publicKey.Type) @@ -33,7 +33,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 @@ -48,9 +48,9 @@ public class EphemeralSignature : Signature { public readonly Signature Signature; - public readonly SignatureVariant Type; + public override Hex Value => Signature.Value; - public EphemeralSignature(LegacySignature signature) + public EphemeralSignature(Signature signature) : base(signature.Type) { Signature = signature; switch (signature.Type) @@ -70,7 +70,7 @@ 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(); return variant switch @@ -79,4 +79,82 @@ public static EphemeralSignature Deserialize(Deserializer 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); + } } \ No newline at end of file diff --git a/Aptos/Aptos.Crypto/Keyless.cs b/Aptos/Aptos.Crypto/Keyless.cs index be71260..25ba1a5 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 @@ -39,9 +38,23 @@ public static byte[] ComputeIdCommitment(string uidKey, string uidVal, string au return BytesHash.BigIntegerToBytesLE(Hash.PoseidonHash(fields), 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; @@ -61,14 +74,6 @@ public KeylessPublicKey(string iss, byte[] idCommitment) : base(PublicKeyVariant 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(); public override byte[] ToByteArray() => BcsToBytes(); @@ -79,7 +84,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(); @@ -91,7 +96,7 @@ public static KeylessPublicKey Deserialize(Deserializer d) } -public class KeylessSignature : LegacySignature +public class KeylessSignature : Signature { public readonly EphemeralCertificate EphemeralCertificate; @@ -126,7 +131,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(); @@ -135,76 +140,6 @@ public static KeylessSignature Deserialize(Deserializer d) EphemeralSignature ephemeralSignature = EphemeralSignature.Deserialize(d); return new KeylessSignature(ephemeralCertificate, jwtHeader, expiryDateSecs, ephemeralPublicKey, ephemeralSignature); } -} - -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); - } -} \ No newline at end of file diff --git a/Aptos/Aptos.Crypto/MultiKey.cs b/Aptos/Aptos.Crypto/MultiKey.cs index d47e184..653516b 100644 --- a/Aptos/Aptos.Crypto/MultiKey.cs +++ b/Aptos/Aptos.Crypto/MultiKey.cs @@ -1,7 +1,5 @@ namespace Aptos; -using Aptos.Schemes; - public partial class MultiKey { @@ -40,69 +38,74 @@ public static int BitCount(byte b) } -public partial class MultiKey : UnifiedAccountPublicKey +public partial class MultiKey : PublicKey { - public readonly List PublicKeys; + public override Hex Value => Hex.FromHexInput(BcsToBytes()); + + public readonly List PublicKeys; public readonly byte SignaturesRequired; - public MultiKey(List publicKeys, byte signaturesRequired) + public MultiKey(List publicKeys, byte signaturesRequired) : base(PublicKeyVariant.MultiKey) { if (signaturesRequired < 1) throw new ArgumentException("Signatures required must be greater than 0"); if (signaturesRequired > publicKeys.Count) throw new ArgumentException("Signatures required must be less than or equal to the number of public keys"); // 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 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 override AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.MultiKey, BcsToBytes()); - public override 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) + public static new MultiKey Deserialize(Deserializer d) { - List publicKeys = d.Vector(AnyPublicKey.Deserialize).Cast().ToList(); + List publicKeys = d.Vector(PublicKey.Deserialize).Cast().ToList(); byte signaturesRequired = d.U8(); return new MultiKey(publicKeys, signaturesRequired); } } -public class MultiKeySignature : UnifiedSignature +public class MultiKeySignature : Signature { + public override Hex Value => Hex.FromHexInput(BcsToBytes()); 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) : this(signatures, MultiKey.CreateBitmap(bitmap)) { } - public MultiKeySignature(List signatures, byte[] bitmap) + public MultiKeySignature(List signatures, byte[] bitmap) : base(SignatureVariant.MultiKey) { // 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) throw new ArgumentException($"Bitmap length should be {BITMAP_LEN}"); @@ -116,13 +119,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) + public static new MultiKeySignature Deserialize(Deserializer d) { - List signatures = d.Vector(AnySignature.Deserialize).Cast().ToList(); + List signatures = d.Vector(Signature.Deserialize).Cast().ToList(); byte[] bitmap = d.Bytes(); return new MultiKeySignature(signatures, bitmap); } diff --git a/Aptos/Aptos.Crypto/PublicKey.cs b/Aptos/Aptos.Crypto/PublicKey.cs index f4590c7..1aaf9a5 100644 --- a/Aptos/Aptos.Crypto/PublicKey.cs +++ b/Aptos/Aptos.Crypto/PublicKey.cs @@ -1,7 +1,33 @@ 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 = 0, + [EnumMember(Value = "secp256k1_ecdsa")] + Secp256k1Ecdsa = 1, + [EnumMember(Value = "keyless")] + Keyless = 3, + // Unified public key variants (these do not exist on the blockchain) + [EnumMember(Value = "multikey")] + MultiKey = 99, +} + +[JsonConverter(typeof(PublicKeyConverter))] +public abstract class PublicKey(PublicKeyVariant type) : Serializable { + [JsonProperty("type")] + public PublicKeyVariant Type = type; + + [JsonProperty("value")] + public abstract Hex Value { get; } + public bool VerifySignature(string message, Signature signature) => VerifySignature(SigningMessage.Convert(message), signature); public abstract bool VerifySignature(byte[] message, Signature signature); @@ -10,4 +36,56 @@ public abstract class PublicKey : Serializable public override string ToString() => Hex.FromHexInput(ToByteArray()).ToString(); + 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), + PublicKeyVariant.MultiKey => MultiKey.Deserialize(d), + _ => throw new ArgumentException("Invalid public key variant"), + }; + } + + public static bool IsSigningKey(PublicKey publicKey) => publicKey is not MultiKey; + } + +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(); + } + } +} \ No newline at end of file diff --git a/Aptos/Aptos.Crypto/Secp256k1.cs b/Aptos/Aptos.Crypto/Secp256k1.cs index 0be30e7..1aa6f36 100644 --- a/Aptos/Aptos.Crypto/Secp256k1.cs +++ b/Aptos/Aptos.Crypto/Secp256k1.cs @@ -20,7 +20,7 @@ public static class Secp256k1 public static readonly BigInteger HALF_CURVE_ORDER = CustomNamedCurves.GetByName("secp256k1").Curve.Order.ShiftRight(1); } -public class Secp256k1PublicKey : LegacyPublicKey +public class Secp256k1PublicKey : PublicKey { static readonly int LENGTH = 65; @@ -57,7 +57,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()); } @@ -144,7 +144,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 : Signature { static readonly int LENGTH = 64; @@ -163,5 +163,5 @@ public Secp256k1Signature(byte[] signature) : base(SignatureVariant.Secp256k1Ecd 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()); } \ No newline at end of file diff --git a/Aptos/Aptos.Crypto/Signature.cs b/Aptos/Aptos.Crypto/Signature.cs index cb96018..704dc2d 100644 --- a/Aptos/Aptos.Crypto/Signature.cs +++ b/Aptos/Aptos.Crypto/Signature.cs @@ -5,16 +5,6 @@ namespace Aptos; 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. -/// -public abstract class Signature : Serializable -{ - public abstract byte[] ToByteArray(); - - public override string ToString() => Hex.FromHexInput(ToByteArray()).ToString(); -} - [JsonConverter(typeof(StringEnumConverter))] public enum SignatureVariant { @@ -24,16 +14,17 @@ public enum SignatureVariant Secp256k1Ecdsa = 1, [EnumMember(Value = "keyless")] Keyless = 3, + // Unified Signatures variants (these do not exist on the blockchain) + [EnumMember(Value = "multikey")] + MultiKey = 99, } + /// -/// Signature for results of signing transactions/messages using a authentication scheme (e.g. Ed25519, Keyless, etc.) +/// Base signatures for anything signed (not specific to signing transactions/messages). This may include all signatures needed for ZK proofs, Certificates, etc. /// -/// -/// The type of the signature (e.g. Ed25519, Keyless, etc.) -/// -[JsonConverter(typeof(LegacySignatureConverter))] -public abstract class LegacySignature(SignatureVariant type) : Signature +[JsonConverter(typeof(SignatureConverter))] +public abstract class Signature(SignatureVariant type) : Serializable { [JsonProperty("type")] public SignatureVariant Type = type; @@ -41,19 +32,34 @@ public abstract class LegacySignature(SignatureVariant type) : Signature [JsonProperty("value")] public abstract Hex Value { get; } + public abstract byte[] ToByteArray(); + + public override string ToString() => Hex.FromHexInput(ToByteArray()).ToString(); + + public static Signature Deserialize(Deserializer d) + { + SignatureVariant variant = (SignatureVariant)d.Uleb128AsU32(); + return variant switch + { + SignatureVariant.Ed25519 => Ed25519Signature.Deserialize(d), + SignatureVariant.Secp256k1Ecdsa => Secp256k1Signature.Deserialize(d), + SignatureVariant.Keyless => KeylessSignature.Deserialize(d), + SignatureVariant.MultiKey => MultiKeySignature.Deserialize(d), + _ => throw new ArgumentException("Invalid signature variant"), + }; + } } -public abstract class UnifiedSignature : Signature { } -public class LegacySignatureConverter : JsonConverter +public class SignatureConverter : JsonConverter { - public override LegacySignature? ReadJson(JsonReader reader, Type objectType, LegacySignature? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override Signature? ReadJson(JsonReader reader, Type objectType, Signature? 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"); + if (anyValue == null) throw new Exception("Invalid Signature shape"); return type switch { @@ -64,7 +70,7 @@ public class LegacySignatureConverter : JsonConverter }; } - public override void WriteJson(JsonWriter writer, LegacySignature? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, Signature? value, JsonSerializer serializer) { if (value == null) writer.WriteNull(); else diff --git a/Aptos/Aptos.Crypto/SingleKey.cs b/Aptos/Aptos.Crypto/SingleKey.cs deleted file mode 100644 index 4103681..0000000 --- a/Aptos/Aptos.Crypto/SingleKey.cs +++ /dev/null @@ -1,83 +0,0 @@ -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"), - }; - } - -} - -public class AnySignature(LegacySignature signature) : UnifiedSignature -{ - public readonly Signature Signature = signature; - - public readonly SignatureVariant Type = signature.Type; - - public AnySignature(Signature signature) : this(signature is LegacySignature accountSignature ? accountSignature : throw new ArgumentException("Invalid signature type")) { } - - public override byte[] ToByteArray() => Signature.ToByteArray(); - - public override void Serialize(Serializer s) - { - s.U32AsUleb128((uint)Type); - Signature.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"), - }; - } -} \ No newline at end of file diff --git a/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs b/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs index 8ed192a..6ff4737 100644 --- a/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs +++ b/Aptos/Aptos.Transactions/Authenticator/AccountAuthenticatorSingleKey.cs @@ -1,22 +1,33 @@ namespace Aptos; -public class AccountAuthenticatorSingleKey(AnyPublicKey publicKey, AnySignature signature) : AccountAuthenticator +public class AccountAuthenticatorSingleKey : AccountAuthenticator { - public readonly AnyPublicKey PublicKey = publicKey; + public readonly PublicKey PublicKey; - public readonly AnySignature Signature = signature; + public readonly Signature Signature; + + public AccountAuthenticatorSingleKey(PublicKey publicKey, Signature signature) + { + if (!PublicKey.IsSigningKey(PublicKey)) throw new ArgumentException("SingleKey account authenticator only supports signing keys (e.g. Ed25519, Keyless, Secp256k1)"); + PublicKey = publicKey; + 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); + Signature signature = Signature.Deserialize(d); return new AccountAuthenticatorSingleKey(publicKey, signature); } } \ No newline at end of file diff --git a/Aptos/Aptos.Transactions/TransactionBuilder.cs b/Aptos/Aptos.Transactions/TransactionBuilder.cs index d50bb0e..cb0b89f 100644 --- a/Aptos/Aptos.Transactions/TransactionBuilder.cs +++ b/Aptos/Aptos.Transactions/TransactionBuilder.cs @@ -17,12 +17,12 @@ 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)); + return new AccountAuthenticatorSingleKey(keylessPublicKey, Keyless.GetSimulationSignature()); } - throw new InvalidPublicKey(publicKey, "Authenticator for simulation cannot be derived for this PublicKey."); + return new AccountAuthenticatorSingleKey(publicKey, invalidSignature); } #endregion @@ -42,7 +42,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 (data.Transaction.SecondarySignerAddresses?.Count > 0 && data.AdditionalSignersAuthenticators == null) throw new ArgumentException("AdditionalSignersAuthenticators is required for transactions that have secondary signer addresses"); @@ -60,6 +60,10 @@ public static SignedTransaction GeneratedSignedTransaction(SubmitTransactionData { transactionAuthenticator = new TransactionAuthenticatorSingleSender(singleKeyAuthenticator); } + else if (data.SenderAuthenticator is AccountAuthenticatorMultiKey multiKeyAuthenticator) + { + transactionAuthenticator = new TransactionAuthenticatorSingleSender(multiKeyAuthenticator); + } if (transactionAuthenticator == null) throw new ArgumentException("Invalid authentication scheme");