跳转到内容

账户抽象

Aptos 上的账户抽象 (AA) 通过 Move 模块实现自定义交易验证逻辑,允许账户在原生加密方案之外定义自己的规则.

定义待调用验证函数的结构体.

struct FunctionInfo has copy, drop, store {
module_address: address,
module_name: String,
function_name: String
}

验证函数负责使用 Move 定义验证逻辑.验证成功时应返回一个 signer,否则中止交易. 可添加到账户上的唯一有效验证函数签名如下:

// 函数在验证成功时可返回 signer,否则中止交易
public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer;

示例 (Move)

module deployer::authenticator {
use aptos_framework::auth_data::{AbstractionAuthData};
public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer {
// ... 验证逻辑 ...
account
}
}

示例 (Typescript)

const authenticationFunction = `${deployer}::authenticator::authenticate`;

定义传递给验证函数的验证数据的枚举变体.包含:

  • digest: 签名消息的 sha256 哈希值
  • authenticator: 将传递给验证函数的抽象字节数据,用于验证交易
enum AbstractionAuthData has copy, drop {
V1 {
digest: vector<u8>, // 签名消息的 SHA3-256 哈希
authenticator: vector<u8> // 自定义验证数据(如签名)
},
}

为什么 digest 很重要?

MoveVM 会检查 digest 以确保提交的交易签名消息与 AbstractionAuthData 中呈现的消息一致. 这很重要,因为它允许验证函数针对正确的交易验证签名.

例如,如果你想允许某个公钥代表用户签署交易,可以授权该公钥对特定 payload 进行签名. 但如果恶意用户发送了正确公钥但 digest 不同 payload 的签名,该签名将无效.

示例 (Move)

此示例展示了一个简单的验证逻辑,检查 authenticator 是否等于 "hello world".

module deployer::hello_world_authenticator {
use aptos_framework::auth_data::{Self, AbstractionAuthData};
public fun authenticate(
account: signer,
auth_data: AbstractionAuthData
): signer {
let authenticator = *auth_data::authenticator(&auth_data);
assert!(authenticator == b"hello world", 1);
account
}
}

示例 (Typescript)

