Skip to content

Commit

Permalink
Add support for federated keyless (#495)
Browse files Browse the repository at this point in the history
* Add support for federated keyless

* update

* federated local

* add example

* undup code

* fmt

* update examples

* update tests

* revert endpoints

* update util

* add todo

* update jwk update

* address comments

* update

* update

* update ternary

* fix async proof submission

* fixes

* fmt

* update changelog

* fix examples

* fund account

* fix test
  • Loading branch information
heliuchuan authored Sep 24, 2024
1 parent c75501c commit b20adf5
Show file tree
Hide file tree
Showing 23 changed files with 1,144 additions and 377 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
- Populate `coinType` for `getAccountCoinAmount` if only `faMetadataAddress` is provided.
- [`Fix`] `getModuleEventsByEventType` will also account for EventHandle events.
- [`Hot Fix`] change regex to find object address when using `createObjectAndPublishPackage` move function
- Add support for federated keyless accounts as defined in AIP-96

# 1.27.1 (2024-08-23)

Expand Down
124 changes: 124 additions & 0 deletions examples/typescript/federated_keyless.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* eslint-disable max-len */
/* eslint-disable no-console */

/**
* This example shows how to use the Federated Keyless accounts on Aptos
*/

import { Account, AccountAddress, Aptos, AptosConfig, EphemeralKeyPair, Network } from "@aptos-labs/ts-sdk";
import * as readlineSync from "readline-sync";

const ALICE_INITIAL_BALANCE = 100_000_000;
const BOB_INITIAL_BALANCE = 100_000_000;
const TRANSFER_AMOUNT = 10_000;

/**
* Prints the balance of an account
* @param aptos
* @param name
* @param address
* @returns {Promise<*>}
*
*/
const balance = async (aptos: Aptos, name: string, address: AccountAddress) => {
const amount = await aptos.getAccountAPTAmount({
accountAddress: address,
});
console.log(`${name}'s balance is: ${amount}`);
return amount;
};

const example = async () => {
// Setup the client
const config = new AptosConfig({ network: Network.DEVNET });
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const ephemeralKeyPair = EphemeralKeyPair.generate();

console.log("\n=== Federated Keyless Account Example ===\n");

const link = `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=${ephemeralKeyPair.nonce}`;
console.log(`${link}\n`);

console.log("1. Open the link above");
console.log("2. Log in with your Google account");
console.log("3. Click 'Exchange authorization code for tokens");
console.log("4. Copy the 'id_token' - (toggling 'Wrap lines' option at the bottom makes this easier)\n");

function inputJwt(): string {
const jwt: string = readlineSync.question("Paste the JWT (id_token) token here and press enter:\n\n", {
hideEchoBack: false,
});
return jwt;
}

const jwt = inputJwt();

const bob = Account.generate();

// Derive the Keyless Account from the JWT and ephemeral key pair.
const alice = await aptos.deriveKeylessAccount({
jwt,
ephemeralKeyPair,
jwkAddress: bob.accountAddress,
});

console.log("\n=== Addresses ===\n");
console.log(`Alice's keyless account address is: ${alice.accountAddress}`);
console.log(`Alice's nonce is: ${ephemeralKeyPair.nonce}`);
console.log(`Alice's ephem pubkey is: ${ephemeralKeyPair.getPublicKey().toString()}`);
console.log(`\nBob's account address is: ${bob.accountAddress}`);

// Fund the accounts
console.log("\n=== Funding accounts ===\n");

await aptos.fundAccount({
accountAddress: alice.accountAddress,
amount: ALICE_INITIAL_BALANCE,
options: { waitForIndexer: false },
});
await aptos.fundAccount({
accountAddress: bob.accountAddress,
amount: BOB_INITIAL_BALANCE,
options: { waitForIndexer: false },
});

// // Show the balances
console.log("\n=== Balances ===\n");
const aliceBalance = await balance(aptos, "Alice", alice.accountAddress);
const bobBalance = await balance(aptos, "Bob", bob.accountAddress);

const iss = "https://dev-qtdgjv22jh0v1k7g.us.auth0.com/";

console.log("\n=== Installing JWKs ===\n");
const jwkTxn = await aptos.updateFederatedKeylessJwkSetTransaction({ sender: bob, iss });
const committedJwkTxn = await aptos.signAndSubmitTransaction({ signer: bob, transaction: jwkTxn });
await aptos.waitForTransaction({ transactionHash: committedJwkTxn.hash });
console.log(`Committed transaction: ${committedJwkTxn.hash}`);

// Transfer between users
const transaction = await aptos.transferCoinTransaction({
sender: alice.accountAddress,
recipient: bob.accountAddress,
amount: TRANSFER_AMOUNT,
});

console.log("\n=== Transferring ===\n");
const committedTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction });

