Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[crypto] Refactor AuthenticationKey #2

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Aptos.Examples/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
{
{ "1", KeylessTransferExample.Run },
{ "2", SimpleTransferExample.Run },
{ "3", PlaygroundExample.Run }
{ "3", PlaygroundExample.Run },
{ "4", SimpleTransferSingleKeyExample.Run },
{ "5", SimpleTransferMultiKeyExample.Run },
};

public static async Task Main()
Expand Down Expand Up @@ -39,7 +41,7 @@
// Run the selected example using arrow keys and enter
if (exampleMap.TryGetValue(keys[selectedIndex], out var selectedExample))
{
Console.WriteLine($"\nThe {exampleMap[keys[selectedIndex]].Method.DeclaringType.Name} example was selected...\n");

Check warning on line 44 in Aptos.Examples/Main.cs

View workflow job for this annotation

GitHub Actions / Run Unit Tests

Dereference of a possibly null reference.

Check warning on line 44 in Aptos.Examples/Main.cs

View workflow job for this annotation

GitHub Actions / Run Unit Tests

Dereference of a possibly null reference.
await selectedExample();
}
break;
Expand All @@ -50,7 +52,7 @@
string input = keyInfo.KeyChar.ToString();
if (exampleMap.TryGetValue(input, out var selectedExample))
{
Console.WriteLine($"\nThe {exampleMap[keys[selectedIndex]].Method.DeclaringType.Name} example was selected...\n");

Check warning on line 55 in Aptos.Examples/Main.cs

View workflow job for this annotation

GitHub Actions / Run Unit Tests

Dereference of a possibly null reference.
await selectedExample();
break;
}
Expand Down
4 changes: 2 additions & 2 deletions Aptos.Examples/SimpleTransfer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
51 changes: 51 additions & 0 deletions Aptos.Examples/SimpleTransferMultiKey.cs
Original file line number Diff line number Diff line change
@@ -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");
}

}
45 changes: 45 additions & 0 deletions Aptos.Examples/SimpleTransferSingleKey.cs
Original file line number Diff line number Diff line change
@@ -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");
}

}
8 changes: 7 additions & 1 deletion Aptos/Aptos.Accounts/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public abstract class Account
/// <summary>
/// Gets the public key of the account.
/// </summary>
public abstract AccountPublicKey PublicKey { get; }
public abstract PublicKey PublicKey { get; }

/// <summary>
/// Gets the address of the account.
Expand All @@ -22,6 +22,12 @@ public abstract class Account
/// </summary>
public abstract SigningScheme SigningScheme { get; }

/// <summary>
/// Gets the authentication key for the account.
/// </summary>
/// <returns>The authentication key for the account.</returns>
public abstract AuthenticationKey AuthKey();

/// <inheritdoc cref="Sign(byte[])"/>
public Signature Sign(string message) => Sign(SigningMessage.Convert(message));

Expand Down
11 changes: 9 additions & 2 deletions Aptos/Aptos.Accounts/Ed25519Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class Ed25519Account : Account
/// <summary>
/// Gets the Ed25519PublicKey for the account.
/// </summary>
public override AccountPublicKey PublicKey => _publicKey;
public override PublicKey PublicKey => _publicKey;

private readonly AccountAddress _address;
/// <summary>
Expand All @@ -30,6 +30,13 @@ public class Ed25519Account : Account
/// </summary>
public override SigningScheme SigningScheme => SigningScheme.Ed25519;

/// <summary
/// Gets the authentication key for the account.
/// </summary>
/// <returns>The authentication key for the account.</returns>
public override AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.Ed25519, _publicKey.ToByteArray());


/// <inheritdoc cref="Ed25519Account(Ed25519PrivateKey, AccountAddress?)"/>
public Ed25519Account(Ed25519PrivateKey privateKey) : this(privateKey, (AccountAddress?)null) { }

Expand All @@ -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;
}

Expand Down
12 changes: 2 additions & 10 deletions Aptos/Aptos.Accounts/EphemeralKeyPair.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand Down
16 changes: 12 additions & 4 deletions Aptos/Aptos.Accounts/KeylessAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class KeylessAccount : Account
/// <summary>
/// Gets the KeylessPublicKey for the account.
/// </summary>
public override AccountPublicKey PublicKey => _publicKey;
public override PublicKey PublicKey => _publicKey;

private readonly AccountAddress _address;
/// <summary>
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
8 changes: 5 additions & 3 deletions Aptos/Aptos.Accounts/MultiKeyAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public class MultiKeyAccount : Account
/// <summary>
/// Gets the MultiKeyPublicKey for the account.
/// </summary>
public override AccountPublicKey PublicKey => _publicKey;
public override PublicKey PublicKey => _publicKey;

/// <summary>
/// Gets the address of the account.
/// </summary>
public override AccountAddress Address => PublicKey.AuthKey().DerivedAddress();
public override AccountAddress Address => AuthKey().DerivedAddress();

/// <summary>
/// The signers used to sign messages. These signers should correspond to public keys in the
Expand All @@ -42,6 +42,8 @@ public class MultiKeyAccount : Account
/// </summary>
public override SigningScheme SigningScheme => SigningScheme.MultiKey;

public override AuthenticationKey AuthKey() => AuthenticationKey.FromSchemeAndBytes(AuthenticationKeyScheme.MultiKey, _publicKey.BcsToBytes());

/// <summary>
/// Initializes a new instance of the MultiKeyAccount class with a MultiKey and a list of signers.
///
Expand All @@ -62,7 +64,7 @@ public MultiKeyAccount(MultiKey multiKey, List<Account> 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
Expand Down
27 changes: 18 additions & 9 deletions Aptos/Aptos.Accounts/SingleKeyAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 8 additions & 8 deletions Aptos/Aptos.Api/AccountSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ILegacyPublicKey> publicKeys, List<LegacySignature> signatures, byte signaturesRequired) : AccountSignature(SigningScheme.MultiKey)
public class AccountMultiKeySignature(List<PublicKey> publicKeys, List<AccountMultiKeySignature.IndexedAccountSignature> 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<ILegacyPublicKey> PublicKeys = publicKeys;
public List<PublicKey> PublicKeys = publicKeys;

[JsonProperty("signatures")]
public List<IndexedAccountSignature> Signatures = signatures.Select((sig, i) => new IndexedAccountSignature((byte)i, sig)).ToList();
public List<IndexedAccountSignature> Signatures = signatures;

[JsonProperty("signatures_required")]
public byte SignaturesRequired = signaturesRequired;
Expand Down
13 changes: 13 additions & 0 deletions Aptos/Aptos.Api/TransactionSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ public class TransactionSignatureConverter : JsonConverter<TransactionSignature>
signature = JsonConvert.DeserializeObject<AccountSingleKeySignature>(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<AccountMultiKeySignature>(jsonObject.ToString());
}
}

if (signature == null) throw new Exception("Invalid account signature");

Expand Down
Loading
Loading