IoTeX でのアカウント抽象化の基本ガイド: p256 署名の実践ガイド

IoTeX でのアカウント抽象化の基本ガイド: p256 署名の実践ガイド

私たちのコミュニティが IoTeX 改善提案 14 に全面的に賛成票を投じたことにより、ついにアカウント抽象化が IoTeX メインネットとテストネットに導入され、その機能はすべてのエコシステム開発者が利用できるようになりました。では、AA とは何ですか、どのように機能し、次のアプリケーションでどのように使用できるのでしょうか?

簡単なおさらい

ERC-4337で定義されているアカウント抽象化 (AA) により、「ユーザーはプライマリ アカウントとして EOA の代わりに、任意の検証ロジックを含むスマート コントラクト ウォレットを使用できるようになります。」 ERC-4337 では、多くのユーザー エクスペリエンス上の利点が導入されており、最も注目すべき点は、人々がプライマリ アカウントとしてスマート コントラクトを使用できるようにすることです。

ERC-4337 はブロックチェーン上で実行され、ブロックチェーン自体に変更を加える必要はありません。現在、IoTeX アカウント抽象化コードは ERC-4337 0.6.0 リリース バージョンに基づいています。

AA インフラのコンポーネント

AA インフラストラクチャのコンポーネントは次のとおりです。

  • バンドラー サービス: Mainnet 用のエンドポイント (https://bundler.w3bstream.com) と Testnet 用のエンドポイント (https://bundler.testnet.w3bstream.com)。バンドラーは、複数の抽象化されたユーザー操作を、基礎となるブロックチェーンが処理できる単一のトランザクションに集約するオフチェーン ノードです。このトランザクションは、EntryPoint コントラクトと呼ばれる他の固定コンポーネントに送信されます。
  • EntryPoint コントラクト: IoTeX には 2 つの EntryPoint コントラクトがデプロイされています。1 つはMainnet用 (0xc3527348De07d591c9d567ce1998eFA2031B8675)、もう 1 つはTestnet用 (0xc3527348De07d591c9d567ce1998eFA2031B8675) です。 EntryPoint コントラクトは、AccountFactory コントラクトと呼ばれる特定の特別なコントラクトの作成/展開を担当し、さらに AccountFactory コントラクトは、特定の目的に使用できる特定のアカウント (ウォレット コントラクト) の作成を担当します。

アカウント抽象化を使用して新しいカスタム アカウントを作成するには、dApp 開発者がアプリケーションのニーズに基づいて作成する必要がある特定のコンポーネントがあります。

  • アカウント コントラクト。 validateUserOp メソッドの検証ロジックと、ユーザー操作に必要な実行ロジックを実装します。
  • AccountFactory コントラクト。上記のように、新しいカスタム アカウント コントラクトの作成/展開を担当します。
  • AccountFactory に実装された検証ルールと互換性のあるユーザー操作を構築する一部のクライアント コード。
  • Paymaster は、AA アーキテクチャのオプションの部分です。 IoTeX は、https://paymaster.testnet.w3bstream.com でのみ Testnet 用の Paymaster サービスを提供します。ペイマスターの役割は、ユーザーの操作を実行するために必要なガスをスポンサーすることです。これは、完全にスポンサーになるか、ユーザーがさまざまなトークンでその料金を支払うことを許可するかのいずれかです。

例: P256AccountFactory


最初の例として、開発者が署名されたユーザー操作を検証できるアカウント コントラクトを作成できるようにする、公式の P256AccountFactory コントラクト (Mainnet 0xD98d2B6cBca981c777037c5784721d8179D7030b および Testnet 0x508Db1A73FcBA98594679aD4f5d8D0B880BbdaFB) を提供しました。 Ethereum や IoTeX ネイティブの「secp256k1」楕円ではなく、「p256」暗号化曲線。これは、開発者が、たとえば、ユーザーが自分の生体認証でトランザクションに署名したり、シード フレーズから離れたり、デバイスが専用のセキュリティ チップ (Android のセキュア エレメントなど) をサポートしている場合に優れたセキュリティを実現したりできるアプリケーションを作成できるため、非常に便利です。 Apple の Secure Enclave など)。 P256AccountFactory のソース コードは https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/accounts/secp256r1/P256AccountFactory.sol で見つけることができますが、オープンソースのアカウント抽象化コントラクトは、による実装に依存しています。 Ethereum 用 EIP-4337 のオリジナルの作成者はこちら https://github.com/iotexproject/account-abstraction-contracts/tree/main。

