mirror of
https://github.com/getwax/wax.git
synced 2026-01-08 22:57:58 -05:00
Merge pull request #223 from porco-rosso-j/anon-aadhaar
Safe AnonAadhaar Plugin
This commit is contained in:
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -9,7 +9,7 @@
|
||||
url = https://github.com/foundry-rs/forge-std
|
||||
[submodule "packages/plugins/lib/openzeppelin-contracts"]
|
||||
path = packages/plugins/lib/openzeppelin-contracts
|
||||
url = https://github.com/openzeppelin/openzeppelin-contracts
|
||||
url = https://github.com/OpenZeppelin/openzeppelin-contracts
|
||||
[submodule "packages/plugins/lib/safe-contracts"]
|
||||
path = packages/plugins/lib/safe-contracts
|
||||
url = https://github.com/safe-global/safe-contracts
|
||||
|
||||
@@ -17,20 +17,27 @@
|
||||
"devDependencies": {
|
||||
"@account-abstraction/contracts": "0.7.0",
|
||||
"@account-abstraction/utils": "^0.6.0",
|
||||
"@anon-aadhaar/core": "2.2.0",
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^2.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-toolbox": "^3.0.0",
|
||||
"@nomicfoundation/hardhat-verify": "^1.0.0",
|
||||
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||
"@nomicfoundation/hardhat-verify": "^2.0.8",
|
||||
"@nomicfoundation/ignition-core": "^0.15.5",
|
||||
"@thehubbleproject/bls": "^0.5.1",
|
||||
"@typechain/ethers-v6": "^0.4.0",
|
||||
"@typechain/hardhat": "^8.0.0",
|
||||
"@typechain/ethers-v6": "^0.5.1",
|
||||
"@typechain/hardhat": "^9.1.0",
|
||||
"@types/chai": "^4.2.0",
|
||||
"@types/circomlibjs": "^0.1.6",
|
||||
"@types/mocha": ">=9.1.0",
|
||||
"@types/node": ">=16.0.0",
|
||||
"@typescript-eslint/eslint-plugin": ">=6.0.0",
|
||||
"@typescript-eslint/parser": ">=6.0.0",
|
||||
"@zk-email/helpers": "^6.1.1",
|
||||
"chai": "^4.2.0",
|
||||
"circomlibjs": "0.1.7",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": ">=8.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
@@ -39,8 +46,8 @@
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"ethers": "^6.4.0",
|
||||
"hardhat": "^2.17.1",
|
||||
"hardhat-gas-reporter": "^1.0.8",
|
||||
"hardhat": "^2.22.6",
|
||||
"hardhat-gas-reporter": "^2.2.0",
|
||||
"hardhat-preprocessor": "^0.1.5",
|
||||
"prettier": "^3.0.3",
|
||||
"solidity-coverage": "^0.8.0",
|
||||
|
||||
58
packages/plugins/src/safe/SafeAnonAadhaarFactory.sol
Normal file
58
packages/plugins/src/safe/SafeAnonAadhaarFactory.sol
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
pragma solidity >=0.7.0 <0.9.0;
|
||||
pragma abicoder v2;
|
||||
|
||||
import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {SafeProxyFactory} from "safe-contracts/contracts/proxies/SafeProxyFactory.sol";
|
||||
import {SafeProxy} from "safe-contracts/contracts/proxies/SafeProxy.sol";
|
||||
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
|
||||
import {SafeAnonAadhaarPlugin} from "./SafeAnonAadhaarPlugin.sol";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract SafeAnonAadhaarFactory {
|
||||
function create(
|
||||
Safe safeSingleton,
|
||||
EntryPoint entryPoint,
|
||||
address owner,
|
||||
uint256 saltNonce,
|
||||
address _anonAadhaarAddr,
|
||||
uint256 _userDataHash
|
||||
) external returns (SafeAnonAadhaarPlugin) {
|
||||
bytes32 salt = keccak256(abi.encodePacked(owner, saltNonce));
|
||||
|
||||
Safe safe = Safe(
|
||||
payable(new SafeProxy{salt: salt}(address(safeSingleton)))
|
||||
);
|
||||
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
|
||||
SafeAnonAadhaarPlugin plugin = new SafeAnonAadhaarPlugin{salt: salt}(
|
||||
address(entryPoint),
|
||||
_anonAadhaarAddr,
|
||||
address(safe),
|
||||
_userDataHash
|
||||
);
|
||||
|
||||
safe.setup(
|
||||
owners,
|
||||
1,
|
||||
address(plugin),
|
||||
abi.encodeCall(
|
||||
SafeAnonAadhaarPlugin.enableMyself,
|
||||
(owner, _userDataHash)
|
||||
),
|
||||
address(plugin),
|
||||
address(0),
|
||||
0,
|
||||
payable(address(0))
|
||||
);
|
||||
|
||||
return SafeAnonAadhaarPlugin(address(safe));
|
||||
}
|
||||
}
|
||||
165
packages/plugins/src/safe/SafeAnonAadhaarPlugin.sol
Normal file
165
packages/plugins/src/safe/SafeAnonAadhaarPlugin.sol
Normal file
@@ -0,0 +1,165 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
pragma solidity >=0.7.0 <0.9.0;
|
||||
pragma abicoder v2;
|
||||
|
||||
import {Safe4337Base, SIG_VALIDATION_FAILED} from "./utils/Safe4337Base.sol";
|
||||
import {IEntryPoint, PackedUserOperation} from "account-abstraction/interfaces/IEntryPoint.sol";
|
||||
import {IAnonAadhaar} from "./utils/anonAadhaar/interfaces/IAnonAadhaar.sol";
|
||||
|
||||
interface ISafe {
|
||||
function enableModule(address module) external;
|
||||
|
||||
function execTransactionFromModule(
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes memory data,
|
||||
uint8 operation
|
||||
) external returns (bool success);
|
||||
}
|
||||
|
||||
struct AnonAadhaarOwnerStorage {
|
||||
address owner;
|
||||
uint256 userDataHash; // the hash of unique and private user data extracted from Aadhaar QR code
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
contract SafeAnonAadhaarPlugin is Safe4337Base {
|
||||
// Should be made possible to enable this if not the last mapping
|
||||
// mapping(address => mapping(uint => bool)) public signalNullifiers;
|
||||
mapping(address => AnonAadhaarOwnerStorage) public anonAadhaarOwnerStorage;
|
||||
|
||||
address public immutable myAddress; // Module address
|
||||
address private immutable _entryPoint;
|
||||
|
||||
address internal constant _SENTINEL_MODULES = address(0x1);
|
||||
|
||||
// external contract managed by Anon Aadhaar with verifyAnonAadhaarProof() method
|
||||
// set to immutable to bypass invalid storage access error and make it accessible via delegatecall.
|
||||
address public immutable anonAadhaarAddr;
|
||||
|
||||
// nullifier for each signal(userOpHash) to prevent on-chain front-running
|
||||
// mapping(uint => bool) public signalNullifiers;
|
||||
|
||||
event OWNER_UPDATED(
|
||||
address indexed safe,
|
||||
address indexed oldOwner,
|
||||
address indexed newOwner
|
||||
);
|
||||
|
||||
constructor(
|
||||
address entryPointAddress,
|
||||
address _anonAadhaarAddr,
|
||||
address _safe,
|
||||
uint256 _userDataHash
|
||||
) {
|
||||
myAddress = address(this);
|
||||
_entryPoint = entryPointAddress;
|
||||
anonAadhaarAddr = _anonAadhaarAddr;
|
||||
anonAadhaarOwnerStorage[_safe].userDataHash = _userDataHash;
|
||||
}
|
||||
|
||||
function getOwner(address safe) external view returns (address owner) {
|
||||
owner = anonAadhaarOwnerStorage[safe].owner;
|
||||
}
|
||||
|
||||
function getUserDataHash(
|
||||
address safe
|
||||
) external view returns (uint userDataHash) {
|
||||
userDataHash = anonAadhaarOwnerStorage[safe].userDataHash;
|
||||
}
|
||||
|
||||
function execTransaction(
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes calldata data
|
||||
) external payable {
|
||||
_requireFromEntryPoint();
|
||||
|
||||
bool success = _currentSafe().execTransactionFromModule(
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
0
|
||||
);
|
||||
|
||||
require(success, "tx failed");
|
||||
}
|
||||
|
||||
function enableMyself(address ownerKey, uint256 userDataHash) public {
|
||||
// Called during safe setup as a delegatecall. This is why we use `this`
|
||||
// to refer to the safe instead of `msg.sender` / _currentSafe().
|
||||
ISafe(address(this)).enableModule(myAddress);
|
||||
|
||||
// Enable the safe address with the defined key
|
||||
// bytes memory _data = abi.encodePacked(ownerKey, userDataHash);
|
||||
bytes memory _data = abi.encode(ownerKey, userDataHash);
|
||||
SafeAnonAadhaarPlugin(myAddress).enable(_data);
|
||||
}
|
||||
|
||||
function entryPoint() public view override returns (IEntryPoint) {
|
||||
return IEntryPoint(_entryPoint);
|
||||
}
|
||||
|
||||
function enable(bytes calldata _data) external payable {
|
||||
// address newOwner = address(bytes20(_data[0:20]));
|
||||
(address newOwner, uint256 userDataHash) = abi.decode(
|
||||
_data,
|
||||
(address, uint)
|
||||
);
|
||||
address oldOwner = anonAadhaarOwnerStorage[msg.sender].owner;
|
||||
anonAadhaarOwnerStorage[msg.sender].owner = newOwner;
|
||||
anonAadhaarOwnerStorage[msg.sender].userDataHash = userDataHash;
|
||||
emit OWNER_UPDATED(msg.sender, oldOwner, newOwner);
|
||||
}
|
||||
|
||||
/// @dev Check if the timestamp is more recent than (current time - 3 hours)
|
||||
/// @param timestamp: msg.sender address.
|
||||
/// @return bool
|
||||
function isLessThan3HoursAgo(uint timestamp) public view returns (bool) {
|
||||
return timestamp > (block.timestamp - 3 * 60 * 60);
|
||||
}
|
||||
|
||||
function _validateSignature(
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 userOpHash
|
||||
) internal view override returns (uint256 validationData) {
|
||||
// decode proof verification params
|
||||
(
|
||||
uint256 nullifierSeed,
|
||||
uint256 timestamp,
|
||||
uint256 signal,
|
||||
uint[4] memory revealArray,
|
||||
uint[8] memory groth16Proof
|
||||
) = abi.decode(userOp.signature, (uint, uint, uint, uint[4], uint[8]));
|
||||
|
||||
// Check if the signal value has already been nullified
|
||||
// require(!signalNullifiers[signal], "DUPLICATED_NULLIFIER");
|
||||
|
||||
// make sure userOpHash == signal
|
||||
require(uint(userOpHash) == signal, "INVALID_SIGNAL");
|
||||
|
||||
// see if the proof is fresh enough
|
||||
// not called to avoid invalid opcode: the use of block.timestamp.
|
||||
// require(isLessThan3HoursAgo(timestamp), "INVALID_TIMESTAMP");
|
||||
|
||||
// verify proof throuugh AnonAadhaar and AnonAadhaarGroth16Verifier contracts
|
||||
if (
|
||||
!IAnonAadhaar(anonAadhaarAddr).verifyAnonAadhaarProof(
|
||||
nullifierSeed,
|
||||
anonAadhaarOwnerStorage[userOp.sender].userDataHash,
|
||||
timestamp,
|
||||
signal,
|
||||
revealArray,
|
||||
groth16Proof
|
||||
)
|
||||
) {
|
||||
return SIG_VALIDATION_FAILED;
|
||||
}
|
||||
|
||||
// signalNullifiers[userOp.sender][signal] = true; // store nullifier
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
77
packages/plugins/src/safe/utils/anonAadhaar/AnonAadhaar.sol
Normal file
77
packages/plugins/src/safe/utils/anonAadhaar/AnonAadhaar.sol
Normal file
@@ -0,0 +1,77 @@
|
||||
//SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "./interfaces/IAnonAadhaarGroth16Verifier.sol";
|
||||
import "./interfaces/IAnonAadhaar.sol";
|
||||
|
||||
// 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.
|
||||
// https://github.com/anon-aadhaar/anon-aadhaar/blob/main/packages/contracts/src/AnonAadhaar.sol
|
||||
|
||||
contract AnonAadhaar is IAnonAadhaar {
|
||||
address public immutable verifier;
|
||||
uint256 public immutable storedPublicKeyHash;
|
||||
|
||||
constructor(address _verifier, uint256 _pubkeyHash) {
|
||||
verifier = _verifier;
|
||||
storedPublicKeyHash = _pubkeyHash;
|
||||
}
|
||||
|
||||
/// @dev Verifies that the public key received is corresponding with the one stored in the contract.
|
||||
/// @param _receivedpubkeyHash: Public key received.
|
||||
/// @return Verified bool
|
||||
function verifyPublicKeyHash(
|
||||
uint256 _receivedpubkeyHash
|
||||
) private view returns (bool) {
|
||||
return storedPublicKeyHash == _receivedpubkeyHash;
|
||||
}
|
||||
|
||||
/// @dev Verifies the AnonAadhaar proof received.
|
||||
/// @param nullifier: Nullifier for the users Aadhaar.
|
||||
/// @param timestamp: Timestamp of when the QR code was signed.
|
||||
/// @param signal: Signal committed while genereting the proof.
|
||||
/// @param revealArray: Array of the values used as input for the proof generation (equal to [0, 0, 0, 0] if no field reveal were asked).
|
||||
/// @param groth16Proof: SNARK Groth16 proof.
|
||||
/// @return Verified bool
|
||||
function verifyAnonAadhaarProof(
|
||||
uint nullifierSeed,
|
||||
uint nullifier,
|
||||
uint timestamp,
|
||||
uint signal,
|
||||
uint[4] memory revealArray,
|
||||
uint[8] memory groth16Proof
|
||||
) public view returns (bool) {
|
||||
uint signalHash = _hash(signal);
|
||||
return
|
||||
IAnonAadhaarGroth16Verifier(verifier).verifyProof(
|
||||
[groth16Proof[0], groth16Proof[1]],
|
||||
[
|
||||
[groth16Proof[2], groth16Proof[3]],
|
||||
[groth16Proof[4], groth16Proof[5]]
|
||||
],
|
||||
[groth16Proof[6], groth16Proof[7]],
|
||||
[
|
||||
storedPublicKeyHash,
|
||||
nullifier,
|
||||
timestamp,
|
||||
// revealAgeAbove18
|
||||
revealArray[0],
|
||||
// revealGender
|
||||
revealArray[1],
|
||||
// revealPincode
|
||||
revealArray[2],
|
||||
// revealState
|
||||
revealArray[3],
|
||||
nullifierSeed,
|
||||
signalHash
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
|
||||
/// @param message: Message to be hashed.
|
||||
/// @return Message digest.
|
||||
function _hash(uint256 message) private pure returns (uint256) {
|
||||
return uint256(keccak256(abi.encodePacked(message))) >> 3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
/*
|
||||
Copyright 2021 0KIMS association.
|
||||
|
||||
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
|
||||
|
||||
snarkJS is a free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
snarkJS is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity >=0.7.0 <0.9.0;
|
||||
|
||||
contract AnonAadhaarVerifier {
|
||||
// Scalar field size
|
||||
uint256 constant r =
|
||||
21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
// Base field size
|
||||
uint256 constant q =
|
||||
21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
// Verification Key data
|
||||
uint256 constant alphax =
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042;
|
||||
uint256 constant alphay =
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958;
|
||||
uint256 constant betax1 =
|
||||
4252822878758300859123897981450591353533073413197771768651442665752259397132;
|
||||
uint256 constant betax2 =
|
||||
6375614351688725206403948262868962793625744043794305715222011528459656738731;
|
||||
uint256 constant betay1 =
|
||||
21847035105528745403288232691147584728191162732299865338377159692350059136679;
|
||||
uint256 constant betay2 =
|
||||
10505242626370262277552901082094356697409835680220590971873171140371331206856;
|
||||
uint256 constant gammax1 =
|
||||
11559732032986387107991004021392285783925812861821192530917403151452391805634;
|
||||
uint256 constant gammax2 =
|
||||
10857046999023057135944570762232829481370756359578518086990519993285655852781;
|
||||
uint256 constant gammay1 =
|
||||
4082367875863433681332203403145435568316851327593401208105741076214120093531;
|
||||
uint256 constant gammay2 =
|
||||
8495653923123431417604973247489272438418190587263600148770280649306958101930;
|
||||
uint256 constant deltax1 =
|
||||
4299544762320140490788925402007646552975895862668459356441621010020815213047;
|
||||
uint256 constant deltax2 =
|
||||
17871422848268104931114959135211373177342070617197528523406495979941661483095;
|
||||
uint256 constant deltay1 =
|
||||
15409138591596798216491700390363405976448424349216823319603684880497172470392;
|
||||
uint256 constant deltay2 =
|
||||
6760351807715958749139905882704408015750124381329342019530380383696940656228;
|
||||
|
||||
uint256 constant IC0x =
|
||||
8193099715697298773004274364923775437331207408810138294417049334729397568044;
|
||||
uint256 constant IC0y =
|
||||
16159858748150857247889758127444994458965923852898118281816624376217211565521;
|
||||
|
||||
uint256 constant IC1x =
|
||||
13108625264357966313259920811838652543870024141705807775281772752705667110553;
|
||||
uint256 constant IC1y =
|
||||
2544332662962865223088426787295310199726639257423304707272531378194627250471;
|
||||
|
||||
uint256 constant IC2x =
|
||||
12195716830305360975540216000332921077692646069403551236428029555879142592435;
|
||||
uint256 constant IC2y =
|
||||
13326636634666739773620233113286098727293935511084177100928164174534804438393;
|
||||
|
||||
uint256 constant IC3x =
|
||||
2688577362875587400781907970343221084288450779702134055253900789420295472719;
|
||||
uint256 constant IC3y =
|
||||
2113388700188757742720394931373303194111138693822901565764110433637094759188;
|
||||
|
||||
uint256 constant IC4x =
|
||||
19945525246931746661643582268635665599397781283994833186263056491807948286051;
|
||||
uint256 constant IC4y =
|
||||
16073805640677310775936718905838964579449451142346916951664145755535936866626;
|
||||
|
||||
uint256 constant IC5x =
|
||||
15285986370760482985887872526745945510369406463801327538950916850999926460800;
|
||||
uint256 constant IC5y =
|
||||
11338735502852181248738133285261397537982982234234692816858010242555671562990;
|
||||
|
||||
uint256 constant IC6x =
|
||||
16599939912082694861350909874825011692719127627200428021965770460961325896451;
|
||||
uint256 constant IC6y =
|
||||
2023372250411688527623252878305455588947803700538403282505116393326811329911;
|
||||
|
||||
uint256 constant IC7x =
|
||||
937704650640100783774452178387730049849091986214664695849468839894667227986;
|
||||
uint256 constant IC7y =
|
||||
1528238522480536593837231193324408021036867916082543038928582346279983380311;
|
||||
|
||||
uint256 constant IC8x =
|
||||
20307194658033847757064139044228229338386028222602331026142092196761205728977;
|
||||
uint256 constant IC8y =
|
||||
20906962585416997063272305175664219300089590012260266224825000463561652784804;
|
||||
|
||||
uint256 constant IC9x =
|
||||
765061429630449224592522816482257405186943062678395274581816041425416367436;
|
||||
uint256 constant IC9y =
|
||||
2812008994701470589234587891573790293274884176359538091522378449656962019510;
|
||||
|
||||
// Memory data
|
||||
uint16 constant pVk = 0;
|
||||
uint16 constant pPairing = 128;
|
||||
|
||||
uint16 constant pLastMem = 896;
|
||||
|
||||
function verifyProof(
|
||||
uint[2] calldata _pA,
|
||||
uint[2][2] calldata _pB,
|
||||
uint[2] calldata _pC,
|
||||
uint[9] calldata _pubSignals
|
||||
) public view returns (bool) {
|
||||
assembly {
|
||||
function checkField(v) {
|
||||
if iszero(lt(v, r)) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
// G1 function to multiply a G1 value(x,y) to value in an address
|
||||
function g1_mulAccC(pR, x, y, s) {
|
||||
let success
|
||||
let mIn := mload(0x40)
|
||||
mstore(mIn, x)
|
||||
mstore(add(mIn, 32), y)
|
||||
mstore(add(mIn, 64), s)
|
||||
|
||||
// `sub(gas(), 2000` replaced with `gas()` to avoid invalid opcode usage
|
||||
// success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
|
||||
success := staticcall(gas(), 7, mIn, 96, mIn, 64)
|
||||
|
||||
if iszero(success) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
|
||||
mstore(add(mIn, 64), mload(pR))
|
||||
mstore(add(mIn, 96), mload(add(pR, 32)))
|
||||
|
||||
// `sub(gas(), 2000` replaced with `gas()` to avoid invalid opcode usage
|
||||
// success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
|
||||
success := staticcall(gas(), 6, mIn, 128, pR, 64)
|
||||
|
||||
if iszero(success) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
|
||||
let _pPairing := add(pMem, pPairing)
|
||||
let _pVk := add(pMem, pVk)
|
||||
|
||||
mstore(_pVk, IC0x)
|
||||
mstore(add(_pVk, 32), IC0y)
|
||||
|
||||
// Compute the linear combination vk_x
|
||||
|
||||
g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
|
||||
|
||||
g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
|
||||
|
||||
g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))
|
||||
|
||||
g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))
|
||||
|
||||
g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128)))
|
||||
|
||||
g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160)))
|
||||
|
||||
g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192)))
|
||||
|
||||
g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224)))
|
||||
|
||||
g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256)))
|
||||
|
||||
// -A
|
||||
mstore(_pPairing, calldataload(pA))
|
||||
mstore(
|
||||
add(_pPairing, 32),
|
||||
mod(sub(q, calldataload(add(pA, 32))), q)
|
||||
)
|
||||
|
||||
// B
|
||||
mstore(add(_pPairing, 64), calldataload(pB))
|
||||
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
|
||||
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
|
||||
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
|
||||
|
||||
// alpha1
|
||||
mstore(add(_pPairing, 192), alphax)
|
||||
mstore(add(_pPairing, 224), alphay)
|
||||
|
||||
// beta2
|
||||
mstore(add(_pPairing, 256), betax1)
|
||||
mstore(add(_pPairing, 288), betax2)
|
||||
mstore(add(_pPairing, 320), betay1)
|
||||
mstore(add(_pPairing, 352), betay2)
|
||||
|
||||
// vk_x
|
||||
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
|
||||
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
|
||||
|
||||
// gamma2
|
||||
mstore(add(_pPairing, 448), gammax1)
|
||||
mstore(add(_pPairing, 480), gammax2)
|
||||
mstore(add(_pPairing, 512), gammay1)
|
||||
mstore(add(_pPairing, 544), gammay2)
|
||||
|
||||
// C
|
||||
mstore(add(_pPairing, 576), calldataload(pC))
|
||||
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
|
||||
|
||||
// delta2
|
||||
mstore(add(_pPairing, 640), deltax1)
|
||||
mstore(add(_pPairing, 672), deltax2)
|
||||
mstore(add(_pPairing, 704), deltay1)
|
||||
mstore(add(_pPairing, 736), deltay2)
|
||||
|
||||
// `sub(gas(), 2000` replaced with `gas()` to avoid invalid opcode usage
|
||||
// let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
|
||||
let success := staticcall(
|
||||
gas(),
|
||||
8,
|
||||
_pPairing,
|
||||
768,
|
||||
_pPairing,
|
||||
0x20
|
||||
)
|
||||
|
||||
isOk := and(success, mload(_pPairing))
|
||||
}
|
||||
|
||||
let pMem := mload(0x40)
|
||||
mstore(0x40, add(pMem, pLastMem))
|
||||
|
||||
// Validate that all evaluations ∈ F
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 0)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 32)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 64)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 96)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 128)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 160)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 192)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 224)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 256)))
|
||||
|
||||
checkField(calldataload(add(_pubSignals, 288)))
|
||||
|
||||
// Validate all evaluations
|
||||
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
|
||||
|
||||
mstore(0, isValid)
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
}
|
||||
5
packages/plugins/src/safe/utils/anonAadhaar/README.md
Normal file
5
packages/plugins/src/safe/utils/anonAadhaar/README.md
Normal 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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -17,3 +17,8 @@ import {Safe} from "safe-contracts/contracts/Safe.sol";
|
||||
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";
|
||||
import {SimpleAccountFactory} from "account-abstraction/samples/SimpleAccountFactory.sol";
|
||||
import {BLSSignatureAggregator} from "account-abstraction/samples/bls/BLSSignatureAggregator.sol";
|
||||
|
||||
// Anon-Aadhaar
|
||||
import {AnonAadhaar} from "./anonAadhaar/AnonAadhaar.sol";
|
||||
import {AnonAadhaarVerifier} from "./anonAadhaar/AnonAadhaarVerifier.sol";
|
||||
|
||||
|
||||
232
packages/plugins/test/e2e/SafeAnonAadhaarPlugin.test.ts
Normal file
232
packages/plugins/test/e2e/SafeAnonAadhaarPlugin.test.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/* eslint-disable @typescript-eslint/comma-dangle */
|
||||
/* eslint-disable prettier/prettier */
|
||||
import { expect } from "chai";
|
||||
import { JsonRpcProvider, NonceManager, Signer, ethers } from "ethers";
|
||||
import DeterministicDeployer from "../../lib-ts/deterministic-deployer/DeterministicDeployer";
|
||||
import {
|
||||
SafeAnonAadhaarFactory__factory,
|
||||
SafeAnonAadhaarPlugin__factory,
|
||||
AnonAadhaarVerifier__factory,
|
||||
AnonAadhaar__factory,
|
||||
Safe,
|
||||
AnonAadhaar,
|
||||
} from "../../typechain-types";
|
||||
import receiptOf from "./utils/receiptOf";
|
||||
import { setupTests } from "./utils/setupTests";
|
||||
import {
|
||||
createAnonAadhaarOperation,
|
||||
sendUserOpWithAnonAadhaarSig,
|
||||
} from "./utils/createUserOp";
|
||||
import {
|
||||
InitArgs,
|
||||
init,
|
||||
generateArgs,
|
||||
prove,
|
||||
artifactUrls,
|
||||
packGroth16Proof,
|
||||
ArtifactsOrigin,
|
||||
} from "@anon-aadhaar/core";
|
||||
import fs from "fs";
|
||||
import { getUserOpHash } from "./utils/userOpUtils";
|
||||
import { copmuteUserNullifier } from "./utils/computeNullifier";
|
||||
import { testQRData } from "./utils/assets/dataInput.json";
|
||||
|
||||
/*
|
||||
|
||||
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;
|
||||
let admin: NonceManager;
|
||||
let owner: Signer;
|
||||
let entryPointAddress: string;
|
||||
let safeSingleton: Safe;
|
||||
let deployer: DeterministicDeployer;
|
||||
|
||||
let certificate: string;
|
||||
let anonAadhaarAddress: string;
|
||||
let anonAadhaar: AnonAadhaar;
|
||||
|
||||
before(async () => {
|
||||
const setup = await setupTests();
|
||||
({
|
||||
provider,
|
||||
bundlerProvider,
|
||||
admin,
|
||||
owner,
|
||||
entryPointAddress,
|
||||
deployer,
|
||||
safeSingleton,
|
||||
} = setup);
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
// Deploy AnonAadhaarGroth16Verifier contract
|
||||
const anonAadhaarVerifier = await new AnonAadhaarVerifier__factory(signer).deploy();
|
||||
await anonAadhaarVerifier.waitForDeployment();
|
||||
|
||||
// Deploy AnonAadhaar contract
|
||||
anonAadhaar = await new AnonAadhaar__factory(signer).deploy(
|
||||
await anonAadhaarVerifier.getAddress(),
|
||||
BigInt(testPublicKeyHash).toString(),
|
||||
);
|
||||
await anonAadhaar.waitForDeployment();
|
||||
|
||||
anonAadhaarAddress = await anonAadhaar.getAddress();
|
||||
|
||||
// load test certificate
|
||||
const certificateDirName = __dirname + "/utils/assets";
|
||||
certificate = fs
|
||||
.readFileSync(certificateDirName + "/testCertificate.pem")
|
||||
.toString();
|
||||
|
||||
// load files needed for proof generation and verification
|
||||
const anonAadhaarInitArgs: InitArgs = {
|
||||
wasmURL: artifactUrls.v2.wasm,
|
||||
zkeyURL: artifactUrls.v2.zkey,
|
||||
vkeyURL: artifactUrls.v2.vk,
|
||||
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 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);
|
||||
|
||||
const createArgs = [
|
||||
safeSingleton,
|
||||
entryPointAddress,
|
||||
await owner.getAddress(),
|
||||
0,
|
||||
anonAadhaarAddress,
|
||||
userDataHash,
|
||||
] satisfies Parameters<typeof safeAnonAadhaarFactory.create.staticCall>;
|
||||
|
||||
const accountAddress = await safeAnonAadhaarFactory.create.staticCall(
|
||||
...createArgs
|
||||
);
|
||||
|
||||
await receiptOf(safeAnonAadhaarFactory.create(...createArgs));
|
||||
|
||||
const safeAnonAadhaarPlugin = SafeAnonAadhaarPlugin__factory.connect(
|
||||
accountAddress,
|
||||
owner
|
||||
);
|
||||
|
||||
// Native tokens for the pre-fund
|
||||
await receiptOf(
|
||||
admin.sendTransaction({
|
||||
to: accountAddress,
|
||||
value: ethers.parseEther("10"),
|
||||
})
|
||||
);
|
||||
|
||||
const recipient = ethers.Wallet.createRandom();
|
||||
const transferAmount = ethers.parseEther("1");
|
||||
// Construct userOp
|
||||
const userOpCallData = safeAnonAadhaarPlugin.interface.encodeFunctionData(
|
||||
"execTransaction",
|
||||
[recipient.address, transferAmount, "0x00"]
|
||||
);
|
||||
|
||||
// create User Operation
|
||||
const unsignedUserOperation = await createAnonAadhaarOperation(
|
||||
provider,
|
||||
accountAddress,
|
||||
userOpCallData,
|
||||
entryPointAddress
|
||||
);
|
||||
|
||||
// get userOpHash
|
||||
const userOpHash = getUserOpHash(
|
||||
unsignedUserOperation,
|
||||
entryPointAddress,
|
||||
Number((await provider.getNetwork()).chainId)
|
||||
);
|
||||
|
||||
// prove with userOpHash as signal
|
||||
const args = await generateArgs({
|
||||
qrData: testQRData,
|
||||
certificateFile: certificate,
|
||||
nullifierSeed: nullifierSeed,
|
||||
signal: userOpHash, // user op hash
|
||||
});
|
||||
|
||||
// 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);
|
||||
console.timeEnd(proofTimingKey);
|
||||
|
||||
const anonAadhaarProof = anonAadhaarCore.proof;
|
||||
const packedGroth16Proof = packGroth16Proof(anonAadhaarProof.groth16Proof);
|
||||
|
||||
// view call to AnonAadhaar contract to see if verification returns true
|
||||
expect(await anonAadhaar.verifyAnonAadhaarProof(
|
||||
nullifierSeed,
|
||||
anonAadhaarProof.nullifier,
|
||||
anonAadhaarProof.timestamp,
|
||||
userOpHash,
|
||||
[
|
||||
anonAadhaarProof.ageAbove18,
|
||||
anonAadhaarProof.gender,
|
||||
anonAadhaarProof.pincode,
|
||||
anonAadhaarProof.state,
|
||||
],
|
||||
packedGroth16Proof
|
||||
)).to.equal(true);
|
||||
|
||||
// encode proof data into userOpSignature
|
||||
const encoder = ethers.AbiCoder.defaultAbiCoder();
|
||||
const userOpSignature = encoder.encode(
|
||||
["uint", "uint", "uint", "uint[4]", "uint[8]"],
|
||||
[
|
||||
BigInt(nullifierSeed),
|
||||
Number(anonAadhaarProof.timestamp),
|
||||
BigInt(userOpHash), // insert userOpHash into signature
|
||||
[
|
||||
anonAadhaarProof.ageAbove18,
|
||||
anonAadhaarProof.gender,
|
||||
anonAadhaarProof.pincode,
|
||||
anonAadhaarProof.state,
|
||||
],
|
||||
packedGroth16Proof,
|
||||
]
|
||||
);
|
||||
|
||||
// send userOp
|
||||
await sendUserOpWithAnonAadhaarSig(
|
||||
bundlerProvider,
|
||||
entryPointAddress,
|
||||
unsignedUserOperation,
|
||||
userOpSignature
|
||||
);
|
||||
|
||||
expect(await provider.getBalance(recipient.address)).to.equal(
|
||||
ethers.parseEther("1")
|
||||
);
|
||||
}).timeout(0);
|
||||
});
|
||||
@@ -25,14 +25,12 @@ describe("SafeSponsorEverythingPaymasterPlugin", () => {
|
||||
} = await setupTests();
|
||||
|
||||
// Deploy paymaster.
|
||||
const paymaster = await deployer.connectOrDeploy(
|
||||
SponsorEverythingPaymaster__factory,
|
||||
[entryPointAddress],
|
||||
);
|
||||
const paymaster = await new SponsorEverythingPaymaster__factory(admin).deploy(entryPointAddress);
|
||||
await paymaster.waitForDeployment();
|
||||
const paymasterAddress = await paymaster.getAddress();
|
||||
|
||||
// Paymaster deposits.
|
||||
await paymaster.deposit({ value: oneEther })
|
||||
await paymaster.connect(admin).deposit({ value: oneEther })
|
||||
|
||||
const recipient = ethers.Wallet.createRandom();
|
||||
const transferAmount = oneEther;
|
||||
|
||||
3
packages/plugins/test/e2e/utils/assets/dataInput.json
Normal file
3
packages/plugins/test/e2e/utils/assets/dataInput.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"testQRData": "8259163575998395410294216884136380576185817320339145460288951755287582961380611852552428987321584902318624273479337130653734982789439199350807739714406680256506601030028361685736660257517232716829232450159251789263870750283214820475102793105777087762238893090228084052270739203426767272062178826235941508196284529472654271516164224874687419158221021213944829682919423174703783469927383220474654008065915029614141226522064062660593170425792840873655513538373377850112144063189928583588899889878172757870400281696669604010659786496608127700010264443115263361656744433002559396889060190428705316366290450741550935385486607346514118464415324976934593027192262025619948063647667007927187736245772179085671658409804311603784752615097922989017361163561315974008304022542448394278143245816470881130080719485003834016131185071765229491892891069788319670287394271744730364788949609836924781874523936880888005883165757273872375006288978183466996520618718348187182821516617721340861010989807614756396013627238651856164981477576514065364628430139194213240602981419233621531616776712580234318576148789862972873366521755587675635811636464535551028275057950562020714225333126426609311459495088802145911084644641596208432517247324679678535859879970296810837735288916946197174410518342751033634782712968162882714769666441813893046220965525694847349131353986974388432968669605721975441870936552792275255624723251162192468002453471184713983574359601113515796454264270501379717344206777921353459767049560942843350534472442799601294637063232419543855742825887931841338302499933012059977947394755335155868283405337181095220998277373266658634859632929226320059674299759100792654417315629048732480315019941928105082550091217622422743467170706956093632228513797781797454779203616427853022505097310749994766657051986303478622173767936568165644251615127773430128638507677775244195799780291921828512257290767451475181728141544788756907393883042588060697683541401090581157249784874529424005078918452607589129440476242749110421616270676359722523229311894327359615548588038186027827017569331332262329182564217789843145105509621002324556840213928256545454178891208004109769624959566302976213521762873815749009289995208912424872527724417047936432945498377307452190302923489092664437908497749093491199476080757200233878726847198496754472664256996743796092233459542884818717466621372105594672115988382565552756801323160697003960485232732393383241422077506009076922303757067128564302338914230360252223406874457414109774901980252709597099278192874164252010830754720603092419792069707099362278082792090307065378744856387301364608460967253691290230861162587170799141457093188189022390589265654613500974699477990974878105678883229707694455342266695530373994049224098435972125150350136428446936271698977517627416435999970351222450833295217051468307037908262231982382247410542334757724852032521780157518474618653527191342825230455100778913195115477763082159513429761573752871477695723697689470263993132596482716347199834315782099668846081963760553679915994617396376870314998926788197388410764535427795200340714967872713095483294486407886767431404892448155562283436571050452251042117926586451385682519188252281397"
|
||||
}
|
||||
23
packages/plugins/test/e2e/utils/assets/testCertificate.pem
Normal file
23
packages/plugins/test/e2e/utils/assets/testCertificate.pem
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID6jCCAtKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBpMRQwEgYDVQQDEwtleGFt
|
||||
cGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQH
|
||||
EwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MB4XDTI0
|
||||
MDMwMTE2MTc0MFoXDTI1MDMwMTE2MTc0MFowaTEUMBIGA1UEAxMLZXhhbXBsZS5v
|
||||
cmcxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxh
|
||||
Y2tzYnVyZzENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAJXoH3UGWVzCYq3c/d012AQFWyrfRtxxnFa1
|
||||
ju4ZmZXvoaMlRxBiCseAHpdvROO+RU2w8ZDj99TjWYlyEXNE3lL894JvhJSIqVmn
|
||||
s9Iett0DRRZi6og+7u/eiJoUmbmkf5UExfCWwmK5bSPRl1AzLZ6X62FB0mHel5lN
|
||||
TEFjypy+PgdyIbRCU9z4FglCi2g1HuPptg0rNR/apu6MKKhFI5+X3nzA/l0UTkdI
|
||||
E/tD7Fg/gbTuMowiFnM0iY0hC6AXom7GiUDwXfIr2cyGu8OkNUOSNy1WYWd2m3Nb
|
||||
oSyjWA+RnBvYunDEwqsKzysJvC+umB88ApWm4enySPUAcwlP+vECAwEAAaOBnDCB
|
||||
mTAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIC9DA7BgNVHSUENDAyBggrBgEFBQcD
|
||||
AQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgwEQYJYIZI
|
||||
AYb4QgEBBAQDAgD3MCwGA1UdEQQlMCOGG2h0dHA6Ly9leGFtcGxlLm9yZy93ZWJp
|
||||
ZCNtZYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAjDnIK8R7ruSiJpdUeZ2ZxaGI
|
||||
ewf020sAc0ENw6x3jaKKFINNLnMqEKLntZzIg7WbjnKLhtUhb/SLHP59mfT2OJVS
|
||||
YMW7uRmnajfEd0MzfrXIUKBwCeWTCyP/bcdlJOT4u24ngFnkUyWaEKnqH3YuI8cw
|
||||
BKWQ26Lq7ODq6e6Otxf0KOtWTeVSzlCL66MGBvQ36LF1g8PLEZ9hvvX06ieaE99W
|
||||
HDK9/pGSuobx6Fi7ufzqymirT/GOuxslquB4mleCu4ArCg2qAVC2wDjQymgvq8FS
|
||||
bVukYnC6XdwqYhvIuTPnEys4gGdeirY+UPGQeqxMrNm+ZpKPTS0NwzxHwlhutw==
|
||||
-----END CERTIFICATE-----
|
||||
73
packages/plugins/test/e2e/utils/computeNullifier.ts
Normal file
73
packages/plugins/test/e2e/utils/computeNullifier.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
convertBigIntToByteArray,
|
||||
decompressByteArray,
|
||||
extractPhoto,
|
||||
} from "@anon-aadhaar/core";
|
||||
import { sha256Pad } from "@zk-email/helpers/dist/sha-utils";
|
||||
import { buildPoseidon } from "circomlibjs";
|
||||
|
||||
// Method to extract a nullifier specific to each Aadhaar ID owner from Aadhaar QR code
|
||||
// https://github.com/anon-aadhaar/anon-aadhaar/blob/main/packages/circuits/test/aadhaar-verifier.test.ts#L140
|
||||
export async function copmuteUserNullifier(
|
||||
nullifierSeed: number,
|
||||
qrData: string
|
||||
): Promise<bigint> {
|
||||
const qrDataBytes = convertBigIntToByteArray(BigInt(qrData));
|
||||
const decodedData = decompressByteArray(qrDataBytes);
|
||||
const signedData = decodedData.slice(0, decodedData.length - 256);
|
||||
const [qrDataPadded, qrDataPaddedLen] = sha256Pad(signedData, 512 * 3);
|
||||
|
||||
const { bytes: photoBytes } = extractPhoto(
|
||||
Array.from(qrDataPadded),
|
||||
qrDataPaddedLen
|
||||
);
|
||||
|
||||
const photoBytesPacked = padArrayWithZeros(
|
||||
bytesToIntChunks(new Uint8Array(photoBytes), 31),
|
||||
32
|
||||
);
|
||||
|
||||
const poseidon = await buildPoseidon();
|
||||
|
||||
const first16 = poseidon([...photoBytesPacked.slice(0, 16)]);
|
||||
const last16 = poseidon([...photoBytesPacked.slice(16, 32)]);
|
||||
const nullifier = poseidon([nullifierSeed, first16, last16]);
|
||||
|
||||
return BigInt(poseidon.F.toString(nullifier));
|
||||
}
|
||||
|
||||
export function bytesToIntChunks(
|
||||
bytes: Uint8Array,
|
||||
maxBytesInField: number
|
||||
): bigint[] {
|
||||
const numChunks = Math.ceil(bytes.length / maxBytesInField);
|
||||
const ints: bigint[] = new Array(numChunks).fill(BigInt(0));
|
||||
|
||||
for (let i = 0; i < numChunks; i++) {
|
||||
let intSum = BigInt(0);
|
||||
for (let j = 0; j < maxBytesInField; j++) {
|
||||
const idx = maxBytesInField * i + j;
|
||||
if (idx >= bytes.length) break; // Stop if we've processed all bytes
|
||||
|
||||
// Shift byte into position and add to current integer sum
|
||||
intSum += BigInt(bytes[idx]) * BigInt(256) ** BigInt(j);
|
||||
}
|
||||
ints[i] = intSum;
|
||||
}
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
export function padArrayWithZeros(
|
||||
bigIntArray: bigint[],
|
||||
requiredLength: number
|
||||
) {
|
||||
const currentLength = bigIntArray.length;
|
||||
const zerosToFill = requiredLength - currentLength;
|
||||
|
||||
if (zerosToFill > 0) {
|
||||
return [...bigIntArray, ...Array(zerosToFill).fill(BigInt(0))];
|
||||
}
|
||||
|
||||
return bigIntArray;
|
||||
}
|
||||
@@ -1,79 +1,81 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable @typescript-eslint/comma-dangle */
|
||||
import { BigNumberish, BytesLike, ethers, getBytes, NonceManager, Signer } from "ethers";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
|
||||
import { SafeProxyFactory } from "../../../typechain-types/lib/safe-contracts/contracts/proxies/SafeProxyFactory";
|
||||
import { Safe } from "../../../typechain-types/lib/safe-contracts/contracts/Safe";
|
||||
import {
|
||||
EntryPoint__factory,
|
||||
SafeBlsPlugin,
|
||||
SafeWebAuthnPlugin,
|
||||
EntryPoint__factory,
|
||||
SafeBlsPlugin,
|
||||
SafeWebAuthnPlugin,
|
||||
} from "../../../typechain-types";
|
||||
import receiptOf from "./receiptOf";
|
||||
import { calculateProxyAddress } from "./calculateProxyAddress";
|
||||
import { getGasEstimates } from "./getGasEstimates";
|
||||
import { getFeeData, getGasEstimates } from "./getGasEstimates";
|
||||
import sendUserOpAndWait from "./sendUserOpAndWait";
|
||||
import {
|
||||
FactoryParams,
|
||||
getUserOpHash,
|
||||
PackedUserOperation,
|
||||
packUserOp,
|
||||
UserOperation,
|
||||
FactoryParams,
|
||||
getUserOpHash,
|
||||
PackedUserOperation,
|
||||
packUserOp,
|
||||
UserOperation,
|
||||
} from "./userOpUtils";
|
||||
|
||||
type Plugin = SafeBlsPlugin | SafeWebAuthnPlugin;
|
||||
|
||||
export const generateFactoryParamsAndAddress = async (
|
||||
admin: NonceManager,
|
||||
owner: NonceManager,
|
||||
plugin: Plugin,
|
||||
safeSingleton: Safe,
|
||||
safeProxyFactory: SafeProxyFactory,
|
||||
admin: NonceManager,
|
||||
owner: NonceManager,
|
||||
plugin: Plugin,
|
||||
safeSingleton: Safe,
|
||||
safeProxyFactory: SafeProxyFactory
|
||||
) => {
|
||||
const pluginAddress = await plugin.getAddress();
|
||||
const singletonAddress = await safeSingleton.getAddress();
|
||||
const factoryAddress = await safeProxyFactory.getAddress();
|
||||
const pluginAddress = await plugin.getAddress();
|
||||
const singletonAddress = await safeSingleton.getAddress();
|
||||
const factoryAddress = await safeProxyFactory.getAddress();
|
||||
|
||||
const moduleInitializer = plugin.interface.encodeFunctionData("enableMyself");
|
||||
const encodedInitializer = safeSingleton.interface.encodeFunctionData(
|
||||
"setup",
|
||||
[
|
||||
[await owner.getAddress()],
|
||||
1,
|
||||
pluginAddress,
|
||||
moduleInitializer,
|
||||
pluginAddress,
|
||||
AddressZero,
|
||||
0,
|
||||
AddressZero,
|
||||
],
|
||||
);
|
||||
const moduleInitializer = plugin.interface.encodeFunctionData("enableMyself");
|
||||
const encodedInitializer = safeSingleton.interface.encodeFunctionData(
|
||||
"setup",
|
||||
[
|
||||
[await owner.getAddress()],
|
||||
1,
|
||||
pluginAddress,
|
||||
moduleInitializer,
|
||||
pluginAddress,
|
||||
AddressZero,
|
||||
0,
|
||||
AddressZero,
|
||||
]
|
||||
);
|
||||
|
||||
const deployedAddress = await calculateProxyAddress(
|
||||
safeProxyFactory,
|
||||
await safeSingleton.getAddress(),
|
||||
encodedInitializer,
|
||||
73,
|
||||
);
|
||||
const deployedAddress = await calculateProxyAddress(
|
||||
safeProxyFactory,
|
||||
await safeSingleton.getAddress(),
|
||||
encodedInitializer,
|
||||
73
|
||||
);
|
||||
|
||||
// Native tokens for the pre-fund
|
||||
await receiptOf(
|
||||
admin.sendTransaction({
|
||||
to: deployedAddress,
|
||||
value: ethers.parseEther("10"),
|
||||
}),
|
||||
);
|
||||
// Native tokens for the pre-fund
|
||||
await receiptOf(
|
||||
admin.sendTransaction({
|
||||
to: deployedAddress,
|
||||
value: ethers.parseEther("10"),
|
||||
})
|
||||
);
|
||||
|
||||
const factoryData = safeProxyFactory.interface.encodeFunctionData(
|
||||
"createProxyWithNonce",
|
||||
[singletonAddress, encodedInitializer, 73],
|
||||
);
|
||||
const factoryData = safeProxyFactory.interface.encodeFunctionData(
|
||||
"createProxyWithNonce",
|
||||
[singletonAddress, encodedInitializer, 73]
|
||||
);
|
||||
|
||||
const factoryParams = {
|
||||
factory: factoryAddress,
|
||||
factoryData,
|
||||
};
|
||||
const factoryParams = {
|
||||
factory: factoryAddress,
|
||||
factoryData,
|
||||
};
|
||||
|
||||
return { factoryParams, deployedAddress };
|
||||
return { factoryParams, deployedAddress };
|
||||
};
|
||||
|
||||
export const createUserOperation = async (
|
||||
@@ -88,25 +90,25 @@ export const createUserOperation = async (
|
||||
paymasterPostOpGasLimit?: BigNumberish,
|
||||
paymasterData?: BytesLike,
|
||||
) => {
|
||||
const entryPoint = EntryPoint__factory.connect(
|
||||
entryPointAddress,
|
||||
await provider.getSigner(),
|
||||
);
|
||||
const nonce = await entryPoint.getNonce(accountAddress, "0x00");
|
||||
const nonceHex = "0x0" + nonce.toString();
|
||||
const entryPoint = EntryPoint__factory.connect(
|
||||
entryPointAddress,
|
||||
await provider.getSigner()
|
||||
);
|
||||
const nonce = await entryPoint.getNonce(accountAddress, "0x00");
|
||||
const nonceHex = "0x0" + nonce.toString();
|
||||
|
||||
let userOp: Partial<UserOperation> = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
callData: userOpCallData,
|
||||
callGasLimit: "0x00",
|
||||
signature: dummySignature,
|
||||
};
|
||||
let userOp: Partial<UserOperation> = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
callData: userOpCallData,
|
||||
callGasLimit: "0x00",
|
||||
signature: dummySignature,
|
||||
};
|
||||
|
||||
if (factoryParams.factory !== "0x") {
|
||||
userOp.factory = factoryParams.factory;
|
||||
userOp.factoryData = factoryParams.factoryData;
|
||||
}
|
||||
if (factoryParams.factory !== "0x") {
|
||||
userOp.factory = factoryParams.factory;
|
||||
userOp.factoryData = factoryParams.factoryData;
|
||||
}
|
||||
|
||||
const {
|
||||
callGasLimit,
|
||||
@@ -140,7 +142,7 @@ export const createUserOperation = async (
|
||||
signature: dummySignature,
|
||||
} satisfies UserOperation;
|
||||
|
||||
return await ethers.resolveProperties(unsignedUserOperation);
|
||||
return await ethers.resolveProperties(unsignedUserOperation);
|
||||
};
|
||||
|
||||
export const createAndSendUserOpWithEcdsaSig = async (
|
||||
@@ -169,22 +171,86 @@ export const createAndSendUserOpWithEcdsaSig = async (
|
||||
paymasterData,
|
||||
);
|
||||
|
||||
const userOpHash = getUserOpHash(
|
||||
unsignedUserOperation,
|
||||
entryPointAddress,
|
||||
Number((await provider.getNetwork()).chainId),
|
||||
);
|
||||
const userOpHash = getUserOpHash(
|
||||
unsignedUserOperation,
|
||||
entryPointAddress,
|
||||
Number((await provider.getNetwork()).chainId)
|
||||
);
|
||||
|
||||
const userOpSignature = await owner.signMessage(getBytes(userOpHash));
|
||||
const userOpSignature = await owner.signMessage(getBytes(userOpHash));
|
||||
|
||||
const userOperation = {
|
||||
...unsignedUserOperation,
|
||||
signature: userOpSignature,
|
||||
};
|
||||
const userOperation = {
|
||||
...unsignedUserOperation,
|
||||
signature: userOpSignature,
|
||||
};
|
||||
|
||||
return await sendUserOpAndWait(
|
||||
userOperation,
|
||||
entryPointAddress,
|
||||
bundlerProvider,
|
||||
);
|
||||
return await sendUserOpAndWait(
|
||||
userOperation,
|
||||
entryPointAddress,
|
||||
bundlerProvider
|
||||
);
|
||||
};
|
||||
|
||||
export const createAnonAadhaarOperation = async (
|
||||
provider: ethers.JsonRpcProvider,
|
||||
accountAddress: string,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string
|
||||
) => {
|
||||
const entryPoint = EntryPoint__factory.connect(
|
||||
entryPointAddress,
|
||||
await provider.getSigner()
|
||||
);
|
||||
const nonce = await entryPoint.getNonce(accountAddress, "0x00");
|
||||
const nonceHex = "0x0" + nonce.toString();
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await getFeeData(provider);
|
||||
|
||||
// Note that gas amount params, such as callGasLimit, verificationGasLimit, preVerificationGas, are constant.
|
||||
// This is because it's challenging to have dynamic gas values for userOp due to the following dependency loop issue.
|
||||
// Doing gas estimation requires a valid signature in the userOp. Otherwise, the estimation fails.
|
||||
// The signautre is an encoded value of zk-proof and signal (userOp Hash), which means signature needs a complete userOp including gas values.
|
||||
// If gas values are changed after creating signature, on-chain verification fails as the new userOp hash doesn't match the proof anymore.
|
||||
|
||||
// solution 1: re-create proof but this takes too much time
|
||||
// solution 2: have fixed gas values
|
||||
// solution 3:
|
||||
// - gas estimation with dummy sig to get at least dynamic callGasLimit. ( pimlico offers such an API call )
|
||||
// - keep verificationGasLimit & preVerificationGas fixed as they are more or less constant
|
||||
|
||||
// this issue is also problematic when simulation call with valid sig is necessary, e.g. userOp w/ paymaster.
|
||||
|
||||
const unsignedUserOperation = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
factory: undefined,
|
||||
factoryData: undefined,
|
||||
callData: userOpCallData,
|
||||
callGasLimit: ethers.toBeHex(150000n),
|
||||
verificationGasLimit: ethers.toBeHex(1000000n),
|
||||
preVerificationGas: ethers.toBeHex(200000n),
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
signature: "0x",
|
||||
} satisfies UserOperation;
|
||||
|
||||
return await ethers.resolveProperties(unsignedUserOperation);
|
||||
};
|
||||
|
||||
export const sendUserOpWithAnonAadhaarSig = async (
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
entryPointAddress: string,
|
||||
unsignedUserOperation: UserOperation,
|
||||
userOpSignature: string
|
||||
) => {
|
||||
const userOperation = {
|
||||
...unsignedUserOperation,
|
||||
signature: userOpSignature,
|
||||
};
|
||||
|
||||
return await sendUserOpAndWait(
|
||||
userOperation,
|
||||
entryPointAddress,
|
||||
bundlerProvider
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/comma-dangle */
|
||||
/* eslint-disable prettier/prettier */
|
||||
import { ethers } from "ethers";
|
||||
import { UserOperation } from "./userOpUtils";
|
||||
|
||||
export const getGasEstimates = async (
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
partialUserOperation: Partial<UserOperation>,
|
||||
entryPointAddress: string,
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
partialUserOperation: Partial<UserOperation>,
|
||||
entryPointAddress: string
|
||||
) => {
|
||||
const gasEstimate = (await bundlerProvider.send(
|
||||
"eth_estimateUserOperationGas",
|
||||
@@ -17,15 +19,15 @@ export const getGasEstimates = async (
|
||||
callGasLimit: string;
|
||||
};
|
||||
|
||||
const safeVerificationGasLimit =
|
||||
BigInt(gasEstimate.verificationGasLimit) +
|
||||
BigInt(gasEstimate.verificationGasLimit); // + 100% TODO: (merge-ok) why do we have to increase the limit so much for all tests to pass?
|
||||
const safeVerificationGasLimit =
|
||||
BigInt(gasEstimate.verificationGasLimit) +
|
||||
BigInt(gasEstimate.verificationGasLimit); // + 100% TODO: (merge-ok) why do we have to increase the limit so much for all tests to pass?
|
||||
|
||||
const safePreVerificationGas =
|
||||
BigInt(gasEstimate.preVerificationGas) +
|
||||
BigInt(gasEstimate.preVerificationGas) / 10n; // + 10%
|
||||
const safePreVerificationGas =
|
||||
BigInt(gasEstimate.preVerificationGas) +
|
||||
BigInt(gasEstimate.preVerificationGas) / 10n; // + 10%
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await getFeeData(provider);
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await getFeeData(provider);
|
||||
|
||||
return {
|
||||
callGasLimit: gasEstimate.callGasLimit,
|
||||
@@ -37,16 +39,16 @@ export const getGasEstimates = async (
|
||||
};
|
||||
};
|
||||
|
||||
async function getFeeData(provider: ethers.Provider) {
|
||||
const feeData = await provider.getFeeData();
|
||||
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas) {
|
||||
throw new Error(
|
||||
"maxFeePerGas or maxPriorityFeePerGas is null or undefined",
|
||||
);
|
||||
}
|
||||
export async function getFeeData(provider: ethers.Provider) {
|
||||
const feeData = await provider.getFeeData();
|
||||
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas) {
|
||||
throw new Error(
|
||||
"maxFeePerGas or maxPriorityFeePerGas is null or undefined"
|
||||
);
|
||||
}
|
||||
|
||||
const maxFeePerGas = "0x" + feeData.maxFeePerGas.toString();
|
||||
const maxPriorityFeePerGas = "0x" + feeData.maxPriorityFeePerGas.toString();
|
||||
const maxFeePerGas = "0x" + feeData.maxFeePerGas.toString();
|
||||
const maxPriorityFeePerGas = "0x" + feeData.maxPriorityFeePerGas.toString();
|
||||
|
||||
return { maxFeePerGas, maxPriorityFeePerGas };
|
||||
return { maxFeePerGas, maxPriorityFeePerGas };
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user