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

Add federated keyless support #8

Merged
merged 5 commits into from
Nov 5, 2024
Merged
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
1 change: 1 addition & 0 deletions Aptos.Examples/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class RunExample
ViewFunctionExample.Run,
ComplexViewFunctionExample.Run,
SimpleTransferKeylessExample.Run,
SimpleTransferFederatedKeylessExample.Run,
SimpleTransferEd25519Example.Run,
SimpleTransferSingleKeyExample.Run,
SimpleTransferMultiKeyExample.Run,
Expand Down
91 changes: 91 additions & 0 deletions Aptos.Examples/SimpleTransferFederatedKeylessExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
namespace Aptos.Examples;

public class SimpleTransferFederatedKeylessExample
{
public static async Task Run()
{
var aptos = new AptosClient(new AptosConfig(Networks.Devnet));
KeylessAccount keylessAccount;
var bob = Account.Generate();
var ekp = EphemeralKeyPair.Generate();

Console.WriteLine("=== Keyless Account Example ===\n");
{
// Begin the login flow

var loginFlow =
$"https://dev-qtdgjv22jh0v1k7g.us.auth0.com/authorize?client_id=dzqI77x0M5YwdOSUx6j25xkdOt8SIxeE&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fcallback&response_type=id_token&scope=openid&nonce={ekp.Nonce}";
Console.WriteLine($"Login URL: {loginFlow} \n");

Console.WriteLine("1. Open the link above in your browser");
Console.WriteLine("2. Login with your Auth0 account");
Console.WriteLine("3. Copy the 'id_token' from the url bar\n");

// Ask for the JWT token

Console.WriteLine("Paste the JWT (id_token) token here and press enter: ");
var jwt = Console.ReadLine();

Console.WriteLine("\nPaste the address where the JWKs are installed: ");
var address = Console.ReadLine();

// Derive the keyless account

Console.WriteLine("\nDeriving federated keyless account...");
if (jwt == null)
throw new ArgumentException("No JWT token provided");
keylessAccount = await aptos.Keyless.DeriveAccount(
jwt,
ekp,
jwkAddress: AccountAddress.FromString(address)
);

Console.WriteLine("=== Addresses ===\n");

Console.WriteLine($"Federated keyless account address is: {keylessAccount.Address}");
Console.WriteLine($"Bob account address is: {bob.Address}");
}

Console.WriteLine("\n=== Funding Accounts ===\n");
{
await aptos.Faucet.FundAccount(keylessAccount.Address, 100_000_000);
await aptos.Faucet.FundAccount(bob.Address, 100_000_000);

Console.WriteLine("Successfully funded keyless account!");
}

Console.WriteLine("\n=== Sending APT from Keyless Account to Bob ===\n");
{
Console.WriteLine("Building transaction...");
var txn = await aptos.Transaction.Build(
sender: keylessAccount,
data: new GenerateEntryFunctionPayloadData(
function: "0x1::aptos_account::transfer_coins",
typeArguments: ["0x1::aptos_coin::AptosCoin"],
functionArguments: [bob.Address, "100000"]
)
);

Console.WriteLine("Signing and submitting transaction...");
var pendingTxn = await aptos.Transaction.SignAndSubmitTransaction(keylessAccount, txn);
Console.WriteLine($"Submitted transaction with hash: {pendingTxn.Hash}");
var committedTxn = await aptos.Transaction.WaitForTransaction(pendingTxn.Hash);
if (committedTxn.Success)
{
Console.WriteLine("Transaction success!");
}
else
{
Console.WriteLine("Transaction failed!");
}
}

Console.WriteLine("\n=== Balances ===\n");
{
var keylessAccountBalance = await aptos.Account.GetCoinBalance(keylessAccount.Address);
var bobAccountBalance = await aptos.Account.GetCoinBalance(bob.Address);
Console.WriteLine($"Keyless account balance: {keylessAccountBalance?.Amount ?? 0} APT");
Console.WriteLine($"Bob account balance: {bobAccountBalance?.Amount ?? 0} APT");
}
}
}
46 changes: 42 additions & 4 deletions Aptos/Aptos.Accounts/KeylessAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,20 @@ public class KeylessAccount : Account

public readonly string Jwt;

