Skip to content

Commit

Permalink
[crypto] Refactor Keys, added examples, and Keyless simulation signat…
Browse files Browse the repository at this point in the history
…ure (#3)

* Add examples

* Refactor key naming

* Add keyless simulation signatures

* Fix formatting

* Add SingleKey tests
  • Loading branch information
GhostWalker562 authored Sep 24, 2024
1 parent de8fa01 commit f481389
Show file tree
Hide file tree
Showing 30 changed files with 743 additions and 582 deletions.
4 changes: 3 additions & 1 deletion Aptos.Examples/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ public class RunExample
{
{ "1", KeylessTransferExample.Run },
{ "2", SimpleTransferExample.Run },
{ "3", PlaygroundExample.Run },
{ "3", SimpleTransferSingleKeyExample.Run },
{ "4", SimpleTransferMultiKeyExample.Run },
{ "5", PlaygroundExample.Run },
};

public static async Task Main()
Expand Down
50 changes: 50 additions & 0 deletions Aptos.Examples/SimpleTransferMultiKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Newtonsoft.Json;

namespace Aptos.Examples;

public class SimpleTransferMultiKeyExample
{
public static async Task Run()
{
Console.WriteLine("=== Addresses ===\n");
var aptos = new AptosClient(new AptosConfig(Networks.Devnet));
var aliceSigner = Account.Generate();
var bobSigner = SingleKeyAccount.Generate();
var account = new MultiKeyAccount(
new([aliceSigner.PublicKey, bobSigner.PublicKey], 1),
[aliceSigner]
);
Console.WriteLine($"Alice-Bob MultiKey (1/2): {account.Address}");

Console.WriteLine("\n=== Funding accounts ===\n");
var aliceFundTxn = await aptos.Faucet.FundAccount(account.Address, 100_000_000);
Console.WriteLine($"Alice-Bob MultiKey (1/2)'s fund transaction: {aliceFundTxn.Hash}");

Console.WriteLine("\n=== Building transaction ===\n");

var txn = await aptos.Transaction.Build(
sender: account.Address,
data: new GenerateEntryFunctionPayloadData(
function: "0x1::coin::transfer",
typeArguments: ["0x1::aptos_coin::AptosCoin"],
functionArguments: [account.Address, "100000"]
)
);
Console.WriteLine($"{JsonConvert.SerializeObject(txn.RawTransaction)}");

Console.WriteLine("\n=== Signing and submitting transaction ===\n");

var pendingTxn = await aptos.Transaction.SignAndSubmitTransaction(account, txn);
Console.WriteLine($"Submitted transaction with hash: {pendingTxn.Hash}");

Console.WriteLine("Waiting for transaction...");
var committedTxn = await aptos.Transaction.WaitForTransaction(pendingTxn.Hash.ToString());
Console.WriteLine(
$"Transaction {committedTxn.Hash} is {(committedTxn.Success ? "success" : "failure")}"
);

Console.WriteLine("\n=== Account Balance ===\n");
var balance = await aptos.Account.GetCoinBalance(account.Address, "0xa");
Console.WriteLine($"Account {account.Address} has {balance?.Amount ?? 0} coin balances");
}
}
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");
}
}
81 changes: 81 additions & 0 deletions Aptos.Tests/Aptos.Crypto/SingleKey.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
namespace Aptos.Tests.Core;

using Xunit.Gherkin.Quick;

