This repository has been archived by the owner on Sep 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #223 from porco-rosso-j/anon-aadhaar
Safe AnonAadhaar Plugin
- Loading branch information
Showing
20 changed files
with
3,834 additions
and
6,275 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
77
packages/plugins/src/safe/utils/anonAadhaar/AnonAadhaar.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.