P256AccountFactory は、Paymaster サービスの管理もサポートしています。このサービスは、VerifyingPaymaster コントラクト (https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/paymaster/VerifyingPaymaster.sol) という 2 つのコンポーネントで構成されています。 ) と、ペイマスター契約の支払い証明を生成するためのオフチェーン サービス エンドポイント (https://paymaster.testnet.w3bstream.com、テストネットのみ)。

以下のコードは、アカウントを作成するために JavaScript クライアントから p256 アカウント実装と対話する方法を示しています。

async function main() {
    // load deployed contracts
    const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
    const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint

    // an EOA account for send UserOperations
    const bundler = new ethers.Wallet(process.env.BUNDLER!, ethers.provider)

    // load secp256r1 keypair
    const keyContent = fs.readFileSync(path.join(__dirname, "key.pem"))
    const keyPair = ecPem.loadPrivateKey(keyContent)

    const publicKey = "0x" + keyPair.getPublicKey("hex").substring(2)
    const index = 0
    const account = await factory.getAddress(publicKey, index)

    // create create account UserOperation
    const initCode = hexConcat([        factory.address,        factory.interface.encodeFunctionData("createAccount", [publicKey, index]),
    ])
    const createOp = {
        sender: account,
        initCode: initCode,
    }

    const fullCreateOp = await fillUserOp(createOp, entryPoint)

    // stake IOTX for gas
    const stake = await entryPoint.balanceOf(account)
    if (stake.isZero()) {
        console.log(`deposit gas for account ${account}`)
        const tx = await entryPoint
            .connect(bundler)
            .depositTo(account, { value: ethers.utils.parseEther("10") })
        await tx.wait()
    }

    // sign UserOperation using secp256r1 curve
    const chainId = (await ethers.provider.getNetwork()).chainId
    const signedOp = await signOp(
        fullCreateOp,
        entryPoint.address,
        chainId,
        new P2565Signer(keyPair)
    )

    // simulate UserOperation
    const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
    if (err.errorName === "FailedOp") {
        console.error(`simulate op error ${err.errorArgs.at(-1)}`)
        return
    } else if (err.errorName !== "ValidationResult") {
        console.error(`unknow error ${err}`)
        return
    }
    console.log(`simulate op success`)

    // send UserOpersion to EntryPoint
    const tx = await entryPoint.connect(bundler).handleOps([signedOp], bundler.address)
    console.log(`create account tx: ${tx.hash}, account: ${account}`)
}

次のコードは、バンドラー サービスとペイマスターを使用して IOTX を転送する方法を示しています。

async function main() {
    const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
    const accountTpl = await ethers.getContractFactory("P256Account")
    const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint
    const paymaster = await ethers.getContract("VerifyingPaymaster")
    const bundler = new JsonRpcProvider("http://localhost:4337")

    const signer = new ethers.Wallet(process.env.PRIVATE_KEY!)

    const keyContent = fs.readFileSync(path.join(__dirname, "key.pem"))
    const keyPair = ecPem.loadPrivateKey(keyContent)

    const publicKey = "0x" + keyPair.getPublicKey("hex").substring(2)

    const index = 0
    const account = await factory.getAddress(publicKey, index)

    const callData = accountTpl.interface.encodeFunctionData("execute", [
        "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        ethers.utils.parseEther("0.1"),
        "0x",
    ])

    const transferOp = {
        sender: account,
        callData,
        preVerificationGas: 50000,
    }

    const fullCreateOp = await fillUserOp(transferOp, entryPoint)
    fullCreateOp.paymasterAndData = hexConcat([
        paymaster.address,
        defaultAbiCoder.encode(["uint48", "uint48"], [0, 0]),
        "0x" + "00".repeat(65),
    ])

    const validAfter = Math.floor(new Date().getTime() / 1000)
    const validUntil = validAfter + 86400 // one day
    const pendingOpHash = await paymaster.getHash(fullCreateOp, validUntil, validAfter)
    const paymasterSignature = await signer.signMessage(arrayify(pendingOpHash))
    fullCreateOp.paymasterAndData = hexConcat([
        paymaster.address,
        defaultAbiCoder.encode(["uint48", "uint48"], [validUntil, validAfter]),
        paymasterSignature,
    ])

    const chainId = (await ethers.provider.getNetwork()).chainId
    const signedOp = await signOp(
        fullCreateOp,
        entryPoint.address,
        chainId,
        new P2565Signer(keyPair)
    )

    const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
    if (err.errorName === "FailedOp") {
        console.error(`simulate op error ${err.errorArgs.at(-1)}`)
        return
    } else if (err.errorName !== "ValidationResult") {
        console.error(`unknow error ${err}`)
        return
    }
    console.log(`simulate op success`)

    const hexifiedUserOp = deepHexlify(await resolveProperties(signedOp))
    const result = await bundler.send("eth_sendUserOperation", [hexifiedUserOp, entryPoint.address])
    console.log(`transfer use bundler success opHash: ${result}`)

JavaScript クライアントから p256 アカウント実装と対話する方法に関する例の残りの部分は、https://github.com/iotexproject/account-abstraction-contracts/tree/main/scripts/secp256r1 にあります。