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");