Le Guide Essentiel de l'Abstraction de Compte sur IoTeX : Un Guide Pratique des Signatures p256

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

Avec notre communauté votant en faveur de la Proposition d'Amélioration IoTeX 14, l'Abstraction de Compte a enfin atteint le Mainnet et le Testnet d'IoTeX, et ses fonctionnalités sont désormais disponibles pour tous les développeurs de l'écosystème. Alors, qu'est-ce que l'AA, comment cela fonctionne-t-il, et comment pouvez-vous l'utiliser dans votre prochaine application ?

Un Petit Rappel

L'Abstraction de Compte (AA) telle que définie par l'ERC-4337, "permet aux utilisateurs d'utiliser des portefeuilles de contrats intelligents contenant une logique de vérification arbitraire au lieu d'EOAs comme compte principal." L'ERC-4337 introduit de nombreux avantages pour l'expérience utilisateur, notamment en permettant aux utilisateurs d'utiliser des contrats intelligents comme comptes principaux.

L'ERC-4337 fonctionne au-dessus de la blockchain et ne nécessite aucun changement à la blockchain elle-même. Actuellement, le code d'Abstraction de Compte IoTeX est basé sur la version 0.6.0 de l'ERC-4337.

Composants de l'Infrastructure AA

AA

Les composants de l'infrastructure AA sont :

  • Services de Regroupement : un point de terminaison pour Mainnet (https://bundler.w3bstream.com) et un pour Testnet (https://bundler.testnet.w3bstream.com). Un regroupeur est un nœud hors chaîne qui agrège plusieurs opérations utilisateur abstraites en une seule transaction que la blockchain sous-jacente peut traiter. Cette transaction est envoyée à l'autre composant fixe, appelé le EntryPoint Contrat.
  • EntryPoint Contrat : Il existe deux contrats EntryPoint déployés sur IoTeX, un pour Mainnet (0xc3527348De07d591c9d567ce1998eFA2031B8675) et un pour Testnet (0xc3527348De07d591c9d567ce1998eFA2031B8675). Un contrat EntryPoint est chargé de créer/déployer certains contrats spéciaux, appelés contrats AccountFactory, qui à leur tour sont chargés de créer certains comptes (contrats de portefeuille) qui peuvent être utilisés à des fins spécifiques.

Pour utiliser l'abstraction de compte pour créer un nouveau compte personnalisé, il y a certains composants que le développeur d'application décentralisée devra créer en fonction des besoins de son application :

  • Contrat Account, qui implémente la logique de validation dans la méthode validateUserOp, et toute logique d'exécution qu'une opération utilisateur peut nécessiter.
  • Contrat AccountFactory, qui est chargé, comme indiqué ci-dessus, de créer/déployer de nouveaux contrats de compte personnalisés.
  • Du code client qui construit les opérations utilisateur compatibles avec les règles de vérification implémentées dans le AccountFactory.
  • Un paymaster est une partie optionnelle de l'architecture AA. IoTeX fournit un service de paymaster uniquement pour le Testnet à https://paymaster.testnet.w3bstream.com. Le rôle du paymaster est de financer le gaz nécessaire à l'exécution des opérations utilisateur, soit en les sponsorisant complètement, soit en permettant aux utilisateurs de les payer avec différents tokens.

Exemple : Le P256AccountFactory

Comme premier exemple, nous avons fourni un contrat officiel P256AccountFactory (Mainnet 0xD98d2B6cBca981c777037c5784721d8179D7030b et Testnet 0x508Db1A73FcBA98594679aD4f5d8D0B880BbdaFB) qui permet aux développeurs de créer des contrats de compte pouvant vérifier des opérations utilisateur signées avec la cryptographie "p256", plutôt qu'avec la courbe elliptique native "secp256k1" d'Ethereum et d'IoTeX. Cela est incroyablement utile, car cela permet aux développeurs de créer des applications où les utilisateurs peuvent, par exemple, signer des transactions avec leurs biométries, ou se passer de phrases de récupération, ou même bénéficier d'une sécurité supérieure lorsque leur appareil prend en charge une puce de sécurité dédiée (par exemple, l'Élément Sécurisé d'Android et l'Enclave Sécurisée d'Apple, etc.). Le code source du P256AccountFactorycan peut être trouvé à https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/accounts/secp256r1/P256AccountFactory.sol tandis que les contrats d'Abstraction de Compte open source reposent sur l'implémentation par l'auteur d'origine de l'EIP-4337 pour Ethereum ici https://github.com/iotexproject/account-abstraction-contracts/tree/main.

Le P256AccountFactory prend également en charge la gestion d'un service de paymaster, composé de deux composants, un contrat VerifyingPaymaster (https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/paymaster/VerifyingPaymaster.sol) et un point de terminaison de service hors chaîne pour générer une preuve de paiement pour le contrat de paymaster (https://paymaster.testnet.w3bstream.com, pour Testnet uniquement).

Le code ci-dessous vous montre comment interagir avec l'implémentation du compte p256 à partir d'un client javascript afin de créer un compte :

async function main() {
    // charger les contrats déployés
    const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
    const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint

    // un compte EOA pour envoyer des UserOperations
    const bundler = new ethers.Wallet(process.env.BUNDLER!, ethers.provider)

    // charger la paire de clés 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)

    // créer l'opération UserOperation de création de compte
    const initCode = hexConcat([        factory.address,        factory.interface.encodeFunctionData("createAccount", [publicKey, index]),
    ])
    const createOp = {
        sender: account,
        initCode: initCode,
    }

    const fullCreateOp = await fillUserOp(createOp, entryPoint)

    // miser IOTX pour le gaz
    const stake = await entryPoint.balanceOf(account)
    if (stake.isZero()) {
        console.log(`déposer du gaz pour le compte ${account}`)
        const tx = await entryPoint
            .connect(bundler)
            .depositTo(account, { value: ethers.utils.parseEther("10") })
        await tx.wait()
    }

    // signer l'opération UserOperation en utilisant la courbe secp256r1
    const chainId = (await ethers.provider.getNetwork()).chainId
    const signedOp = await signOp(
        fullCreateOp,
        entryPoint.address,
        chainId,
        new P2565Signer(keyPair)
    )

    // simuler l'opération UserOperation
    const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
    if (err.errorName === "FailedOp") {
        console.error(`simuler l'erreur d'opération ${err.errorArgs.at(-1)}`)
        return
    } else if (err.errorName !== "ValidationResult") {
        console.error(`erreur inconnue ${err}`)
        return
    }
    console.log(`simulation de l'opération réussie`)

    // envoyer UserOperation à EntryPoint
    const tx = await entryPoint.connect(bundler).handleOps([signedOp], bundler.address)
    console.log(`transaction de création de compte : ${tx.hash}, compte : ${account}`)
}

Tandis que le code suivant vous montrera comment transférer IOTX en utilisant le service de regroupement et le 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 // un jour
    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(`simuler l'erreur d'opération ${err.errorArgs.at(-1)}`)
        return
    } else if (err.errorName !== "ValidationResult") {
        console.error(`erreur inconnue ${err}`)
        return
    }
    console.log(`simulation de l'opération réussie`)

    const hexifiedUserOp = deepHexlify(await resolveProperties(signedOp))
    const result = await bundler.send("eth_sendUserOperation", [hexifiedUserOp, entryPoint.address])
    console.log(`transfert utilisant le service de regroupement réussi opHash : ${result}`)
}

Le reste de l'exemple sur la façon d'interagir avec l'implémentation du compte p256 à partir d'un client javascript peut être trouvé sur https://github.com/iotexproject/account-abstraction-contracts/tree/main/scripts/secp256r1