Merge pull request #1 from getwax/anon-aadhaar

Use correct verfier for AnonAadhaar
This commit is contained in:
porco
2024-07-05 18:58:39 +04:00
committed by GitHub
15 changed files with 703 additions and 430 deletions

View File

@@ -17,16 +17,18 @@
"devDependencies": { "devDependencies": {
"@account-abstraction/contracts": "0.7.0", "@account-abstraction/contracts": "0.7.0",
"@account-abstraction/utils": "^0.6.0", "@account-abstraction/utils": "^0.6.0",
"@anon-aadhaar/contracts": "2.2.0",
"@anon-aadhaar/core": "2.2.0", "@anon-aadhaar/core": "2.2.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0", "@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0", "@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-ignition": "^0.15.5",
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0", "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^3.0.0", "@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@nomicfoundation/hardhat-verify": "^1.0.0", "@nomicfoundation/hardhat-verify": "^2.0.8",
"@nomicfoundation/ignition-core": "^0.15.5",
"@thehubbleproject/bls": "^0.5.1", "@thehubbleproject/bls": "^0.5.1",
"@typechain/ethers-v6": "^0.4.0", "@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^8.0.0", "@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.2.0", "@types/chai": "^4.2.0",
"@types/circomlibjs": "^0.1.6", "@types/circomlibjs": "^0.1.6",
"@types/mocha": ">=9.1.0", "@types/mocha": ">=9.1.0",
@@ -44,8 +46,8 @@
"eslint-plugin-import": "^2.28.1", "eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"ethers": "^6.4.0", "ethers": "^6.4.0",
"hardhat": "^2.17.1", "hardhat": "^2.22.6",
"hardhat-gas-reporter": "^1.0.8", "hardhat-gas-reporter": "^2.2.0",
"hardhat-preprocessor": "^0.1.5", "hardhat-preprocessor": "^0.1.5",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"solidity-coverage": "^0.8.0", "solidity-coverage": "^0.8.0",
@@ -53,4 +55,4 @@
"typechain": "^8.1.0", "typechain": "^8.1.0",
"typescript": "^5.4.3" "typescript": "^5.4.3"
} }
} }

View File

@@ -0,0 +1,3 @@
# Paymaster
These are exemplary paymaster contracts. The SponsorEverythingPaymaster contract and its test can serve as references when developing more complex paymasters for your specific use cases.

View File

@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;
import {IEntryPoint} from 'account-abstraction/interfaces/IEntryPoint.sol';
import {BasePaymaster} from 'account-abstraction/core/BasePaymaster.sol';
import {UserOperationLib} from 'account-abstraction/core/UserOperationLib.sol';
import {PackedUserOperation} from 'account-abstraction/interfaces/PackedUserOperation.sol';
/*//////////////////////////////////////////////////////////////////////////
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
//////////////////////////////////////////////////////////////////////////*/
/// @title This paymaster sponsors everything.
contract SponsorEverythingPaymaster is BasePaymaster {
using UserOperationLib for PackedUserOperation;
constructor(IEntryPoint _entryPoint) BasePaymaster(_entryPoint) {}
/**
* Validate a user operation.
* @param userOp - The user operation.
* @param userOpHash - The hash of the user operation.
* @param maxCost - The maximum cost of the user operation.
*/
function _validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) internal virtual override returns (bytes memory context, uint256 validationData) {
// Validation logic comes here.
// Approve everything.
return ("", 0);
}
}

View File

