-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[abstraction] Add support for AccountAbstraction (#622)
* Fix FA tests * Initial work for AccountAbstraction * Add tests * Update CHANGELOG.md * Fix dependency cycle * Add permissioned delegation tests * Update tests * Update with new abstraction function names * Remove any_authenticator example for complex public key delegation example
- Loading branch information
1 parent
5d60650
commit e48a65f
Showing
29 changed files
with
1,218 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
move/moonCoin/moonCoin.json | ||
move/moonCoin/test-package.json | ||
move/facoin/facoin.json | ||
move/facoin/facoin.json | ||
move/account_abstraction/*.json |
86 changes: 86 additions & 0 deletions
86
examples/typescript/hello_world_authenticator_account_abstraction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" } |
14 changes: 14 additions & 0 deletions
14
examples/typescript/move/account_abstraction/sources/hello_world_authenticator.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module deployer::hello_world_authenticator { | ||
use aptos_framework::auth_data::{Self, AbstractionAuthData}; | ||
|
||
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 | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
examples/typescript/move/account_abstraction/sources/public_key_authenticator.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
module deployer::public_key_authenticator { | ||
use std::signer; | ||
use aptos_std::smart_table::{Self, SmartTable}; | ||
use aptos_std::ed25519::{ | ||
Self, | ||
new_signature_from_bytes, | ||
new_unvalidated_public_key_from_bytes, | ||
unvalidated_public_key_to_bytes | ||
}; | ||
use aptos_framework::bcs_stream::{Self, deserialize_u8}; | ||
use aptos_framework::auth_data::{Self, AbstractionAuthData}; | ||
|
||
// ====== Error Codes ====== // | ||
|
||
const EINVALID_PUBLIC_KEY: u64 = 0x20000; | ||
const EPUBLIC_KEY_NOT_PERMITTED: u64 = 0x20001; | ||
const EENTRY_ALREADY_EXISTS: u64 = 0x20002; | ||
const ENO_PERMISSIONS: u64 = 0x20003; | ||
const EINVALID_SIGNATURE: u64 = 0x20004; | ||
|
||
// ====== Data Structures ====== // | ||
|
||
struct PublicKeyPermissions has key { | ||
public_key_table: SmartTable<vector<u8>, bool>, | ||
} | ||
|
||
// ====== Authenticator ====== // | ||
|
||
public fun authenticate( | ||
account: signer, | ||
auth_data: AbstractionAuthData | ||
): signer acquires PublicKeyPermissions { | ||
let account_addr = signer::address_of(&account); | ||
assert!(exists<PublicKeyPermissions>(account_addr), ENO_PERMISSIONS); | ||
let permissions = borrow_global<PublicKeyPermissions>(account_addr); | ||
|
||
// Extract the public key and signature from the authenticator | ||
let authenticator = *auth_data::authenticator(&auth_data); | ||
let stream = bcs_stream::new(authenticator); | ||
let public_key = new_unvalidated_public_key_from_bytes( | ||
bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x)) | ||
); | ||
let signature = new_signature_from_bytes( | ||
bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x)) | ||
); | ||
|
||
// Check if the public key is permitted | ||
assert!(smart_table::contains(&permissions.public_key_table, unvalidated_public_key_to_bytes(&public_key)), EPUBLIC_KEY_NOT_PERMITTED); | ||
|
||
// Verify the signature | ||
let digest = *auth_data::digest(&auth_data); | ||
assert!(ed25519::signature_verify_strict(&signature, &public_key, digest), EINVALID_SIGNATURE); | ||
|
||
account | ||
} | ||
|
||
// ====== Core Functionality ====== // | ||
|
||
public entry fun permit_public_key( | ||
signer: &signer, | ||
public_key: vector<u8> | ||
) acquires PublicKeyPermissions { | ||
let account_addr = signer::address_of(signer); | ||
assert!(std::vector::length(&public_key) == 32, EINVALID_PUBLIC_KEY); | ||
|
||
if (!exists<PublicKeyPermissions>(account_addr)) { | ||
move_to(signer, PublicKeyPermissions { | ||
public_key_table: smart_table::new(), | ||
}); | ||
}; | ||
|
||
let permissions = borrow_global_mut<PublicKeyPermissions>(account_addr); | ||
assert!( | ||
!smart_table::contains(&permissions.public_key_table, public_key), | ||
EENTRY_ALREADY_EXISTS | ||
); | ||
|
||
smart_table::add(&mut permissions.public_key_table, public_key, true); | ||
|
||
} | ||
|
||
public entry fun revoke_public_key( | ||
signer: &signer, | ||
public_key: vector<u8> | ||
) acquires PublicKeyPermissions { | ||
let account_addr = signer::address_of(signer); | ||
|
||
assert!(exists<PublicKeyPermissions>(account_addr), ENO_PERMISSIONS); | ||
|
||
let permissions = borrow_global_mut<PublicKeyPermissions>(account_addr); | ||
smart_table::remove(&mut permissions.public_key_table, public_key); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
examples/typescript/public_key_authenticator_account_abstraction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* eslint-disable no-console */ | ||
|
||
import { | ||
Account, | ||
AbstractedAccount, | ||
Aptos, | ||
Network, | ||
AptosConfig, | ||
UserTransactionResponse, | ||
Serializer, | ||
} 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(); | ||
const bob = Account.generate(); | ||
|
||
console.log("\n=== Addresses ==="); | ||
console.log(`Alice: ${alice.accountAddress.toString()}`); | ||
console.log(`Bob: ${bob.accountAddress.toString()}`); | ||
console.log("\n=== Funding Accounts ==="); | ||
await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 1000000000000000 }); | ||
console.log("Finished funding accounts!"); | ||
|
||
console.log("\n=== Compiling public_key_authenticator package locally ==="); | ||
compilePackage( | ||
"move/account_abstraction", | ||
"move/account_abstraction/public_key_authenticator.json", | ||
[{ name: "deployer", address: alice.accountAddress }], | ||
["--move-2"], | ||
); | ||
const { metadataBytes, byteCode } = getPackageBytesToPublish( | ||
"move/account_abstraction/public_key_authenticator.json", | ||
); | ||
console.log(`\n=== Publishing public_key_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}::public_key_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=== Enabling account abstraction for ${alice.accountAddress.toString()} ===`); | ||
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=== Permitting Bob's public key to sign on behalf of Alice"); | ||
const enableBobPublicKeyTransaction = await aptos.transaction.build.simple({ | ||
sender: alice.accountAddress, | ||
data: { | ||
function: `${alice.accountAddress}::public_key_authenticator::permit_public_key`, | ||
typeArguments: [], | ||
functionArguments: [bob.publicKey.toUint8Array()], | ||
}, | ||
}); | ||
const pendingEnableBobPublicKeyTransaction = await aptos.signAndSubmitTransaction({ | ||
signer: alice, | ||
transaction: enableBobPublicKeyTransaction, | ||
}); | ||
console.log(`Enable Bob's public key transaction hash: ${pendingEnableBobPublicKeyTransaction.hash}`); | ||
await aptos.waitForTransaction({ transactionHash: pendingEnableBobPublicKeyTransaction.hash }); | ||
|
||
console.log("\n=== Signing a transaction with the abstracted account using Bob's signer ==="); | ||
|
||
const abstractedAccount = new AbstractedAccount({ | ||
accountAddress: alice.accountAddress, | ||
signer: (digest) => { | ||
const serializer = new Serializer(); | ||
bob.publicKey.serialize(serializer); | ||
bob.sign(digest).serialize(serializer); | ||
return serializer.toUint8Array(); | ||
}, | ||
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.