await aptos.waitForTransaction({ transactionHash: committedTxn.hash });
console.log(`Committed transaction: ${committedTxn.hash}`);

console.log("\n=== Balances after transfer ===\n");
const newAliceBalance = await balance(aptos, "Alice", alice.accountAddress);
const newBobBalance = await balance(aptos, "Bob", bob.accountAddress);

// Bob should have the transfer amount minus gas to insert jwk
if (TRANSFER_AMOUNT <= newBobBalance - bobBalance) throw new Error("Bob's balance after transfer is incorrect");

// Alice should have the remainder minus gas
if (TRANSFER_AMOUNT >= aliceBalance - newAliceBalance) throw new Error("Alice's balance after transfer is incorrect");
};

example();
68 changes: 68 additions & 0 deletions examples/typescript/jwk_update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint-disable max-len */
/* eslint-disable no-console */

/**
* This example shows how to use install JWKs on an account to support Federated Keyless Accounts
*/

import { Aptos, AptosConfig, EphemeralKeyPair, Network } from "@aptos-labs/ts-sdk";
import * as readlineSync from "readline-sync";

const example = async () => {
const config = new AptosConfig({ network: Network.DEVNET });
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const ephemeralKeyPair = EphemeralKeyPair.generate();

console.log("\n=== Federated Keyless JWK Installation ===\n");

const link = `https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&prompt=consent&response_type=code&client_id=407408718192.apps.googleusercontent.com&scope=openid&access_type=offline&service=lso&o2v=2&theme=glif&flowName=GeneralOAuthFlow&nonce=${ephemeralKeyPair.nonce}`;
console.log(`${link}\n`);

console.log("1. Open the link above");
console.log("2. Log in with your Google account");
console.log("3. Click 'Exchange authorization code for tokens");
console.log("4. Copy the 'id_token' - (toggling 'Wrap lines' option at the bottom makes this easier)\n");

function inputJwt(): string {
const jwt: string = readlineSync.question("Paste the JWT (id_token) token here and press enter:\n\n", {
hideEchoBack: false,
});
return jwt;
}

function inputIss(): string {
const jwt: string = readlineSync.question(
"\nInput the iss claim of your federated OIDC provider and press enter (e.g. https://dev-qtdgjv22jh0v1k7g.us.auth0.com/):\n\n",
{
hideEchoBack: false,
},
);
return jwt;
}

const jwt = inputJwt();
const iss = inputIss();

const alice = await aptos.deriveKeylessAccount({
jwt,
ephemeralKeyPair,
});
await aptos.fundAccount({
accountAddress: alice.accountAddress,
amount: 100_000_000,
});

const jwkTxn = await aptos.updateFederatedKeylessJwkSetTransaction({ sender: alice, iss });
await aptos.signAndSubmitTransaction({ signer: alice, transaction: jwkTxn });

console.log("\n=== Addresses ===\n");
console.log(`JWKs were installed at - ${alice.accountAddress}\n`);
console.log("Use it to construct Federated Keyless Accounts for your federated JWT tokens\n\n");
console.log(
`await aptos.deriveKeylessAccount({\n jwt,\n ephemeralKeyPair,\n jwkAddress: "${alice.accountAddress}",\n});`,
);
};

example();
12 changes: 4 additions & 8 deletions examples/typescript/keyless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { Account, AccountAddress, Aptos, AptosConfig, EphemeralKeyPair, Network } from "@aptos-labs/ts-sdk";
import * as readlineSync from "readline-sync";