@@ -4,7 +4,7 @@ pragma abicoder v2;
import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol"; import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
import {IEntryPoint, PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol"; import {IEntryPoint, PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
import {IAnonAadhaar} from "@anon-aadhaar/contracts/interfaces/IAnonAadhaar.sol"; import {IAnonAadhaar} from "./utils/anonAadhaar/interfaces/IAnonAadhaar.sol";
interface ISafe { interface ISafe {
function enableModule(address module) external; function enableModule(address module) external;

View File

@@ -1,8 +1,8 @@
//SPDX-License-Identifier: Unlicense //SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.19; pragma solidity ^0.8.19;
import "@anon-aadhaar/contracts/interfaces/IAnonAadhaarGroth16Verifier.sol"; import "./interfaces/IAnonAadhaarGroth16Verifier.sol";
import "@anon-aadhaar/contracts/interfaces/IAnonAadhaar.sol"; import "./interfaces/IAnonAadhaar.sol";
// Note: This is a AnonAadhaar contract with a modification that made`verifier` state variable immutable // Note: This is a AnonAadhaar contract with a modification that made`verifier` state variable immutable
// so that verification doesn't fail due to invalid storage access. // so that verification doesn't fail due to invalid storage access.

View File

@@ -20,7 +20,7 @@
pragma solidity >=0.7.0 <0.9.0; pragma solidity >=0.7.0 <0.9.0;
contract Verifier { contract AnonAadhaarVerifier {
// Scalar field size // Scalar field size
uint256 constant r = uint256 constant r =
21888242871839275222246405745257275088548364400416034343698204186575808495617; 21888242871839275222246405745257275088548364400416034343698204186575808495617;

View File

@@ -0,0 +1,5 @@
# Anon Aadhaar Contracts
These contracts are copied from https://github.com/anon-aadhaar/anon-aadhaar/tree/main/packages/contracts @ `v2.2.0`. This has been done for 2 reasons:
- `AnonAadhaarVerifier.sol` needed to be modified to use gas opcodes that work within the [validation cycle gas opcode limitations for ERC-4337 (OP-012)](https://eips.ethereum.org/EIPS/eip-7562#opcode-rules).
- Using the `@anon-aadhaar/contracts` npm package's contract interfaces causes issues with Typechain generation in this project.

View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;
interface IAnonAadhaar {
function verifyAnonAadhaarProof(
uint nullifierSeed,
uint nullifier,
uint timestamp,
uint signal,
uint[4] memory revealArray,
uint[8] memory groth16Proof
) external view returns (bool);
}

View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;
interface IAnonAadhaarGroth16Verifier {
function verifyProof(
uint[2] calldata _pA,
uint[2][2] calldata _pB,
uint[2] calldata _pC,
uint[9] calldata publicInputs
) external view returns (bool);
}

View File

@@ -17,3 +17,8 @@ import {Safe} from "safe-contracts/contracts/Safe.sol";
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol"; import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
import {SimpleAccountFactory} from "account-abstraction/samples/SimpleAccountFactory.sol"; import {SimpleAccountFactory} from "account-abstraction/samples/SimpleAccountFactory.sol";
import {BLSSignatureAggregator} from "account-abstraction/samples/bls/BLSSignatureAggregator.sol"; import {BLSSignatureAggregator} from "account-abstraction/samples/bls/BLSSignatureAggregator.sol";
// Anon-Aadhaar
import {AnonAadhaar} from "./anonAadhaar/AnonAadhaar.sol";
import {AnonAadhaarVerifier} from "./anonAadhaar/AnonAadhaarVerifier.sol";

View File

@@ -6,7 +6,7 @@ import DeterministicDeployer from "../../lib-ts/deterministic-deployer/Determini
import { import {
SafeAnonAadhaarFactory__factory, SafeAnonAadhaarFactory__factory,
SafeAnonAadhaarPlugin__factory, SafeAnonAadhaarPlugin__factory,
Verifier__factory, AnonAadhaarVerifier__factory,
AnonAadhaar__factory, AnonAadhaar__factory,
Safe, Safe,
AnonAadhaar, AnonAadhaar,
@@ -74,13 +74,15 @@ describe("SafeAnonAadhaarPlugin", () => {
const signer = await provider.getSigner(); const signer = await provider.getSigner();
// Deploy AnonAadhaarGroth16Verifier contract // Deploy AnonAadhaarGroth16Verifier contract
const anonAadhaarVerifier = await new Verifier__factory(signer).deploy(); const anonAadhaarVerifier = await new AnonAadhaarVerifier__factory(signer).deploy();
await anonAadhaarVerifier.waitForDeployment();
// Deploy AnonAadhaar contract // Deploy AnonAadhaar contract
anonAadhaar = await new AnonAadhaar__factory(signer).deploy( anonAadhaar = await new AnonAadhaar__factory(signer).deploy(
await anonAadhaarVerifier.getAddress(), await anonAadhaarVerifier.getAddress(),
BigInt(testPublicKeyHash).toString() BigInt(testPublicKeyHash).toString(),
); );
await anonAadhaar.waitForDeployment();
anonAadhaarAddress = await anonAadhaar.getAddress(); anonAadhaarAddress = await anonAadhaar.getAddress();
@@ -173,12 +175,17 @@ describe("SafeAnonAadhaarPlugin", () => {
}); });
// proving // proving
console.debug("Generating Anon Aadhaar proof. This could take some time...");
const proofTimingKey = "Anon Aadhaar proof generation time";
console.time(proofTimingKey);
const anonAadhaarCore = await prove(args); const anonAadhaarCore = await prove(args);
console.timeEnd(proofTimingKey);
const anonAadhaarProof = anonAadhaarCore.proof; const anonAadhaarProof = anonAadhaarCore.proof;
const packedGroth16Proof = packGroth16Proof(anonAadhaarProof.groth16Proof); const packedGroth16Proof = packGroth16Proof(anonAadhaarProof.groth16Proof);
// view call to AnonAadhaar contract to see if verification returns true // view call to AnonAadhaar contract to see if verification returns true
const ret = await anonAadhaar.verifyAnonAadhaarProof( expect(await anonAadhaar.verifyAnonAadhaarProof(
nullifierSeed, nullifierSeed,
anonAadhaarProof.nullifier, anonAadhaarProof.nullifier,
anonAadhaarProof.timestamp, anonAadhaarProof.timestamp,
@@ -190,8 +197,7 @@ describe("SafeAnonAadhaarPlugin", () => {
anonAadhaarProof.state, anonAadhaarProof.state,
], ],
packedGroth16Proof packedGroth16Proof
); )).to.equal(true);
console.log("ret: ", ret);
// encode proof data into userOpSignature // encode proof data into userOpSignature
const encoder = ethers.AbiCoder.defaultAbiCoder(); const encoder = ethers.AbiCoder.defaultAbiCoder();
@@ -199,7 +205,7 @@ describe("SafeAnonAadhaarPlugin", () => {
["uint", "uint", "uint", "uint[4]", "uint[8]"], ["uint", "uint", "uint", "uint[4]", "uint[8]"],
[ [
BigInt(nullifierSeed), BigInt(nullifierSeed),
Number(anonAadhaarCore?.proof.timestamp), Number(anonAadhaarProof.timestamp),
BigInt(userOpHash), // insert userOpHash into signature BigInt(userOpHash), // insert userOpHash into signature
[ [
anonAadhaarProof.ageAbove18, anonAadhaarProof.ageAbove18,

View File

@@ -0,0 +1,110 @@
import { expect } from "chai";
import { ethers } from "ethers";
import {
SafeECDSAFactory__factory,
SafeECDSAPlugin__factory,
SponsorEverythingPaymaster__factory,
EntryPoint__factory
} from "../../typechain-types";
import receiptOf from "./utils/receiptOf";
import { setupTests } from "./utils/setupTests";
import { createAndSendUserOpWithEcdsaSig } from "./utils/createUserOp";
const oneEther = ethers.parseEther("1");
describe("SafeSponsorEverythingPaymasterPlugin", () => {
it("should pass the ERC4337 validation", async () => {
const {
bundlerProvider,
provider,
admin,
owner,
entryPointAddress,
deployer,
safeSingleton,
} = await setupTests();
// Deploy paymaster.
const paymaster = await new SponsorEverythingPaymaster__factory(admin).deploy(entryPointAddress);
await paymaster.waitForDeployment();
const paymasterAddress = await paymaster.getAddress();
// Paymaster deposits.
await paymaster.connect(admin).deposit({ value: oneEther })
const recipient = ethers.Wallet.createRandom();
const transferAmount = oneEther;
const dummySignature = await owner.signMessage("dummy sig");
// Deploy ecdsa plugin
const safeECDSAFactory = await deployer.connectOrDeploy(
SafeECDSAFactory__factory,
[],
);
const createArgs = [
safeSingleton,
entryPointAddress,
await owner.getAddress(),
0,
] satisfies Parameters<typeof safeECDSAFactory.create.staticCall>;
const accountAddress = await safeECDSAFactory.create.staticCall(
...createArgs,
);
await receiptOf(safeECDSAFactory.create(...createArgs));
const safeEcdsaPlugin = SafeECDSAPlugin__factory.connect(
accountAddress,
owner,
);
// Native tokens for the pre-fund
await receiptOf(
admin.sendTransaction({
to: accountAddress,
value: oneEther,
}),
);
// Construct userOp
const userOpCallData = safeEcdsaPlugin.interface.encodeFunctionData(
"execTransaction",
[recipient.address, transferAmount, "0x00"],
);
// Note: factoryParams is not used because we need to create both the safe
// proxy and the plugin, and 4337 currently only allows one contract
// creation in this step. Since we need an extra step anyway, it's simpler
// to do the whole create outside of 4337.
const factoryParams = {
factory: "0x",
factoryData: "0x",
};
// Check paymaster balances before and after sending UserOp.
const entrypoint = EntryPoint__factory.connect(entryPointAddress, provider)
const paymasterBalanceBefore = await entrypoint.balanceOf(paymasterAddress)
// Send userOp
await createAndSendUserOpWithEcdsaSig(
provider,
bundlerProvider,
owner,
accountAddress,
factoryParams,
userOpCallData,
entryPointAddress,
dummySignature,
paymasterAddress,
3e5,
"0x"
);
const paymasterBalanceAfter = await entrypoint.balanceOf(paymasterAddress)
expect(paymasterBalanceBefore).greaterThan(paymasterBalanceAfter)
expect(await provider.getBalance(recipient.address)).to.equal(oneEther);
});
});

View File

@@ -1,6 +1,6 @@
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/comma-dangle */ /* eslint-disable @typescript-eslint/comma-dangle */
import { ethers, getBytes, NonceManager, Signer } from "ethers"; import { BigNumberish, BytesLike, ethers, getBytes, NonceManager, Signer } from "ethers";
import { AddressZero } from "@ethersproject/constants"; import { AddressZero } from "@ethersproject/constants";
import { SafeProxyFactory } from "../../../typechain-types/lib/safe-contracts/contracts/proxies/SafeProxyFactory"; import { SafeProxyFactory } from "../../../typechain-types/lib/safe-contracts/contracts/proxies/SafeProxyFactory";
@@ -79,13 +79,16 @@ export const generateFactoryParamsAndAddress = async (
}; };
export const createUserOperation = async ( export const createUserOperation = async (
provider: ethers.JsonRpcProvider, provider: ethers.JsonRpcProvider,
bundlerProvider: ethers.JsonRpcProvider, bundlerProvider: ethers.JsonRpcProvider,
accountAddress: string, accountAddress: string,
factoryParams: FactoryParams, factoryParams: FactoryParams,
userOpCallData: string, userOpCallData: string,
entryPointAddress: string, entryPointAddress: string,
dummySignature: string dummySignature: string,
paymaster?: string,
paymasterPostOpGasLimit?: BigNumberish,
paymasterData?: BytesLike,
) => { ) => {
const entryPoint = EntryPoint__factory.connect( const entryPoint = EntryPoint__factory.connect(
entryPointAddress, entryPointAddress,
@@ -107,55 +110,66 @@ export const createUserOperation = async (
userOp.factoryData = factoryParams.factoryData; userOp.factoryData = factoryParams.factoryData;
} }
const { const {
callGasLimit, callGasLimit,
verificationGasLimit, verificationGasLimit,
preVerificationGas, preVerificationGas,
maxFeePerGas, paymasterVerificationGasLimit,
maxPriorityFeePerGas, maxFeePerGas,
} = await getGasEstimates( maxPriorityFeePerGas,
provider, } = await getGasEstimates(
bundlerProvider, provider,
userOp, bundlerProvider,
entryPointAddress userOp,
); entryPointAddress,
);
const unsignedUserOperation = { const unsignedUserOperation = {
sender: accountAddress, sender: accountAddress,
nonce: nonceHex, nonce: nonceHex,
factory: userOp.factory, factory: userOp.factory,
factoryData: userOp.factoryData, factoryData: userOp.factoryData,
callData: userOpCallData, callData: userOpCallData,
callGasLimit, callGasLimit,
verificationGasLimit, verificationGasLimit,
preVerificationGas, preVerificationGas,
maxFeePerGas, maxFeePerGas,
maxPriorityFeePerGas, maxPriorityFeePerGas,
signature: dummySignature, paymaster: paymaster,
} satisfies UserOperation; paymasterVerificationGasLimit: paymaster ? paymasterVerificationGasLimit : undefined,
paymasterPostOpGasLimit: paymasterPostOpGasLimit,
paymasterData: paymasterData,
signature: dummySignature,
} satisfies UserOperation;
return await ethers.resolveProperties(unsignedUserOperation); return await ethers.resolveProperties(unsignedUserOperation);
}; };
export const createAndSendUserOpWithEcdsaSig = async ( export const createAndSendUserOpWithEcdsaSig = async (
provider: ethers.JsonRpcProvider, provider: ethers.JsonRpcProvider,
bundlerProvider: ethers.JsonRpcProvider, bundlerProvider: ethers.JsonRpcProvider,
owner: Signer, owner: Signer,
accountAddress: string, accountAddress: string,
factoryParams: FactoryParams, factoryParams: FactoryParams,
userOpCallData: string, userOpCallData: string,
entryPointAddress: string, entryPointAddress: string,
dummySignature: string dummySignature: string,
paymaster?: string,
paymasterPostOpGasLimit?: BigNumberish,
paymasterData?: BytesLike,
) => { ) => {
const unsignedUserOperation = await createUserOperation( const unsignedUserOperation = await createUserOperation(
provider, provider,
bundlerProvider, bundlerProvider,
accountAddress, accountAddress,
factoryParams, factoryParams,
userOpCallData, userOpCallData,
entryPointAddress, entryPointAddress,
dummySignature dummySignature,
); paymaster,
paymasterPostOpGasLimit,
paymasterData,
);
const userOpHash = getUserOpHash( const userOpHash = getUserOpHash(
unsignedUserOperation, unsignedUserOperation,

View File

@@ -9,14 +9,15 @@ export const getGasEstimates = async (
partialUserOperation: Partial<UserOperation>, partialUserOperation: Partial<UserOperation>,
entryPointAddress: string entryPointAddress: string
) => { ) => {
const gasEstimate = (await bundlerProvider.send( const gasEstimate = (await bundlerProvider.send(
"eth_estimateUserOperationGas", "eth_estimateUserOperationGas",
[partialUserOperation, entryPointAddress] [partialUserOperation, entryPointAddress],
)) as { )) as {
verificationGasLimit: string; verificationGasLimit: string;
preVerificationGas: string; preVerificationGas: string;
callGasLimit: string; paymasterVerificationGasLimit: string;
}; callGasLimit: string;
};
const safeVerificationGasLimit = const safeVerificationGasLimit =
BigInt(gasEstimate.verificationGasLimit) + BigInt(gasEstimate.verificationGasLimit) +
@@ -28,13 +29,14 @@ export const getGasEstimates = async (
const { maxFeePerGas, maxPriorityFeePerGas } = await getFeeData(provider); const { maxFeePerGas, maxPriorityFeePerGas } = await getFeeData(provider);
return { return {
callGasLimit: gasEstimate.callGasLimit, callGasLimit: gasEstimate.callGasLimit,
verificationGasLimit: ethers.toBeHex(safeVerificationGasLimit), verificationGasLimit: ethers.toBeHex(safeVerificationGasLimit),
preVerificationGas: ethers.toBeHex(safePreVerificationGas), preVerificationGas: ethers.toBeHex(safePreVerificationGas),
maxFeePerGas, paymasterVerificationGasLimit: ethers.toBeHex(safeVerificationGasLimit),
maxPriorityFeePerGas, maxFeePerGas,
}; maxPriorityFeePerGas,
};
}; };
export async function getFeeData(provider: ethers.Provider) { export async function getFeeData(provider: ethers.Provider) {

File diff suppressed because it is too large Load Diff