This commit is contained in:
Porco
2024-04-21 03:34:11 +04:00
parent 9fb3175f2d
commit eca354890d
3 changed files with 178 additions and 261 deletions

View File

@@ -1,17 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/comma-dangle */
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { expect } from "chai";
import {
BytesLike,
JsonRpcProvider,
NonceManager,
Signer,
ethers,
} from "ethers";
import { JsonRpcProvider, NonceManager, Signer, ethers } from "ethers";
import DeterministicDeployer from "../../lib-ts/deterministic-deployer/DeterministicDeployer";
import {
SafeAnonAadhaarFactory__factory,
@@ -31,23 +19,30 @@ import {
init,
generateArgs,
prove,
verify,
artifactUrls,
packGroth16Proof,
ArtifactsOrigin,
} from "@anon-aadhaar/core";
import { testQRData } from "./utils/assets/dataInput.json";
import fs from "fs";
import { getUserOpHash } from "./utils/userOpUtils";
import { copmuteUserNullifier } from "./utils/computeNullifier";
import { getUserOpHash } from "@account-abstraction/utils";
import { testQRData } from "./utils/assets/dataInput.json";
export const testPublicKeyHash =
"15134874015316324267425466444584014077184337590635665158241104437045239495873";
const oneEther = ethers.parseEther("1");
// what is nullifier seed: https://anon-aadhaar-documentation.vercel.app/docs/nullifiers
/*
This uses test Aadhaar QR code along with test Indian government's public key and certificate.
*/
// Nullifier seed: https://anon-aadhaar-documentation.vercel.app/docs/nullifiers
// using wallet address makes sense...?
const nullifierSeed = 1234;
// test version of UIDAI public key
// more info: https://anon-aadhaar-documentation.vercel.app/docs/how-does-it-work#1-extract-and-process-the-data-from-the-qr-code
const testPublicKeyHash =
"15134874015316324267425466444584014077184337590635665158241104437045239495873";
describe("SafeAnonAadhaarPlugin", () => {
let bundlerProvider: JsonRpcProvider;
let provider: JsonRpcProvider;
@@ -72,26 +67,18 @@ describe("SafeAnonAadhaarPlugin", () => {
safeSingleton,
} = setup);
console.log("admin: ", await admin.getAddress());
console.log("owner: ", await owner.getAddress());
console.log("entryPointAddress: ", entryPointAddress);
console.log("safeSingleton: ", await safeSingleton.getAddress());
const signer = await provider.getSigner();
// Deploy AnonAadhaarGroth16Verifier contract
const anonAadhaarVerifier = await new Verifier__factory(signer).deploy();
console.log(
"anonAadhaarVerifier: ",
await anonAadhaarVerifier.getAddress()
);
// Deploy AnonAadhaar contract
const anonAadhaar = await new AnonAadhaar__factory(signer).deploy(
await anonAadhaarVerifier.getAddress(),
BigInt(testPublicKeyHash).toString()
);
anonAadhaarAddress = await anonAadhaar.getAddress();
console.log("anonAadhaarAddress: ", anonAadhaarAddress);
// load test certificate
const certificateDirName = __dirname + "/utils/assets";
@@ -99,6 +86,7 @@ describe("SafeAnonAadhaarPlugin", () => {
.readFileSync(certificateDirName + "/testCertificate.pem")
.toString();
// load files needed for proof generation and verification
const anonAadhaarInitArgs: InitArgs = {
wasmURL: artifactUrls.v2.wasm,
zkeyURL: artifactUrls.v2.zkey,
@@ -106,18 +94,20 @@ describe("SafeAnonAadhaarPlugin", () => {
artifactsOrigin: ArtifactsOrigin.server,
};
// pass initArgs
await init(anonAadhaarInitArgs);
});
it("should pass the ERC4337 validation", async () => {
// Deploy SafeAnonAadhaarFactory contract
const safeAnonAadhaarFactory = await deployer.connectOrDeploy(
SafeAnonAadhaarFactory__factory,
[]
);
// get user_data_hash
// get userDataHash out of nullifier seed and test QR data
// userDataHash is an unique identifier that is specific to each user and stored the plugin contract
const userDataHash = await copmuteUserNullifier(nullifierSeed, testQRData);
console.log("userDataHash: ", userDataHash);
const createArgs = [
safeSingleton,
@@ -132,8 +122,6 @@ describe("SafeAnonAadhaarPlugin", () => {
...createArgs
);
console.log("accountAddress: ", accountAddress);
await receiptOf(safeAnonAadhaarFactory.create(...createArgs));
const safeAnonAadhaarPlugin = SafeAnonAadhaarPlugin__factory.connect(
@@ -157,9 +145,7 @@ describe("SafeAnonAadhaarPlugin", () => {
[recipient.address, transferAmount, "0x00"]
);
console.log("userOpCallData: ", userOpCallData);
// get userOp
// create User Operation
const unsignedUserOperation = await createAnonAadhaarOperation(
provider,
accountAddress,
@@ -174,9 +160,7 @@ describe("SafeAnonAadhaarPlugin", () => {
Number((await provider.getNetwork()).chainId)
);
console.log("userOpHash: ", userOpHash);
// prove
// prove with userOpHash as signal
const args = await generateArgs({
qrData: testQRData,
certificateFile: certificate,
@@ -184,22 +168,12 @@ describe("SafeAnonAadhaarPlugin", () => {
signal: userOpHash, // user op hash
});
console.log("args: ", args);
// proving
const anonAadhaarCore = await prove(args);
const ret = await verify(anonAadhaarCore);
console.log("ret: ", ret);
const anonAadhaarProof = anonAadhaarCore.proof;
const packedGroth16Proof = packGroth16Proof(anonAadhaarProof.groth16Proof);
console.log("anonAadhaarCore: ", anonAadhaarCore);
console.log("nullifierSeed: ", anonAadhaarCore.proof.nullifierSeed);
console.log("nullifier: ", anonAadhaarProof.nullifier);
console.log("nullifier bg: ", BigInt(anonAadhaarProof.nullifier));
console.log("timestamp: ", Number(anonAadhaarCore?.proof.timestamp));
// encode signautre
// encode proof data into userOpSignature
const encoder = ethers.AbiCoder.defaultAbiCoder();
const userOpSignature = encoder.encode(
["uint", "uint", "uint", "uint[4]", "uint[8]"],
@@ -217,8 +191,6 @@ describe("SafeAnonAadhaarPlugin", () => {
]
);
console.log("userOpSignature: ", userOpSignature);
// send userOp
await sendUserOpWithAnonAadhaarSig(
bundlerProvider,
@@ -227,61 +199,8 @@ describe("SafeAnonAadhaarPlugin", () => {
userOpSignature
);
console.log("userOp sent");
expect(await provider.getBalance(recipient.address)).to.equal(oneEther);
expect(await provider.getBalance(recipient.address)).to.equal(
ethers.parseEther("1")
);
}).timeout(0);
// it("should not allow execTransaction from unrelated address", async () => {
// const {
// provider,
// admin,
// owner,
// entryPointAddress,
// deployer,
// safeSingleton,
// } = await setupTests();
// const safeAnonAadhaarFactory = await deployer.connectOrDeploy(
// SafeAnonAadhaarFactory__factory,
// []
// );
// const createArgs = [
// safeSingleton,
// entryPointAddress,
// await owner.getAddress(),
// 0,
// "0x", // _anonAadhaarAddr
// "0x", // _userDataHash
// ] satisfies Parameters<typeof safeAnonAadhaarFactory.create.staticCall>;
// const accountAddress = await safeAnonAadhaarFactory.create.staticCall(
// ...createArgs
// );
// await receiptOf(safeAnonAadhaarFactory.create(...createArgs));
// const unrelatedWallet = ethers.Wallet.createRandom(provider);
// await receiptOf(
// admin.sendTransaction({
// to: unrelatedWallet.address,
// value: 100n * oneEther,
// })
// );
// const account = SafeAnonAadhaarPlugin__factory.connect(
// accountAddress,
// unrelatedWallet
// );
// const recipient = ethers.Wallet.createRandom(provider);
// await expect(
// receiptOf(account.execTransaction(recipient.address, oneEther, "0x"))
// ).to.eventually.rejected;
// await expect(provider.getBalance(recipient)).to.eventually.equal(0n);
// });
});

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/comma-dangle */
/* eslint-disable prettier/prettier */
import { ethers, getBytes, NonceManager, Signer } from "ethers";
import { AddressZero } from "@ethersproject/constants";

View File

@@ -1,11 +1,11 @@
import {
AbiCoder,
BigNumberish,
BytesLike,
concat,
hexlify,
isHexString,
keccak256,
AbiCoder,
BigNumberish,
BytesLike,
concat,
hexlify,
isHexString,
keccak256,
} from "ethers";
import { PackedUserOperationStruct } from "../../../typechain-types/lib/account-abstraction/contracts/core/EntryPoint";
@@ -21,26 +21,26 @@ import { PackedUserOperationStruct } from "../../../typechain-types/lib/account-
export type PackedUserOperation = PackedUserOperationStruct;
export type UserOperation = {
sender: string;
nonce: BigNumberish;
factory?: string;
factoryData?: BytesLike;
callData: BytesLike;
callGasLimit: BigNumberish;
verificationGasLimit: BigNumberish;
preVerificationGas: BigNumberish;
maxFeePerGas: BigNumberish;
maxPriorityFeePerGas: BigNumberish;
paymaster?: string;
paymasterVerificationGasLimit?: BigNumberish;
paymasterPostOpGasLimit?: BigNumberish;
paymasterData?: BytesLike;
signature: BytesLike;
sender: string;
nonce: BigNumberish;
factory?: string;
factoryData?: BytesLike;
callData: BytesLike;
callGasLimit: BigNumberish;
verificationGasLimit: BigNumberish;
preVerificationGas: BigNumberish;
maxFeePerGas: BigNumberish;
maxPriorityFeePerGas: BigNumberish;
paymaster?: string;
paymasterVerificationGasLimit?: BigNumberish;
paymasterPostOpGasLimit?: BigNumberish;
paymasterData?: BytesLike;
signature: BytesLike;
};
export type FactoryParams = {
factory: string;
factoryData?: BytesLike;
factory: string;
factoryData?: BytesLike;
};
/**
@@ -53,17 +53,17 @@ export type FactoryParams = {
* @param chainId
*/
export function getUserOpHash(
op: UserOperation,
entryPoint: string,
chainId: number,
op: UserOperation,
entryPoint: string,
chainId: number
): string {
const userOpHash = keccak256(encodeUserOp(op, true));
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
const enc = defaultAbiCoder.encode(
["bytes32", "address", "uint256"],
[userOpHash, entryPoint, chainId],
);
return keccak256(enc);
const userOpHash = keccak256(encodeUserOp(op, true));
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
const enc = defaultAbiCoder.encode(
["bytes32", "address", "uint256"],
[userOpHash, entryPoint, chainId]
);
return keccak256(enc);
}
/**
@@ -73,122 +73,122 @@ export function getUserOpHash(
* "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
*/
export function encodeUserOp(
op1: PackedUserOperation | UserOperation,
forSignature = true,
op1: PackedUserOperation | UserOperation,
forSignature = true
): string {
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
let op: PackedUserOperation;
if ("callGasLimit" in op1) {
op = packUserOp(op1);
} else {
op = op1;
}
let op: PackedUserOperation;
if ("callGasLimit" in op1) {
op = packUserOp(op1);
} else {
op = op1;
}
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
if (forSignature) {
return defaultAbiCoder.encode(
[
"address",
"uint256",
"bytes32",
"bytes32",
"bytes32",
"uint256",
"bytes32",
"bytes32",
],
[
op.sender,
op.nonce,
keccak256(op.initCode),
keccak256(op.callData),
op.accountGasLimits,
op.preVerificationGas,
op.gasFees,
keccak256(op.paymasterAndData),
],
);
} else {
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
return defaultAbiCoder.encode(
[
"address",
"uint256",
"bytes",
"bytes",
"bytes32",
"uint256",
"bytes32",
"bytes",
"bytes",
],
[
op.sender,
op.nonce,
op.initCode,
op.callData,
op.accountGasLimits,
op.preVerificationGas,
op.gasFees,
op.paymasterAndData,
op.signature,
],
);
}
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
if (forSignature) {
return defaultAbiCoder.encode(
[
"address",
"uint256",
"bytes32",
"bytes32",
"bytes32",
"uint256",
"bytes32",
"bytes32",
],
[
op.sender,
op.nonce,
keccak256(op.initCode),
keccak256(op.callData),
op.accountGasLimits,
op.preVerificationGas,
op.gasFees,
keccak256(op.paymasterAndData),
]
);
} else {
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
return defaultAbiCoder.encode(
[
"address",
"uint256",
"bytes",
"bytes",
"bytes32",
"uint256",
"bytes32",
"bytes",
"bytes",
],
[
op.sender,
op.nonce,
op.initCode,
op.callData,
op.accountGasLimits,
op.preVerificationGas,
op.gasFees,
op.paymasterAndData,
op.signature,
]
);
}
}
export function packUserOp(op: UserOperation): PackedUserOperation {
let paymasterAndData: BytesLike;
if (op.paymaster == null) {
paymasterAndData = "0x";
} else {
if (
op.paymasterVerificationGasLimit == null ||
op.paymasterPostOpGasLimit == null
) {
throw new Error("paymaster with no gas limits");
}
paymasterAndData = packPaymasterData(
op.paymaster,
op.paymasterVerificationGasLimit,
op.paymasterPostOpGasLimit,
op.paymasterData,
);
}
let paymasterAndData: BytesLike;
if (op.paymaster == null) {
paymasterAndData = "0x";
} else {
if (
op.paymasterVerificationGasLimit == null ||
op.paymasterPostOpGasLimit == null
) {
throw new Error("paymaster with no gas limits");
}
paymasterAndData = packPaymasterData(
op.paymaster,
op.paymasterVerificationGasLimit,
op.paymasterPostOpGasLimit,
op.paymasterData
);
}
return {
sender: op.sender,
nonce: "0x" + BigInt(op.nonce).toString(16),
initCode:
op.factory == null ? "0x" : concat([op.factory, op.factoryData ?? ""]),
callData: op.callData,
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
preVerificationGas: "0x" + BigInt(op.preVerificationGas).toString(16),
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
paymasterAndData,
signature: op.signature,
};
return {
sender: op.sender,
nonce: "0x" + BigInt(op.nonce).toString(16),
initCode:
op.factory == null ? "0x" : concat([op.factory, op.factoryData ?? ""]),
callData: op.callData,
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
preVerificationGas: "0x" + BigInt(op.preVerificationGas).toString(16),
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
paymasterAndData,
signature: op.signature,
};
}
export function packPaymasterData(
paymaster: string,
paymasterVerificationGasLimit: BigNumberish,
postOpGasLimit: BigNumberish,
paymasterData?: BytesLike,
paymaster: string,
paymasterVerificationGasLimit: BigNumberish,
postOpGasLimit: BigNumberish,
paymasterData?: BytesLike
): BytesLike {
return concat([
paymaster,
packUint(paymasterVerificationGasLimit, postOpGasLimit),
paymasterData ?? "0x",
]);
return concat([
paymaster,
packUint(paymasterVerificationGasLimit, postOpGasLimit),
paymasterData ?? "0x",
]);
}
export function packUint(high128: BigNumberish, low128: BigNumberish): string {
high128 = BigInt(high128);
low128 = BigInt(low128);
const packed = "0x" + ((high128 << 128n) + low128).toString(16);
return hexZeroPad(packed, 32);
high128 = BigInt(high128);
low128 = BigInt(low128);
const packed = "0x" + ((high128 << 128n) + low128).toString(16);
return hexZeroPad(packed, 32);
}
/**
@@ -196,19 +196,19 @@ export function packUint(high128: BigNumberish, low128: BigNumberish): string {
* in a way the bundler doesn't expect and an error is thrown
*/
export function hexZeroPad(value: BytesLike, length: number): string {
if (typeof value !== "string") {
value = hexlify(value);
} else if (!isHexString(value)) {
console.log("invalid hex string", "value", value);
}
if (typeof value !== "string") {
value = hexlify(value);
} else if (!isHexString(value)) {
console.log("invalid hex string", "value", value);
}
if (value.length > 2 * length + 2) {
console.log("value out of range", "value", arguments[1]);
}
if (value.length > 2 * length + 2) {
console.log("value out of range", "value", arguments[1]);
}
while (value.length < 2 * length + 2) {
value = "0x0" + value.substring(2);
}
while (value.length < 2 * length + 2) {
value = "0x0" + value.substring(2);
}
return value;
return value;
}