Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #223 from porco-rosso-j/anon-aadhaar
Browse files Browse the repository at this point in the history
Safe AnonAadhaar Plugin
  • Loading branch information
jacque006 authored Jul 5, 2024
2 parents 84dc6d1 + a66a3d5 commit 1cc5e79
Show file tree
Hide file tree
Showing 20 changed files with 3,834 additions and 6,275 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 13 additions & 6 deletions packages/plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
58 changes: 58 additions & 0 deletions packages/plugins/src/safe/SafeAnonAadhaarFactory.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 165 additions & 0 deletions packages/plugins/src/safe/SafeAnonAadhaarPlugin.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 77 additions & 0 deletions packages/plugins/src/safe/utils/anonAadhaar/AnonAadhaar.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 1cc5e79

Please sign in to comment.