const abstractedAccount = new AbstractedAccount({
/**
* 签名器函数的执行结果将作为 `authenticator` 字段存在于 `AbstractionAuthData` 枚举变体中。
*/
signer: () => new TextEncoder().encode("hello world"),
/**
* 需要调用的认证函数。
*/
authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`,
});
    1. 部署认证模块

    本示例中,我们将部署 hello_world_authenticator 模块.authenticate 函数接收一个 AbstractionAuthData,若认证成功则返回 signer,否则终止交易.认证逻辑仅允许 authenticator 等于 "hello world" 的交易.

    module deployer::hello_world_authenticator {
    use aptos_framework::auth_data::{Self, AbstractionAuthData};
    use std::bcs;
    public fun authenticate(
    account: signer,
    auth_data: AbstractionAuthData
    ): signer {
    let authenticator = *auth_data::authenticator(&auth_data);
    assert!(authenticator == b"hello world", 1);
    account
    }
    }

    部署模块可使用 Aptos CLI 的以下命令.假设您已通过 aptos init 设置工作区,并在 Move.toml 文件中声明了命名地址.

    Terminal window
    aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890
    1. 设置环境

    部署完成后,您可以设置环境.本示例中,我们将使用 Devnet 并创建名为 alice 的账户作为用户.

    const DEPLOYER = "0x<hello_world_authenticator_deployer>"
    const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET }));
    const alice = Account.generate();
    const authenticationFunctionInfo = `${deployer}::hello_world_authenticator::authenticate`;
    1. (可选)检查账户抽象是否启用

    在要求启用账户抽象前,您可以通过调用 isAccountAbstractionEnabled 函数检查账户是否已启用账户抽象.该函数返回布尔值表示账户抽象状态.

    const accountAbstractionStatus = await aptos.abstraction.isAccountAbstractionEnabled({
    accountAddress: alice.accountAddress,
    authenticationFunction,
    });
    console.log("账户抽象状态: ", accountAbstractionStatus);
    1. 启用认证功能

    若账户未启用账户抽象,您需要通过调用 enableAccountAbstractionTransaction 函数为账户启用认证功能.这将创建一个需要签名并提交到网络的原始交易.本示例中,alice 账户将被启用.

    const transaction = aptos.abstraction.enableAccountAbstractionTransaction({
    accountAddress: alice.accountAddress,
    authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`,
    });
    const pendingTransaction = await aptos.signAndSubmitTransaction({
    transaction,
    signer: alice.signer,
    });
    await aptos.waitForTransaction({ hash: pendingTransaction.hash });
    console.log("Account Abstraction enabled for account: ", alice.accountAddress);
    钱包适配器示例
    export default function useEnableAbstraction() {
    const { account, signTransaction } = useWallet();
    return {
    enableAbstraction: async () => {
    if (!account) return;
    // 注意:Aptos 客户端必须在应用某处定义
    const transaction = aptos.abstraction.enableAccountAbstractionTransaction({
    accountAddress: account.address,
    authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`,
    });
    const senderAuthenticator = await signTransaction(txn);
    const pendingTxn = await aptos.transaction.submit.simple({
    transaction: txn,
    senderAuthenticator,
    });
    return await aptos.waitForTransaction({ hash: pendingTxn.hash });
    }
    }
    }
    1. 创建抽象账户

    启用认证函数后,可以创建用于交易签名的抽象账户对象.需要提供用于验证交易的认证函数, 以及用于签署交易的 signer 函数.signer 函数负责生成传递给认证函数的验证器.

    const abstractedAccount = new AbstractedAccount({
    accountAddress: alice.accountAddress,
    signer: () => new TextEncoder().encode("hello world"),
    authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`,
    });
    1. 使用抽象账户签署并提交交易

    创建抽象账户后,可正常使用它来签署交易.需确保交易中的 sender 字段 与抽象账户地址一致.

    const coinTransferTransaction = await aptos.transaction.build.simple({
    sender: abstractedAccount.accountAddress,
    data: {
    function: "0x1::coin::transfer",
    typeArguments: ["0x1::aptos_coin::AptosCoin"],
    functionArguments: [alice.accountAddress, 100],
    },
    });
    const pendingCoinTransferTransaction = await aptos.transaction.signAndSubmitTransaction({
    transaction: coinTransferTransaction,
    signer: abstractedAccount,
    });
    await aptos.waitForTransaction({ transactionHash: pendingCoinTransferTransaction.hash });
    console.log("代币转账交易已提交! ", pendingCoinTransferTransaction.hash);
    1. 结论

    要验证您是否已成功使用抽象账户签名并提交交易,您可以使用区块链浏览器检查交易记录.如果交易签名中包含 function_infoauth_data 字段,则表明您已成功使用账户抽象功能!完整端到端演示代码可在此处查看.

    交易签名

现在您已基本了解账户抽象的工作原理,接下来我们将深入探讨一个更复杂的示例.

在本示例中,我们将创建一个认证器,允许用户授权特定公钥代表抽象账户签署交易.

    1. 创建认证器模块

    我们将部署 public_key_authenticator 模块,该模块实现以下两个功能:

    • 允许用户授权和/或撤销代表用户签名的公钥
    • 允许用户使用账户抽象机制代表他人进行认证
    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};
    // ====== 错误码 ====== //
    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;
    // ====== 数据结构 ====== //
    struct PublicKeyPermissions has key {
    public_key_table: SmartTable<vector<u8>, bool>,
    }
    // ====== 认证器 ====== //
    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);
    // 从认证器中提取公钥和签名
    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))
    );
    // 检查公钥是否已授权
    assert!(smart_table::contains(&permissions.public_key_table, unvalidated_public_key_to_bytes(&public_key)), EPUBLIC_KEY_NOT_PERMITTED);
    // 验证签名
    let digest = *auth_data::digest(&auth_data);
    assert!(ed25519::signature_verify_strict(&signature, &public_key, digest), EINVALID_SIGNATURE);
    account
    }
    // ====== 核心功能 ====== //
    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);
    }
    }

    让我们分解这个模块…

    存储公钥

    PublicKeyPermissions 结构体是一个包含 SmartTable 的键,用于存储公钥并确定是否允许该公钥代表用户签署交易.

    module deployer::public_key_authenticator {
    // ...
    struct PublicKeyPermissions has key {
    public_key_table: SmartTable<vector<u8>, bool>,
    }
    }

    授权与撤销公钥

    我们定义了两个入口函数来授权和撤销公钥.这些函数用于在 PublicKeyPermissions 结构体中添加和移除公钥.

    module deployer::public_key_authenticator {
    // ...
    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);
    }
    }

    代理身份验证

    authenticate 函数是核心功能,允许用户通过账户抽象机制代表他人进行身份验证.authenticator 将包含用户的 公钥签名.我们将验证公钥是否被授权以及签名是否有效.

    签名是对 digest 进行签名的结果.digest签名消息 的 sha256 哈希值,包含交易相关信息.通过对 digest 签名,我们确认用户已批准提交的特定交易.

    module deployer::public_key_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);
    // 从认证器中提取公钥和签名
    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))
    );
    // 检查公钥是否被授权
    assert!(smart_table::contains(&permissions.public_key_table, unvalidated_public_key_to_bytes(&public_key)), EPUBLIC_KEY_NOT_PERMITTED);
    // 验证签名
    let digest = *auth_data::digest(&auth_data);
    assert!(ed25519::signature_verify_strict(&signature, &public_key, digest), EINVALID_SIGNATURE);
    account
    }
    }

    要部署该模块,您可以使用 Aptos CLI 的以下命令.我们假设您已经通过 aptos init 设置了工作区,并在 Move.toml 文件中声明了命名地址.

    Terminal window
    aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890
    1. 设置环境

    部署完成后,您可以设置环境.在本示例中,我们将使用 Devnet 并创建一个名为 alice 的账户作为被认证用户,以及 bob 作为被授权代表 alice 签署交易的用户.

    const DEPLOYER = "0x<public_key_authenticator_deployer>"
    const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET }));
    const alice = Account.generate();
    const bob = Account.generate();
    const authenticationFunctionInfo = `${deployer}::public_key_authenticator::authenticate`;
    1. (可选)检查账户抽象是否启用

    在启用认证功能之前,我们可以通过调用 isAccountAbstractionEnabled 函数来检查账户是否已启用账户抽象.这将返回一个布尔值,表示账户是否启用了账户抽象.

    const accountAbstractionStatus = await aptos.abstraction.isAccountAbstractionEnabled({
    accountAddress: alice.accountAddress,
    authenticationFunction,
    });
    console.log("Account Abstraction status: ", accountAbstractionStatus);
    1. 启用认证功能

    假设账户尚未启用账户抽象,我们需要为账户启用认证功能.这可以通过调用 enableAccountAbstractionTransaction 函数来完成.这会创建一个需要签名并提交到网络的原始交易.在本示例中,alice 将是被启用的账户.

    const transaction = await aptos.abstraction.enableAccountAbstractionTransaction({
    accountAddress: alice.accountAddress,
    authenticationFunction,
    });
    const pendingTransaction = await aptos.signAndSubmitTransaction({
    transaction,
    signer: alice.signer,
    });
    await aptos.waitForTransaction({ hash: pendingTransaction.hash });
    console.log("Account Abstraction enabled for account: ", alice.accountAddress);
    1. 授权 Bob 的公钥

    现在我们已经启用了认证功能,可以授权 bob 的公钥代表 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,
    });
    await aptos.waitForTransaction({ hash: pendingEnableBobPublicKeyTransaction.hash });
    console.log(`Enable Bob's public key transaction hash: ${pendingEnableBobPublicKeyTransaction.hash}`);
    1. 创建抽象账户

    现在我们已经授权了 bob 的公钥,可以创建一个抽象账户,用于代表 alice 签署交易. 注意 signer 函数使用的是 bob 的签名器.

    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,
    });
    1. 使用抽象账户签署并提交交易

    创建抽象账户后,我们可以正常使用它来签署交易.需要注意的是,交易中的 sender 字段必须与抽象账户地址一致.

    const coinTransferTransaction = new aptos.transaction.build.simple({
    sender: abstractedAccount.accountAddress,
    data: {
    function: "0x1::coin::transfer",
    typeArguments: ["0x1::aptos_coin::AptosCoin"],
    functionArguments: [alice.accountAddress, 100],
    },
    });
    const pendingCoinTransferTransaction = await aptos.transaction.signAndSubmitTransaction({
    transaction: coinTransferTransaction,
    signer: abstractedAccount,
    });
    await aptos.waitForTransaction({ hash: pendingCoinTransferTransaction.hash });
    console.log("Coin transfer transaction submitted! ", pendingCoinTransferTransaction.hash);
    1. 结论

    要验证是否成功使用抽象账户签署并提交了交易,可以通过区块链浏览器检查该交易.如果交易签名中包含 function_infoauth_data 字段,则表示您已成功使用账户抽象功能!完整端到端示例代码可在此处查看 here

    交易签名

如需禁用某个账户的账户抽象功能,可以使用 disableAccountAbstractionTransaction 方法.如果不指定认证函数,该交易将禁用该账户的所有认证函数.

const transaction = aptos.abstraction.disableAccountAbstractionTransaction({
accountAddress: alice.accountAddress,
/**
* 指定要禁用的认证函数。若保持 `undefined` 则禁用所有认证函数
*/
authenticationFunction,
});

希望利用账户抽象功能的应用程序,应当提供允许用户检查账户是否启用了账户抽象功能,并在未启用时进行启用的用户体验.

下图展示了启用账户抽象功能的用户体验流程.账户抽象用户体验