From 033b4957e0c3cf32e8a6d86951e731c6a5b5da9d Mon Sep 17 00:00:00 2001 From: daksh Date: Sun, 4 Aug 2024 00:13:41 +0530 Subject: [PATCH] feat: add worldid verifier --- src/WorldcoinVerifier.sol | 85 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/WorldcoinVerifier.sol diff --git a/src/WorldcoinVerifier.sol b/src/WorldcoinVerifier.sol new file mode 100644 index 0000000..25e5a51 --- /dev/null +++ b/src/WorldcoinVerifier.sol @@ -0,0 +1,85 @@ +library ByteHasher { + /// @dev Creates a keccak256 hash of a bytestring. + /// @param value The bytestring to hash + /// @return The hash of the specified value + /// @dev `>> 8` makes sure that the result is included in our field + function hashToField(bytes memory value) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(value))) >> 8; + } +} + +interface IWorldID { + /// @notice Reverts if the zero-knowledge proof is invalid. + /// @param root The of the Merkle tree + /// @param groupId The id of the Semaphore group + /// @param signalHash A keccak256 hash of the Semaphore signal + /// @param nullifierHash The nullifier hash + /// @param externalNullifierHash A keccak256 hash of the external nullifier + /// @param proof The zero-knowledge proof + /// @dev Note that a double-signaling check is not included here, and should be carried by the caller. + function verifyProof( + uint256 root, + uint256 groupId, + uint256 signalHash, + uint256 nullifierHash, + uint256 externalNullifierHash, + uint256[8] calldata proof + ) external view; +} + +contract WorldcoinVerifier { + using ByteHasher for bytes; + + /// @notice Thrown when attempting to reuse a nullifier + error InvalidNullifier(); + + /// @dev The address of the World ID Router contract that will be used for verifying proofs + IWorldID internal immutable worldId; + + /// @dev The keccak256 hash of the externalNullifier (unique identifier of the action performed), combination of appId and action + uint256 internal immutable externalNullifierHash; + + /// @dev The World ID group ID (1 for Orb-verified) + uint256 internal immutable groupId = 1; + + /// @dev Whether a nullifier hash has been used already. Used to guarantee an action is only performed once by a single person + mapping(uint256 => bool) internal nullifierHashes; + + /// @param _worldId The address of the WorldIDRouter that will verify the proofs + /// @param _appId The World ID App ID (from Developer Portal) + /// @param _action The World ID Action (from Developer Portal) + constructor( + IWorldID _worldId, + string memory _appId, + string memory _action + ) { + worldId = _worldId; + externalNullifierHash = abi + .encodePacked(abi.encodePacked(_appId).hashToField(), _action) + .hashToField(); + } + + /// @param signal An arbitrary input from the user that cannot be tampered with. In this case, it is the user's wallet address. + /// @param root The root (returned by the IDKit widget). + /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the IDKit widget). + /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the IDKit widget). + function verifyAndExecute( + address signal, + uint256 root, + uint256 nullifierHash, + uint256[8] calldata proof + ) public { + if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); + + nullifierHashes[nullifierHash] = true; + + worldId.verifyProof( + root, + groupId, // set to "1" in the constructor + abi.encodePacked(signal).hashToField(), + nullifierHash, + externalNullifierHash, + proof + ); + } +}