const COIN_STORE = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>";
const ALICE_INITIAL_BALANCE = 100_000_000;
const BOB_INITIAL_BALANCE = 100;
const TRANSFER_AMOUNT = 10_000;
Expand All @@ -22,20 +21,17 @@ const TRANSFER_AMOUNT = 10_000;
*
*/
const balance = async (aptos: Aptos, name: string, address: AccountAddress) => {
type Coin = { coin: { value: string } };
const resource = await aptos.getAccountResource<Coin>({
const amount = await aptos.getAccountAPTAmount({
accountAddress: address,
resourceType: COIN_STORE,
});
const amount = Number(resource.coin.value);

console.log(`${name}'s balance is: ${amount}`);
return amount;
};

const example = async () => {
// Setup the client
const config = new AptosConfig({ network: Network.DEVNET });
const network = Network.DEVNET;
const config = new AptosConfig({ network });
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
Expand Down Expand Up @@ -101,7 +97,7 @@ const example = async () => {
const committedTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction });

await aptos.waitForTransaction({ transactionHash: committedTxn.hash });
console.log(`Committed transaction: ${committedTxn.hash}`);
console.log(`\nCommitted transaction:\nhttps://explorer.aptoslabs.com/txn/${committedTxn.hash}?network=${network}`);

console.log("\n=== Balances after transfer ===\n");
const newAliceBalance = await balance(aptos, "Alice", alice.accountAddress);
Expand Down
6 changes: 1 addition & 5 deletions examples/typescript/keyless_mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { AccountAddress, Aptos, AptosConfig, EphemeralKeyPair, Network } from "@aptos-labs/ts-sdk";
import * as readlineSync from "readline-sync";

const COIN_STORE = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>";
const TRANSFER_AMOUNT = 10;

/**
Expand All @@ -19,12 +18,9 @@ const TRANSFER_AMOUNT = 10;
*
*/
const balance = async (aptos: Aptos, address: AccountAddress) => {
type Coin = { coin: { value: string } };
const resource = await aptos.getAccountResource<Coin>({
const amount = await aptos.getAccountAPTAmount({
accountAddress: address,
resourceType: COIN_STORE,
});
const amount = Number(resource.coin.value);
return amount;
};

Expand Down
15 changes: 15 additions & 0 deletions examples/typescript/move/update_jwk/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "update_jwk"
version = "1.0.0"
authors = []

[addresses]

[dev-addresses]

[dependencies.AptosFramework]
git = "https://github.com/aptos-labs/aptos-core.git"
rev = "devnet"
subdir = "aptos-move/framework/aptos-framework"

[dev-dependencies]
Binary file added examples/typescript/move/update_jwk/script.mv
Binary file not shown.
25 changes: 25 additions & 0 deletions examples/typescript/move/update_jwk/scripts/update_jwk.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
script {
use aptos_framework::jwks;
use std::string::String;
use std::vector;
fun main(account: &signer, iss: vector<u8>, kid_vec: vector<String>, alg_vec: vector<String>, e_vec: vector<String>, n_vec: vector<String>) {
assert!(!vector::is_empty(&kid_vec), 0);
let num_jwk = vector::length<String>(&kid_vec);
assert!(vector::length(&alg_vec) == num_jwk , 0);
assert!(vector::length(&e_vec) == num_jwk, 0);
assert!(vector::length(&n_vec) == num_jwk, 0);

let remove_all_patch = jwks::new_patch_remove_all();
let patches = vector[remove_all_patch];
while (!vector::is_empty(&kid_vec)) {
let kid = vector::pop_back(&mut kid_vec);
let alg = vector::pop_back(&mut alg_vec);
let e = vector::pop_back(&mut e_vec);
let n = vector::pop_back(&mut n_vec);
let jwk = jwks::new_rsa_jwk(kid, alg, e, n);
let patch = jwks::new_patch_upsert_jwk(iss, jwk);
vector::push_back(&mut patches, patch)
};
jwks::patch_federated_jwks(account, patches);
}
}
2 changes: 2 additions & 0 deletions examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"external_signing": "ts-node external_signing.ts",
"your_coin": "ts-node your_coin.ts",
"your_fungible_asset": "ts-node your_fungible_asset.ts",
"federated_keyless": "ts-node federated_keyless.ts",
"jwk_update": "ts-node jwk_update.ts",
"keyless": "ts-node keyless.ts",
"keyless_mainnet": "ts-node keyless_mainnet.ts",
"local_node": "ts-node local_node.ts",
Expand Down
11 changes: 11 additions & 0 deletions examples/typescript/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,14 @@ export function getPackageBytesToPublish(filePath: string) {

return { metadataBytes, byteCode };
}

/**
* A convenience function to get a scripts byteCode
* @param filePath a path relative to the current working directory
*/
export function getMoveBytes(filePath: string) {
const cwd = process.cwd();
const modulePath = path.join(cwd, filePath);
const buffer = fs.readFileSync(modulePath);
return Uint8Array.from(buffer);
}
Loading

0 comments on commit b20adf5

Please sign in to comment.