public KeylessAccount(
private KeylessAccount(
SingleKey verifyingKey,
string jwt,
EphemeralKeyPair ekp,
ZeroKnowledgeSignature proof,
byte[] pepper,
string uidKey = "sub",
AccountAddress? address = null
string uidKey,
AccountAddress? address
)
{
if (pepper.Length != PEPPER_LENGTH)
throw new ArgumentException($"Pepper length in bytes should be {PEPPER_LENGTH}");

_verifyingKey = new SingleKey(KeylessPublicKey.FromJwt(jwt, pepper, uidKey));
_verifyingKey = verifyingKey;
_address = address ?? _verifyingKey.AuthKey().DerivedAddress();
EphemeralKeyPair = ekp;
Proof = proof;
Expand All @@ -66,6 +67,43 @@ public KeylessAccount(
UidVal = token.GetClaim(uidKey).Value;
}

public KeylessAccount(
string jwt,
EphemeralKeyPair ekp,
ZeroKnowledgeSignature proof,
byte[] pepper,
string uidKey = "sub",
AccountAddress? address = null
)
: this(
new SingleKey(KeylessPublicKey.FromJwt(jwt, pepper, uidKey)),
jwt,
ekp,
proof,
pepper,
uidKey,
address
) { }

public KeylessAccount(
AccountAddress jwkAddress,
string jwt,
EphemeralKeyPair ekp,
ZeroKnowledgeSignature proof,
byte[] pepper,
string uidKey = "sub",
AccountAddress? address = null
)
: this(
new SingleKey(FederatedKeylessPublicKey.FromJwt(jwt, pepper, jwkAddress, uidKey)),
jwt,
ekp,
proof,
pepper,
uidKey,
address
) { }

public bool VerifySignature(byte[] message, KeylessSignature signature)
{
if (EphemeralKeyPair.IsExpired())
Expand Down
21 changes: 14 additions & 7 deletions Aptos/Aptos.Clients/KeylessClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,29 @@ public async Task<KeylessAccount> DeriveAccount(
string jwt,
EphemeralKeyPair ekp,
string uidKey = "sub",
byte[]? pepper = null
byte[]? pepper = null,
AccountAddress? jwkAddress = null
)
{
if (pepper == null)
pepper = await GetPepper(jwt, ekp, uidKey);
var proof = await GetProof(jwt, ekp, pepper, uidKey);

// Derive the keyless account from the JWT and EphemeralKeyPair
var publicKey = KeylessPublicKey.FromJwt(jwt, pepper, uidKey);

var address = await _client.Account.LookupOriginalAccountAddress(
new SingleKey(publicKey).AuthKey().DerivedAddress().ToString()
new SingleKey(
jwkAddress != null
? FederatedKeylessPublicKey.FromJwt(jwt, pepper, jwkAddress, uidKey)
: KeylessPublicKey.FromJwt(jwt, pepper, uidKey)
)
.AuthKey()
.DerivedAddress()
.ToString()
);

// Create and return the keyless account
return new KeylessAccount(jwt, ekp, proof, pepper, uidKey, address);
// Create and return the keyless account using the appropriate constructor
return jwkAddress != null
? new KeylessAccount(jwkAddress, jwt, ekp, proof, pepper, uidKey, address)
: new KeylessAccount(jwt, ekp, proof, pepper, uidKey, address);
}

public async Task<byte[]> GetPepper(
Expand Down
3 changes: 3 additions & 0 deletions Aptos/Aptos.Crypto/PublicKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ JsonSerializer serializer
"ed25519" => new Ed25519PublicKey(anyValue.Value),
"secp256k1_ecdsa" => new Secp256k1PublicKey(anyValue.Value),
"keyless" => KeylessPublicKey.Deserialize(new Deserializer(anyValue.Value)),
"federated_keyless" => FederatedKeylessPublicKey.Deserialize(
new Deserializer(anyValue.Value)
),
_ => throw new Exception($"Unknown public key type: {type}"),
};
}
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- There should only be one <DefaultVersion> in the file. If moved, it should be updated in the GitHub Actions workflow. -->
<DefaultVersion>0.0.12</DefaultVersion>
<DefaultVersion>0.0.13</DefaultVersion>
<DefaultTargetFrameworks>net8.0;net7.0;net6.0;netstandard2.1</DefaultTargetFrameworks>
<DefaultTestingFrameworks>net8.0</DefaultTestingFrameworks>
</PropertyGroup>
Expand Down