IoTeXにおけるアカウント抽象化の基本ガイド:p256署名の実用ガイド

The Essential Guide to Account Abstraction on IoTeX: A Practical Guide to p256 Signatures

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

簡単なリフレッシャー

アカウント抽象化(AA)は、ERC-4337によって定義されており、「ユーザーが主アカウントとしてEOAではなく任意の検証ロジックを含むスマートコントラクトウォレットを使用できるようにします。」ERC-4337は、特にスマートコントラクトを主アカウントとして利用できるようにすることで、多くのユーザー体験の利点をもたらします。

ERC-4337はブロックチェーンの上で動作し、ブロックチェーン自体に変更を必要としません。現在、IoTeXのアカウント抽象化コードは、ERC-4337 0.6.0リリースバージョンに基づいています。

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

AA

AAインフラのコンポーネントは以下の通りです:

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

アカウント抽象化を使用して新しいカスタムアカウントを作成するには、dApp開発者がアプリケーションのニーズに基づいて作成しなければならないいくつかのコンポーネントがあります:

  • Account契約:validateUserOpメソッドに検証ロジックを実装し、ユーザー操作に必要な任意の実行ロジックを含みます。
  • AccountFactory契約:上記の通り、新しいカスタムアカウント契約を作成/デプロイする責任を持っています。
  • AccountFactoryで実装された検証ルールに適合するユーザー操作を構築するためのいくつかのクライアントコード。
  • ペイマスターはAAアーキテクチャのオプションの一部です。IoTeXはテストネット専用のペイマスターサービスをhttps://paymaster.testnet.w3bstream.comで提供しています。ペイマスターの役割は、ユーザー操作を実行するために必要なガスをスポンサーします。完全にスポンサーするか、ユーザーがさまざまなトークンで支払うことを許可します。

例: P256AccountFactory

最初の例として、開発者が「p256」暗号で署名されたユーザー操作を検証できるアカウント契約を作成するための公式のP256AccountFactory契約(メインネット0xD98d2B6cBca981c777037c5784721d8179D7030bおよびテストネット0x508Db1A73FcBA98594679aD4f5d8D0B880BbdaFB)を提供しました。これは、開発者がユーザーがバイオメトリクスでトランザクションに署名したり、シードフレーズから移行したり、専用のセキュリティチップ(例:AndroidのSecure ElementやAppleのSecure Enclaveなど)をサポートするデバイスを持っている場合に、より優れたセキュリティを確保できるアプリケーションを作成できるようにします。P256AccountFactorycanのソースコードは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は、2つのコンポーネントから構成されるペイマスターサービスの管理もサポートしています。VerifyingPaymaster契約(https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/paymaster/VerifyingPaymaster.sol)と、ペイマスター契約の支払い証明を生成するためのオフチェーンサービスエンドポイント(https://paymaster.testnet.w3bstream.com、テストネット用のみ)です。

以下のコードは、P256アカウントの実装とインタラクションする方法を示しています。こちらのアカウントを作成するためのjavascriptクライアントのコードです:

async function main() {
    //デプロイされたコントラクトを読み込む
    const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
    const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint

    //ユーザー操作を送信するためのEOAアカウント
    const bundler = new ethers.Wallet(process.env.BUNDLER!, ethers.provider)

    //secp256r1鍵ペアを読み込む
    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 initCode = hexConcat([        factory.address,        factory.interface.encodeFunctionData("createAccount", [publicKey, index]),
    ])
    const createOp = {
        sender: account,
        initCode: initCode,
    }

    const fullCreateOp = await fillUserOp(createOp, entryPoint)

    //ガスのためにIOTXをステークする
    const stake = await entryPoint.balanceOf(account)
    if (stake.isZero()) {
        console.log(`アカウント${account}のためにガスを預けます`)
        const tx = await entryPoint
            .connect(bundler)
            .depositTo(account, { value: ethers.utils.parseEther("10") })
        await tx.wait()
    }

    //secp256r1曲線を使用してUserOperationに署名する
    const chainId = (await ethers.provider.getNetwork()).chainId
    const signedOp = await signOp(
        fullCreateOp,
        entryPoint.address,
        chainId,
        new P2565Signer(keyPair)
    )

    //UserOperationをシミュレーションする
    const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
    if (err.errorName === "FailedOp") {
        console.error(`シミュレーションオペレーションエラー ${err.errorArgs.at(-1)}`)
        return
    } else if (err.errorName !== "ValidationResult") {
        console.error(`不明なエラー ${err}`)
        return
    }
    console.log(`シミュレーションオペレーション成功`)

    //UserOperationをEntryPointに送信する
    const tx = await entryPoint.connect(bundler).handleOps([signedOp], bundler.address)
    console.log(`アカウント作成トランザクション: ${tx.hash}, アカウント: ${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 // 一日
    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(`シミュレーションオペレーションエラー ${err.errorArgs.at(-1)}`)
        return
    } else if (err.errorName !== "ValidationResult") {
        console.error(`不明なエラー ${err}`)
        return
    }
    console.log(`シミュレーションオペレーション成功`)

    const hexifiedUserOp = deepHexlify(await resolveProperties(signedOp))
    const result = await bundler.send("eth_sendUserOperation", [hexifiedUserOp, entryPoint.address])
    console.log(`バンドラー使用による転送成功、opHash: ${result}`)
}

p256アカウント実装とのインタラクションに関する残りの例は、https://github.com/iotexproject/account-abstraction-contracts/tree/main/scripts/secp256r1で見つけることができます。