[FeatureFile("../../../../features/single_key.feature")]
public sealed class SingleKeyFeatureTests : Feature
{
private string? _publicKeyType;

private dynamic? _inputValue;

private dynamic? _output;

[Given(@"(ed25519|secp256k1|keyless) (.*)")]
public void GivenValue(string type, string value)
{
PublicKey publicKey = type switch
{
"ed25519" => Ed25519PublicKey.Deserialize(new(value)),
"secp256k1" => Secp256k1PublicKey.Deserialize(new(value)),
"keyless" => KeylessPublicKey.Deserialize(new(value)),
_ => throw new ArgumentException("Invalid public key type"),
};
_publicKeyType = type;
_inputValue = new SingleKey(publicKey);
}

[When(@"I serialize")]
public void WhenISerialize()
{
if (_inputValue == null)
throw new ArgumentException("No input value");
if (_inputValue is SingleKey singleKey)
_output = singleKey.BcsToHex();
}

[When(@"I derive authentication key")]
public void WhenIDeriveAuthenticationKey()
{
if (_inputValue == null)
throw new ArgumentException("No input value");
if (_inputValue is SingleKey singleKey)
_output = singleKey.AuthKey().BcsToHex();
}

[When(@"I verify signature (.*) with message (.*)")]
public void WhenIVerifySignatureWithMessage(string signature, string message)
{
if (_inputValue == null)
throw new ArgumentException("No input value");

if (_inputValue is SingleKey singleKey)
{
PublicKeySignature publicKeySignature = _publicKeyType switch
{
"ed25519" => Ed25519Signature.Deserialize(new(signature)),
"secp256k1" => Secp256k1Signature.Deserialize(new(signature)),
"keyless" => KeylessSignature.Deserialize(new(signature)),
_ => throw new ArgumentException("Invalid signature public key type"),
};
_output = singleKey.VerifySignature(message, publicKeySignature);
}
}

[Then(@"the result should be (.*) (.*)")]
public void ThenTheResultShouldBeTypeValue(string type, string value)
{
if (_output == null)
throw new ArgumentException("No output value");

switch (type)
{
case "bool":
Assert.Equal(bool.Parse(value), _output);
break;
case "bcs":
Assert.Equal(Hex.FromHexString(value), _output);
break;
}
}
}
36 changes: 18 additions & 18 deletions Aptos/Aptos.Accounts/Account.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Aptos;

using Aptos.Schemes;

namespace Aptos;

/// <summary>
/// Abstract class representing a signer account.
/// </summary>
Expand All @@ -10,7 +10,7 @@ public abstract class Account
/// <summary>
/// Gets the public key of the account.
/// </summary>
public abstract AccountPublicKey PublicKey { get; }
public abstract IVerifyingKey VerifyingKey { get; }

/// <summary>
/// Gets the address of the account.
Expand All @@ -25,39 +25,39 @@ public abstract class Account
/// <inheritdoc cref="Sign(byte[])"/>
public Signature Sign(string message) => Sign(SigningMessage.Convert(message));

/// <summary>
/// Signs a transaction using the account's private key.
/// </summary>
/// <param name="transaction">The transaction to sign.</param>
/// <returns>The signed transaction.</returns>
public virtual Signature Sign(AnyRawTransaction transaction) =>
Sign(SigningMessage.GenerateForTransaction(transaction));

/// <summary>
/// Signs a message with the using the signer.
/// </summary>
/// <param name="message">The message to sign as a byte array.</param>
/// <returns>The signed message.</returns>
public abstract Signature Sign(byte[] message);

/// <summary>
/// Signs a transaction using the account's private key.
/// </summary>
/// <param name="transaction">The transaction to sign.</param>
/// <returns>The signature of the transaction.</returns>
public abstract Signature SignTransaction(AnyRawTransaction transaction);

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

/// <summary>
/// Signs a message and returns an authenticator for the account.
/// Signs a transaction and returns an authenticator for the account.
/// </summary>
/// <param name="message">The message to sign as a byte array.</param>
/// <param name="transaction">The transaction to sign.</param>
/// <returns>The authenticator containing the signature.</returns>
public abstract AccountAuthenticator SignWithAuthenticator(byte[] message);
public virtual AccountAuthenticator SignWithAuthenticator(AnyRawTransaction transaction) =>
SignWithAuthenticator(SigningMessage.GenerateForTransaction(transaction));

/// <summary>
/// Signs a transaction and returns an authenticator for the account.
/// Signs a message and returns an authenticator for the account.
/// </summary>
/// <param name="transaction">The transaction to sign.</param>
/// <param name="message">The message to sign as a byte array.</param>
/// <returns>The authenticator containing the signature.</returns>
public abstract AccountAuthenticator SignTransactionWithAuthenticator(
AnyRawTransaction transaction
);
public abstract AccountAuthenticator SignWithAuthenticator(byte[] message);

/// <summary>
/// Generates a new Ed25519 account.
Expand Down
35 changes: 11 additions & 24 deletions Aptos/Aptos.Accounts/Ed25519Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ public class Ed25519Account : Account
/// </summary>
public readonly Ed25519PrivateKey PrivateKey;

