From 8d96b0509229fa9adc8430226dc73d3199eea29a Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Mon, 9 Dec 2024 22:53:54 -0800 Subject: [PATCH 01/28] chore: forge init --- pbh-verifier/.github/workflows/test.yml | 45 +++++++++++++++++ pbh-verifier/.gitignore | 14 ++++++ pbh-verifier/README.md | 66 +++++++++++++++++++++++++ pbh-verifier/foundry.toml | 6 +++ pbh-verifier/script/Counter.s.sol | 19 +++++++ pbh-verifier/src/Counter.sol | 14 ++++++ pbh-verifier/test/Counter.t.sol | 24 +++++++++ 7 files changed, 188 insertions(+) create mode 100644 pbh-verifier/.github/workflows/test.yml create mode 100644 pbh-verifier/.gitignore create mode 100644 pbh-verifier/README.md create mode 100644 pbh-verifier/foundry.toml create mode 100644 pbh-verifier/script/Counter.s.sol create mode 100644 pbh-verifier/src/Counter.sol create mode 100644 pbh-verifier/test/Counter.t.sol diff --git a/pbh-verifier/.github/workflows/test.yml b/pbh-verifier/.github/workflows/test.yml new file mode 100644 index 0000000..762a296 --- /dev/null +++ b/pbh-verifier/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/pbh-verifier/.gitignore b/pbh-verifier/.gitignore new file mode 100644 index 0000000..85198aa --- /dev/null +++ b/pbh-verifier/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/pbh-verifier/README.md b/pbh-verifier/README.md new file mode 100644 index 0000000..9265b45 --- /dev/null +++ b/pbh-verifier/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/pbh-verifier/foundry.toml b/pbh-verifier/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/pbh-verifier/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/pbh-verifier/script/Counter.s.sol b/pbh-verifier/script/Counter.s.sol new file mode 100644 index 0000000..cdc1fe9 --- /dev/null +++ b/pbh-verifier/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/pbh-verifier/src/Counter.sol b/pbh-verifier/src/Counter.sol new file mode 100644 index 0000000..aded799 --- /dev/null +++ b/pbh-verifier/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/pbh-verifier/test/Counter.t.sol b/pbh-verifier/test/Counter.t.sol new file mode 100644 index 0000000..54b724f --- /dev/null +++ b/pbh-verifier/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From 57e09632fad190a5b4c614a7dfd34e9f0df1376b Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Mon, 9 Dec 2024 22:53:57 -0800 Subject: [PATCH 02/28] forge install: forge-std v1.9.4 --- .gitmodules | 3 +++ pbh-verifier/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 pbh-verifier/lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..443492a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pbh-verifier/lib/forge-std"] + path = pbh-verifier/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/pbh-verifier/lib/forge-std b/pbh-verifier/lib/forge-std new file mode 160000 index 0000000..1eea5ba --- /dev/null +++ b/pbh-verifier/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 From 0d2fd70b69494f8c674a992d20533cd36b84fe1f Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Mon, 9 Dec 2024 23:22:34 -0800 Subject: [PATCH 03/28] wip forge init --- pbh-verifier/README.md | 67 +++--------------------------------- pbh-verifier/src/Counter.sol | 33 +++++++++++++----- 2 files changed, 29 insertions(+), 71 deletions(-) diff --git a/pbh-verifier/README.md b/pbh-verifier/README.md index 9265b45..3230cc8 100644 --- a/pbh-verifier/README.md +++ b/pbh-verifier/README.md @@ -1,66 +1,7 @@ -## Foundry +# PBH Validator -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +As mentioned previously, Stage 1 of 4337 PBH features a PBHSignatureAggregator and PBHValidator contract, allowing a user to include a World ID proof encoded in the UserOp signature. The signature aggregator will call the PBHValidator for each UserOp included in the bundle, verifying the associated proof. -Foundry consists of: +The `PBHValidator` contract will extract the proof data from the signature, validate proof inputs and verify the proof. The `signal` for the proof will consist of the `userOpHash`. Upon successful verification of the proof, the `PBHValidator` will bump the PBH nonce for the `nullifierHash` associated with the proof. The PBH nonce is used to ensure that a given World ID user does not use more than `n` transactions a month. -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. - -## Documentation - -https://book.getfoundry.sh/ - -## Usage - -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +If the UserOp successfully clears all of these checks, a `PBH` event will be emitted indicating to the builder that this UserOp is a valid PBH user operation. The builder will only consider a “PBH” bundle for priority inclusion if all UserOps in the bundle emit a PBH event, and `aggregator` is specified as the `PBHSignatureAggregator`. diff --git a/pbh-verifier/src/Counter.sol b/pbh-verifier/src/Counter.sol index aded799..057e61c 100644 --- a/pbh-verifier/src/Counter.sol +++ b/pbh-verifier/src/Counter.sol @@ -1,14 +1,31 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -contract Counter { - uint256 public number; +contract PBHValidator { - function setNumber(uint256 newNumber) public { - number = newNumber; - } + mapping (externalNullifier bytes32 => pbhNonce uint16) pbhNonces; - function increment() public { - number++; - } + function validateUserOp(PackedUserOperation calldata userOp) + external view override { + // Decode proof from signature + (Proof proof, _) = abi.decode(userOp.signature, (Proof, bytes)); + + // Validate proof inputs + // --snip-- + + // Verify proof + worldIdIdentityManager.verifyProof(proof); + + // Bump PBH nonce + pbhNonce = pbhNonces[proof.nullifierHash] + 1; + require(pbhNonce <= pbhNonceLimit); + pbhNonces[proof.nullifierHash] = pbhNonce; + + // Emit PBH event after successful verification + // The builder will prioritize bundles where every userop + // in the bundle emits a PBH event + emit PBH(proof.nullifierHash, userOpHash); + + } + } } From 42a210d60999f069a1259b0c4279677512ff6f10 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Mon, 9 Dec 2024 23:23:11 -0800 Subject: [PATCH 04/28] forge install: world-id-contracts v1.0.0 --- .gitmodules | 3 +++ pbh-verifier/lib/world-id-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 pbh-verifier/lib/world-id-contracts diff --git a/.gitmodules b/.gitmodules index 443492a..920a5c5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "pbh-verifier/lib/forge-std"] path = pbh-verifier/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "pbh-verifier/lib/world-id-contracts"] + path = pbh-verifier/lib/world-id-contracts + url = https://github.com/worldcoin/world-id-contracts diff --git a/pbh-verifier/lib/world-id-contracts b/pbh-verifier/lib/world-id-contracts new file mode 160000 index 0000000..f41cd61 --- /dev/null +++ b/pbh-verifier/lib/world-id-contracts @@ -0,0 +1 @@ +Subproject commit f41cd618cadec07a5bc5b66e608df4bc13ffbb54 From a4df74063e8e1be6779cf952964a1de48cc1776b Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Mon, 9 Dec 2024 23:32:23 -0800 Subject: [PATCH 05/28] forge install: semaphore-v3 v3.2.3 --- .gitmodules | 3 +++ pbh-verifier/lib/semaphore-v3 | 1 + 2 files changed, 4 insertions(+) create mode 160000 pbh-verifier/lib/semaphore-v3 diff --git a/.gitmodules b/.gitmodules index 920a5c5..ab6b916 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "pbh-verifier/lib/world-id-contracts"] path = pbh-verifier/lib/world-id-contracts url = https://github.com/worldcoin/world-id-contracts +[submodule "pbh-verifier/lib/semaphore-v3"] + path = pbh-verifier/lib/semaphore-v3 + url = https://github.com/worldcoin/semaphore-v3 diff --git a/pbh-verifier/lib/semaphore-v3 b/pbh-verifier/lib/semaphore-v3 new file mode 160000 index 0000000..59d2d13 --- /dev/null +++ b/pbh-verifier/lib/semaphore-v3 @@ -0,0 +1 @@ +Subproject commit 59d2d1367ce6bea3f7f606c993a4dd044cd8c78b From a51e13da515cdee7694aed1ec740d360bce5a805 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 02:07:17 -0800 Subject: [PATCH 06/28] WIP --- pbh-verifier/remappings.txt | 1 + .../{Counter.s.sol => Counter.s.sol.bak} | 0 pbh-verifier/src/Counter.sol | 31 ----- pbh-verifier/src/PBHVerifier.sol | 130 ++++++++++++++++++ pbh-verifier/src/helpers/ByteHasher.sol | 12 ++ .../{Counter.t.sol => PBHVerifier.t.sol.bak} | 6 +- 6 files changed, 146 insertions(+), 34 deletions(-) create mode 100644 pbh-verifier/remappings.txt rename pbh-verifier/script/{Counter.s.sol => Counter.s.sol.bak} (100%) delete mode 100644 pbh-verifier/src/Counter.sol create mode 100644 pbh-verifier/src/PBHVerifier.sol create mode 100644 pbh-verifier/src/helpers/ByteHasher.sol rename pbh-verifier/test/{Counter.t.sol => PBHVerifier.t.sol.bak} (79%) diff --git a/pbh-verifier/remappings.txt b/pbh-verifier/remappings.txt new file mode 100644 index 0000000..966c24c --- /dev/null +++ b/pbh-verifier/remappings.txt @@ -0,0 +1 @@ +@world-id-contracts/=lib/world-id-contracts/src/ diff --git a/pbh-verifier/script/Counter.s.sol b/pbh-verifier/script/Counter.s.sol.bak similarity index 100% rename from pbh-verifier/script/Counter.s.sol rename to pbh-verifier/script/Counter.s.sol.bak diff --git a/pbh-verifier/src/Counter.sol b/pbh-verifier/src/Counter.sol deleted file mode 100644 index 057e61c..0000000 --- a/pbh-verifier/src/Counter.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract PBHValidator { - - mapping (externalNullifier bytes32 => pbhNonce uint16) pbhNonces; - - function validateUserOp(PackedUserOperation calldata userOp) - external view override { - // Decode proof from signature - (Proof proof, _) = abi.decode(userOp.signature, (Proof, bytes)); - - // Validate proof inputs - // --snip-- - - // Verify proof - worldIdIdentityManager.verifyProof(proof); - - // Bump PBH nonce - pbhNonce = pbhNonces[proof.nullifierHash] + 1; - require(pbhNonce <= pbhNonceLimit); - pbhNonces[proof.nullifierHash] = pbhNonce; - - // Emit PBH event after successful verification - // The builder will prioritize bundles where every userop - // in the bundle emits a PBH event - emit PBH(proof.nullifierHash, userOpHash); - - } - } -} diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol new file mode 100644 index 0000000..730c0e8 --- /dev/null +++ b/pbh-verifier/src/PBHVerifier.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {ByteHasher} from "./helpers/ByteHasher.sol"; +import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; + +contract PBHVerifier { + using ByteHasher for bytes; + + /////////////////////////////////////////////////////////////////////////////// + /// ERRORS /// + ////////////////////////////////////////////////////////////////////////////// + + /// @notice Thrown when attempting to reuse a nullifier + error InvalidNullifier(); + + /** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ + struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; + } + + struct PBHPayload { + uint256 root; + uint256 groupId; + uint256 signalHash; + uint256 nullifierHash; + uint256 externalNullifierHash; + uint256[8] proof; + } + + /// @dev The World ID instance that will be used for verifying proofs + IWorldIDGroups internal immutable worldId; + + /// @dev The contract's external nullifier hash + uint256 internal immutable externalNullifier; + + /// @dev The World ID group ID (always 1) + 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 WorldID instance that will verify the proofs + /// @param _appId The World ID app ID + /// @param _actionId The World ID action ID + constructor( + IWorldIDGroups _worldId, + string memory _appId, + string memory _actionId + ) { + worldId = _worldId; + externalNullifier = abi + .encodePacked(abi.encodePacked(_appId).hashToField(), _actionId) + .hashToField(); + } + + /// @param signal An arbitrary input from the user, usually the user's wallet address (check README for further details) + /// @param root The root of the Merkle tree (returned by the JS widget). + /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). + /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). + /// @dev Feel free to rename this method however you want! We've used `claim`, `verify` or `execute` in the past. + function verifyAndExecute( + address signal, + uint256 root, + uint256 nullifierHash, + uint256[8] calldata proof + ) public { + // First, we make sure this person hasn't done this before + if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); + + // We now verify the provided proof is valid and the user is verified by World ID + worldId.verifyProof( + root, + groupId, + abi.encodePacked(signal).hashToField(), + nullifierHash, + externalNullifier, + proof + ); + + // We now record the user has done this, so they can't do it again (proof of uniqueness) + nullifierHashes[nullifierHash] = true; + + // Finally, execute your logic here, for example issue a token, NFT, etc... + // Make sure to emit some kind of event afterwards! + } + + function validateUserOp(PackedUserOperation calldata userOp) external view override { + // Decode proof from signature + (Proof proof, ) = abi.decode(userOp.signature, (Proof, bytes)); + + // Validate proof inputs + // --snip-- + + // Verify proof + worldIdIdentityManager.verifyProof(proof); + + // Bump PBH nonce + pbhNonce = pbhNonces[proof.nullifierHash] + 1; + require(pbhNonce <= pbhNonceLimit); + pbhNonces[proof.nullifierHash] = pbhNonce; + + // Emit PBH event after successful verification + // The builder will prioritize bundles where every userop + // in the bundle emits a PBH event + emit PBH(proof.nullifierHash, userOpHash); + } +} + diff --git a/pbh-verifier/src/helpers/ByteHasher.sol b/pbh-verifier/src/helpers/ByteHasher.sol new file mode 100644 index 0000000..f1e0b22 --- /dev/null +++ b/pbh-verifier/src/helpers/ByteHasher.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +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; + } +} diff --git a/pbh-verifier/test/Counter.t.sol b/pbh-verifier/test/PBHVerifier.t.sol.bak similarity index 79% rename from pbh-verifier/test/Counter.t.sol rename to pbh-verifier/test/PBHVerifier.t.sol.bak index 54b724f..e30a19c 100644 --- a/pbh-verifier/test/Counter.t.sol +++ b/pbh-verifier/test/PBHVerifier.t.sol.bak @@ -2,10 +2,10 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; +import {PBHVerifier} from "../src/PBHVerifier.sol"; -contract CounterTest is Test { - Counter public counter; +contract PBHVerifierTest is Test { + PBHVerifier public counter; function setUp() public { counter = new Counter(); From ee0126f988ca5083e91773952ed6514bec4f6473 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 02:29:11 -0800 Subject: [PATCH 07/28] wip --- pbh-verifier/src/PBHVerifier.sol | 35 +++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 730c0e8..4e0fc06 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -14,6 +14,13 @@ contract PBHVerifier { /// @notice Thrown when attempting to reuse a nullifier error InvalidNullifier(); + /// @notice Emitted when a verifier is updated in the lookup table. + /// + /// @param nullifierHash The nullifier hash that was used. + event PBH( + uint256 indexed nullifierHash + ); + /** * User Operation struct * @param sender - The sender account of this request. @@ -45,6 +52,7 @@ contract PBHVerifier { uint256 groupId; uint256 signalHash; uint256 nullifierHash; + // uint256 externalNullifierHash; uint256 externalNullifierHash; uint256[8] proof; } @@ -74,6 +82,11 @@ contract PBHVerifier { .encodePacked(abi.encodePacked(_appId).hashToField(), _actionId) .hashToField(); } + + function getCalldataHash() public view returns (uint256) { + // Get the keccak256 hash of the calldata + return ByteHasher.hashToField(msg.data); + } /// @param signal An arbitrary input from the user, usually the user's wallet address (check README for further details) /// @param root The root of the Merkle tree (returned by the JS widget). @@ -106,20 +119,18 @@ contract PBHVerifier { // Make sure to emit some kind of event afterwards! } - function validateUserOp(PackedUserOperation calldata userOp) external view override { + function validateUserOp(uint256 root, PackedUserOperation calldata userOp) external view override { // Decode proof from signature - (Proof proof, ) = abi.decode(userOp.signature, (Proof, bytes)); + (PBHPayload pbhPayload, ) = abi.decode(userOp.signature, (PBHPayload, bytes)); + // TODO: Decide what the signal should contain + signal = abi.encodePacked(userOp.callData).hashToField(); - // Validate proof inputs - // --snip-- - - // Verify proof - worldIdIdentityManager.verifyProof(proof); - - // Bump PBH nonce - pbhNonce = pbhNonces[proof.nullifierHash] + 1; - require(pbhNonce <= pbhNonceLimit); - pbhNonces[proof.nullifierHash] = pbhNonce; + verifyAndExecute( + userOp.callData, + root, + pbhPayload.nullifierHash, + pbhPayload.proof + ); // Emit PBH event after successful verification // The builder will prioritize bundles where every userop From bf756b0b6ebd3658798ee30c7eb02f494be8cd8d Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 02:38:25 -0800 Subject: [PATCH 08/28] wip --- pbh-verifier/src/PBHVerifier.sol | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 4e0fc06..3e3516f 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -119,23 +119,19 @@ contract PBHVerifier { // Make sure to emit some kind of event afterwards! } - function validateUserOp(uint256 root, PackedUserOperation calldata userOp) external view override { + function validateUserOp(PackedUserOperation calldata userOp) external view { // Decode proof from signature - (PBHPayload pbhPayload, ) = abi.decode(userOp.signature, (PBHPayload, bytes)); - // TODO: Decide what the signal should contain - signal = abi.encodePacked(userOp.callData).hashToField(); + (PBHPayload memory pbhPayload, ) = abi.decode(userOp.signature, (PBHPayload, bytes)); + // TODO: Decide what the signal should contain verifyAndExecute( userOp.callData, - root, + pbhPayload.root, pbhPayload.nullifierHash, pbhPayload.proof ); - // Emit PBH event after successful verification - // The builder will prioritize bundles where every userop - // in the bundle emits a PBH event - emit PBH(proof.nullifierHash, userOpHash); + emit PBH(pbhPayload.nullifierHash); } } From c4c32039d906f96c55bc5dc00d619e8debe03768 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 02:38:32 -0800 Subject: [PATCH 09/28] forge install: account-abstraction v0.7.0 --- .gitmodules | 3 +++ pbh-verifier/lib/account-abstraction | 1 + 2 files changed, 4 insertions(+) create mode 160000 pbh-verifier/lib/account-abstraction diff --git a/.gitmodules b/.gitmodules index ab6b916..c9f0876 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "pbh-verifier/lib/semaphore-v3"] path = pbh-verifier/lib/semaphore-v3 url = https://github.com/worldcoin/semaphore-v3 +[submodule "pbh-verifier/lib/account-abstraction"] + path = pbh-verifier/lib/account-abstraction + url = https://github.com/eth-infinitism/account-abstraction diff --git a/pbh-verifier/lib/account-abstraction b/pbh-verifier/lib/account-abstraction new file mode 160000 index 0000000..7af70c8 --- /dev/null +++ b/pbh-verifier/lib/account-abstraction @@ -0,0 +1 @@ +Subproject commit 7af70c8993a6f42973f520ae0752386a5032abe7 From 938bac08b77396c8f47c0d1f874c0a5a3fa9c390 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 03:09:04 -0800 Subject: [PATCH 10/28] wip --- pbh-verifier/remappings.txt | 2 ++ pbh-verifier/src/PBHSignatureAggregator.sol | 22 +++++++++++++++++++++ pbh-verifier/src/PBHVerifier.sol | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 pbh-verifier/src/PBHSignatureAggregator.sol diff --git a/pbh-verifier/remappings.txt b/pbh-verifier/remappings.txt index 966c24c..1b35a04 100644 --- a/pbh-verifier/remappings.txt +++ b/pbh-verifier/remappings.txt @@ -1 +1,3 @@ @world-id-contracts/=lib/world-id-contracts/src/ +@account-abstraction/=lib/account-abstraction/contracts/ + diff --git a/pbh-verifier/src/PBHSignatureAggregator.sol b/pbh-verifier/src/PBHSignatureAggregator.sol new file mode 100644 index 0000000..10aa596 --- /dev/null +++ b/pbh-verifier/src/PBHSignatureAggregator.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {ByteHasher} from "./helpers/ByteHasher.sol"; +import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; +import {BaseAccount} from "@account-abstraction/core/BaseAccount.sol"; +import {BLSSignatureAggregator} from "@account-abstraction/samples/bls/BLSSignatureAggregator.sol"; + +contract PBHSignatureAggregator is BLSSignatureAggregator { + + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) + external view override { + super.validateSignatures(userOps, signature) + + for (uint256 i = 0; i < userOpsLen; i++) { + PackedUserOperation memory userOp = userOps[i]; + + pbhValidator.validateUserOp(userOp); + + } + } +} diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 3e3516f..a682d2c 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.13; import {ByteHasher} from "./helpers/ByteHasher.sol"; import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; +import {BaseAccount} from "@account-abstraction/core/BaseAccount.sol"; -contract PBHVerifier { +contract PBHVerifier is BaseAccount { using ByteHasher for bytes; /////////////////////////////////////////////////////////////////////////////// From 8d4469cc10b952f17da9688a2107f97341d96508 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 16:13:03 -0800 Subject: [PATCH 11/28] wip --- pbh-verifier/src/PBHSignatureAggregator.sol | 22 -------------- pbh-verifier/src/PBHVerifier.sol | 32 ++++++++------------- 2 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 pbh-verifier/src/PBHSignatureAggregator.sol diff --git a/pbh-verifier/src/PBHSignatureAggregator.sol b/pbh-verifier/src/PBHSignatureAggregator.sol deleted file mode 100644 index 10aa596..0000000 --- a/pbh-verifier/src/PBHSignatureAggregator.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import {ByteHasher} from "./helpers/ByteHasher.sol"; -import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; -import {BaseAccount} from "@account-abstraction/core/BaseAccount.sol"; -import {BLSSignatureAggregator} from "@account-abstraction/samples/bls/BLSSignatureAggregator.sol"; - -contract PBHSignatureAggregator is BLSSignatureAggregator { - - function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) - external view override { - super.validateSignatures(userOps, signature) - - for (uint256 i = 0; i < userOpsLen; i++) { - PackedUserOperation memory userOp = userOps[i]; - - pbhValidator.validateUserOp(userOp); - - } - } -} diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index a682d2c..013c604 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -89,25 +89,31 @@ contract PBHVerifier is BaseAccount { return ByteHasher.hashToField(msg.data); } - /// @param signal An arbitrary input from the user, usually the user's wallet address (check README for further details) + /// @param userOp A packed user operation, used to generate the signal hash /// @param root The root of the Merkle tree (returned by the JS widget). /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). - /// @dev Feel free to rename this method however you want! We've used `claim`, `verify` or `execute` in the past. - function verifyAndExecute( - address signal, + function verifyProof( + PackedUserOperation memory userOp, uint256 root, uint256 nullifierHash, - uint256[8] calldata proof + uint256[8] memory proof ) public { // First, we make sure this person hasn't done this before if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); + // We now generate the signal hash from the sender, nonce, and calldata + uint256 signalHash = abi.encodePacked( + userOp.sender, + userOp.nonce, + userOp.callData + ).hashToField(); + // We now verify the provided proof is valid and the user is verified by World ID worldId.verifyProof( root, groupId, - abi.encodePacked(signal).hashToField(), + signalHash, nullifierHash, externalNullifier, proof @@ -118,20 +124,6 @@ contract PBHVerifier is BaseAccount { // Finally, execute your logic here, for example issue a token, NFT, etc... // Make sure to emit some kind of event afterwards! - } - - function validateUserOp(PackedUserOperation calldata userOp) external view { - // Decode proof from signature - (PBHPayload memory pbhPayload, ) = abi.decode(userOp.signature, (PBHPayload, bytes)); - - // TODO: Decide what the signal should contain - verifyAndExecute( - userOp.callData, - pbhPayload.root, - pbhPayload.nullifierHash, - pbhPayload.proof - ); - emit PBH(pbhPayload.nullifierHash); } } From 2da172da18d4b593188be5f2c4469be025c6941a Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 16:13:09 -0800 Subject: [PATCH 12/28] forge install: BokkyPooBahsDateTimeLibrary --- .gitmodules | 3 +++ pbh-verifier/lib/BokkyPooBahsDateTimeLibrary | 1 + 2 files changed, 4 insertions(+) create mode 160000 pbh-verifier/lib/BokkyPooBahsDateTimeLibrary diff --git a/.gitmodules b/.gitmodules index c9f0876..acc50dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "pbh-verifier/lib/account-abstraction"] path = pbh-verifier/lib/account-abstraction url = https://github.com/eth-infinitism/account-abstraction +[submodule "pbh-verifier/lib/BokkyPooBahsDateTimeLibrary"] + path = pbh-verifier/lib/BokkyPooBahsDateTimeLibrary + url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary diff --git a/pbh-verifier/lib/BokkyPooBahsDateTimeLibrary b/pbh-verifier/lib/BokkyPooBahsDateTimeLibrary new file mode 160000 index 0000000..1dc26f9 --- /dev/null +++ b/pbh-verifier/lib/BokkyPooBahsDateTimeLibrary @@ -0,0 +1 @@ +Subproject commit 1dc26f977c57a6ba3ed6d7c53cafdb191e7e59ae From 990ae18eac95cae31f799342fb577f1e21e3cb90 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 17:43:36 -0800 Subject: [PATCH 13/28] cleanup --- pbh-verifier/foundry.toml | 3 +- pbh-verifier/lib/account-abstraction | 2 +- pbh-verifier/lib/forge-std | 2 +- pbh-verifier/lib/semaphore-v3 | 2 +- pbh-verifier/lib/world-id-contracts | 2 +- pbh-verifier/remappings.txt | 2 +- pbh-verifier/src/PBHVerifier.sol | 88 ++++++++++++++++++++-------- 7 files changed, 68 insertions(+), 33 deletions(-) diff --git a/pbh-verifier/foundry.toml b/pbh-verifier/foundry.toml index 25b918f..2b2473a 100644 --- a/pbh-verifier/foundry.toml +++ b/pbh-verifier/foundry.toml @@ -2,5 +2,4 @@ src = "src" out = "out" libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +via_ir = true \ No newline at end of file diff --git a/pbh-verifier/lib/account-abstraction b/pbh-verifier/lib/account-abstraction index 7af70c8..6f02f5a 160000 --- a/pbh-verifier/lib/account-abstraction +++ b/pbh-verifier/lib/account-abstraction @@ -1 +1 @@ -Subproject commit 7af70c8993a6f42973f520ae0752386a5032abe7 +Subproject commit 6f02f5a28a20e804d0410b4b5b570dd4b076dcf9 diff --git a/pbh-verifier/lib/forge-std b/pbh-verifier/lib/forge-std index 1eea5ba..d3db4ef 160000 --- a/pbh-verifier/lib/forge-std +++ b/pbh-verifier/lib/forge-std @@ -1 +1 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 +Subproject commit d3db4ef90a72b7d24aa5a2e5c649593eaef7801d diff --git a/pbh-verifier/lib/semaphore-v3 b/pbh-verifier/lib/semaphore-v3 index 59d2d13..1a3d901 160000 --- a/pbh-verifier/lib/semaphore-v3 +++ b/pbh-verifier/lib/semaphore-v3 @@ -1 +1 @@ -Subproject commit 59d2d1367ce6bea3f7f606c993a4dd044cd8c78b +Subproject commit 1a3d9019a77948f55b1aaa7fe7ac98ddf20dc30a diff --git a/pbh-verifier/lib/world-id-contracts b/pbh-verifier/lib/world-id-contracts index f41cd61..5835348 160000 --- a/pbh-verifier/lib/world-id-contracts +++ b/pbh-verifier/lib/world-id-contracts @@ -1 +1 @@ -Subproject commit f41cd618cadec07a5bc5b66e608df4bc13ffbb54 +Subproject commit 5835348737a699c3704a12adb298ce10c9e513f5 diff --git a/pbh-verifier/remappings.txt b/pbh-verifier/remappings.txt index 1b35a04..0569c5f 100644 --- a/pbh-verifier/remappings.txt +++ b/pbh-verifier/remappings.txt @@ -1,3 +1,3 @@ @world-id-contracts/=lib/world-id-contracts/src/ @account-abstraction/=lib/account-abstraction/contracts/ - +@BokkyPooBahsDateTimeLibrary/=lib/BokkyPooBahsDateTimeLibrary/contracts/ diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 013c604..289cc9d 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.20; import {ByteHasher} from "./helpers/ByteHasher.sol"; import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; import {BaseAccount} from "@account-abstraction/core/BaseAccount.sol"; +import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; -contract PBHVerifier is BaseAccount { +contract PBHVerifier { using ByteHasher for bytes; /////////////////////////////////////////////////////////////////////////////// @@ -15,6 +16,22 @@ contract PBHVerifier is BaseAccount { /// @notice Thrown when attempting to reuse a nullifier error InvalidNullifier(); + /// @notice Thrown when the provided external nullifier year doesn't + /// match the current year + error InvalidExternalNullifierYear(); + + /// @notice Thrown when the provided external nullifier month doesn't + /// match the current month + error InvalidExternalNullifierMonth(); + + /// @notice Thrown when the provided external + /// nullifier pbhNonce >= numPbhPerMonth + error InvalidPbhNonce(); + + /////////////////////////////////////////////////////////////////////////////// + /// Events /// + ////////////////////////////////////////////////////////////////////////////// + /// @notice Emitted when a verifier is updated in the lookup table. /// /// @param nullifierHash The nullifier hash that was used. @@ -22,6 +39,10 @@ contract PBHVerifier is BaseAccount { uint256 indexed nullifierHash ); + /////////////////////////////////////////////////////////////////////////////// + /// Structs /// + ////////////////////////////////////////////////////////////////////////////// + /** * User Operation struct * @param sender - The sender account of this request. @@ -50,55 +71,67 @@ contract PBHVerifier is BaseAccount { struct PBHPayload { uint256 root; - uint256 groupId; - uint256 signalHash; uint256 nullifierHash; - // uint256 externalNullifierHash; - uint256 externalNullifierHash; + ExternalNullifier externalNullifier; uint256[8] proof; } + /** + * External Nullifier struct + * @param pbhNonce - A nonce between 0 and numPbhPerMonth. + * @param month - An integer representing the current month. + * @param year - An integer representing the current year. + */ + struct ExternalNullifier { + uint8 pbhNonce; + uint16 month; + uint8 year; + } + + /////////////////////////////////////////////////////////////////////////////// + /// Vars /// + ////////////////////////////////////////////////////////////////////////////// + /// @dev The World ID instance that will be used for verifying proofs IWorldIDGroups internal immutable worldId; - /// @dev The contract's external nullifier hash - uint256 internal immutable externalNullifier; - /// @dev The World ID group ID (always 1) uint256 internal immutable groupId = 1; + + /// @dev Make this configurable + uint256 internal immutable numPbhPerMonth = 30; /// @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; + + /////////////////////////////////////////////////////////////////////////////// + /// Functions /// + ////////////////////////////////////////////////////////////////////////////// /// @param _worldId The WorldID instance that will verify the proofs - /// @param _appId The World ID app ID - /// @param _actionId The World ID action ID constructor( - IWorldIDGroups _worldId, - string memory _appId, - string memory _actionId + IWorldIDGroups _worldId ) { worldId = _worldId; - externalNullifier = abi - .encodePacked(abi.encodePacked(_appId).hashToField(), _actionId) - .hashToField(); } - function getCalldataHash() public view returns (uint256) { - // Get the keccak256 hash of the calldata - return ByteHasher.hashToField(msg.data); + function verifyExternalNullifier(ExternalNullifier memory externalNullifer) public view { + require(externalNullifer.year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); + require(externalNullifer.month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); + require(externalNullifer.pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); } /// @param userOp A packed user operation, used to generate the signal hash /// @param root The root of the Merkle tree (returned by the JS widget). /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). - function verifyProof( + function verifyPbhProof( PackedUserOperation memory userOp, uint256 root, uint256 nullifierHash, + ExternalNullifier memory externalNullifier, uint256[8] memory proof - ) public { + ) external { // First, we make sure this person hasn't done this before if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); @@ -109,22 +142,25 @@ contract PBHVerifier is BaseAccount { userOp.callData ).hashToField(); + // Verify the external nullifier + verifyExternalNullifier(externalNullifier); + + uint256 externalNullifierHash = abi.encode(externalNullifier).hashToField(); + // We now verify the provided proof is valid and the user is verified by World ID worldId.verifyProof( root, groupId, signalHash, nullifierHash, - externalNullifier, + externalNullifierHash, proof ); // We now record the user has done this, so they can't do it again (proof of uniqueness) nullifierHashes[nullifierHash] = true; - // Finally, execute your logic here, for example issue a token, NFT, etc... - // Make sure to emit some kind of event afterwards! - emit PBH(pbhPayload.nullifierHash); + emit PBH(nullifierHash); } } From 28aa3ce56912d11e3de31d5ee383a0186bf842cb Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 10 Dec 2024 17:57:27 -0800 Subject: [PATCH 14/28] cleanup --- .gitmodules | 6 ------ pbh-verifier/lib/account-abstraction | 1 - pbh-verifier/lib/semaphore-v3 | 1 - pbh-verifier/src/PBHVerifier.sol | 1 - pbh-verifier/test/PBHVerifier.t.sol | 16 ++++++++++++++++ pbh-verifier/test/PBHVerifier.t.sol.bak | 24 ------------------------ 6 files changed, 16 insertions(+), 33 deletions(-) delete mode 160000 pbh-verifier/lib/account-abstraction delete mode 160000 pbh-verifier/lib/semaphore-v3 create mode 100644 pbh-verifier/test/PBHVerifier.t.sol delete mode 100644 pbh-verifier/test/PBHVerifier.t.sol.bak diff --git a/.gitmodules b/.gitmodules index acc50dc..f921d27 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,12 +4,6 @@ [submodule "pbh-verifier/lib/world-id-contracts"] path = pbh-verifier/lib/world-id-contracts url = https://github.com/worldcoin/world-id-contracts -[submodule "pbh-verifier/lib/semaphore-v3"] - path = pbh-verifier/lib/semaphore-v3 - url = https://github.com/worldcoin/semaphore-v3 -[submodule "pbh-verifier/lib/account-abstraction"] - path = pbh-verifier/lib/account-abstraction - url = https://github.com/eth-infinitism/account-abstraction [submodule "pbh-verifier/lib/BokkyPooBahsDateTimeLibrary"] path = pbh-verifier/lib/BokkyPooBahsDateTimeLibrary url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary diff --git a/pbh-verifier/lib/account-abstraction b/pbh-verifier/lib/account-abstraction deleted file mode 160000 index 6f02f5a..0000000 --- a/pbh-verifier/lib/account-abstraction +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6f02f5a28a20e804d0410b4b5b570dd4b076dcf9 diff --git a/pbh-verifier/lib/semaphore-v3 b/pbh-verifier/lib/semaphore-v3 deleted file mode 160000 index 1a3d901..0000000 --- a/pbh-verifier/lib/semaphore-v3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1a3d9019a77948f55b1aaa7fe7ac98ddf20dc30a diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 289cc9d..1739656 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.20; import {ByteHasher} from "./helpers/ByteHasher.sol"; import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; -import {BaseAccount} from "@account-abstraction/core/BaseAccount.sol"; import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; contract PBHVerifier { diff --git a/pbh-verifier/test/PBHVerifier.t.sol b/pbh-verifier/test/PBHVerifier.t.sol new file mode 100644 index 0000000..c5f3b9b --- /dev/null +++ b/pbh-verifier/test/PBHVerifier.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {PBHVerifier} from "../src/PBHVerifier.sol"; + +contract PBHVerifierTest is Test { + PBHVerifier public pbhVerifier; + + function setUp() public { + // pbhVerifier = new PBHVerifier(); + } + + function testTest() public { + } +} diff --git a/pbh-verifier/test/PBHVerifier.t.sol.bak b/pbh-verifier/test/PBHVerifier.t.sol.bak deleted file mode 100644 index e30a19c..0000000 --- a/pbh-verifier/test/PBHVerifier.t.sol.bak +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {PBHVerifier} from "../src/PBHVerifier.sol"; - -contract PBHVerifierTest is Test { - PBHVerifier public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From 35d1a28bbf2fe4c8d60c68eccaaeeb2f55196316 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 14:49:35 -0800 Subject: [PATCH 15/28] wip --- pbh-verifier/remappings.txt | 1 + pbh-verifier/src/PBHVerifier.sol | 33 +++--- .../src/helpers/PBHExternalNullifierLib.sol | 66 +++++++++++ pbh-verifier/test/PBHExternalNullifier.sol | 108 ++++++++++++++++++ 4 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 pbh-verifier/src/helpers/PBHExternalNullifierLib.sol create mode 100644 pbh-verifier/test/PBHExternalNullifier.sol diff --git a/pbh-verifier/remappings.txt b/pbh-verifier/remappings.txt index 0569c5f..802e37b 100644 --- a/pbh-verifier/remappings.txt +++ b/pbh-verifier/remappings.txt @@ -1,3 +1,4 @@ @world-id-contracts/=lib/world-id-contracts/src/ @account-abstraction/=lib/account-abstraction/contracts/ @BokkyPooBahsDateTimeLibrary/=lib/BokkyPooBahsDateTimeLibrary/contracts/ +@helpers/=src/helpers/ diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 1739656..c0ff3d5 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -15,17 +15,7 @@ contract PBHVerifier { /// @notice Thrown when attempting to reuse a nullifier error InvalidNullifier(); - /// @notice Thrown when the provided external nullifier year doesn't - /// match the current year - error InvalidExternalNullifierYear(); - - /// @notice Thrown when the provided external nullifier month doesn't - /// match the current month - error InvalidExternalNullifierMonth(); - - /// @notice Thrown when the provided external - /// nullifier pbhNonce >= numPbhPerMonth - error InvalidPbhNonce(); + /////////////////////////////////////////////////////////////////////////////// /// Events /// @@ -113,12 +103,29 @@ contract PBHVerifier { ) { worldId = _worldId; } + + function get(ExternalNullifier memory externalNullifer) public view { + require(externalNullifer.year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); + require(externalNullifer.month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); + require(externalNullifer.pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); + } - function verifyExternalNullifier(ExternalNullifier memory externalNullifer) public view { + /// @param externalNullifer The external nullifer is used to ensure that each pbhNonce can be used only once per month. + /// The value is encoded by bit shifting 3 uint8s corresponding to + /// * pbhNonce - An 8 bit nonce between 0 and numPbhPerMonth. + /// * month - An 8 bit value representing the current month. + /// * year - A 16 bit value representing the current year. + /// [0][0][0][0][0][0][0][0] [0][0][0][0][0][0][0][0] [0][0][0][0][0][0][0][0] [0][0][0][0][pbhNonce][month][yearA][yearB] + function verifyExternalNullifier(uint256 externalNullifer) public view { require(externalNullifer.year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); require(externalNullifer.month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); require(externalNullifer.pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); } + function encodeExternalNullifier(uint8 pbhNonce, uint8 month, uint16 year) public pure returns (uint32) { + require(month >= 1 && month <= 12, "Invalid month"); + require(year <= 9999, "Invalid year"); + return (uint32(year) << 16) | (uint32(month) << 8) | uint32(pbhNonce); + } /// @param userOp A packed user operation, used to generate the signal hash /// @param root The root of the Merkle tree (returned by the JS widget). @@ -128,7 +135,7 @@ contract PBHVerifier { PackedUserOperation memory userOp, uint256 root, uint256 nullifierHash, - ExternalNullifier memory externalNullifier, + uint256 externalNullifier, uint256[8] memory proof ) external { // First, we make sure this person hasn't done this before diff --git a/pbh-verifier/src/helpers/PBHExternalNullifierLib.sol b/pbh-verifier/src/helpers/PBHExternalNullifierLib.sol new file mode 100644 index 0000000..06a498c --- /dev/null +++ b/pbh-verifier/src/helpers/PBHExternalNullifierLib.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; + +/// @title PBHExternalNullifierLib +/// @notice Library for encoding, decoding, and verifying PBH external nullifiers. +/// External nullifiers are used to uniquely identify actions or events +/// within a specific year and month using a nonce. +/// @dev Utilizes `PBHExternalNullifier` as a custom type for encoded nullifiers. +/// @dev The encoding format is as follows: +/// - Bits 32-255: Empty +/// - Bits 16-31: Year +/// - Bits 8-15: Month +/// - Bits 0-7: Nonce +type PBHExternalNullifier is uint256; + +library PBHExternalNulliferLib { + /// @notice Thrown when the provided external nullifier year doesn't + /// match the current year + error InvalidExternalNullifierYear(); + + /// @notice Thrown when the provided external nullifier month doesn't + /// match the current month + error InvalidExternalNullifierMonth(); + + /// @notice Thrown when the provided external + /// nullifier pbhNonce >= numPbhPerMonth + error InvalidPbhNonce(); + + /// @notice Encodes a PBH external nullifier using the provided year, month, and nonce. + /// @param pbhNonce An 8-bit nonce value (0-255) used to uniquely identify the nullifier within a month. + /// @param month An 8-bit 1-indexed value representing the month (1-12). + /// @param year A 16-bit value representing the year (e.g., 2024). + /// @return The encoded PBHExternalNullifier. + function encode(uint8 pbhNonce, uint8 month, uint16 year) internal pure returns (PBHExternalNullifier) { + require(month > 0 && month < 13, InvalidExternalNullifierMonth()); + require(year <= 9999, InvalidExternalNullifierYear()); + uint256 encoded = (uint32(year) << 16) | (uint32(month) << 8) | uint32(pbhNonce); + return PBHExternalNullifier.wrap(encoded); + } + + /// @notice Decodes an encoded PBHExternalNullifier into its constituent components. + /// @param externalNullifier The encoded external nullifier to decode. + /// @return pbhNonce The 8-bit nonce extracted from the external nullifier. + /// @return month The 8-bit month extracted from the external nullifier. + /// @return year The 16-bit year extracted from the external nullifier. + function decode(PBHExternalNullifier externalNullifier) internal pure returns (uint8 pbhNonce, uint8 month, uint16 year) { + uint256 encoded = PBHExternalNullifier.unwrap(externalNullifier); + year = uint16(encoded >> 16); + month = uint8((encoded >> 8) & 0xFF); + pbhNonce = uint8(encoded & 0xFF); + } + + /// @notice Verifies the validity of a PBHExternalNullifier by checking its components. + /// @param externalNullifier The external nullifier to verify. + /// @param numPbhPerMonth The maximum allowed value for the `pbhNonce` in the nullifier. + /// @dev This function ensures the external nullifier matches the current year and month, + /// and that the nonce does not exceed `numPbhPerMonth`. + function verify(PBHExternalNullifier externalNullifier, uint8 numPbhPerMonth) public view { + (uint8 pbhNonce, uint8 month, uint16 year) = PBHExternalNulliferLib.decode(externalNullifier); + require(year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); + require(month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); + require(pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); + } +} diff --git a/pbh-verifier/test/PBHExternalNullifier.sol b/pbh-verifier/test/PBHExternalNullifier.sol new file mode 100644 index 0000000..06e4e25 --- /dev/null +++ b/pbh-verifier/test/PBHExternalNullifier.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "forge-std/Test.sol"; +import "@helpers/PBHExternalNullifierLib.sol"; +import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; + +contract PBHExternalNullifierLibTest is Test { + using PBHExternalNulliferLib for PBHExternalNullifier; + + uint8 constant VALID_PBH_NONCE = 5; + uint8 constant VALID_MONTH = 12; + uint16 constant VALID_YEAR = 2024; + uint8 constant MAX_PBH_PER_MONTH = 10; + + function testEncodeDecodeValidInput() public pure { + // Arrange + uint8 pbhNonce = VALID_PBH_NONCE; + uint8 month = VALID_MONTH; + uint16 year = VALID_YEAR; + + // Act + PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(pbhNonce, month, year); + (uint8 decodedNonce, uint8 decodedMonth, uint16 decodedYear) = PBHExternalNulliferLib.decode(encoded); + + // Assert + assertEq(decodedNonce, pbhNonce, "Decoded nonce should match the original"); + assertEq(decodedMonth, month, "Decoded month should match the original"); + assertEq(decodedYear, year, "Decoded year should match the original"); + } + + function testEncodeInvalidMonth() public { + uint8 invalidMonth = 13; + + vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierMonth.selector); + PBHExternalNulliferLib.encode(VALID_PBH_NONCE, invalidMonth, VALID_YEAR); + } + + function testEncodeInvalidYear() public { + uint16 invalidYear = 10000; + + vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierYear.selector); + PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, invalidYear); + } + + function testVerifyValidExternalNullifier() public { + // Mock the current date to match VALID_YEAR and VALID_MONTH + uint256 timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime( + VALID_YEAR, + VALID_MONTH, + 1, + 0, + 0, + 0 + ); + vm.warp(timestamp); + + PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); + + // Act & Assert + PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + } + + function testVerifyInvalidYear() public { + uint256 currentTimestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime( + 2023, // Mock the year to 2023 + VALID_MONTH, + 1, + 0, + 0, + 0 + ); + vm.warp(currentTimestamp); + + PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); + + vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierYear.selector); + PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + } + + function testVerifyInvalidMonth() public { + uint256 currentTimestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime( + VALID_YEAR, + 11, // Mock the month to November + 1, + 0, + 0, + 0 + ); + vm.warp(currentTimestamp); + + PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); + + vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierMonth.selector); + PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + } + + function testVerifyInvalidPbhNonce() public { + PBHExternalNullifier encoded = PBHExternalNulliferLib.encode( + MAX_PBH_PER_MONTH + 1, // Invalid nonce exceeding max + VALID_MONTH, + VALID_YEAR + ); + + vm.expectRevert(PBHExternalNulliferLib.InvalidPbhNonce.selector); + PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + } +} \ No newline at end of file From 6ddd84566ca776ac8e19c7afdfe52da87f6fc1c1 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 15:29:39 -0800 Subject: [PATCH 16/28] wip --- pbh-verifier/src/PBHVerifier.sol | 81 +++++-------------- ...lifierLib.sol => PBHExternalNullifier.sol} | 22 +++-- 2 files changed, 27 insertions(+), 76 deletions(-) rename pbh-verifier/src/helpers/{PBHExternalNullifierLib.sol => PBHExternalNullifier.sol} (79%) diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index c0ff3d5..7600a06 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {ByteHasher} from "./helpers/ByteHasher.sol"; +import {PBHExternalNullifier} from "./helpers/PBHExternalNullifier.sol"; import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; @@ -14,8 +15,6 @@ contract PBHVerifier { /// @notice Thrown when attempting to reuse a nullifier error InvalidNullifier(); - - /////////////////////////////////////////////////////////////////////////////// /// Events /// @@ -32,32 +31,6 @@ contract PBHVerifier { /// Structs /// ////////////////////////////////////////////////////////////////////////////// - /** - * User Operation struct - * @param sender - The sender account of this request. - * @param nonce - Unique value the sender uses to verify it is not a replay. - * @param initCode - If set, the account contract will be created by this constructor/ - * @param callData - The method call to execute on this account. - * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. - * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. - * Covers batch overhead. - * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. - * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data - * The paymaster will pay for the transaction instead of the sender. - * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. - */ - struct PackedUserOperation { - address sender; - uint256 nonce; - bytes initCode; - bytes callData; - bytes32 accountGasLimits; - uint256 preVerificationGas; - bytes32 gasFees; - bytes paymasterAndData; - bytes signature; - } - struct PBHPayload { uint256 root; uint256 nullifierHash; @@ -88,7 +61,7 @@ contract PBHVerifier { uint256 internal immutable groupId = 1; /// @dev Make this configurable - uint256 internal immutable numPbhPerMonth = 30; + uint8 internal immutable numPbhPerMonth; /// @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; @@ -99,43 +72,26 @@ contract PBHVerifier { /// @param _worldId The WorldID instance that will verify the proofs constructor( - IWorldIDGroups _worldId + IWorldIDGroups _worldId, + uint8 _numPbhPerMonth ) { worldId = _worldId; + numPbhPerMonth = _numPbhPerMonth; } - function get(ExternalNullifier memory externalNullifer) public view { - require(externalNullifer.year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); - require(externalNullifer.month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); - require(externalNullifer.pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); - } - - /// @param externalNullifer The external nullifer is used to ensure that each pbhNonce can be used only once per month. - /// The value is encoded by bit shifting 3 uint8s corresponding to - /// * pbhNonce - An 8 bit nonce between 0 and numPbhPerMonth. - /// * month - An 8 bit value representing the current month. - /// * year - A 16 bit value representing the current year. - /// [0][0][0][0][0][0][0][0] [0][0][0][0][0][0][0][0] [0][0][0][0][0][0][0][0] [0][0][0][0][pbhNonce][month][yearA][yearB] - function verifyExternalNullifier(uint256 externalNullifer) public view { - require(externalNullifer.year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); - require(externalNullifer.month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); - require(externalNullifer.pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); - } - function encodeExternalNullifier(uint8 pbhNonce, uint8 month, uint16 year) public pure returns (uint32) { - require(month >= 1 && month <= 12, "Invalid month"); - require(year <= 9999, "Invalid year"); - return (uint32(year) << 16) | (uint32(month) << 8) | uint32(pbhNonce); - } - - /// @param userOp A packed user operation, used to generate the signal hash /// @param root The root of the Merkle tree (returned by the JS widget). + /// @param sender The root of the Merkle tree (returned by the JS widget). + /// @param nonce The root of the Merkle tree (returned by the JS widget). + /// @param callData The root of the Merkle tree (returned by the JS widget). /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). function verifyPbhProof( - PackedUserOperation memory userOp, uint256 root, + address sender, + uint256 nonce, + bytes memory callData, + uint256 pbhExternalNullifier, uint256 nullifierHash, - uint256 externalNullifier, uint256[8] memory proof ) external { // First, we make sure this person hasn't done this before @@ -143,15 +99,14 @@ contract PBHVerifier { // We now generate the signal hash from the sender, nonce, and calldata uint256 signalHash = abi.encodePacked( - userOp.sender, - userOp.nonce, - userOp.callData + sender, + nonce, + callData ).hashToField(); // Verify the external nullifier - verifyExternalNullifier(externalNullifier); - - uint256 externalNullifierHash = abi.encode(externalNullifier).hashToField(); + PBHExternalNullifier.verify(pbhExternalNullifier, numPbhPerMonth); + // We now verify the provided proof is valid and the user is verified by World ID worldId.verifyProof( @@ -159,7 +114,7 @@ contract PBHVerifier { groupId, signalHash, nullifierHash, - externalNullifierHash, + pbhExternalNullifier, proof ); diff --git a/pbh-verifier/src/helpers/PBHExternalNullifierLib.sol b/pbh-verifier/src/helpers/PBHExternalNullifier.sol similarity index 79% rename from pbh-verifier/src/helpers/PBHExternalNullifierLib.sol rename to pbh-verifier/src/helpers/PBHExternalNullifier.sol index 06a498c..9b07c61 100644 --- a/pbh-verifier/src/helpers/PBHExternalNullifierLib.sol +++ b/pbh-verifier/src/helpers/PBHExternalNullifier.sol @@ -13,9 +13,7 @@ import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; /// - Bits 16-31: Year /// - Bits 8-15: Month /// - Bits 0-7: Nonce -type PBHExternalNullifier is uint256; - -library PBHExternalNulliferLib { +library PBHExternalNullifier { /// @notice Thrown when the provided external nullifier year doesn't /// match the current year error InvalidExternalNullifierYear(); @@ -33,11 +31,10 @@ library PBHExternalNulliferLib { /// @param month An 8-bit 1-indexed value representing the month (1-12). /// @param year A 16-bit value representing the year (e.g., 2024). /// @return The encoded PBHExternalNullifier. - function encode(uint8 pbhNonce, uint8 month, uint16 year) internal pure returns (PBHExternalNullifier) { + function encode(uint8 pbhNonce, uint8 month, uint16 year) internal pure returns (uint256) { require(month > 0 && month < 13, InvalidExternalNullifierMonth()); require(year <= 9999, InvalidExternalNullifierYear()); - uint256 encoded = (uint32(year) << 16) | (uint32(month) << 8) | uint32(pbhNonce); - return PBHExternalNullifier.wrap(encoded); + return (uint32(year) << 16) | (uint32(month) << 8) | uint32(pbhNonce); } /// @notice Decodes an encoded PBHExternalNullifier into its constituent components. @@ -45,11 +42,10 @@ library PBHExternalNulliferLib { /// @return pbhNonce The 8-bit nonce extracted from the external nullifier. /// @return month The 8-bit month extracted from the external nullifier. /// @return year The 16-bit year extracted from the external nullifier. - function decode(PBHExternalNullifier externalNullifier) internal pure returns (uint8 pbhNonce, uint8 month, uint16 year) { - uint256 encoded = PBHExternalNullifier.unwrap(externalNullifier); - year = uint16(encoded >> 16); - month = uint8((encoded >> 8) & 0xFF); - pbhNonce = uint8(encoded & 0xFF); + function decode(uint256 externalNullifier) internal pure returns (uint8 pbhNonce, uint8 month, uint16 year) { + year = uint16(externalNullifier >> 16); + month = uint8((externalNullifier >> 8) & 0xFF); + pbhNonce = uint8(externalNullifier & 0xFF); } /// @notice Verifies the validity of a PBHExternalNullifier by checking its components. @@ -57,8 +53,8 @@ library PBHExternalNulliferLib { /// @param numPbhPerMonth The maximum allowed value for the `pbhNonce` in the nullifier. /// @dev This function ensures the external nullifier matches the current year and month, /// and that the nonce does not exceed `numPbhPerMonth`. - function verify(PBHExternalNullifier externalNullifier, uint8 numPbhPerMonth) public view { - (uint8 pbhNonce, uint8 month, uint16 year) = PBHExternalNulliferLib.decode(externalNullifier); + function verify(uint256 externalNullifier, uint8 numPbhPerMonth) public view { + (uint8 pbhNonce, uint8 month, uint16 year) = PBHExternalNullifier.decode(externalNullifier); require(year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); require(month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); require(pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); From 7af6d9f2aa3c2843fb66f780333ff64aa7c68e18 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 15:41:55 -0800 Subject: [PATCH 17/28] fix tests --- ...llifier.sol => PBHExternalNullifier.t.sol} | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) rename pbh-verifier/test/{PBHExternalNullifier.sol => PBHExternalNullifier.t.sol} (59%) diff --git a/pbh-verifier/test/PBHExternalNullifier.sol b/pbh-verifier/test/PBHExternalNullifier.t.sol similarity index 59% rename from pbh-verifier/test/PBHExternalNullifier.sol rename to pbh-verifier/test/PBHExternalNullifier.t.sol index 06e4e25..4489f62 100644 --- a/pbh-verifier/test/PBHExternalNullifier.sol +++ b/pbh-verifier/test/PBHExternalNullifier.t.sol @@ -2,12 +2,10 @@ pragma solidity ^0.8.10; import "forge-std/Test.sol"; -import "@helpers/PBHExternalNullifierLib.sol"; +import "@helpers/PBHExternalNullifier.sol"; import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; contract PBHExternalNullifierLibTest is Test { - using PBHExternalNulliferLib for PBHExternalNullifier; - uint8 constant VALID_PBH_NONCE = 5; uint8 constant VALID_MONTH = 12; uint16 constant VALID_YEAR = 2024; @@ -20,8 +18,8 @@ contract PBHExternalNullifierLibTest is Test { uint16 year = VALID_YEAR; // Act - PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(pbhNonce, month, year); - (uint8 decodedNonce, uint8 decodedMonth, uint16 decodedYear) = PBHExternalNulliferLib.decode(encoded); + uint256 encoded = PBHExternalNullifier.encode(pbhNonce, month, year); + (uint8 decodedNonce, uint8 decodedMonth, uint16 decodedYear) = PBHExternalNullifier.decode(encoded); // Assert assertEq(decodedNonce, pbhNonce, "Decoded nonce should match the original"); @@ -32,15 +30,15 @@ contract PBHExternalNullifierLibTest is Test { function testEncodeInvalidMonth() public { uint8 invalidMonth = 13; - vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierMonth.selector); - PBHExternalNulliferLib.encode(VALID_PBH_NONCE, invalidMonth, VALID_YEAR); + vm.expectRevert(PBHExternalNullifier.InvalidExternalNullifierMonth.selector); + PBHExternalNullifier.encode(VALID_PBH_NONCE, invalidMonth, VALID_YEAR); } function testEncodeInvalidYear() public { uint16 invalidYear = 10000; - vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierYear.selector); - PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, invalidYear); + vm.expectRevert(PBHExternalNullifier.InvalidExternalNullifierYear.selector); + PBHExternalNullifier.encode(VALID_PBH_NONCE, VALID_MONTH, invalidYear); } function testVerifyValidExternalNullifier() public { @@ -55,10 +53,10 @@ contract PBHExternalNullifierLibTest is Test { ); vm.warp(timestamp); - PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); + uint256 encoded = PBHExternalNullifier.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); // Act & Assert - PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + PBHExternalNullifier.verify(encoded, MAX_PBH_PER_MONTH); } function testVerifyInvalidYear() public { @@ -72,10 +70,10 @@ contract PBHExternalNullifierLibTest is Test { ); vm.warp(currentTimestamp); - PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); + uint256 encoded = PBHExternalNullifier.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); - vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierYear.selector); - PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + vm.expectRevert(PBHExternalNullifier.InvalidExternalNullifierYear.selector); + PBHExternalNullifier.verify(encoded, MAX_PBH_PER_MONTH); } function testVerifyInvalidMonth() public { @@ -89,20 +87,30 @@ contract PBHExternalNullifierLibTest is Test { ); vm.warp(currentTimestamp); - PBHExternalNullifier encoded = PBHExternalNulliferLib.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); + uint256 encoded = PBHExternalNullifier.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); - vm.expectRevert(PBHExternalNulliferLib.InvalidExternalNullifierMonth.selector); - PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + vm.expectRevert(PBHExternalNullifier.InvalidExternalNullifierMonth.selector); + PBHExternalNullifier.verify(encoded, MAX_PBH_PER_MONTH); } function testVerifyInvalidPbhNonce() public { - PBHExternalNullifier encoded = PBHExternalNulliferLib.encode( + uint256 timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime( + VALID_YEAR, + VALID_MONTH, + 1, + 0, + 0, + 0 + ); + vm.warp(timestamp); + + uint256 encoded = PBHExternalNullifier.encode( MAX_PBH_PER_MONTH + 1, // Invalid nonce exceeding max VALID_MONTH, VALID_YEAR ); - vm.expectRevert(PBHExternalNulliferLib.InvalidPbhNonce.selector); - PBHExternalNulliferLib.verify(encoded, MAX_PBH_PER_MONTH); + vm.expectRevert(PBHExternalNullifier.InvalidPbhNonce.selector); + PBHExternalNullifier.verify(encoded, MAX_PBH_PER_MONTH); } } \ No newline at end of file From cc3d03a34f951d701487e964495cbed2557c53ef Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 18:36:51 -0800 Subject: [PATCH 18/28] cleanup --- pbh-verifier/Dockerfile | 17 +++++++++ pbh-verifier/LICENSE.md | 18 +++++++++ pbh-verifier/script/Counter.s.sol.bak | 19 ---------- pbh-verifier/src/PBHVerifier.sol | 37 ++++--------------- pbh-verifier/src/helpers/ByteHasher.sol | 2 +- .../src/helpers/PBHExternalNullifier.sol | 5 +-- pbh-verifier/test/PBHExternalNullifier.t.sol | 4 +- pbh-verifier/test/PBHVerifier.t.sol | 4 +- 8 files changed, 50 insertions(+), 56 deletions(-) create mode 100644 pbh-verifier/Dockerfile create mode 100644 pbh-verifier/LICENSE.md delete mode 100644 pbh-verifier/script/Counter.s.sol.bak diff --git a/pbh-verifier/Dockerfile b/pbh-verifier/Dockerfile new file mode 100644 index 0000000..3c21914 --- /dev/null +++ b/pbh-verifier/Dockerfile @@ -0,0 +1,17 @@ +FROM ghcr.io/foundry-rs/foundry:latest + +WORKDIR /world-id + +COPY . . + +# Fetch libs +RUN forge install + +# Build the project +RUN forge build + +# RUN ls script; exit 1 +RUN ./script/generate_anvil_state.sh + +ENTRYPOINT ["anvil", "--host", "0.0.0.0", "--load-state", "state.json"] +CMD [] diff --git a/pbh-verifier/LICENSE.md b/pbh-verifier/LICENSE.md new file mode 100644 index 0000000..4c1e8d7 --- /dev/null +++ b/pbh-verifier/LICENSE.md @@ -0,0 +1,18 @@ +MIT License + +Copyright 2023 Worldcoin Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pbh-verifier/script/Counter.s.sol.bak b/pbh-verifier/script/Counter.s.sol.bak deleted file mode 100644 index cdc1fe9..0000000 --- a/pbh-verifier/script/Counter.s.sol.bak +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 7600a06..10cb68c 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -26,42 +26,23 @@ contract PBHVerifier { event PBH( uint256 indexed nullifierHash ); - - /////////////////////////////////////////////////////////////////////////////// - /// Structs /// - ////////////////////////////////////////////////////////////////////////////// - - struct PBHPayload { - uint256 root; - uint256 nullifierHash; - ExternalNullifier externalNullifier; - uint256[8] proof; - } - - /** - * External Nullifier struct - * @param pbhNonce - A nonce between 0 and numPbhPerMonth. - * @param month - An integer representing the current month. - * @param year - An integer representing the current year. - */ - struct ExternalNullifier { - uint8 pbhNonce; - uint16 month; - uint8 year; - } /////////////////////////////////////////////////////////////////////////////// /// Vars /// ////////////////////////////////////////////////////////////////////////////// + /// @dev The World ID group ID (always 1) + uint256 internal immutable GROUP_ID = 1; + /// @dev The World ID instance that will be used for verifying proofs IWorldIDGroups internal immutable worldId; - /// @dev The World ID group ID (always 1) - uint256 internal immutable groupId = 1; - /// @dev Make this configurable uint8 internal immutable numPbhPerMonth; + + /////////////////////////////////////////////////////////////////////////////// + /// Mappings /// + ////////////////////////////////////////////////////////////////////////////// /// @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; @@ -107,11 +88,10 @@ contract PBHVerifier { // Verify the external nullifier PBHExternalNullifier.verify(pbhExternalNullifier, numPbhPerMonth); - // We now verify the provided proof is valid and the user is verified by World ID worldId.verifyProof( root, - groupId, + GROUP_ID, signalHash, nullifierHash, pbhExternalNullifier, @@ -124,4 +104,3 @@ contract PBHVerifier { emit PBH(nullifierHash); } } - diff --git a/pbh-verifier/src/helpers/ByteHasher.sol b/pbh-verifier/src/helpers/ByteHasher.sol index f1e0b22..c655a1d 100644 --- a/pbh-verifier/src/helpers/ByteHasher.sol +++ b/pbh-verifier/src/helpers/ByteHasher.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity ^0.8.20; library ByteHasher { /// @dev Creates a keccak256 hash of a bytestring. diff --git a/pbh-verifier/src/helpers/PBHExternalNullifier.sol b/pbh-verifier/src/helpers/PBHExternalNullifier.sol index 9b07c61..e3a1d8f 100644 --- a/pbh-verifier/src/helpers/PBHExternalNullifier.sol +++ b/pbh-verifier/src/helpers/PBHExternalNullifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity ^0.8.20; import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; @@ -7,7 +7,6 @@ import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; /// @notice Library for encoding, decoding, and verifying PBH external nullifiers. /// External nullifiers are used to uniquely identify actions or events /// within a specific year and month using a nonce. -/// @dev Utilizes `PBHExternalNullifier` as a custom type for encoded nullifiers. /// @dev The encoding format is as follows: /// - Bits 32-255: Empty /// - Bits 16-31: Year @@ -33,7 +32,7 @@ library PBHExternalNullifier { /// @return The encoded PBHExternalNullifier. function encode(uint8 pbhNonce, uint8 month, uint16 year) internal pure returns (uint256) { require(month > 0 && month < 13, InvalidExternalNullifierMonth()); - require(year <= 9999, InvalidExternalNullifierYear()); + require(year < 10000, InvalidExternalNullifierYear()); return (uint32(year) << 16) | (uint32(month) << 8) | uint32(pbhNonce); } diff --git a/pbh-verifier/test/PBHExternalNullifier.t.sol b/pbh-verifier/test/PBHExternalNullifier.t.sol index 4489f62..a9356d5 100644 --- a/pbh-verifier/test/PBHExternalNullifier.t.sol +++ b/pbh-verifier/test/PBHExternalNullifier.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "@helpers/PBHExternalNullifier.sol"; @@ -113,4 +113,4 @@ contract PBHExternalNullifierLibTest is Test { vm.expectRevert(PBHExternalNullifier.InvalidPbhNonce.selector); PBHExternalNullifier.verify(encoded, MAX_PBH_PER_MONTH); } -} \ No newline at end of file +} diff --git a/pbh-verifier/test/PBHVerifier.t.sol b/pbh-verifier/test/PBHVerifier.t.sol index c5f3b9b..256f45f 100644 --- a/pbh-verifier/test/PBHVerifier.t.sol +++ b/pbh-verifier/test/PBHVerifier.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; import {Test, console} from "forge-std/Test.sol"; import {PBHVerifier} from "../src/PBHVerifier.sol"; From 3ec365ade8c906e3aee154e652b8557b4832d807 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 19:21:36 -0800 Subject: [PATCH 19/28] add proxy --- pbh-verifier/src/PBHVerifier.sol | 45 ++++++++++++++++++++++++++- pbh-verifier/src/PBHVerifierProxy.sol | 31 ++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 pbh-verifier/src/PBHVerifierProxy.sol diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index 10cb68c..ed216bb 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -4,9 +4,52 @@ pragma solidity ^0.8.20; import {ByteHasher} from "./helpers/ByteHasher.sol"; import {PBHExternalNullifier} from "./helpers/PBHExternalNullifier.sol"; import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; +import {WorldIDImpl} from "@world-id-contracts/abstract/WorldIDProxy.sol"; import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; -contract PBHVerifier { +/// @title PBH Verifier Implementation Version 1 +/// @author Worldcoin +/// @notice An implementation of a batch-based identity manager for the WorldID protocol. +/// @dev This is the implementation delegated to by a proxy. +contract PBHVerifierImplV1 is WorldIDImpl { + + /////////////////////////////////////////////////////////////////////////////// + /// A NOTE ON IMPLEMENTATION CONTRACTS /// + /////////////////////////////////////////////////////////////////////////////// + + // This contract is designed explicitly to operate from behind a proxy contract. As a result, + // there are a few important implementation considerations: + // + // - All updates made after deploying a given version of the implementation should inherit from + // the latest version of the implementation. This prevents storage clashes. + // - All functions that are less access-restricted than `private` should be marked `virtual` in + // order to enable the fixing of bugs in the existing interface. + // - Any function that reads from or modifies state (i.e. is not marked `pure`) must be + // annotated with the `onlyProxy` and `onlyInitialized` modifiers. This ensures that it can + // only be called when it has access to the data in the proxy, otherwise results are likely to + // be nonsensical. + // - This contract deals with important data for the PBH system. Ensure that all newly-added + // functionality is carefully access controlled using `onlyOwner`, or a more granular access + // mechanism. + // - Do not assign any contract-level variables at the definition site unless they are + // `constant`. + // + // Additionally, the following notes apply: + // + // - Initialisation and ownership management are not protected behind `onlyProxy` intentionally. + // This ensures that the contract can safely be disposed of after it is no longer used. + // - Carefully consider what data recovery options are presented as new functionality is added. + // Care must be taken to ensure that a migration plan can exist for cases where upgrades + // cannot recover from an issue or vulnerability. + + /////////////////////////////////////////////////////////////////////////////// + /// !!!!! DATA: DO NOT REORDER !!!!! /// + /////////////////////////////////////////////////////////////////////////////// + + // To ensure compatibility between upgrades, it is exceedingly important that no reordering of + // these variables takes place. If reordering happens, a storage clash will occur (effectively a + // memory safety error). + using ByteHasher for bytes; /////////////////////////////////////////////////////////////////////////////// diff --git a/pbh-verifier/src/PBHVerifierProxy.sol b/pbh-verifier/src/PBHVerifierProxy.sol new file mode 100644 index 0000000..12899b8 --- /dev/null +++ b/pbh-verifier/src/PBHVerifierProxy.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {WorldIDProxy} from "@world-id-contracts/abstract/WorldIDProxy.sol"; + +/// @title PBH Verifier +/// @author Worldcoin +/// @notice An implementation of an on chain PBH verifier. +contract PBHVerifier is WorldIDProxy { + /////////////////////////////////////////////////////////////////////////////// + /// !!!! DO NOT ADD MEMBERS HERE !!!! /// + /////////////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////////////// + /// CONSTRUCTION /// + /////////////////////////////////////////////////////////////////////////////// + + /// @notice Constructs a new instance of the PBH Verifier. + /// @dev This constructor is only called once, and can be called with the encoded call necessary + /// to initialize the logic contract. + /// + /// @param _logic The initial implementation (delegate) of the contract that this acts as a proxy + /// for. + /// @param _data If this is non-empty, it is used as the data for a `delegatecall` to `_logic`. + /// This is usually an encoded function call, and allows for initialising the storage of + /// the proxy in a way similar to a traditional solidity constructor. + constructor(address _logic, bytes memory _data) payable WorldIDProxy(_logic, _data) { + // !!!! DO NOT PUT PROGRAM LOGIC HERE !!!! + // It should go in the `initialize` function of the delegate instead. + } +} From 010c3cd834a0e75ad8a6229d169a24bd938fd03a Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 19:22:28 -0800 Subject: [PATCH 20/28] rename --- pbh-verifier/src/PBHVerifier.sol | 152 +++---------------------- pbh-verifier/src/PBHVerifierImplV1.sol | 149 ++++++++++++++++++++++++ pbh-verifier/src/PBHVerifierProxy.sol | 31 ----- 3 files changed, 166 insertions(+), 166 deletions(-) create mode 100644 pbh-verifier/src/PBHVerifierImplV1.sol delete mode 100644 pbh-verifier/src/PBHVerifierProxy.sol diff --git a/pbh-verifier/src/PBHVerifier.sol b/pbh-verifier/src/PBHVerifier.sol index ed216bb..12899b8 100644 --- a/pbh-verifier/src/PBHVerifier.sol +++ b/pbh-verifier/src/PBHVerifier.sol @@ -1,149 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {ByteHasher} from "./helpers/ByteHasher.sol"; -import {PBHExternalNullifier} from "./helpers/PBHExternalNullifier.sol"; -import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; -import {WorldIDImpl} from "@world-id-contracts/abstract/WorldIDProxy.sol"; -import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; +import {WorldIDProxy} from "@world-id-contracts/abstract/WorldIDProxy.sol"; -/// @title PBH Verifier Implementation Version 1 +/// @title PBH Verifier /// @author Worldcoin -/// @notice An implementation of a batch-based identity manager for the WorldID protocol. -/// @dev This is the implementation delegated to by a proxy. -contract PBHVerifierImplV1 is WorldIDImpl { - +/// @notice An implementation of an on chain PBH verifier. +contract PBHVerifier is WorldIDProxy { /////////////////////////////////////////////////////////////////////////////// - /// A NOTE ON IMPLEMENTATION CONTRACTS /// + /// !!!! DO NOT ADD MEMBERS HERE !!!! /// /////////////////////////////////////////////////////////////////////////////// - // This contract is designed explicitly to operate from behind a proxy contract. As a result, - // there are a few important implementation considerations: - // - // - All updates made after deploying a given version of the implementation should inherit from - // the latest version of the implementation. This prevents storage clashes. - // - All functions that are less access-restricted than `private` should be marked `virtual` in - // order to enable the fixing of bugs in the existing interface. - // - Any function that reads from or modifies state (i.e. is not marked `pure`) must be - // annotated with the `onlyProxy` and `onlyInitialized` modifiers. This ensures that it can - // only be called when it has access to the data in the proxy, otherwise results are likely to - // be nonsensical. - // - This contract deals with important data for the PBH system. Ensure that all newly-added - // functionality is carefully access controlled using `onlyOwner`, or a more granular access - // mechanism. - // - Do not assign any contract-level variables at the definition site unless they are - // `constant`. - // - // Additionally, the following notes apply: - // - // - Initialisation and ownership management are not protected behind `onlyProxy` intentionally. - // This ensures that the contract can safely be disposed of after it is no longer used. - // - Carefully consider what data recovery options are presented as new functionality is added. - // Care must be taken to ensure that a migration plan can exist for cases where upgrades - // cannot recover from an issue or vulnerability. - - /////////////////////////////////////////////////////////////////////////////// - /// !!!!! DATA: DO NOT REORDER !!!!! /// /////////////////////////////////////////////////////////////////////////////// - - // To ensure compatibility between upgrades, it is exceedingly important that no reordering of - // these variables takes place. If reordering happens, a storage clash will occur (effectively a - // memory safety error). - - using ByteHasher for bytes; - + /// CONSTRUCTION /// /////////////////////////////////////////////////////////////////////////////// - /// ERRORS /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Thrown when attempting to reuse a nullifier - error InvalidNullifier(); - /////////////////////////////////////////////////////////////////////////////// - /// Events /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Emitted when a verifier is updated in the lookup table. + /// @notice Constructs a new instance of the PBH Verifier. + /// @dev This constructor is only called once, and can be called with the encoded call necessary + /// to initialize the logic contract. /// - /// @param nullifierHash The nullifier hash that was used. - event PBH( - uint256 indexed nullifierHash - ); - - /////////////////////////////////////////////////////////////////////////////// - /// Vars /// - ////////////////////////////////////////////////////////////////////////////// - - /// @dev The World ID group ID (always 1) - uint256 internal immutable GROUP_ID = 1; - - /// @dev The World ID instance that will be used for verifying proofs - IWorldIDGroups internal immutable worldId; - - /// @dev Make this configurable - uint8 internal immutable numPbhPerMonth; - - /////////////////////////////////////////////////////////////////////////////// - /// Mappings /// - ////////////////////////////////////////////////////////////////////////////// - - /// @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; - - /////////////////////////////////////////////////////////////////////////////// - /// Functions /// - ////////////////////////////////////////////////////////////////////////////// - - /// @param _worldId The WorldID instance that will verify the proofs - constructor( - IWorldIDGroups _worldId, - uint8 _numPbhPerMonth - ) { - worldId = _worldId; - numPbhPerMonth = _numPbhPerMonth; - } - - /// @param root The root of the Merkle tree (returned by the JS widget). - /// @param sender The root of the Merkle tree (returned by the JS widget). - /// @param nonce The root of the Merkle tree (returned by the JS widget). - /// @param callData The root of the Merkle tree (returned by the JS widget). - /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). - /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). - function verifyPbhProof( - uint256 root, - address sender, - uint256 nonce, - bytes memory callData, - uint256 pbhExternalNullifier, - uint256 nullifierHash, - uint256[8] memory proof - ) external { - // First, we make sure this person hasn't done this before - if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); - - // We now generate the signal hash from the sender, nonce, and calldata - uint256 signalHash = abi.encodePacked( - sender, - nonce, - callData - ).hashToField(); - - // Verify the external nullifier - PBHExternalNullifier.verify(pbhExternalNullifier, numPbhPerMonth); - - // We now verify the provided proof is valid and the user is verified by World ID - worldId.verifyProof( - root, - GROUP_ID, - signalHash, - nullifierHash, - pbhExternalNullifier, - proof - ); - - // We now record the user has done this, so they can't do it again (proof of uniqueness) - nullifierHashes[nullifierHash] = true; - - emit PBH(nullifierHash); + /// @param _logic The initial implementation (delegate) of the contract that this acts as a proxy + /// for. + /// @param _data If this is non-empty, it is used as the data for a `delegatecall` to `_logic`. + /// This is usually an encoded function call, and allows for initialising the storage of + /// the proxy in a way similar to a traditional solidity constructor. + constructor(address _logic, bytes memory _data) payable WorldIDProxy(_logic, _data) { + // !!!! DO NOT PUT PROGRAM LOGIC HERE !!!! + // It should go in the `initialize` function of the delegate instead. } } diff --git a/pbh-verifier/src/PBHVerifierImplV1.sol b/pbh-verifier/src/PBHVerifierImplV1.sol new file mode 100644 index 0000000..ed216bb --- /dev/null +++ b/pbh-verifier/src/PBHVerifierImplV1.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ByteHasher} from "./helpers/ByteHasher.sol"; +import {PBHExternalNullifier} from "./helpers/PBHExternalNullifier.sol"; +import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; +import {WorldIDImpl} from "@world-id-contracts/abstract/WorldIDProxy.sol"; +import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; + +/// @title PBH Verifier Implementation Version 1 +/// @author Worldcoin +/// @notice An implementation of a batch-based identity manager for the WorldID protocol. +/// @dev This is the implementation delegated to by a proxy. +contract PBHVerifierImplV1 is WorldIDImpl { + + /////////////////////////////////////////////////////////////////////////////// + /// A NOTE ON IMPLEMENTATION CONTRACTS /// + /////////////////////////////////////////////////////////////////////////////// + + // This contract is designed explicitly to operate from behind a proxy contract. As a result, + // there are a few important implementation considerations: + // + // - All updates made after deploying a given version of the implementation should inherit from + // the latest version of the implementation. This prevents storage clashes. + // - All functions that are less access-restricted than `private` should be marked `virtual` in + // order to enable the fixing of bugs in the existing interface. + // - Any function that reads from or modifies state (i.e. is not marked `pure`) must be + // annotated with the `onlyProxy` and `onlyInitialized` modifiers. This ensures that it can + // only be called when it has access to the data in the proxy, otherwise results are likely to + // be nonsensical. + // - This contract deals with important data for the PBH system. Ensure that all newly-added + // functionality is carefully access controlled using `onlyOwner`, or a more granular access + // mechanism. + // - Do not assign any contract-level variables at the definition site unless they are + // `constant`. + // + // Additionally, the following notes apply: + // + // - Initialisation and ownership management are not protected behind `onlyProxy` intentionally. + // This ensures that the contract can safely be disposed of after it is no longer used. + // - Carefully consider what data recovery options are presented as new functionality is added. + // Care must be taken to ensure that a migration plan can exist for cases where upgrades + // cannot recover from an issue or vulnerability. + + /////////////////////////////////////////////////////////////////////////////// + /// !!!!! DATA: DO NOT REORDER !!!!! /// + /////////////////////////////////////////////////////////////////////////////// + + // To ensure compatibility between upgrades, it is exceedingly important that no reordering of + // these variables takes place. If reordering happens, a storage clash will occur (effectively a + // memory safety error). + + using ByteHasher for bytes; + + /////////////////////////////////////////////////////////////////////////////// + /// ERRORS /// + ////////////////////////////////////////////////////////////////////////////// + + /// @notice Thrown when attempting to reuse a nullifier + error InvalidNullifier(); + + /////////////////////////////////////////////////////////////////////////////// + /// Events /// + ////////////////////////////////////////////////////////////////////////////// + + /// @notice Emitted when a verifier is updated in the lookup table. + /// + /// @param nullifierHash The nullifier hash that was used. + event PBH( + uint256 indexed nullifierHash + ); + + /////////////////////////////////////////////////////////////////////////////// + /// Vars /// + ////////////////////////////////////////////////////////////////////////////// + + /// @dev The World ID group ID (always 1) + uint256 internal immutable GROUP_ID = 1; + + /// @dev The World ID instance that will be used for verifying proofs + IWorldIDGroups internal immutable worldId; + + /// @dev Make this configurable + uint8 internal immutable numPbhPerMonth; + + /////////////////////////////////////////////////////////////////////////////// + /// Mappings /// + ////////////////////////////////////////////////////////////////////////////// + + /// @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; + + /////////////////////////////////////////////////////////////////////////////// + /// Functions /// + ////////////////////////////////////////////////////////////////////////////// + + /// @param _worldId The WorldID instance that will verify the proofs + constructor( + IWorldIDGroups _worldId, + uint8 _numPbhPerMonth + ) { + worldId = _worldId; + numPbhPerMonth = _numPbhPerMonth; + } + + /// @param root The root of the Merkle tree (returned by the JS widget). + /// @param sender The root of the Merkle tree (returned by the JS widget). + /// @param nonce The root of the Merkle tree (returned by the JS widget). + /// @param callData The root of the Merkle tree (returned by the JS widget). + /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). + /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). + function verifyPbhProof( + uint256 root, + address sender, + uint256 nonce, + bytes memory callData, + uint256 pbhExternalNullifier, + uint256 nullifierHash, + uint256[8] memory proof + ) external { + // First, we make sure this person hasn't done this before + if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); + + // We now generate the signal hash from the sender, nonce, and calldata + uint256 signalHash = abi.encodePacked( + sender, + nonce, + callData + ).hashToField(); + + // Verify the external nullifier + PBHExternalNullifier.verify(pbhExternalNullifier, numPbhPerMonth); + + // We now verify the provided proof is valid and the user is verified by World ID + worldId.verifyProof( + root, + GROUP_ID, + signalHash, + nullifierHash, + pbhExternalNullifier, + proof + ); + + // We now record the user has done this, so they can't do it again (proof of uniqueness) + nullifierHashes[nullifierHash] = true; + + emit PBH(nullifierHash); + } +} diff --git a/pbh-verifier/src/PBHVerifierProxy.sol b/pbh-verifier/src/PBHVerifierProxy.sol deleted file mode 100644 index 12899b8..0000000 --- a/pbh-verifier/src/PBHVerifierProxy.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {WorldIDProxy} from "@world-id-contracts/abstract/WorldIDProxy.sol"; - -/// @title PBH Verifier -/// @author Worldcoin -/// @notice An implementation of an on chain PBH verifier. -contract PBHVerifier is WorldIDProxy { - /////////////////////////////////////////////////////////////////////////////// - /// !!!! DO NOT ADD MEMBERS HERE !!!! /// - /////////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////////// - /// CONSTRUCTION /// - /////////////////////////////////////////////////////////////////////////////// - - /// @notice Constructs a new instance of the PBH Verifier. - /// @dev This constructor is only called once, and can be called with the encoded call necessary - /// to initialize the logic contract. - /// - /// @param _logic The initial implementation (delegate) of the contract that this acts as a proxy - /// for. - /// @param _data If this is non-empty, it is used as the data for a `delegatecall` to `_logic`. - /// This is usually an encoded function call, and allows for initialising the storage of - /// the proxy in a way similar to a traditional solidity constructor. - constructor(address _logic, bytes memory _data) payable WorldIDProxy(_logic, _data) { - // !!!! DO NOT PUT PROGRAM LOGIC HERE !!!! - // It should go in the `initialize` function of the delegate instead. - } -} From cf16121b004b0d37970e0fbe6638510044a1aa63 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 20:03:52 -0800 Subject: [PATCH 21/28] fix remappings --- pbh-verifier/remappings.txt | 2 ++ pbh-verifier/src/PBHVerifierImplV1.sol | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pbh-verifier/remappings.txt b/pbh-verifier/remappings.txt index 802e37b..025934e 100644 --- a/pbh-verifier/remappings.txt +++ b/pbh-verifier/remappings.txt @@ -2,3 +2,5 @@ @account-abstraction/=lib/account-abstraction/contracts/ @BokkyPooBahsDateTimeLibrary/=lib/BokkyPooBahsDateTimeLibrary/contracts/ @helpers/=src/helpers/ + +openzeppelin-contracts/=lib/world-id-contracts/lib/openzeppelin-contracts/contracts/ diff --git a/pbh-verifier/src/PBHVerifierImplV1.sol b/pbh-verifier/src/PBHVerifierImplV1.sol index ed216bb..b6bd1bf 100644 --- a/pbh-verifier/src/PBHVerifierImplV1.sol +++ b/pbh-verifier/src/PBHVerifierImplV1.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {ByteHasher} from "./helpers/ByteHasher.sol"; import {PBHExternalNullifier} from "./helpers/PBHExternalNullifier.sol"; import {IWorldIDGroups} from "@world-id-contracts/interfaces/IWorldIDGroups.sol"; -import {WorldIDImpl} from "@world-id-contracts/abstract/WorldIDProxy.sol"; +import {WorldIDImpl} from "@world-id-contracts/abstract/WorldIDImpl.sol"; import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; /// @title PBH Verifier Implementation Version 1 From 495b1f9074f061e8f2ec4f09d0d656c26d789261 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 20:30:36 -0800 Subject: [PATCH 22/28] proxy work --- pbh-verifier/src/PBHVerifierImplV1.sol | 72 +++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/pbh-verifier/src/PBHVerifierImplV1.sol b/pbh-verifier/src/PBHVerifierImplV1.sol index b6bd1bf..445277c 100644 --- a/pbh-verifier/src/PBHVerifierImplV1.sol +++ b/pbh-verifier/src/PBHVerifierImplV1.sol @@ -58,6 +58,9 @@ contract PBHVerifierImplV1 is WorldIDImpl { /// @notice Thrown when attempting to reuse a nullifier error InvalidNullifier(); + + /// @notice Thrown if the World ID instance is set to the zero address + error InvalidWorldId(); /////////////////////////////////////////////////////////////////////////////// /// Events /// @@ -69,7 +72,11 @@ contract PBHVerifierImplV1 is WorldIDImpl { event PBH( uint256 indexed nullifierHash ); - + + event PBHVerifierImplInitialized( + uint8 indexed numPbhPerMonth + ); + /////////////////////////////////////////////////////////////////////////////// /// Vars /// ////////////////////////////////////////////////////////////////////////////// @@ -78,10 +85,10 @@ contract PBHVerifierImplV1 is WorldIDImpl { uint256 internal immutable GROUP_ID = 1; /// @dev The World ID instance that will be used for verifying proofs - IWorldIDGroups internal immutable worldId; + IWorldIDGroups internal worldId; /// @dev Make this configurable - uint8 internal immutable numPbhPerMonth; + uint8 internal numPbhPerMonth; /////////////////////////////////////////////////////////////////////////////// /// Mappings /// @@ -90,18 +97,67 @@ contract PBHVerifierImplV1 is WorldIDImpl { /// @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; + /////////////////////////////////////////////////////////////////////////////// - /// Functions /// - ////////////////////////////////////////////////////////////////////////////// + /// INITIALIZATION /// + /////////////////////////////////////////////////////////////////////////////// + + /// @notice Constructs the contract. + constructor() { + // When called in the constructor, this is called in the context of the implementation and + // not the proxy. Calling this thereby ensures that the contract cannot be spuriously + // initialized on its own. + _disableInitializers(); + } - /// @param _worldId The WorldID instance that will verify the proofs - constructor( + /// @notice Initializes the contract. + /// @dev Must be called exactly once. + /// @dev This is marked `reinitializer()` to allow for updated initialisation steps when working + /// with upgrades based upon this contract. Be aware that there are only 256 (zero-indexed) + /// initialisations allowed, so decide carefully when to use them. Many cases can safely be + /// replaced by use of setters. + /// @dev This function is explicitly not virtual as it does not make sense to override even when + /// upgrading. Create a separate initializer function instead. + /// + /// @param _worldId The World ID instance that will be used for verifying proofs. + /// @param _numPbhPerMonth The number of allowed PBH transactions per month. + /// + /// @custom:reverts string If called more than once at the same initialisation number. + /// @custom:reverts InvalidWorldId if `_worldId` is set to the zero address + function initialize( IWorldIDGroups _worldId, uint8 _numPbhPerMonth - ) { + ) public reinitializer(1) { + if (address(_worldId) == address(0)) { + revert InvalidWorldId(); + } + + // First, ensure that all of the parent contracts are initialised. + __delegateInit(); + worldId = _worldId; numPbhPerMonth = _numPbhPerMonth; + + // Say that the contract is initialized. + __setInitialized(); + + emit PBHVerifierImplInitialized(_numPbhPerMonth); + } + + /// @notice Responsible for initialising all of the supertypes of this contract. + /// @dev Must be called exactly once. + /// @dev When adding new superclasses, ensure that any initialization that they need to perform + /// is accounted for here. + /// + /// @custom:reverts string If called more than once. + function __delegateInit() internal virtual onlyInitializing { + __WorldIDImpl_init(); } + + + /////////////////////////////////////////////////////////////////////////////// + /// Functions /// + ////////////////////////////////////////////////////////////////////////////// /// @param root The root of the Merkle tree (returned by the JS widget). /// @param sender The root of the Merkle tree (returned by the JS widget). From 248091f84d5587253109c307593ad480dec14b08 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 20:33:36 -0800 Subject: [PATCH 23/28] onlyProxy --- pbh-verifier/src/PBHVerifierImplV1.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pbh-verifier/src/PBHVerifierImplV1.sol b/pbh-verifier/src/PBHVerifierImplV1.sol index 445277c..7a5b6f8 100644 --- a/pbh-verifier/src/PBHVerifierImplV1.sol +++ b/pbh-verifier/src/PBHVerifierImplV1.sol @@ -154,7 +154,6 @@ contract PBHVerifierImplV1 is WorldIDImpl { __WorldIDImpl_init(); } - /////////////////////////////////////////////////////////////////////////////// /// Functions /// ////////////////////////////////////////////////////////////////////////////// @@ -173,7 +172,7 @@ contract PBHVerifierImplV1 is WorldIDImpl { uint256 pbhExternalNullifier, uint256 nullifierHash, uint256[8] memory proof - ) external { + ) external virtual onlyProxy onlyInitialized { // First, we make sure this person hasn't done this before if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); From 73324000e2f2a52c89762168576d5bc88f6e239d Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 21:31:22 -0800 Subject: [PATCH 24/28] tmp --- .../test.yml => .github/workflows/foundry.yml | 3 ++ .gitmodules | 9 ---- .gitmodules.bak | 9 ++++ {pbh-verifier => contracts}/.gitignore | 0 {pbh-verifier => contracts}/LICENSE.md | 0 {pbh-verifier => contracts}/README.md | 0 {pbh-verifier => contracts}/foundry.toml | 0 {pbh-verifier => contracts}/remappings.txt | 0 .../src/PBHVerifier.sol | 0 .../src/PBHVerifierImplV1.sol | 46 ++++++------------- .../src/helpers/ByteHasher.sol | 0 .../src/helpers/PBHExternalNullifier.sol | 16 +++---- .../test/PBHExternalNullifier.t.sol | 18 +------- .../test/PBHVerifier.t.sol | 3 +- pbh-verifier/Dockerfile | 17 ------- pbh-verifier/lib/BokkyPooBahsDateTimeLibrary | 1 - pbh-verifier/lib/forge-std | 1 - pbh-verifier/lib/world-id-contracts | 1 - 18 files changed, 36 insertions(+), 88 deletions(-) rename pbh-verifier/.github/workflows/test.yml => .github/workflows/foundry.yml (87%) delete mode 100644 .gitmodules create mode 100644 .gitmodules.bak rename {pbh-verifier => contracts}/.gitignore (100%) rename {pbh-verifier => contracts}/LICENSE.md (100%) rename {pbh-verifier => contracts}/README.md (100%) rename {pbh-verifier => contracts}/foundry.toml (100%) rename {pbh-verifier => contracts}/remappings.txt (100%) rename {pbh-verifier => contracts}/src/PBHVerifier.sol (100%) rename {pbh-verifier => contracts}/src/PBHVerifierImplV1.sol (93%) rename {pbh-verifier => contracts}/src/helpers/ByteHasher.sol (100%) rename {pbh-verifier => contracts}/src/helpers/PBHExternalNullifier.sol (92%) rename {pbh-verifier => contracts}/test/PBHExternalNullifier.t.sol (93%) rename {pbh-verifier => contracts}/test/PBHVerifier.t.sol (89%) delete mode 100644 pbh-verifier/Dockerfile delete mode 160000 pbh-verifier/lib/BokkyPooBahsDateTimeLibrary delete mode 160000 pbh-verifier/lib/forge-std delete mode 160000 pbh-verifier/lib/world-id-contracts diff --git a/pbh-verifier/.github/workflows/test.yml b/.github/workflows/foundry.yml similarity index 87% rename from pbh-verifier/.github/workflows/test.yml rename to .github/workflows/foundry.yml index 762a296..64c33cb 100644 --- a/pbh-verifier/.github/workflows/test.yml +++ b/.github/workflows/foundry.yml @@ -30,16 +30,19 @@ jobs: forge --version - name: Run Forge fmt + working-directory: contracts run: | forge fmt --check id: fmt - name: Run Forge build + working-directory: contracts run: | forge build --sizes id: build - name: Run Forge tests + working-directory: contracts run: | forge test -vvv id: test diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f921d27..0000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "pbh-verifier/lib/forge-std"] - path = pbh-verifier/lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "pbh-verifier/lib/world-id-contracts"] - path = pbh-verifier/lib/world-id-contracts - url = https://github.com/worldcoin/world-id-contracts -[submodule "pbh-verifier/lib/BokkyPooBahsDateTimeLibrary"] - path = pbh-verifier/lib/BokkyPooBahsDateTimeLibrary - url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary diff --git a/.gitmodules.bak b/.gitmodules.bak new file mode 100644 index 0000000..a3ae759 --- /dev/null +++ b/.gitmodules.bak @@ -0,0 +1,9 @@ +[submodule "contracts/lib/forge-std"] + path = contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "contracts/lib/world-id-contracts"] + path = contracts/lib/world-id-contracts + url = https://github.com/worldcoin/world-id-contracts +[submodule "contracts/lib/BokkyPooBahsDateTimeLibrary"] + path = contracts/lib/BokkyPooBahsDateTimeLibrary + url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary diff --git a/pbh-verifier/.gitignore b/contracts/.gitignore similarity index 100% rename from pbh-verifier/.gitignore rename to contracts/.gitignore diff --git a/pbh-verifier/LICENSE.md b/contracts/LICENSE.md similarity index 100% rename from pbh-verifier/LICENSE.md rename to contracts/LICENSE.md diff --git a/pbh-verifier/README.md b/contracts/README.md similarity index 100% rename from pbh-verifier/README.md rename to contracts/README.md diff --git a/pbh-verifier/foundry.toml b/contracts/foundry.toml similarity index 100% rename from pbh-verifier/foundry.toml rename to contracts/foundry.toml diff --git a/pbh-verifier/remappings.txt b/contracts/remappings.txt similarity index 100% rename from pbh-verifier/remappings.txt rename to contracts/remappings.txt diff --git a/pbh-verifier/src/PBHVerifier.sol b/contracts/src/PBHVerifier.sol similarity index 100% rename from pbh-verifier/src/PBHVerifier.sol rename to contracts/src/PBHVerifier.sol diff --git a/pbh-verifier/src/PBHVerifierImplV1.sol b/contracts/src/PBHVerifierImplV1.sol similarity index 93% rename from pbh-verifier/src/PBHVerifierImplV1.sol rename to contracts/src/PBHVerifierImplV1.sol index 7a5b6f8..070d219 100644 --- a/pbh-verifier/src/PBHVerifierImplV1.sol +++ b/contracts/src/PBHVerifierImplV1.sol @@ -12,7 +12,6 @@ import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; /// @notice An implementation of a batch-based identity manager for the WorldID protocol. /// @dev This is the implementation delegated to by a proxy. contract PBHVerifierImplV1 is WorldIDImpl { - /////////////////////////////////////////////////////////////////////////////// /// A NOTE ON IMPLEMENTATION CONTRACTS /// /////////////////////////////////////////////////////////////////////////////// @@ -58,25 +57,21 @@ contract PBHVerifierImplV1 is WorldIDImpl { /// @notice Thrown when attempting to reuse a nullifier error InvalidNullifier(); - + /// @notice Thrown if the World ID instance is set to the zero address error InvalidWorldId(); /////////////////////////////////////////////////////////////////////////////// /// Events /// ////////////////////////////////////////////////////////////////////////////// - + /// @notice Emitted when a verifier is updated in the lookup table. /// /// @param nullifierHash The nullifier hash that was used. - event PBH( - uint256 indexed nullifierHash - ); - - event PBHVerifierImplInitialized( - uint8 indexed numPbhPerMonth - ); - + event PBH(uint256 indexed nullifierHash); + + event PBHVerifierImplInitialized(uint8 indexed numPbhPerMonth); + /////////////////////////////////////////////////////////////////////////////// /// Vars /// ////////////////////////////////////////////////////////////////////////////// @@ -89,15 +84,14 @@ contract PBHVerifierImplV1 is WorldIDImpl { /// @dev Make this configurable uint8 internal numPbhPerMonth; - + /////////////////////////////////////////////////////////////////////////////// /// Mappings /// ////////////////////////////////////////////////////////////////////////////// /// @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; - - + /////////////////////////////////////////////////////////////////////////////// /// INITIALIZATION /// /////////////////////////////////////////////////////////////////////////////// @@ -124,10 +118,7 @@ contract PBHVerifierImplV1 is WorldIDImpl { /// /// @custom:reverts string If called more than once at the same initialisation number. /// @custom:reverts InvalidWorldId if `_worldId` is set to the zero address - function initialize( - IWorldIDGroups _worldId, - uint8 _numPbhPerMonth - ) public reinitializer(1) { + function initialize(IWorldIDGroups _worldId, uint8 _numPbhPerMonth) public reinitializer(1) { if (address(_worldId) == address(0)) { revert InvalidWorldId(); } @@ -153,7 +144,7 @@ contract PBHVerifierImplV1 is WorldIDImpl { function __delegateInit() internal virtual onlyInitializing { __WorldIDImpl_init(); } - + /////////////////////////////////////////////////////////////////////////////// /// Functions /// ////////////////////////////////////////////////////////////////////////////// @@ -177,24 +168,13 @@ contract PBHVerifierImplV1 is WorldIDImpl { if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); // We now generate the signal hash from the sender, nonce, and calldata - uint256 signalHash = abi.encodePacked( - sender, - nonce, - callData - ).hashToField(); + uint256 signalHash = abi.encodePacked(sender, nonce, callData).hashToField(); // Verify the external nullifier PBHExternalNullifier.verify(pbhExternalNullifier, numPbhPerMonth); - + // We now verify the provided proof is valid and the user is verified by World ID - worldId.verifyProof( - root, - GROUP_ID, - signalHash, - nullifierHash, - pbhExternalNullifier, - proof - ); + worldId.verifyProof(root, GROUP_ID, signalHash, nullifierHash, pbhExternalNullifier, proof); // We now record the user has done this, so they can't do it again (proof of uniqueness) nullifierHashes[nullifierHash] = true; diff --git a/pbh-verifier/src/helpers/ByteHasher.sol b/contracts/src/helpers/ByteHasher.sol similarity index 100% rename from pbh-verifier/src/helpers/ByteHasher.sol rename to contracts/src/helpers/ByteHasher.sol diff --git a/pbh-verifier/src/helpers/PBHExternalNullifier.sol b/contracts/src/helpers/PBHExternalNullifier.sol similarity index 92% rename from pbh-verifier/src/helpers/PBHExternalNullifier.sol rename to contracts/src/helpers/PBHExternalNullifier.sol index e3a1d8f..1710bcd 100644 --- a/pbh-verifier/src/helpers/PBHExternalNullifier.sol +++ b/contracts/src/helpers/PBHExternalNullifier.sol @@ -5,7 +5,7 @@ import "@BokkyPooBahsDateTimeLibrary/BokkyPooBahsDateTimeLibrary.sol"; /// @title PBHExternalNullifierLib /// @notice Library for encoding, decoding, and verifying PBH external nullifiers. -/// External nullifiers are used to uniquely identify actions or events +/// External nullifiers are used to uniquely identify actions or events /// within a specific year and month using a nonce. /// @dev The encoding format is as follows: /// - Bits 32-255: Empty @@ -16,12 +16,12 @@ library PBHExternalNullifier { /// @notice Thrown when the provided external nullifier year doesn't /// match the current year error InvalidExternalNullifierYear(); - + /// @notice Thrown when the provided external nullifier month doesn't /// match the current month error InvalidExternalNullifierMonth(); - - /// @notice Thrown when the provided external + + /// @notice Thrown when the provided external /// nullifier pbhNonce >= numPbhPerMonth error InvalidPbhNonce(); @@ -46,7 +46,7 @@ library PBHExternalNullifier { month = uint8((externalNullifier >> 8) & 0xFF); pbhNonce = uint8(externalNullifier & 0xFF); } - + /// @notice Verifies the validity of a PBHExternalNullifier by checking its components. /// @param externalNullifier The external nullifier to verify. /// @param numPbhPerMonth The maximum allowed value for the `pbhNonce` in the nullifier. @@ -54,8 +54,8 @@ library PBHExternalNullifier { /// and that the nonce does not exceed `numPbhPerMonth`. function verify(uint256 externalNullifier, uint8 numPbhPerMonth) public view { (uint8 pbhNonce, uint8 month, uint16 year) = PBHExternalNullifier.decode(externalNullifier); - require(year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); - require(month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); - require(pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); + require(year == BokkyPooBahsDateTimeLibrary.getYear(block.timestamp), InvalidExternalNullifierYear()); + require(month == BokkyPooBahsDateTimeLibrary.getMonth(block.timestamp), InvalidExternalNullifierMonth()); + require(pbhNonce <= numPbhPerMonth, InvalidPbhNonce()); } } diff --git a/pbh-verifier/test/PBHExternalNullifier.t.sol b/contracts/test/PBHExternalNullifier.t.sol similarity index 93% rename from pbh-verifier/test/PBHExternalNullifier.t.sol rename to contracts/test/PBHExternalNullifier.t.sol index a9356d5..78e6ebb 100644 --- a/pbh-verifier/test/PBHExternalNullifier.t.sol +++ b/contracts/test/PBHExternalNullifier.t.sol @@ -43,14 +43,7 @@ contract PBHExternalNullifierLibTest is Test { function testVerifyValidExternalNullifier() public { // Mock the current date to match VALID_YEAR and VALID_MONTH - uint256 timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime( - VALID_YEAR, - VALID_MONTH, - 1, - 0, - 0, - 0 - ); + uint256 timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime(VALID_YEAR, VALID_MONTH, 1, 0, 0, 0); vm.warp(timestamp); uint256 encoded = PBHExternalNullifier.encode(VALID_PBH_NONCE, VALID_MONTH, VALID_YEAR); @@ -94,14 +87,7 @@ contract PBHExternalNullifierLibTest is Test { } function testVerifyInvalidPbhNonce() public { - uint256 timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime( - VALID_YEAR, - VALID_MONTH, - 1, - 0, - 0, - 0 - ); + uint256 timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDateTime(VALID_YEAR, VALID_MONTH, 1, 0, 0, 0); vm.warp(timestamp); uint256 encoded = PBHExternalNullifier.encode( diff --git a/pbh-verifier/test/PBHVerifier.t.sol b/contracts/test/PBHVerifier.t.sol similarity index 89% rename from pbh-verifier/test/PBHVerifier.t.sol rename to contracts/test/PBHVerifier.t.sol index 256f45f..8484dd9 100644 --- a/pbh-verifier/test/PBHVerifier.t.sol +++ b/contracts/test/PBHVerifier.t.sol @@ -11,6 +11,5 @@ contract PBHVerifierTest is Test { // pbhVerifier = new PBHVerifier(); } - function testTest() public { - } + function testTest() public {} } diff --git a/pbh-verifier/Dockerfile b/pbh-verifier/Dockerfile deleted file mode 100644 index 3c21914..0000000 --- a/pbh-verifier/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ghcr.io/foundry-rs/foundry:latest - -WORKDIR /world-id - -COPY . . - -# Fetch libs -RUN forge install - -# Build the project -RUN forge build - -# RUN ls script; exit 1 -RUN ./script/generate_anvil_state.sh - -ENTRYPOINT ["anvil", "--host", "0.0.0.0", "--load-state", "state.json"] -CMD [] diff --git a/pbh-verifier/lib/BokkyPooBahsDateTimeLibrary b/pbh-verifier/lib/BokkyPooBahsDateTimeLibrary deleted file mode 160000 index 1dc26f9..0000000 --- a/pbh-verifier/lib/BokkyPooBahsDateTimeLibrary +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1dc26f977c57a6ba3ed6d7c53cafdb191e7e59ae diff --git a/pbh-verifier/lib/forge-std b/pbh-verifier/lib/forge-std deleted file mode 160000 index d3db4ef..0000000 --- a/pbh-verifier/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d3db4ef90a72b7d24aa5a2e5c649593eaef7801d diff --git a/pbh-verifier/lib/world-id-contracts b/pbh-verifier/lib/world-id-contracts deleted file mode 160000 index 5835348..0000000 --- a/pbh-verifier/lib/world-id-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5835348737a699c3704a12adb298ce10c9e513f5 From bf02c010d6fab5de7973216c9cb0f7c611fd09fa Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 21:31:29 -0800 Subject: [PATCH 25/28] forge install: forge-std v1.9.4 --- .gitmodules | 3 +++ contracts/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 contracts/lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c65a596 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "contracts/lib/forge-std"] + path = contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std new file mode 160000 index 0000000..1eea5ba --- /dev/null +++ b/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 From 4374fe325236783bba4356fd50ed9e049564b0ab Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 21:32:05 -0800 Subject: [PATCH 26/28] forge install: world-id-contracts v1.0.0 --- .gitmodules | 3 +++ contracts/lib/world-id-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 contracts/lib/world-id-contracts diff --git a/.gitmodules b/.gitmodules index c65a596..fad3196 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "contracts/lib/forge-std"] path = contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "contracts/lib/world-id-contracts"] + path = contracts/lib/world-id-contracts + url = https://github.com/worldcoin/world-id-contracts diff --git a/contracts/lib/world-id-contracts b/contracts/lib/world-id-contracts new file mode 160000 index 0000000..f41cd61 --- /dev/null +++ b/contracts/lib/world-id-contracts @@ -0,0 +1 @@ +Subproject commit f41cd618cadec07a5bc5b66e608df4bc13ffbb54 From 397d3e63841d5bda6768a275696bd31b5b37bbc6 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 21:32:16 -0800 Subject: [PATCH 27/28] forge install: BokkyPooBahsDateTimeLibrary --- .gitmodules | 3 +++ contracts/lib/BokkyPooBahsDateTimeLibrary | 1 + 2 files changed, 4 insertions(+) create mode 160000 contracts/lib/BokkyPooBahsDateTimeLibrary diff --git a/.gitmodules b/.gitmodules index fad3196..a3ae759 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "contracts/lib/world-id-contracts"] path = contracts/lib/world-id-contracts url = https://github.com/worldcoin/world-id-contracts +[submodule "contracts/lib/BokkyPooBahsDateTimeLibrary"] + path = contracts/lib/BokkyPooBahsDateTimeLibrary + url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary diff --git a/contracts/lib/BokkyPooBahsDateTimeLibrary b/contracts/lib/BokkyPooBahsDateTimeLibrary new file mode 160000 index 0000000..1dc26f9 --- /dev/null +++ b/contracts/lib/BokkyPooBahsDateTimeLibrary @@ -0,0 +1 @@ +Subproject commit 1dc26f977c57a6ba3ed6d7c53cafdb191e7e59ae From 9ba0c7560c5dc49d94b776cdf2c7c473d0d35fdb Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Dec 2024 21:33:08 -0800 Subject: [PATCH 28/28] gitmodules --- .gitmodules.bak | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .gitmodules.bak diff --git a/.gitmodules.bak b/.gitmodules.bak deleted file mode 100644 index a3ae759..0000000 --- a/.gitmodules.bak +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "contracts/lib/forge-std"] - path = contracts/lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "contracts/lib/world-id-contracts"] - path = contracts/lib/world-id-contracts - url = https://github.com/worldcoin/world-id-contracts -[submodule "contracts/lib/BokkyPooBahsDateTimeLibrary"] - path = contracts/lib/BokkyPooBahsDateTimeLibrary - url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary