Hướng Dẫn Toàn Diện Về Account Abstraction Trên IoTeX: Hướng Dẫn Thực Hành Về Chữ Ký p256

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

Với sự đồng thuận tuyệt đối từ cộng đồng đối với Đề Xuất Cải Tiến IoTeX 14, Account Abstraction cuối cùng đã có mặt trên IoTeX Mainnet và Testnet, và các tính năng của nó hiện đã sẵn sàng cho tất cả các nhà phát triển trong hệ sinh thái. Vậy AA là gì, nó hoạt động như thế nào, và bạn có thể sử dụng nó trong ứng dụng tiếp theo của mình ra sao?

Ôn Lại Nhanh

Account Abstraction (AA) theo định nghĩa của ERC-4337, "cho phép người dùng sử dụng ví hợp đồng thông minh chứa logic xác minh tùy ý thay vì EOA làm tài khoản chính của họ." ERC-4337 giới thiệu nhiều lợi ích về trải nghiệm người dùng, đáng chú ý nhất là cho phép mọi người sử dụng Hợp Đồng Thông Minh làm tài khoản chính của họ.

ERC-4337 chạy trên nền blockchain và không yêu cầu bất kỳ thay đổi nào đối với chính blockchain. Hiện tại, mã nguồn Account Abstraction của IoTeX dựa trên phiên bản ERC-4337 0.6.0.

Các Thành Phần Của Hạ Tầng AA

AA

Các thành phần của hạ tầng AA bao gồm:

  • Dịch Vụ Bundler: một endpoint cho Mainnet (https://bundler.w3bstream.com) và một cho Testnet (https://bundler.testnet.w3bstream.com). Bundler là một node off-chain tổng hợp nhiều thao tác người dùng (user operations) trừu tượng thành một giao dịch duy nhất mà blockchain nền tảng có thể xử lý. Giao dịch này được gửi đến thành phần cố định khác, gọi là hợp đồng EntryPoint.
  • Hợp đồng EntryPoint: Có hai hợp đồng EntryPoint được triển khai trên IoTeX, một cho Mainnet (0xc3527348De07d591c9d567ce1998eFA2031B8675) và một cho Testnet (0xc3527348De07d591c9d567ce1998eFA2031B8675). Hợp đồng EntryPoint chịu trách nhiệm tạo/triển khai một số hợp đồng đặc biệt, gọi là hợp đồng AccountFactory, mà lần lượt các hợp đồng này chịu trách nhiệm tạo ra các tài khoản nhất định (hợp đồng ví) có thể được sử dụng cho các mục đích cụ thể.

Để sử dụng account abstraction nhằm tạo một tài khoản tùy chỉnh mới, có một số thành phần mà nhà phát triển dApp sẽ phải tạo dựa trên nhu cầu của ứng dụng của họ:

  • Hợp đồng Account, hợp đồng này triển khai logic xác thực trong phương thức validateUserOp, và bất kỳ logic thực thi nào mà một thao tác người dùng có thể yêu cầu.
  • Hợp đồng AccountFactory, như đã đề cập ở trên, chịu trách nhiệm tạo/triển khai các hợp đồng tài khoản tùy chỉnh mới.
  • Một số mã nguồn phía client để xây dựng các thao tác người dùng tương thích với các quy tắc xác minh được triển khai trong AccountFactory.
  • Một paymaster là một phần tùy chọn của kiến trúc AA. IoTeX cung cấp dịch vụ paymaster chỉ dành cho Testnet tại https://paymaster.testnet.w3bstream.com. Vai trò của paymaster là tài trợ gas cần thiết để thực thi các thao tác người dùng, hoặc tài trợ hoàn toàn hoặc cho phép người dùng thanh toán bằng nhiều loại token khác nhau.

Ví Dụ: P256AccountFactory

Là ví dụ đầu tiên, chúng tôi đã cung cấp một hợp đồng chính thức P256AccountFactory (Mainnet 0xD98d2B6cBca981c777037c5784721d8179D7030b và Testnet 0x508Db1A73FcBA98594679aD4f5d8D0B880BbdaFB) cho phép các nhà phát triển tạo ra các hợp đồng tài khoản có thể xác minh các thao tác người dùng được ký bằng mật mã "p256", thay vì bằng đường cong elliptic "secp256k1" gốc của Ethereum và IoTeX. Điều này vô cùng hữu ích, vì nó trao quyền cho các nhà phát triển tạo ra các ứng dụng nơi người dùng có thể, ví dụ, ký giao dịch bằng sinh trắc học của họ, hoặc từ bỏ cụm từ hạt giống (seed phrase), hoặc thậm chí có bảo mật vượt trội khi thiết bị của họ hỗ trợ chip bảo mật chuyên dụng (ví dụ: Secure Element của Android và Secure Enclave của Apple, v.v.). Mã nguồn của P256AccountFactorycan có thể được tìm thấy tại https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/accounts/secp256r1/P256AccountFactory.sol trong khi các hợp đồng Account Abstraction mã nguồn mở dựa trên việc triển khai của tác giả gốc của EIP-4337 dành cho Ethereum tại đây https://github.com/iotexproject/account-abstraction-contracts/tree/main.

P256AccountFactory cũng hỗ trợ quản lý dịch vụ paymaster, được tạo thành từ hai thành phần, một hợp đồng VerifyingPaymaster (https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/paymaster/VerifyingPaymaster.sol) và một endpoint dịch vụ off-chain để tạo bằng chứng thanh toán cho hợp đồng paymaster (https://paymaster.testnet.w3bstream.com, chỉ dành cho Testnet).

Đoạn mã dưới đây cho bạn thấy cách tương tác với việc triển khai tài khoản p256 từ một client javascript nhằm tạo một tài khoản:

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

Trong khi đoạn mã sau sẽ cho bạn thấy cách chuyển IOTX sử dụng dịch vụ bundler và paymaster:

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

Phần còn lại của ví dụ về cách tương tác với việc triển khai tài khoản p256 từ một client javascript, có thể được tìm thấy tại https://github.com/iotexproject/account-abstraction-contracts/tree/main/scripts/secp256r1