private readonly Ed25519PublicKey _publicKey;
private readonly Ed25519PublicKey _verifyingKey;

/// <summary>
/// Gets the Ed25519PublicKey for the account.
/// </summary>
public override AccountPublicKey PublicKey => _publicKey;
public override IVerifyingKey VerifyingKey => _verifyingKey;

/// <summary>
/// Gets the Ed25519PublicKey for the account.
/// </summary>
public PublicKey PublicKey => _verifyingKey;

private readonly AccountAddress _address;

Expand Down Expand Up @@ -50,8 +55,8 @@ public Ed25519Account(Ed25519PrivateKey privateKey, byte[]? address = null)
/// <param name="address">The account address.</param>
public Ed25519Account(Ed25519PrivateKey privateKey, AccountAddress? address = null)
{
_publicKey = (Ed25519PublicKey)privateKey.PublicKey();
_address = address ?? PublicKey.AuthKey().DerivedAddress();
_verifyingKey = (Ed25519PublicKey)privateKey.PublicKey();
_address = address ?? _verifyingKey.AuthKey().DerivedAddress();
PrivateKey = privateKey;
}

Expand All @@ -62,15 +67,7 @@ public Ed25519Account(Ed25519PrivateKey privateKey, AccountAddress? address = nu
/// <param name="signature">The signed message to verify.</param>
/// <returns>True if the signature is valid; otherwise, false.</returns>
public bool VerifySignature(byte[] message, Ed25519Signature signature) =>
PublicKey.VerifySignature(message, signature);

/// <summary>
/// Signs a transaction using the account's private key.
/// </summary>
/// <param name="transaction">The transaction to sign.</param>
/// <returns>The transaction signature.</returns>
public override Signature SignTransaction(AnyRawTransaction transaction) =>
Sign(SigningMessage.GenerateForTransaction(transaction));
_verifyingKey.VerifySignature(message, signature);

/// <summary>
/// Signs a message with the using the account's private key.
Expand All @@ -85,17 +82,7 @@ public override Signature SignTransaction(AnyRawTransaction transaction) =>
/// <param name="message">The message to sign as a byte array.</param>
/// <returns>The authenticator containing the signature.</returns>
public override AccountAuthenticator SignWithAuthenticator(byte[] message) =>
new AccountAuthenticatorEd25519(_publicKey, (Ed25519Signature)PrivateKey.Sign(message));

/// <summary>
/// Signs a transaction and returns an authenticator with the signature.
/// </summary>
/// <param name="transaction">The transaction to sign.</param>
/// <returns>The authenticator containing the signature.</returns>
public override AccountAuthenticator SignTransactionWithAuthenticator(
AnyRawTransaction transaction
) =>
new AccountAuthenticatorEd25519(_publicKey, (Ed25519Signature)SignTransaction(transaction));
new AccountAuthenticatorEd25519(_verifyingKey, (Ed25519Signature)Sign(message));

/// <summary>
/// Generates a new Ed25519 account.
Expand Down
17 changes: 2 additions & 15 deletions Aptos/Aptos.Accounts/EphemeralKeyPair.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,7 @@ public EphemeralKeyPair(
)
{
_privateKey = privateKey;
if (privateKey.PublicKey() is LegacyAccountPublicKey publicKey)
{
PublicKey = new EphemeralPublicKey(publicKey);
}
else
throw new ArgumentException(
"Invalid PrivateKey passed to EphemeralKeyPair. Expected LegacyAccountPublicKey."
);
PublicKey = new EphemeralPublicKey(privateKey.PublicKey());

// By default, the expiry timestamp is 14 days from now.
ExpiryTimestamp =
Expand All @@ -78,13 +71,7 @@ public EphemeralSignature Sign(byte[] data)
if (IsExpired())
throw new Exception("EphemeralKeyPair is expired");
var signature = _privateKey.Sign(data);
if (signature is LegacySignature legacySignature)
{
return new EphemeralSignature(legacySignature);
}
throw new ArgumentException(
"Invalid PrivateKey passed to EphemeralKeyPair. Expecting a legacy private key."
);
return new EphemeralSignature(signature);
}

public override void Serialize(Serializer s)
Expand Down
Loading

0 comments on commit f481389

Please sign in to comment.