Skip to content
🎉 Welcome to the new Aptos Docs! Click here to submit an issue.

账户抽象

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

核心概念

FunctionInfo

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

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`;

AbstractionAuthData

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

  • 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 文件中声明了命名地址。

aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890

2. 设置环境

部署完成后,您可以设置环境。本示例中,我们将使用 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`;

3. (可选)检查账户抽象是否启用

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

const accountAbstractionStatus = await aptos.abstraction.isAccountAbstractionEnabled({
    accountAddress: alice.accountAddress,
    authenticationFunction,
});
 
console.log("账户抽象状态: ", accountAbstractionStatus);

4. 启用认证功能

若账户未启用账户抽象,您需要通过调用 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);
钱包适配器示例
💡

如果使用钱包适配器,可以通过 signTransaction 函数在交易提交到网络前进行签名。

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 });
    }
  }
}

5. 创建抽象账户

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

const abstractedAccount = new AbstractedAccount({
  accountAddress: alice.accountAddress,
  signer: () => new TextEncoder().encode("hello world"),
  authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`,
});

6. 使用抽象账户签署并提交交易

创建抽象账户后,可正常使用它来签署交易。需确保交易中的 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);

7. 结论

要验证您是否已成功使用抽象账户签名并提交交易,您可以使用区块链浏览器检查交易记录。如果交易签名中包含 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 文件中声明了命名地址。

aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890

2. 设置环境

部署完成后,您可以设置环境。在本示例中,我们将使用 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`;

3. (可选)检查账户抽象是否启用

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

const accountAbstractionStatus = await aptos.abstraction.isAccountAbstractionEnabled({
    accountAddress: alice.accountAddress,
    authenticationFunction,
});
 
console.log("Account Abstraction status: ", accountAbstractionStatus);

4. 启用认证功能

假设账户尚未启用账户抽象,我们需要为账户启用认证功能。这可以通过调用 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);

5. 授权 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}`);

6. 创建抽象账户

现在我们已经授权了 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,
});

7. 使用抽象账户签署并提交交易

创建抽象账户后,我们可以正常使用它来签署交易。需要注意的是,交易中的 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);

8. 结论

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

交易签名

管理操作

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

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

应用用户体验

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

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