From 21d8f9e43582fcb37d1eb06837698d7df7c82517 Mon Sep 17 00:00:00 2001 From: Philip Date: Sun, 19 Jan 2025 01:18:18 -0800 Subject: [PATCH 1/5] Initial work for AccountAbstraction --- examples/typescript/.gitignore | 3 +- .../any_authenticator_account_abstraction.ts | 92 +++++++ ...world_authenticator_account_abstraction.ts | 86 +++++++ .../move/account_abstraction/Move.toml | 10 + .../sources/any_authenticator.move | 11 + .../sources/hello_world_authenticator.move | 15 ++ examples/typescript/package.json | 2 + examples/typescript/utils.ts | 5 +- src/account/AbstractedAccount.ts | 81 ++++++ src/account/index.ts | 1 + src/api/account.ts | 7 +- src/api/account/abstraction.ts | 236 ++++++++++++++++++ src/api/aptos.ts | 3 + src/core/crypto/abstraction.ts | 47 ++++ src/internal/abstraction.ts | 79 ++++++ src/transactions/authenticator/account.ts | 50 +++- .../transactionBuilder/helpers.ts | 5 + src/types/abstraction.ts | 6 + src/types/types.ts | 1 + 19 files changed, 736 insertions(+), 4 deletions(-) create mode 100644 examples/typescript/any_authenticator_account_abstraction.ts create mode 100644 examples/typescript/hello_world_authenticator_account_abstraction.ts create mode 100644 examples/typescript/move/account_abstraction/Move.toml create mode 100644 examples/typescript/move/account_abstraction/sources/any_authenticator.move create mode 100644 examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move create mode 100644 src/account/AbstractedAccount.ts create mode 100644 src/api/account/abstraction.ts create mode 100644 src/core/crypto/abstraction.ts create mode 100644 src/internal/abstraction.ts create mode 100644 src/types/abstraction.ts diff --git a/examples/typescript/.gitignore b/examples/typescript/.gitignore index 0562045dd..200127b48 100644 --- a/examples/typescript/.gitignore +++ b/examples/typescript/.gitignore @@ -1,3 +1,4 @@ move/moonCoin/moonCoin.json move/moonCoin/test-package.json -move/facoin/facoin.json \ No newline at end of file +move/facoin/facoin.json +move/account_abstraction/*.json \ No newline at end of file diff --git a/examples/typescript/any_authenticator_account_abstraction.ts b/examples/typescript/any_authenticator_account_abstraction.ts new file mode 100644 index 000000000..56b14126f --- /dev/null +++ b/examples/typescript/any_authenticator_account_abstraction.ts @@ -0,0 +1,92 @@ +/* eslint-disable no-console */ + +import { + Account, + AbstractedAccount, + Aptos, + Network, + AptosConfig, + UserTransactionResponse, + Hex, +} from "@aptos-labs/ts-sdk"; +import { compilePackage, getPackageBytesToPublish } from "./utils"; + +const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); + +const main = async () => { + const alice = Account.generate(); + + console.log("\n=== Addresses ==="); + console.log(`Alice: ${alice.accountAddress.toString()}`); + + console.log("\n=== Funding Accounts ==="); + await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 1000000000000000 }); + console.log("Finished funding accounts!"); + + console.log("\n=== Compiling any_authenticator package locally ==="); + compilePackage( + "move/account_abstraction", + "move/account_abstraction/any_authenticator.json", + [{ name: "deployer", address: alice.accountAddress }], + ["--move-2"], + ); + const { metadataBytes, byteCode } = getPackageBytesToPublish("move/account_abstraction/any_authenticator.json"); + console.log(`\n=== Publishing any_authenticator package to ${aptos.config.network} network ===`); + const publishTxn = await aptos.publishPackageTransaction({ + account: alice.accountAddress, + metadataBytes, + moduleBytecode: byteCode, + }); + const pendingPublishTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: publishTxn }); + console.log(`Publish package transaction hash: ${pendingPublishTxn.hash}`); + await aptos.waitForTransaction({ transactionHash: pendingPublishTxn.hash }); + + console.log("\n=== Dispatchable authentication function info ==="); + + const authenticationFunction = `${alice.accountAddress}::any_authenticator::authenticate`; + const [moduleAddress, moduleName, functionName] = authenticationFunction.split("::"); + + console.log(`Module address: ${moduleAddress}`); + console.log(`Module name: ${moduleName}`); + console.log(`Function name: ${functionName}`); + + console.log( + `\n=== Changing ${alice.accountAddress.toString()} to use any_authenticator's AccountAbstraction function ===`, + ); + const enableAccountAbstractionTransaction = await aptos.abstraction.enableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingEnableAccountAbstractionTransaction = await aptos.signAndSubmitTransaction({ + signer: alice, + transaction: enableAccountAbstractionTransaction, + }); + console.log(`Enable account abstraction transaction hash: ${pendingEnableAccountAbstractionTransaction.hash}`); + await aptos.waitForTransaction({ transactionHash: pendingEnableAccountAbstractionTransaction.hash }); + + console.log("\n=== Signing a transaction with the abstracted account ==="); + + const abstractedAccount = new AbstractedAccount({ + signer: () => Hex.fromHexString("0x01").toUint8Array(), + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingTransferTxn = await aptos.signAndSubmitTransaction({ + signer: abstractedAccount, + transaction: await aptos.transferCoinTransaction({ + sender: abstractedAccount.accountAddress, + recipient: abstractedAccount.accountAddress, + amount: 100, + }), + }); + + const response = await aptos.waitForTransaction({ transactionHash: pendingTransferTxn.hash }); + console.log(`Committed transaction: ${response.hash}`); + + const txn = (await aptos.getTransactionByHash({ + transactionHash: pendingTransferTxn.hash, + })) as UserTransactionResponse; + console.log(`Transaction Signature: ${JSON.stringify(txn.signature, null, 2)}`); +}; + +main(); diff --git a/examples/typescript/hello_world_authenticator_account_abstraction.ts b/examples/typescript/hello_world_authenticator_account_abstraction.ts new file mode 100644 index 000000000..beb2a3ea1 --- /dev/null +++ b/examples/typescript/hello_world_authenticator_account_abstraction.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ + +import { Account, AbstractedAccount, Aptos, Network, AptosConfig, UserTransactionResponse } from "@aptos-labs/ts-sdk"; +import { compilePackage, getPackageBytesToPublish } from "./utils"; + +const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); + +const main = async () => { + const alice = Account.generate(); + + console.log("\n=== Addresses ==="); + console.log(`Alice: ${alice.accountAddress.toString()}`); + + console.log("\n=== Funding Accounts ==="); + await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 1000000000000000 }); + console.log("Finished funding accounts!"); + + console.log("\n=== Compiling hello_world_authenticator package locally ==="); + compilePackage( + "move/account_abstraction", + "move/account_abstraction/hello_world_authenticator.json", + [{ name: "deployer", address: alice.accountAddress }], + ["--move-2"], + ); + const { metadataBytes, byteCode } = getPackageBytesToPublish( + "move/account_abstraction/hello_world_authenticator.json", + ); + console.log(`\n=== Publishing hello_world_authenticator package to ${aptos.config.network} network ===`); + const publishTxn = await aptos.publishPackageTransaction({ + account: alice.accountAddress, + metadataBytes, + moduleBytecode: byteCode, + }); + const pendingPublishTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: publishTxn }); + console.log(`Publish package transaction hash: ${pendingPublishTxn.hash}`); + await aptos.waitForTransaction({ transactionHash: pendingPublishTxn.hash }); + + console.log("\n=== Dispatchable authentication function info ==="); + + const authenticationFunction = `${alice.accountAddress}::hello_world_authenticator::authenticate`; + const [moduleAddress, moduleName, functionName] = authenticationFunction.split("::"); + + console.log(`Module address: ${moduleAddress}`); + console.log(`Module name: ${moduleName}`); + console.log(`Function name: ${functionName}`); + + console.log( + `\n=== Changing ${alice.accountAddress.toString()} to use any_authenticator's AccountAbstraction function ===`, + ); + const enableAccountAbstractionTransaction = await aptos.abstraction.enableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingEnableAccountAbstractionTransaction = await aptos.signAndSubmitTransaction({ + signer: alice, + transaction: enableAccountAbstractionTransaction, + }); + console.log(`Enable account abstraction transaction hash: ${pendingEnableAccountAbstractionTransaction.hash}`); + await aptos.waitForTransaction({ transactionHash: pendingEnableAccountAbstractionTransaction.hash }); + + console.log("\n=== Signing a transaction with the abstracted account ==="); + + const abstractedAccount = new AbstractedAccount({ + accountAddress: alice.accountAddress, + signer: () => new TextEncoder().encode("hello world"), + authenticationFunction, + }); + const pendingTransferTxn = await aptos.signAndSubmitTransaction({ + signer: abstractedAccount, + transaction: await aptos.transferCoinTransaction({ + sender: abstractedAccount.accountAddress, + recipient: abstractedAccount.accountAddress, + amount: 100, + }), + }); + + const response = await aptos.waitForTransaction({ transactionHash: pendingTransferTxn.hash }); + console.log(`Committed transaction: ${response.hash}`); + + const txn = (await aptos.getTransactionByHash({ + transactionHash: pendingTransferTxn.hash, + })) as UserTransactionResponse; + console.log(`Transaction Signature: ${JSON.stringify(txn.signature, null, 2)}`); +}; + +main(); diff --git a/examples/typescript/move/account_abstraction/Move.toml b/examples/typescript/move/account_abstraction/Move.toml new file mode 100644 index 000000000..c9c250e1a --- /dev/null +++ b/examples/typescript/move/account_abstraction/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "account_abstraction" +version = "0.0.0" + +[addresses] +deployer = "_" + +[dependencies] +AptosFramework = { git = "https://github.com/aptos-labs/aptos-framework.git", subdir = "aptos-framework", rev = "devnet" } +AptosStdlib = { git = "https://github.com/aptos-labs/aptos-framework.git", subdir = "aptos-stdlib", rev = "devnet" } diff --git a/examples/typescript/move/account_abstraction/sources/any_authenticator.move b/examples/typescript/move/account_abstraction/sources/any_authenticator.move new file mode 100644 index 000000000..7d768a53c --- /dev/null +++ b/examples/typescript/move/account_abstraction/sources/any_authenticator.move @@ -0,0 +1,11 @@ +module deployer::any_authenticator { + use aptos_framework::auth_data::{AbstractionAuthData}; + + public fun authenticate( + account: signer, + _signing_data: AbstractionAuthData, + ): signer { + account + } + +} \ No newline at end of file diff --git a/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move b/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move new file mode 100644 index 000000000..6731dfc22 --- /dev/null +++ b/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move @@ -0,0 +1,15 @@ +module deployer::hello_world_authenticator { + use aptos_framework::auth_data::{Self, AbstractionAuthData}; + use std::bcs; + + const EINVALID_SIGNATURE: u64 = 1; + + public fun authenticate( + account: signer, + signing_data: AbstractionAuthData, + ): signer { + let authenticator = *auth_data::authenticator(&signing_data); // Dereference to get owned vector + assert!(authenticator == bcs::to_bytes(&b"hello world"), EINVALID_SIGNATURE); + account + } +} \ No newline at end of file diff --git a/examples/typescript/package.json b/examples/typescript/package.json index 7b186df56..99c970a0a 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -21,6 +21,8 @@ "keyless": "ts-node keyless.ts", "keyless_mainnet": "ts-node keyless_mainnet.ts", "local_node": "ts-node local_node.ts", + "any_authenticator_account_abstraction": "ts-node any_authenticator_account_abstraction.ts", + "hello_world_authenticator_account_abstraction": "ts-node hello_world_authenticator_account_abstraction.ts", "test": "run-s simple_transfer multi_agent_transfer simple_sponsored_transaction transfer_coin custom_client publish_package_from_filepath external_signing sign_struct publish_package_from_filepath external_signing your_coin your_fungible_asset" }, "keywords": [], diff --git a/examples/typescript/utils.ts b/examples/typescript/utils.ts index 5752a1b6e..b3f611749 100644 --- a/examples/typescript/utils.ts +++ b/examples/typescript/utils.ts @@ -16,6 +16,7 @@ export function compilePackage( packageDir: string, outputFile: string, namedAddresses: Array<{ name: string; address: AccountAddress }>, + args?: string[], ) { console.log("In order to run compilation, you must have the `aptos` CLI installed."); try { @@ -27,7 +28,9 @@ export function compilePackage( const addressArg = namedAddresses.map(({ name, address }) => `${name}=${address}`).join(" "); // Assume-yes automatically overwrites the previous compiled version, only do this if you are sure you want to overwrite the previous version. - const compileCommand = `aptos move build-publish-payload --json-output-file ${outputFile} --package-dir ${packageDir} --named-addresses ${addressArg} --assume-yes`; + let compileCommand = `aptos move build-publish-payload --json-output-file ${outputFile} --package-dir ${packageDir} --named-addresses ${addressArg} --assume-yes`; + if (args) compileCommand += ` ${args.join(" ")}`; + console.log("Running the compilation locally, in a real situation you may want to compile this ahead of time."); console.log(compileCommand); execSync(compileCommand); diff --git a/src/account/AbstractedAccount.ts b/src/account/AbstractedAccount.ts new file mode 100644 index 000000000..b74759cf8 --- /dev/null +++ b/src/account/AbstractedAccount.ts @@ -0,0 +1,81 @@ +import { sha3_256 } from "@noble/hashes/sha3"; +import { AccountAddress, AccountPublicKey } from "../core"; +import { AbstractPublicKey, AbstractSignature } from "../core/crypto/abstraction"; +import { SigningScheme, HexInput } from "../types"; +import { Account } from "./Account"; +import { AnyRawTransaction } from "../transactions/types"; +import { generateSigningMessageForTransaction } from "../transactions/transactionBuilder/signingMessage"; +import { AccountAuthenticatorAbstraction } from "../transactions/authenticator/account"; +import { isValidFunctionInfo } from "../transactions/transactionBuilder/helpers"; +import { Ed25519Account } from "./Ed25519Account"; +import { Serializer } from "../bcs/serializer"; + +type AbstractedAccountConstructorArgs = { + accountAddress: AccountAddress; + signer: (digest: HexInput) => HexInput; + authenticationFunction: string; +}; + +export class AbstractedAccount extends Account { + public readonly publicKey: AccountPublicKey; + + readonly accountAddress: AccountAddress; + + readonly authenticationFunction: string; + + readonly signingScheme = SigningScheme.SingleKey; + + constructor({ signer, accountAddress, authenticationFunction }: AbstractedAccountConstructorArgs) { + super(); + + if (!isValidFunctionInfo(authenticationFunction)) { + throw new Error(`Invalid authentication function ${authenticationFunction} passed into AbstractedAccount`); + } + + this.authenticationFunction = authenticationFunction; + this.accountAddress = accountAddress; + this.publicKey = new AbstractPublicKey(this.accountAddress); + this.sign = (digest: HexInput) => new AbstractSignature(signer(digest)); + } + + public static fromPermissionedSigner({ signer }: { signer: Ed25519Account }) { + return new AbstractedAccount({ + signer: (digest: HexInput) => { + const serializer = new Serializer(); + signer.publicKey.serialize(serializer); + signer.sign(digest).serialize(serializer); + return serializer.toUint8Array(); + }, + accountAddress: signer.accountAddress, + authenticationFunction: `${signer.accountAddress}::permissioned_delegation::authenticate`, + }); + } + + signWithAuthenticator(message: HexInput): AccountAuthenticatorAbstraction { + return new AccountAuthenticatorAbstraction( + this.authenticationFunction, + sha3_256(message), + this.sign(sha3_256(message)).toUint8Array(), + ); + } + + signTransactionWithAuthenticator(transaction: AnyRawTransaction): AccountAuthenticatorAbstraction { + return this.signWithAuthenticator(generateSigningMessageForTransaction(transaction)); + } + + sign: (message: HexInput) => AbstractSignature; + + signTransaction(transaction: AnyRawTransaction): AbstractSignature { + return this.sign(generateSigningMessageForTransaction(transaction)); + } + + /** + * Update the signer function for the account. This can be done after asynchronous operations are complete + * to update the context of the signer function. + * + * @param signer - The new signer function to use for the account. + */ + public setSigner(signer: (digest: HexInput) => HexInput): void { + this.sign = (digest: HexInput) => new AbstractSignature(signer(digest)); + } +} diff --git a/src/account/index.ts b/src/account/index.ts index 2893a580f..516c89ba7 100644 --- a/src/account/index.ts +++ b/src/account/index.ts @@ -7,3 +7,4 @@ export * from "./AbstractKeylessAccount"; export * from "./FederatedKeylessAccount"; export * from "./MultiKeyAccount"; export * from "./AccountUtils"; +export * from "./AbstractedAccount"; diff --git a/src/api/account.ts b/src/api/account.ts index 6252a6d38..60393e098 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -47,12 +47,15 @@ import { CurrentFungibleAssetBalancesBoolExp } from "../types/generated/types"; import { view } from "../internal/view"; import { isEncodedStruct, parseEncodedStruct } from "../utils"; import { memoizeAsync } from "../utils/memoize"; +import { AccountAbstraction } from "./account/abstraction"; /** * A class to query all `Account` related queries on Aptos. * @group Account */ export class Account { + abstraction: AccountAbstraction; + /** * Creates an instance of the Aptos client with the provided configuration. * @@ -73,7 +76,9 @@ export class Account { * ``` * @group Account */ - constructor(readonly config: AptosConfig) {} + constructor(readonly config: AptosConfig) { + this.abstraction = new AccountAbstraction(config); + } /** * Queries the current state for an Aptos account given its account address. diff --git a/src/api/account/abstraction.ts b/src/api/account/abstraction.ts new file mode 100644 index 000000000..d18786944 --- /dev/null +++ b/src/api/account/abstraction.ts @@ -0,0 +1,236 @@ +import { AccountAddress, AccountAddressInput } from "../../core"; +import { + addDispatchableAuthenticationFunctionTransaction, + removeDispatchableAuthenticationFunctionTransaction, + removeDispatchableAuthenticatorTransaction, +} from "../../internal/abstraction"; +import { view } from "../../internal/view"; +import { getFunctionParts, InputGenerateTransactionOptions, TypeTagAddress } from "../../transactions"; +import { MoveFunctionId } from "../../types"; +import { AptosConfig } from "../aptosConfig"; + +export class AccountAbstraction { + constructor(readonly config: AptosConfig) {} + + /** + * Adds a dispatchable authentication function to the account. + * + * @example + * ```ts + * const txn = await aptos.abstraction.addDispatchableAuthenticationFunctionTransaction({ + * accountAddress: alice.accountAddress, + * authenticationFunction: `${alice.accountAddress}::any_authenticator::authenticate`, + * }); + * + * const txn = await aptos.signAndSubmitTransaction({ signer: alice, transaction}); + * await aptos.waitForTransaction({ transactionHash: txn.hash }); + * ``` + * + * @param args.accountAddress - The account to add the authentication function to. + * @param args.authenticationFunction - The authentication function info to add. + * @param args.options - The options for the transaction. + * @returns A transaction to add the authentication function to the account. + */ + public async addDispatchableAuthenticationFunctionTransaction(args: { + accountAddress: AccountAddressInput; + authenticationFunction: string; + options?: InputGenerateTransactionOptions; + }) { + const { accountAddress, authenticationFunction, options } = args; + return addDispatchableAuthenticationFunctionTransaction({ + aptosConfig: this.config, + authenticationFunction, + sender: accountAddress, + options, + }); + } + + /** + * Removes a dispatchable authentication function from the account. + * + * @example + * ```ts + * const txn = await aptos.abstraction.removeDispatchableAuthenticationFunctionTransaction({ + * accountAddress: alice.accountAddress, + * authenticationFunction: `${alice.accountAddress}::any_authenticator::authenticate`, + * }); + * + * const txn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + * await aptos.waitForTransaction({ transactionHash: txn.hash }); + * ``` + * + * @param args.accountAddress - The account to remove the authentication function from. + * @param args.authenticationFunction - The authentication function info to remove. + * @param args.options - The options for the transaction. + * @returns A transaction to remove the authentication function from the account. + */ + public async removeDispatchableAuthenticationFunctionTransaction(args: { + accountAddress: AccountAddressInput; + authenticationFunction: string; + options?: InputGenerateTransactionOptions; + }) { + const { accountAddress, authenticationFunction, options } = args; + return removeDispatchableAuthenticationFunctionTransaction({ + aptosConfig: this.config, + sender: accountAddress, + authenticationFunction, + options, + }); + } + + /** + * Removes a dispatchable authenticator from the account. + * + * @example + * ```ts + * const txn = await aptos.abstraction.removeDispatchableAuthenticatorTransaction({ + * accountAddress: alice.accountAddress, + * }); + * + * const txn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + * await aptos.waitForTransaction({ transactionHash: txn.hash }); + * ``` + * + * @param args.accountAddress - The account to remove the authenticator from. + * @param args.options - The options for the transaction. + * @returns A transaction to remove the authenticator from the account. + */ + public async removeDispatchableAuthenticatorTransaction(args: { + accountAddress: AccountAddressInput; + options?: InputGenerateTransactionOptions; + }) { + const { accountAddress, options } = args; + return removeDispatchableAuthenticatorTransaction({ aptosConfig: this.config, sender: accountAddress, options }); + } + + /** + * Gets the dispatchable authentication function for the account. + * + * @example + * ```ts + * const functionInfos = await aptos.abstraction.getDispatchableAuthenticationFunction({ + * accountAddress: alice.accountAddress, + * }); + * + * if (functionInfos) { + * console.log(`Account ${alice.accountAddress.toString()} is using account abstraction!`); + * } else { + * console.log(`Account ${alice.accountAddress.toString()} is not using account abstraction.`); + * } + * ``` + * + * @param args.accountAddress - The account to get the dispatchable authentication function for. + * @returns The dispatchable authentication function for the account. + */ + public async getDispatchableAuthenticationFunction(args: { accountAddress: AccountAddressInput }) { + const { accountAddress } = args; + const [{ vec: functionInfoOption }] = await view< + [{ vec: { function_name: string; module_name: string; module_address: string }[][] }] + >({ + aptosConfig: this.config, + payload: { + function: "0x1::account_abstraction::dispatchable_authenticator", + functionArguments: [AccountAddress.from(accountAddress)], + abi: { typeParameters: [], parameters: [new TypeTagAddress()], returnTypes: [] }, + }, + }); + + if (functionInfoOption.length === 0) return undefined; + + return functionInfoOption[0].map((functionInfo) => ({ + moduleAddress: AccountAddress.fromString(functionInfo.module_address), + moduleName: functionInfo.module_name, + functionName: functionInfo.function_name, + })); + } + + /** + * Will return the authentication function if the account is abstracted, otherwise undefined. + * + * @example + * ```ts + * const isAccountAbstractionEnabled = await aptos.abstraction.isAccountAbstractionEnabled({ + * accountAddress: alice.accountAddress, + * authenticationFunction: `${alice.accountAddress}::any_authenticator::authenticate`, + * }); + * if (isAccountAbstractionEnabled) { + * console.log(`Account ${alice.accountAddress.toString()} is using account abstraction!`); + * } else { + * console.log(`Account ${alice.accountAddress.toString()} is not using account abstraction.`); + * } + * ``` + * + * @param args.accountAddress - The account to check. + * @returns The authentication function if the account is abstracted, otherwise undefined. + */ + public isAccountAbstractionEnabled = async (args: { + accountAddress: AccountAddressInput; + authenticationFunction: string; + }) => { + const functionInfos = await this.getDispatchableAuthenticationFunction(args); + const { moduleAddress, moduleName, functionName } = getFunctionParts(args.authenticationFunction as MoveFunctionId); + return functionInfos?.some( + (functionInfo) => + AccountAddress.fromString(moduleAddress).equals(functionInfo.moduleAddress) && + moduleName === functionInfo.moduleName && + functionName === functionInfo.functionName, + ); + }; + + /** + * Creates a transaction to enable account abstraction with the given authentication function. + * + * @example + * ```ts + * const txn = await aptos.abstraction.enableAccountAbstractionTransaction({ + * accountAddress: alice.accountAddress, + * authenticationFunction: `{alice.accountAddress}::any_authenticator::authenticate`, + * }); + * + * const txn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + * await aptos.waitForTransaction({ transactionHash: txn.hash }); + * ``` + * + * @param args.accountAddress - The account to enable account abstraction for. + * @param args.authenticationFunction - The authentication function info to use. + * @param args.options - The options for the transaction. + * @returns A transaction to enable account abstraction for the account. + */ + public enableAccountAbstractionTransaction = this.addDispatchableAuthenticationFunctionTransaction; + + /** + * Creates a transaction to disable account abstraction. If an authentication function is provided, it will specify to + * remove the authentication function. + * + * @example + * ```ts + * const txn = await aptos.abstraction.disableAccountAbstractionTransaction({ + * accountAddress: alice.accountAddress, + * authenticationFunction: `${alice.accountAddress}::any_authenticator::authenticate`, + * }); + * + * const txn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + * await aptos.waitForTransaction({ transactionHash: txn.hash }); + * ``` + * + * @param args.accountAddress - The account to disable account abstraction for. + * @param args.authenticationFunction - The authentication function info to remove. + * @param args.options - The options for the transaction. + * @returns A transaction to disable account abstraction for the account. + */ + public disableAccountAbstractionTransaction = async (args: { + accountAddress: AccountAddressInput; + authenticationFunction?: string; + options?: InputGenerateTransactionOptions; + }) => { + const { accountAddress, authenticationFunction, options } = args; + if (authenticationFunction) { + return this.removeDispatchableAuthenticationFunctionTransaction({ + accountAddress, + authenticationFunction, + options, + }); + } + return this.removeDispatchableAuthenticatorTransaction({ accountAddress, options }); + }; +} diff --git a/src/api/aptos.ts b/src/api/aptos.ts index 23a114e38..d4a6a0207 100644 --- a/src/api/aptos.ts +++ b/src/api/aptos.ts @@ -15,6 +15,7 @@ import { Transaction } from "./transaction"; import { Table } from "./table"; import { Keyless } from "./keyless"; import { AptosObject } from "./object"; +import { AccountAbstraction } from "./account/abstraction"; /** * The main entry point for interacting with the Aptos APIs, @@ -94,6 +95,7 @@ export class Aptos { constructor(settings?: AptosConfig) { this.config = new AptosConfig(settings); this.account = new Account(this.config); + this.abstraction = new AccountAbstraction(this.config); this.ans = new ANS(this.config); this.coin = new Coin(this.config); this.digitalAsset = new DigitalAsset(this.config); @@ -149,6 +151,7 @@ function applyMixin(targetClass: any, baseClass: any, baseClassProp: string) { } applyMixin(Aptos, Account, "account"); +applyMixin(Aptos, AccountAbstraction, "abstraction"); applyMixin(Aptos, ANS, "ans"); applyMixin(Aptos, Coin, "coin"); applyMixin(Aptos, DigitalAsset, "digitalAsset"); diff --git a/src/core/crypto/abstraction.ts b/src/core/crypto/abstraction.ts new file mode 100644 index 000000000..26e9ba5ce --- /dev/null +++ b/src/core/crypto/abstraction.ts @@ -0,0 +1,47 @@ +import { Deserializer, Serializer } from "../../bcs"; +import { HexInput } from "../../types"; +import { AccountAddress } from "../accountAddress"; +import { AuthenticationKey } from "../authenticationKey"; +import { Hex } from "../hex"; +import { AccountPublicKey, VerifySignatureArgs } from "./publicKey"; +import { Signature } from "./signature"; + +export class AbstractSignature extends Signature { + readonly value: Hex; + + constructor(value: HexInput) { + super(); + this.value = Hex.fromHexInput(value); + } + + serialize(serializer: Serializer): void { + serializer.serializeBytes(this.value.toUint8Array()); + } + + static deserialize(deserializer: Deserializer): AbstractSignature { + return new AbstractSignature(deserializer.deserializeBytes()); + } +} + +export class AbstractPublicKey extends AccountPublicKey { + readonly accountAddress: AccountAddress; + + constructor(accountAddress: AccountAddress) { + super(); + this.accountAddress = accountAddress; + } + + authKey(): AuthenticationKey { + return new AuthenticationKey({ data: this.accountAddress.toUint8Array() }); + } + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + verifySignature(args: VerifySignatureArgs): boolean { + throw new Error("This function is not implemented for AbstractPublicKey."); + } + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + serialize(serializer: Serializer): void { + throw new Error("This function is not implemented for AbstractPublicKey."); + } +} diff --git a/src/internal/abstraction.ts b/src/internal/abstraction.ts new file mode 100644 index 000000000..b166c6913 --- /dev/null +++ b/src/internal/abstraction.ts @@ -0,0 +1,79 @@ +import { + SimpleTransaction, + InputGenerateTransactionOptions, + TypeTagAddress, + TypeTagStruct, + stringStructTag, + getFunctionParts, +} from "../transactions"; +import { AccountAddressInput } from "../core"; +import { generateTransaction } from "./transactionSubmission"; +import { MoveFunctionId } from "../types"; +import { AptosConfig } from "../api/aptosConfig"; + +export async function addDispatchableAuthenticationFunctionTransaction(args: { + aptosConfig: AptosConfig; + sender: AccountAddressInput; + authenticationFunction: string; + options?: InputGenerateTransactionOptions; +}): Promise { + const { aptosConfig, sender, authenticationFunction, options } = args; + const { moduleAddress, moduleName, functionName } = getFunctionParts(authenticationFunction as MoveFunctionId); + return generateTransaction({ + aptosConfig, + sender, + data: { + function: "0x1::account_abstraction::add_dispatchable_authentication_function", + typeArguments: [], + functionArguments: [moduleAddress, moduleName, functionName], + abi: { + typeParameters: [], + parameters: [new TypeTagAddress(), new TypeTagStruct(stringStructTag()), new TypeTagStruct(stringStructTag())], + }, + }, + options, + }); +} + +export async function removeDispatchableAuthenticationFunctionTransaction(args: { + aptosConfig: AptosConfig; + sender: AccountAddressInput; + authenticationFunction: string; + options?: InputGenerateTransactionOptions; +}) { + const { aptosConfig, sender, authenticationFunction, options } = args; + const { moduleAddress, moduleName, functionName } = getFunctionParts(authenticationFunction as MoveFunctionId); + return generateTransaction({ + aptosConfig, + sender, + data: { + function: "0x1::account_abstraction::remove_dispatchable_authentication_function", + typeArguments: [], + functionArguments: [moduleAddress, moduleName, functionName], + abi: { + typeParameters: [], + parameters: [new TypeTagAddress(), new TypeTagStruct(stringStructTag()), new TypeTagStruct(stringStructTag())], + }, + }, + options, + }); +} + +export async function removeDispatchableAuthenticatorTransaction(args: { + aptosConfig: AptosConfig; + sender: AccountAddressInput; + options?: InputGenerateTransactionOptions; +}) { + const { aptosConfig, sender, options } = args; + return generateTransaction({ + aptosConfig, + sender, + data: { + function: "0x1::account_abstraction::remove_dispatchable_authenticator", + typeArguments: [], + functionArguments: [], + abi: { typeParameters: [], parameters: [] }, + }, + options, + }); +} diff --git a/src/transactions/authenticator/account.ts b/src/transactions/authenticator/account.ts index 4f1ca8fb1..15299dfee 100644 --- a/src/transactions/authenticator/account.ts +++ b/src/transactions/authenticator/account.ts @@ -8,7 +8,10 @@ import { AnyPublicKey, AnySignature } from "../../core/crypto"; import { Ed25519PublicKey, Ed25519Signature } from "../../core/crypto/ed25519"; import { MultiEd25519PublicKey, MultiEd25519Signature } from "../../core/crypto/multiEd25519"; import { MultiKey, MultiKeySignature } from "../../core/crypto/multiKey"; -import { AccountAuthenticatorVariant } from "../../types"; +import { AccountAuthenticatorVariant, HexInput, MoveFunctionId } from "../../types"; +import { AbstractionAuthDataVariant } from "../../types/abstraction"; +import { AccountAddress, Hex } from "../../core"; +import { getFunctionParts, isValidFunctionInfo } from "../transactionBuilder/helpers"; /** * Represents an account authenticator that can handle multiple authentication variants. @@ -43,6 +46,8 @@ export abstract class AccountAuthenticator extends Serializable { return AccountAuthenticatorMultiKey.load(deserializer); case AccountAuthenticatorVariant.NoAccountAuthenticator: return AccountAuthenticatorNoAccountAuthenticator.load(deserializer); + case AccountAuthenticatorVariant.Abstraction: + return AccountAuthenticatorAbstraction.load(deserializer); default: throw new Error(`Unknown variant index for AccountAuthenticator: ${index}`); } @@ -263,3 +268,46 @@ export class AccountAuthenticatorNoAccountAuthenticator extends AccountAuthentic return new AccountAuthenticatorNoAccountAuthenticator(); } } + +export class AccountAuthenticatorAbstraction extends AccountAuthenticator { + public readonly functionInfo: string; + + public readonly signingMessageDigest: Hex; + + public readonly authenticator: Hex; + + constructor(functionInfo: string, signingMessageDigest: HexInput, authenticator: HexInput) { + super(); + if (!isValidFunctionInfo(functionInfo)) { + throw new Error(`Invalid function info ${functionInfo} passed into AccountAuthenticatorAbstraction`); + } + this.functionInfo = functionInfo; + this.authenticator = Hex.fromHexInput(authenticator); + this.signingMessageDigest = Hex.fromHexInput(Hex.fromHexInput(signingMessageDigest).toUint8Array()); + } + + serialize(serializer: Serializer): void { + serializer.serializeU32AsUleb128(AccountAuthenticatorVariant.Abstraction); + const { moduleAddress, moduleName, functionName } = getFunctionParts(this.functionInfo as MoveFunctionId); + AccountAddress.fromString(moduleAddress).serialize(serializer); + serializer.serializeStr(moduleName); + serializer.serializeStr(functionName); + serializer.serializeU32AsUleb128(AbstractionAuthDataVariant.V1); + serializer.serializeBytes(this.signingMessageDigest.toUint8Array()); + serializer.serializeFixedBytes(this.authenticator.toUint8Array()); + } + + static load(deserializer: Deserializer): AccountAuthenticatorAbstraction { + const moduleAddress = AccountAddress.deserialize(deserializer); + const moduleName = deserializer.deserializeStr(); + const functionName = deserializer.deserializeStr(); + deserializer.deserializeUleb128AsU32(); + const signingMessageDigest = deserializer.deserializeBytes(); + const authenticator = deserializer.deserializeFixedBytes(deserializer.remaining()); + return new AccountAuthenticatorAbstraction( + `${moduleAddress}::${moduleName}::${functionName}`, + signingMessageDigest, + authenticator, + ); + } +} diff --git a/src/transactions/transactionBuilder/helpers.ts b/src/transactions/transactionBuilder/helpers.ts index a4e74fbd4..2388722da 100644 --- a/src/transactions/transactionBuilder/helpers.ts +++ b/src/transactions/transactionBuilder/helpers.ts @@ -338,3 +338,8 @@ export function getFunctionParts(functionArg: MoveFunctionId) { const functionName = funcNameParts[2]; return { moduleAddress, moduleName, functionName }; } + +export function isValidFunctionInfo(functionInfo: string): boolean { + const parts = functionInfo.split("::"); + return parts.length === 3 && AccountAddress.isValid({ input: parts[0] }).valid; +} diff --git a/src/types/abstraction.ts b/src/types/abstraction.ts new file mode 100644 index 000000000..9330aa76c --- /dev/null +++ b/src/types/abstraction.ts @@ -0,0 +1,6 @@ +/** + * The variant for the AbstractionAuthData enum. + */ +export enum AbstractionAuthDataVariant { + V1 = 0, +} diff --git a/src/types/types.ts b/src/types/types.ts index a0a733047..69d995038 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -106,6 +106,7 @@ export enum AccountAuthenticatorVariant { SingleKey = 2, MultiKey = 3, NoAccountAuthenticator = 4, + Abstraction = 5, } /** From 796da5c185429de0917eb18f6d509d11610716e7 Mon Sep 17 00:00:00 2001 From: GhostWalker562 Date: Mon, 27 Jan 2025 17:47:59 -0800 Subject: [PATCH 2/5] Add tests --- .../sources/hello_world_authenticator.move | 2 +- src/account/AbstractedAccount.ts | 24 +++ src/api/account/abstraction.ts | 16 +- tests/e2e/api/abstraction.test.ts | 176 ++++++++++++++++++ tests/e2e/transaction/helper.ts | 30 +++ tests/move/account_abstraction/Move.toml | 10 + .../sources/any_authenticator.move | 10 + .../sources/hello_world_authenticator.move | 15 ++ 8 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 tests/e2e/api/abstraction.test.ts create mode 100644 tests/move/account_abstraction/Move.toml create mode 100644 tests/move/account_abstraction/sources/any_authenticator.move create mode 100644 tests/move/account_abstraction/sources/hello_world_authenticator.move diff --git a/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move b/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move index 6731dfc22..e53442326 100644 --- a/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move +++ b/examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move @@ -9,7 +9,7 @@ module deployer::hello_world_authenticator { signing_data: AbstractionAuthData, ): signer { let authenticator = *auth_data::authenticator(&signing_data); // Dereference to get owned vector - assert!(authenticator == bcs::to_bytes(&b"hello world"), EINVALID_SIGNATURE); + assert!(authenticator == b"hello world", EINVALID_SIGNATURE); account } } \ No newline at end of file diff --git a/src/account/AbstractedAccount.ts b/src/account/AbstractedAccount.ts index b74759cf8..76d21797b 100644 --- a/src/account/AbstractedAccount.ts +++ b/src/account/AbstractedAccount.ts @@ -11,8 +11,25 @@ import { Ed25519Account } from "./Ed25519Account"; import { Serializer } from "../bcs/serializer"; type AbstractedAccountConstructorArgs = { + /** + * The account address of the account. + */ accountAddress: AccountAddress; + /** + * The signer function signs transactions and returns the `authenticator` bytes in the `AbstractionAuthData`. + * + * @param digest - The SHA256 hash of the transaction signing message + * @returns The `authenticator` bytes that can be used to verify the signature. + */ signer: (digest: HexInput) => HexInput; + /** + * The authentication function that will be used to verify the signature. + * + * @example + * ```ts + * const authenticationFunction = `${accountAddress}::permissioned_delegation::authenticate`; + * ``` + */ authenticationFunction: string; }; @@ -38,6 +55,13 @@ export class AbstractedAccount extends Account { this.sign = (digest: HexInput) => new AbstractSignature(signer(digest)); } + /** + * Creates an `AbstractedAccount` from an `Ed25519Account` that has a permissioned signer function and + * using the `0x1::permissioned_delegation::authenticate` function to verify the signature. + * + * @param signer - The `Ed25519Account` that can be used to sign permissioned transactions. + * @returns The `AbstractedAccount` + */ public static fromPermissionedSigner({ signer }: { signer: Ed25519Account }) { return new AbstractedAccount({ signer: (digest: HexInput) => { diff --git a/src/api/account/abstraction.ts b/src/api/account/abstraction.ts index d18786944..ac9a8aeb9 100644 --- a/src/api/account/abstraction.ts +++ b/src/api/account/abstraction.ts @@ -145,7 +145,7 @@ export class AccountAbstraction { } /** - * Will return the authentication function if the account is abstracted, otherwise undefined. + * Will return true if the account is abstracted, otherwise false. * * @example * ```ts @@ -161,7 +161,7 @@ export class AccountAbstraction { * ``` * * @param args.accountAddress - The account to check. - * @returns The authentication function if the account is abstracted, otherwise undefined. + * @returns Whether the account is abstracted. */ public isAccountAbstractionEnabled = async (args: { accountAddress: AccountAddressInput; @@ -169,11 +169,13 @@ export class AccountAbstraction { }) => { const functionInfos = await this.getDispatchableAuthenticationFunction(args); const { moduleAddress, moduleName, functionName } = getFunctionParts(args.authenticationFunction as MoveFunctionId); - return functionInfos?.some( - (functionInfo) => - AccountAddress.fromString(moduleAddress).equals(functionInfo.moduleAddress) && - moduleName === functionInfo.moduleName && - functionName === functionInfo.functionName, + return ( + functionInfos?.some( + (functionInfo) => + AccountAddress.fromString(moduleAddress).equals(functionInfo.moduleAddress) && + moduleName === functionInfo.moduleName && + functionName === functionInfo.functionName, + ) ?? false ); }; diff --git a/tests/e2e/api/abstraction.test.ts b/tests/e2e/api/abstraction.test.ts new file mode 100644 index 000000000..63d037375 --- /dev/null +++ b/tests/e2e/api/abstraction.test.ts @@ -0,0 +1,176 @@ +import { AbstractedAccount, Network } from "../../../src"; +import { Ed25519Account } from "../../../src/account/Ed25519Account"; +import {} from "../../../src/core/crypto"; +import { FUND_AMOUNT } from "../../unit/helper"; +import { getAptosClient } from "../helper"; +import { publishAnyAuthenticatorAAPackage, publishHelloWorldAAPackage } from "../transaction/helper"; + +describe("abstraction api", () => { + // TODO: Change to localnet when CLI supports indexing aa signatures + const { aptos } = getAptosClient({ network: Network.DEVNET }); + + describe("enable and disable account abstraction", () => { + const alice = Ed25519Account.generate(); + + const authenticationFunction = "0x1::permissioned_delegation::authenticate"; + + beforeAll(async () => { + await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: FUND_AMOUNT }); + }); + + it("should fetch account abstraction is enabled to be false", async () => { + const status = await aptos.abstraction.isAccountAbstractionEnabled({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + expect(status).toBe(false); + }); + + it("should enable account abstraction", async () => { + const txn = await aptos.abstraction.enableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + expect(response.success).toBe(true); + }); + + it("should fetch whether account abstraction is enabled to be true", async () => { + const status = await aptos.abstraction.isAccountAbstractionEnabled({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + expect(status).toBe(true); + }); + + it("should disable account abstraction", async () => { + const txn = await aptos.abstraction.disableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + expect(response.success).toBe(true); + }); + }); + + describe("enable account abstraction, send a transaction, and disable all account abstraction", () => { + const alice = Ed25519Account.generate(); + const deployer = Ed25519Account.generate(); + const authenticationFunction = `${deployer.accountAddress}::any_authenticator::authenticate`; + + beforeAll(async () => { + await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: FUND_AMOUNT }); + await aptos.fundAccount({ accountAddress: deployer.accountAddress, amount: FUND_AMOUNT }); + await publishAnyAuthenticatorAAPackage(aptos, deployer); + }); + + it("should enable account abstraction", async () => { + const txn = await aptos.abstraction.enableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + expect(response.success).toBe(true); + }); + + it("should be able to send a transaction using acount abstraction", async () => { + const abstractAccount = new AbstractedAccount({ + signer: () => new Uint8Array(0), + accountAddress: alice.accountAddress, + authenticationFunction, + }); + + const txn = await aptos.transaction.signAndSubmitTransaction({ + signer: abstractAccount, + transaction: await aptos.transferCoinTransaction({ + sender: alice.accountAddress, + recipient: alice.accountAddress, + amount: 100, + }), + }); + + const response = await aptos.waitForTransaction({ transactionHash: txn.hash }); + expect(response.success).toBe(true); + }); + + it("should disable account abstraction without specifying authentication function", async () => { + const txn = await aptos.abstraction.disableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + expect(response.success).toBe(true); + + const status = await aptos.abstraction.isAccountAbstractionEnabled({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + expect(status).toBe(false); + }); + }); + + // eslint-disable-next-line max-len + describe("enable custom account abstraction, send a transaction with custom signer, and send a transaction with an invalid signer", () => { + const alice = Ed25519Account.generate(); + const deployer = Ed25519Account.generate(); + const authenticationFunction = `${deployer.accountAddress}::hello_world_authenticator::authenticate`; + + beforeAll(async () => { + await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: FUND_AMOUNT }); + await aptos.fundAccount({ accountAddress: deployer.accountAddress, amount: FUND_AMOUNT }); + await publishHelloWorldAAPackage(aptos, deployer); + }); + + it("should enable account abstraction", async () => { + const txn = await aptos.abstraction.enableAccountAbstractionTransaction({ + accountAddress: alice.accountAddress, + authenticationFunction, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + expect(response.success).toBe(true); + }); + + it("should be able to send a transaction using custom signer", async () => { + const abstractAccount = new AbstractedAccount({ + signer: () => new TextEncoder().encode("hello world"), + accountAddress: alice.accountAddress, + authenticationFunction, + }); + + const txn = await aptos.transaction.signAndSubmitTransaction({ + signer: abstractAccount, + transaction: await aptos.transferCoinTransaction({ + sender: alice.accountAddress, + recipient: alice.accountAddress, + amount: 100, + }), + }); + + const response = await aptos.waitForTransaction({ transactionHash: txn.hash }); + expect(response.success).toBe(true); + }); + + it("should fail to send a transaction with wrong custom signer", async () => { + const abstractAccount = new AbstractedAccount({ + signer: () => new Uint8Array(0), + accountAddress: alice.accountAddress, + authenticationFunction, + }); + + expect(async () => { + await aptos.transaction.signAndSubmitTransaction({ + signer: abstractAccount, + transaction: await aptos.transferCoinTransaction({ + sender: alice.accountAddress, + recipient: alice.accountAddress, + amount: 100, + }), + }); + }).rejects.toThrow(); + }); + }); +}); diff --git a/tests/e2e/transaction/helper.ts b/tests/e2e/transaction/helper.ts index ba02a4f6e..409f2ad44 100644 --- a/tests/e2e/transaction/helper.ts +++ b/tests/e2e/transaction/helper.ts @@ -369,3 +369,33 @@ export async function publishTransferPackage(aptos: Aptos, senderAccount: Accoun ], ); } + +export async function publishAnyAuthenticatorAAPackage(aptos: Aptos, senderAccount: Account) { + return publishPackage( + aptos, + senderAccount, + // eslint-disable-next-line max-len + "0x136163636f756e745f6162737472616374696f6e0100000000000000004043323435373234443345363341303030313333383334363235413837443444353943413846383746373941423132413430393934344632463339444636453938be011f8b08000000000002ffad8d310ec2300c45f79c22ca0c2d3312030b1760acaa2a4d4c1bb54da2d82d4288bbe314581023f262fb7dfb55519b4177500baf279007a9b43161f6d4e8162969432e78251648c84de6bb824b0951696b132002d6c2421cc30d52e64d66bc006fc11b97f13152c05362c135a4814377d939cae19e28e2be2c79ece7b630612a750e6f47d6bfdbcbe7b0e094da489c5beb56d517679660c9c0c2e281947cbcd467b2a36bffedc5f5eb2fe9137667044d550100000211616e795f61757468656e74696361746f72a1011f8b08000000000002ff6d4e3b0e83300cdd730a8fadd413a41352ef1199606854b051e2a84288bb37405b31f036bfaf0769724fd0d0d8cb44d15ae4c961d627b1068f2a116603053911e0a8925c1b71a0b7c4573117a36b50d1dab9aa9346f41a84ab423f0abbdccd961d73dd070f6d663854d3651357a0f792592da4d031c5db5f702b11b8db47e06463f75e7f51f8be7b68ddeec598e50372893c7bec00000000001968656c6c6f5f776f726c645f61757468656e74696361746f72a7021f8b08000000000002ff6d51c16ac3300cbdf72bb41e4a320265307670c821903202a38775db35388e9286b976b0e59552f2ef73d2ae4b4b053ec8ef497a7adae9ca49840a3ba90f6818dba294bad86b23ab823bdaa2a25670d2068e33f0e12c02ef48dba2367c879ef8cdd8402c2a4e9cb1e306651d415a5a325c50ab55eac1cc637d7c6960a962ac14369e8d5f422b4bb0cad75fe95b9e159bfc759d7e7cbeaf18b8976748e0e9cceb5c295b01b553309186c1080ec185d04e1103db360a4d740186bc55cd49e33d71276af85709e76d87904870ed44028f938dafb060311d15c6b05c4286066bff9440200d8defa7f70a2bf841e14bfed55b8b861e829b610978a718235d9407421b2ccaf97824188f340fa33bce85f1ad2763decffa5f73d86bdef101000000000300000000000000000000000000000000000000000000000000000000000000010e4170746f734672616d65776f726b00000000000000000000000000000000000000000000000000000000000000010b4170746f735374646c696200000000000000000000000000000000000000000000000000000000000000010a4d6f76655374646c696200", + [ + // eslint-disable-next-line max-len + `0xa11ceb0b0700000a08010004020404030806050e0707153d0852401092011f0cb101090000010301020300000100010001020c0800010c0011616e795f61757468656e74696361746f720c61757468656e746963617465134162737472616374696f6e417574684461746109617574685f64617461${senderAccount.accountAddress.toStringWithoutPrefix()}000000000000000000000000000000000000000000000000000000000000000114636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e310001000002020b000200`, + // eslint-disable-next-line max-len + `0xa11ceb0b0700000a0a010006020604030a13041d02051f160735600895014006d5010f10e401520cb6022300000103010501020300000100010001010403040001020606050100010205020c0800010c000106080001060a02010a02010609001968656c6c6f5f776f726c645f61757468656e74696361746f720c61757468656e746963617465134162737472616374696f6e417574684461746109617574685f646174610d61757468656e74696361746f720362637308746f5f6279746573${senderAccount.accountAddress.toStringWithoutPrefix()}00000000000000000000000000000000000000000000000000000000000000010a020c0b68656c6c6f20776f726c6414636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e31126170746f733a3a6d657461646174615f76311f0101000000000000001245494e56414c49445f5349474e415455524500000000010000050d0e0111011407000c020e02380021040b0b00020601000000000000002700`, + ], + ); +} + +export async function publishHelloWorldAAPackage(aptos: Aptos, senderAccount: Account) { + return publishPackage( + aptos, + senderAccount, + // eslint-disable-next-line max-len + "0x136163636f756e745f6162737472616374696f6e0100000000000000004033303330333941354643303931353046373736393036413943394237303731374132444535383637443838394642363835433936303446463534453630334535be011f8b08000000000002ffad8d310ec2300c45f79c22ca0c2d3312030b1760acaa2a4d4c1bb54da2d82d4288bbe314581023f262fb7dfb55519b4177500baf279007a9b43161f6d4e8162969432e78251648c84de6bb824b0951696b132002d6c2421cc30d52e64d66bc006fc11b97f13152c05362c135a4814377d939cae19e28e2be2c79ece7b630612a750e6f47d6bfdbcbe7b0e094da489c5beb56d517679660c9c0c2e281947cbcd467b2a36bffedc5f5eb2fe9137667044d550100000211616e795f61757468656e74696361746f72a1011f8b08000000000002ff6d4e3b0e83300cdd730a8fadd413a41352ef1199606854b051e2a84288bb37405b31f036bfaf0769724fd0d0d8cb44d15ae4c961d627b1068f2a116603053911e0a8925c1b71a0b7c4573117a36b50d1dab9aa9346f41a84ab423f0abbdccd961d73dd070f6d663854d3651357a0f792592da4d031c5db5f702b11b8db47e06463f75e7f51f8be7b68ddeec598e50372893c7bec00000000001968656c6c6f5f776f726c645f61757468656e74696361746f7294021f8b08000000000002ff6d51c16ac3300cbde72bb41e46320265307670c821903202a38775db35388e9286b976b0e59551f2ef73d26e4d4b053a88f724bd27ed74ed24428dbdd43f6818dba294badc6b23eb923bdaa2a24e70d2060e01f8701681f7a46dd918be434ffc626c24963527ced86183b28921ab2c192ea8d32af360eeb12109a609422b4bb02ad69fd96b91979be2659dbd7fbcad18b8e72748e1f1c4eb5d253b018d53301382e1048ec185d04e1103dbb50a4dfc0f8c75a7daa3a25b528ed4e8af134edec6904870e93b858799bf0b2cbc9faf8a12582e2147838d4f25104843ebe7e9bdc21abe51f896b37a6bd1d05d78b52c856a31fd00a61f2ce21b978a92eb1b4cf5100cbff2d5ce70cf01000000000300000000000000000000000000000000000000000000000000000000000000010e4170746f734672616d65776f726b00000000000000000000000000000000000000000000000000000000000000010b4170746f735374646c696200000000000000000000000000000000000000000000000000000000000000010a4d6f76655374646c696200", + [ + // eslint-disable-next-line max-len + `0xa11ceb0b0700000a08010004020404030806050e0707153d0852401092011f0cb101090000010301020300000100010001020c0800010c0011616e795f61757468656e74696361746f720c61757468656e746963617465134162737472616374696f6e417574684461746109617574685f64617461${senderAccount.accountAddress.toStringWithoutPrefix()}000000000000000000000000000000000000000000000000000000000000000114636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e310001000002020b000200`, + // eslint-disable-next-line max-len + `0xa11ceb0b0700000a0901000402040403080c05140f07235308764006b6010f10c501520c97021d0000010301020300000100010001010403040001020c0800010c000106080001060a021968656c6c6f5f776f726c645f61757468656e74696361746f720c61757468656e746963617465134162737472616374696f6e417574684461746109617574685f646174610d61757468656e74696361746f72${senderAccount.accountAddress.toStringWithoutPrefix()}00000000000000000000000000000000000000000000000000000000000000010a020c0b68656c6c6f20776f726c6414636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e31126170746f733a3a6d657461646174615f76311f0101000000000000001245494e56414c49445f5349474e415455524500000000010000020a0e0111011407002104080b00020601000000000000002700`, + ], + ); +} diff --git a/tests/move/account_abstraction/Move.toml b/tests/move/account_abstraction/Move.toml new file mode 100644 index 000000000..c9c250e1a --- /dev/null +++ b/tests/move/account_abstraction/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "account_abstraction" +version = "0.0.0" + +[addresses] +deployer = "_" + +[dependencies] +AptosFramework = { git = "https://github.com/aptos-labs/aptos-framework.git", subdir = "aptos-framework", rev = "devnet" } +AptosStdlib = { git = "https://github.com/aptos-labs/aptos-framework.git", subdir = "aptos-stdlib", rev = "devnet" } diff --git a/tests/move/account_abstraction/sources/any_authenticator.move b/tests/move/account_abstraction/sources/any_authenticator.move new file mode 100644 index 000000000..4e1f5500a --- /dev/null +++ b/tests/move/account_abstraction/sources/any_authenticator.move @@ -0,0 +1,10 @@ +module deployer::any_authenticator { + use aptos_framework::auth_data::{AbstractionAuthData}; + + public fun authenticate( + account: signer, + _signing_data: AbstractionAuthData, + ): signer { + account + } +} \ No newline at end of file diff --git a/tests/move/account_abstraction/sources/hello_world_authenticator.move b/tests/move/account_abstraction/sources/hello_world_authenticator.move new file mode 100644 index 000000000..e53442326 --- /dev/null +++ b/tests/move/account_abstraction/sources/hello_world_authenticator.move @@ -0,0 +1,15 @@ +module deployer::hello_world_authenticator { + use aptos_framework::auth_data::{Self, AbstractionAuthData}; + use std::bcs; + + const EINVALID_SIGNATURE: u64 = 1; + + public fun authenticate( + account: signer, + signing_data: AbstractionAuthData, + ): signer { + let authenticator = *auth_data::authenticator(&signing_data); // Dereference to get owned vector + assert!(authenticator == b"hello world", EINVALID_SIGNATURE); + account + } +} \ No newline at end of file From 43412e79d13dc04c76137f2a7e9e184072d98012 Mon Sep 17 00:00:00 2001 From: GhostWalker562 Date: Mon, 27 Jan 2025 17:53:06 -0800 Subject: [PATCH 3/5] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e74169af6..6f2745a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T # Unreleased +- Add `AbstractedAccount` class to support account abstraction with custom signers. +- Add `aptos.abstraction` namespace to support account abstraction APIs. Notable functions are: `isAccountAbstractionEnabled`, `enableAccountAbstractionTransaction`, and `disableAccountAbstractionTransaction`. + # 1.33.2 (2025-01-22) - [`Fix`] Fixes pagination for GetAccountModules and GetAccountResources. Also, adds more appropriate documentation on offset. From 4a6ea5575f2bff844f0ea680ab3def3a7f7697fb Mon Sep 17 00:00:00 2001 From: GhostWalker562 Date: Mon, 27 Jan 2025 18:01:48 -0800 Subject: [PATCH 4/5] Fix dependency cycle --- src/account/AbstractedAccount.ts | 2 +- src/api/account/abstraction.ts | 3 +- src/internal/abstraction.ts | 2 +- src/transactions/authenticator/account.ts | 2 +- .../transactionBuilder/helpers.ts | 28 +----------------- .../transactionBuilder/transactionBuilder.ts | 3 +- src/utils/helpers.ts | 29 ++++++++++++++++++- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/account/AbstractedAccount.ts b/src/account/AbstractedAccount.ts index 76d21797b..9ed8b5e27 100644 --- a/src/account/AbstractedAccount.ts +++ b/src/account/AbstractedAccount.ts @@ -6,9 +6,9 @@ import { Account } from "./Account"; import { AnyRawTransaction } from "../transactions/types"; import { generateSigningMessageForTransaction } from "../transactions/transactionBuilder/signingMessage"; import { AccountAuthenticatorAbstraction } from "../transactions/authenticator/account"; -import { isValidFunctionInfo } from "../transactions/transactionBuilder/helpers"; import { Ed25519Account } from "./Ed25519Account"; import { Serializer } from "../bcs/serializer"; +import { isValidFunctionInfo } from "../utils/helpers"; type AbstractedAccountConstructorArgs = { /** diff --git a/src/api/account/abstraction.ts b/src/api/account/abstraction.ts index ac9a8aeb9..19847a9e7 100644 --- a/src/api/account/abstraction.ts +++ b/src/api/account/abstraction.ts @@ -5,8 +5,9 @@ import { removeDispatchableAuthenticatorTransaction, } from "../../internal/abstraction"; import { view } from "../../internal/view"; -import { getFunctionParts, InputGenerateTransactionOptions, TypeTagAddress } from "../../transactions"; +import { InputGenerateTransactionOptions, TypeTagAddress } from "../../transactions"; import { MoveFunctionId } from "../../types"; +import { getFunctionParts } from "../../utils/helpers"; import { AptosConfig } from "../aptosConfig"; export class AccountAbstraction { diff --git a/src/internal/abstraction.ts b/src/internal/abstraction.ts index b166c6913..66b494d53 100644 --- a/src/internal/abstraction.ts +++ b/src/internal/abstraction.ts @@ -4,12 +4,12 @@ import { TypeTagAddress, TypeTagStruct, stringStructTag, - getFunctionParts, } from "../transactions"; import { AccountAddressInput } from "../core"; import { generateTransaction } from "./transactionSubmission"; import { MoveFunctionId } from "../types"; import { AptosConfig } from "../api/aptosConfig"; +import { getFunctionParts } from "../utils/helpers"; export async function addDispatchableAuthenticationFunctionTransaction(args: { aptosConfig: AptosConfig; diff --git a/src/transactions/authenticator/account.ts b/src/transactions/authenticator/account.ts index 15299dfee..ba2f28a3b 100644 --- a/src/transactions/authenticator/account.ts +++ b/src/transactions/authenticator/account.ts @@ -11,7 +11,7 @@ import { MultiKey, MultiKeySignature } from "../../core/crypto/multiKey"; import { AccountAuthenticatorVariant, HexInput, MoveFunctionId } from "../../types"; import { AbstractionAuthDataVariant } from "../../types/abstraction"; import { AccountAddress, Hex } from "../../core"; -import { getFunctionParts, isValidFunctionInfo } from "../transactionBuilder/helpers"; +import { getFunctionParts, isValidFunctionInfo } from "../../utils/helpers"; /** * Represents an account authenticator that can handle multiple authentication variants. diff --git a/src/transactions/transactionBuilder/helpers.ts b/src/transactions/transactionBuilder/helpers.ts index 2388722da..6866f5df0 100644 --- a/src/transactions/transactionBuilder/helpers.ts +++ b/src/transactions/transactionBuilder/helpers.ts @@ -10,7 +10,7 @@ import { } from "../types"; import { Bool, FixedBytes, MoveOption, MoveString, MoveVector, U128, U16, U256, U32, U64, U8 } from "../../bcs"; import { AccountAddress } from "../../core"; -import { MoveFunction, MoveFunctionId } from "../../types"; +import { MoveFunction } from "../../types"; /** * Determines if the provided argument is of type boolean. @@ -317,29 +317,3 @@ export function findFirstNonSignerArg(functionAbi: MoveFunction): number { } return index; } - -/** - * Splits a function identifier into its constituent parts: module address, module name, and function name. - * This function helps in validating and extracting details from a function identifier string. - * - * @param functionArg - The function identifier string in the format "moduleAddress::moduleName::functionName". - * @returns An object containing the module address, module name, and function name. - * @throws Error if the function identifier does not contain exactly three parts. - * @group Implementation - * @category Transactions - */ -export function getFunctionParts(functionArg: MoveFunctionId) { - const funcNameParts = functionArg.split("::"); - if (funcNameParts.length !== 3) { - throw new Error(`Invalid function ${functionArg}`); - } - const moduleAddress = funcNameParts[0]; - const moduleName = funcNameParts[1]; - const functionName = funcNameParts[2]; - return { moduleAddress, moduleName, functionName }; -} - -export function isValidFunctionInfo(functionInfo: string): boolean { - const parts = functionInfo.split("::"); - return parts.length === 3 && AccountAddress.isValid({ input: parts[0] }).valid; -} diff --git a/src/transactions/transactionBuilder/transactionBuilder.ts b/src/transactions/transactionBuilder/transactionBuilder.ts index 5527302d2..ac0ea8488 100644 --- a/src/transactions/transactionBuilder/transactionBuilder.ts +++ b/src/transactions/transactionBuilder/transactionBuilder.ts @@ -77,9 +77,10 @@ import { } from "../types"; import { convertArgument, fetchEntryFunctionAbi, fetchViewFunctionAbi, standardizeTypeTags } from "./remoteAbi"; import { memoizeAsync } from "../../utils/memoize"; -import { getFunctionParts, isScriptDataInput } from "./helpers"; +import { isScriptDataInput } from "./helpers"; import { SimpleTransaction } from "../instances/simpleTransaction"; import { MultiAgentTransaction } from "../instances/multiAgentTransaction"; +import { getFunctionParts } from "../../utils/helpers"; /** * Builds a transaction payload based on the provided arguments and returns a transaction payload. diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index b012d777d..a48a8ae95 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { decode } from "js-base64"; -import { MoveStructId } from "../types"; +import { MoveFunctionId, MoveStructId } from "../types"; +import { AccountAddress } from "../core/accountAddress"; /** * Sleep for the specified amount of time in milliseconds. @@ -175,3 +176,29 @@ export const isEncodedStruct = ( typeof structObj.account_address === "string" && typeof structObj.module_name === "string" && typeof structObj.struct_name === "string"; + +/** + * Splits a function identifier into its constituent parts: module address, module name, and function name. + * This function helps in validating and extracting details from a function identifier string. + * + * @param functionArg - The function identifier string in the format "moduleAddress::moduleName::functionName". + * @returns An object containing the module address, module name, and function name. + * @throws Error if the function identifier does not contain exactly three parts. + * @group Implementation + * @category Transactions + */ +export function getFunctionParts(functionArg: MoveFunctionId) { + const funcNameParts = functionArg.split("::"); + if (funcNameParts.length !== 3) { + throw new Error(`Invalid function ${functionArg}`); + } + const moduleAddress = funcNameParts[0]; + const moduleName = funcNameParts[1]; + const functionName = funcNameParts[2]; + return { moduleAddress, moduleName, functionName }; +} + +export function isValidFunctionInfo(functionInfo: string): boolean { + const parts = functionInfo.split("::"); + return parts.length === 3 && AccountAddress.isValid({ input: parts[0] }).valid; +} From 538bc15a7da0c899a3815372278da6478dc6a060 Mon Sep 17 00:00:00 2001 From: GhostWalker562 Date: Thu, 30 Jan 2025 16:15:42 -0800 Subject: [PATCH 5/5] Add permissioned delegation tests --- src/account/AbstractedAccount.ts | 6 +-- src/core/crypto/abstraction.ts | 4 +- tests/e2e/api/abstraction.test.ts | 41 +++++++++++++++++-- tests/e2e/transaction/helper.ts | 4 ++ .../sources/add_permission_delegation.move | 19 +++++++++ .../sources/hello_world_authenticator.move | 1 - 6 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 tests/move/account_abstraction/sources/add_permission_delegation.move diff --git a/src/account/AbstractedAccount.ts b/src/account/AbstractedAccount.ts index 9ed8b5e27..79037c014 100644 --- a/src/account/AbstractedAccount.ts +++ b/src/account/AbstractedAccount.ts @@ -1,5 +1,5 @@ import { sha3_256 } from "@noble/hashes/sha3"; -import { AccountAddress, AccountPublicKey } from "../core"; +import { AccountAddress } from "../core"; import { AbstractPublicKey, AbstractSignature } from "../core/crypto/abstraction"; import { SigningScheme, HexInput } from "../types"; import { Account } from "./Account"; @@ -34,7 +34,7 @@ type AbstractedAccountConstructorArgs = { }; export class AbstractedAccount extends Account { - public readonly publicKey: AccountPublicKey; + public readonly publicKey: AbstractPublicKey; readonly accountAddress: AccountAddress; @@ -71,7 +71,7 @@ export class AbstractedAccount extends Account { return serializer.toUint8Array(); }, accountAddress: signer.accountAddress, - authenticationFunction: `${signer.accountAddress}::permissioned_delegation::authenticate`, + authenticationFunction: "0x1::permissioned_delegation::authenticate", }); } diff --git a/src/core/crypto/abstraction.ts b/src/core/crypto/abstraction.ts index 26e9ba5ce..0463b781e 100644 --- a/src/core/crypto/abstraction.ts +++ b/src/core/crypto/abstraction.ts @@ -15,11 +15,11 @@ export class AbstractSignature extends Signature { } serialize(serializer: Serializer): void { - serializer.serializeBytes(this.value.toUint8Array()); + serializer.serializeFixedBytes(this.value.toUint8Array()); } static deserialize(deserializer: Deserializer): AbstractSignature { - return new AbstractSignature(deserializer.deserializeBytes()); + return new AbstractSignature(deserializer.deserializeFixedBytes(deserializer.remaining())); } } diff --git a/tests/e2e/api/abstraction.test.ts b/tests/e2e/api/abstraction.test.ts index 63d037375..71e62c470 100644 --- a/tests/e2e/api/abstraction.test.ts +++ b/tests/e2e/api/abstraction.test.ts @@ -1,9 +1,12 @@ -import { AbstractedAccount, Network } from "../../../src"; +import { AbstractedAccount, MoveVector, Network } from "../../../src"; import { Ed25519Account } from "../../../src/account/Ed25519Account"; -import {} from "../../../src/core/crypto"; import { FUND_AMOUNT } from "../../unit/helper"; import { getAptosClient } from "../helper"; -import { publishAnyAuthenticatorAAPackage, publishHelloWorldAAPackage } from "../transaction/helper"; +import { + addPermissionDelegationScriptBytecode, + publishAnyAuthenticatorAAPackage, + publishHelloWorldAAPackage, +} from "../transaction/helper"; describe("abstraction api", () => { // TODO: Change to localnet when CLI supports indexing aa signatures @@ -173,4 +176,36 @@ describe("abstraction api", () => { }).rejects.toThrow(); }); }); + + describe("enable permissioned delegation and send a transaction with permissions", () => { + const alice = Ed25519Account.generate(); + const recipient = Ed25519Account.generate(); + + beforeAll(async () => { + await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: FUND_AMOUNT }); + await aptos.fundAccount({ accountAddress: recipient.accountAddress, amount: FUND_AMOUNT }); + const txn = await aptos.transaction.build.simple({ + sender: alice.accountAddress, + data: { + bytecode: addPermissionDelegationScriptBytecode, + functionArguments: [MoveVector.U8(alice.publicKey.toUint8Array())], + }, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); + await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + }); + + it("should be able to send a transaction with permissioned signer", async () => { + const abstractedAccount = AbstractedAccount.fromPermissionedSigner({ signer: alice }); + const txn = await aptos.transferFungibleAsset({ + amount: 100, + fungibleAssetMetadataAddress: "0xa", + recipient: recipient.accountAddress.toString(), + sender: abstractedAccount, + }); + const pendingTxn = await aptos.signAndSubmitTransaction({ signer: abstractedAccount, transaction: txn }); + const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + expect(response.success).toBe(true); + }); + }); }); diff --git a/tests/e2e/transaction/helper.ts b/tests/e2e/transaction/helper.ts index 409f2ad44..e1b611bb4 100644 --- a/tests/e2e/transaction/helper.ts +++ b/tests/e2e/transaction/helper.ts @@ -370,6 +370,10 @@ export async function publishTransferPackage(aptos: Aptos, senderAccount: Accoun ); } +export const addPermissionDelegationScriptBytecode = + // eslint-disable-next-line max-len + "a11ceb0b0700000a0901001402141a032e38046604056a4807b201a70308d904400699054d10e6051f0103010401060109010d010e01120114011601190002080002080700030b0700040c0f000510070100000818070001050301010001020704050001030a05060001050f01080100010311090a000106130b01000107150b0100010817040c0001091a0d0100010002030702060c0a020001080001060c010a02010801010802010803010b0401090004060c08020b0401080303010c03060c060c0301080504060c0508050805050802030b04010803060c0c083c53454c463e5f30046d61696e094170746f73436f696e0a6170746f735f636f696e04636f696e196d6967726174655f746f5f66756e6769626c655f73746f72650765643235353139256e65775f756e76616c6964617465645f7075626c69635f6b65795f66726f6d5f627974657314556e76616c6964617465645075626c69634b6579177065726d697373696f6e65645f64656c65676174696f6e0f67656e5f656432353531395f6b65790d44656c65676174696f6e4b65790b526174654c696d697465720c726174655f6c696d69746572066f7074696f6e046e6f6e65064f7074696f6e176164645f7065726d697373696f6e65645f68616e646c65167072696d6172795f66756e6769626c655f73746f7265146772616e745f6170745f7065726d697373696f6e167472616e73616374696f6e5f76616c69646174696f6e146772616e745f6761735f7065726d697373696f6e06737472696e67047574663806537472696e67136163636f756e745f6162737472616374696f6e286164645f646973706174636861626c655f61757468656e7469636174696f6e5f66756e6374696f6effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001052000000000000000000000000000000000000000000000000000000000000000010a0218177065726d697373696f6e65645f64656c65676174696f6e0a020d0c61757468656e74696361746514636f6d70696c6174696f6e5f6d65746164617461090003322e3003322e3100000e1e0a0038000b01110111020c020a0038010c040b020b040600a0724e1809000011040c060a000e060600e1f5050000000011050a000e060600e1f5050000000011060b0007000701110707021107110802"; + export async function publishAnyAuthenticatorAAPackage(aptos: Aptos, senderAccount: Account) { return publishPackage( aptos, diff --git a/tests/move/account_abstraction/sources/add_permission_delegation.move b/tests/move/account_abstraction/sources/add_permission_delegation.move new file mode 100644 index 000000000..338f8eff4 --- /dev/null +++ b/tests/move/account_abstraction/sources/add_permission_delegation.move @@ -0,0 +1,19 @@ +script { + use aptos_framework::coin::{Self}; + use aptos_framework::transaction_validation::{Self}; + use aptos_framework::primary_fungible_store::{Self}; + use aptos_framework::account_abstraction::{Self}; + use aptos_framework::permissioned_delegation::{Self}; + use aptos_std::ed25519; + use std::string::utf8; + use std::option; + + fun main(sender: &signer, sender_public_key: vector) { + coin::migrate_to_fungible_store(sender); + let key = permissioned_delegation::gen_ed25519_key(ed25519::new_unvalidated_public_key_from_bytes(sender_public_key)); + let permissioned_signer = permissioned_delegation::add_permissioned_handle(sender, key, option::none(), 10000000000000); + primary_fungible_store::grant_apt_permission(sender, &permissioned_signer, 100000000); + transaction_validation::grant_gas_permission(sender, &permissioned_signer, 100000000); + account_abstraction::add_dispatchable_authentication_function(sender, @aptos_framework, utf8(b"permissioned_delegation"), utf8(b"authenticate")); + } +} diff --git a/tests/move/account_abstraction/sources/hello_world_authenticator.move b/tests/move/account_abstraction/sources/hello_world_authenticator.move index e53442326..64acf458b 100644 --- a/tests/move/account_abstraction/sources/hello_world_authenticator.move +++ b/tests/move/account_abstraction/sources/hello_world_authenticator.move @@ -1,6 +1,5 @@ module deployer::hello_world_authenticator { use aptos_framework::auth_data::{Self, AbstractionAuthData}; - use std::bcs; const EINVALID_SIGNATURE: u64 = 1;