Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions contracts/src/core/WorldIDRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.13;

import {FullStorageBinaryIMT, FullBinaryIMTData} from "./libraries/FullStorageBinaryIMT.sol";
import {WorldIDRegistryV1Tree} from "./libraries/WorldIDRegistryV1Tree.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {PackedAccountData} from "./libraries/PackedAccountData.sol";
Expand Down Expand Up @@ -121,7 +122,7 @@ contract WorldIDRegistry is WorldIDBase, IWorldIDRegistry {

// Insert a sentinel leaf to start leaf indexes at 1.
// The 0-index of the tree is RESERVED.
_tree.insert(uint256(0));
_insertLeaf(uint256(0));
_nextLeafIndex = 1;
_recordCurrentRoot();

Expand Down Expand Up @@ -365,10 +366,20 @@ contract WorldIDRegistry is WorldIDBase, IWorldIDRegistry {
uint256 oldOffchainSignerCommitment,
uint256 newOffchainSignerCommitment
) internal virtual {
_tree.update(uint256(leafIndex), oldOffchainSignerCommitment, newOffchainSignerCommitment);
WorldIDRegistryV1Tree.update(
_tree, uint256(leafIndex), oldOffchainSignerCommitment, newOffchainSignerCommitment
);
_recordCurrentRoot();
}

function _insertLeaf(uint256 offchainSignerCommitment) internal virtual {
WorldIDRegistryV1Tree.insert(_tree, offchainSignerCommitment);
}

function _insertManyLeaves(uint256[] memory offchainSignerCommitments) internal virtual {
WorldIDRegistryV1Tree.insertMany(_tree, offchainSignerCommitments);
}

/**
* @dev Internal function to register an account.
* @param recoveryAddress The recovery address for the new account.
Expand Down Expand Up @@ -457,7 +468,7 @@ contract WorldIDRegistry is WorldIDBase, IWorldIDRegistry {
revert InsufficientFunds();
}
_registerAccount(recoveryAddress, authenticatorAddresses, authenticatorPubkeys, offchainSignerCommitment);
_tree.insert(offchainSignerCommitment);
_insertLeaf(offchainSignerCommitment);
_recordCurrentRoot();
}

Expand Down Expand Up @@ -493,7 +504,7 @@ contract WorldIDRegistry is WorldIDBase, IWorldIDRegistry {
}

// Update tree
_tree.insertMany(offchainSignerCommitments);
_insertManyLeaves(offchainSignerCommitments);
_recordCurrentRoot();
}

Expand Down
18 changes: 18 additions & 0 deletions contracts/src/core/WorldIDRegistryV2Unreleased.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.13;
import {WorldIDRegistry} from "./WorldIDRegistry.sol";
import {IWorldIDRegistry} from "./interfaces/IWorldIDRegistry.sol";
import {IWorldIDRegistryV2} from "./interfaces/IWorldIDRegistryV2.sol";
import {FullStorageBinaryIMT} from "./libraries/FullStorageBinaryIMT.sol";
import {PackedAccountData} from "./libraries/PackedAccountData.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

Expand Down Expand Up @@ -93,6 +94,23 @@ contract WorldIDRegistryV2 is IWorldIDRegistryV2, WorldIDRegistry {
return ts + _rootValidityWindow;
}

function _updateLeafAndRecord(
uint64 leafIndex,
uint256 oldOffchainSignerCommitment,
uint256 newOffchainSignerCommitment
) internal virtual override {
FullStorageBinaryIMT.update(_tree, uint256(leafIndex), oldOffchainSignerCommitment, newOffchainSignerCommitment);
_recordCurrentRoot();
}

function _insertLeaf(uint256 offchainSignerCommitment) internal virtual override {
FullStorageBinaryIMT.insert(_tree, offchainSignerCommitment);
}

function _insertManyLeaves(uint256[] memory offchainSignerCommitments) internal virtual override {
FullStorageBinaryIMT.insertMany(_tree, offchainSignerCommitments);
}

////////////////////////////////////////////////////////////
// AUTHENTICATOR MANAGEMENT //
////////////////////////////////////////////////////////////
Expand Down
11 changes: 11 additions & 0 deletions contracts/src/core/hash/Poseidon2V1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Poseidon2T2} from "./Poseidon2.sol";

/// @dev V1 compatibility wrapper that keeps Poseidon behind a public library call.
library Poseidon2T2V1 {
function compress(uint256[2] memory inputs) public pure returns (uint256) {
return Poseidon2T2.compress(inputs);
}
}
10 changes: 5 additions & 5 deletions contracts/src/core/libraries/FullStorageBinaryIMT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ library FullStorageBinaryIMT {
}

/// @dev Encode (level, idx) into a single mapping key.
function _key(uint256 level, uint256 idx) private pure returns (uint256) {
function _key(uint256 level, uint256 idx) internal pure returns (uint256) {
return (level << 32) | idx;
}

/// @dev Returns the node value at (level, index). Unset nodes return the
/// default zero for that level.
function _getNode(FullBinaryIMTData storage self, uint256 level, uint256 idx, uint256 numLeaves)
private
internal
view
returns (uint256)
{
Expand All @@ -135,7 +135,7 @@ library FullStorageBinaryIMT {

/// @dev Inserts a leaf at the next available position.
/// Writes the leaf and every internal node on its path to the root.
function insert(FullBinaryIMTData storage self, uint256 leaf) internal returns (uint256) {
function insert(FullBinaryIMTData storage self, uint256 leaf) public returns (uint256) {
uint256 depth = self.depth;

if (leaf >= SNARK_SCALAR_FIELD) {
Expand Down Expand Up @@ -185,7 +185,7 @@ library FullStorageBinaryIMT {
/// [start >> (L+1), (start+k-1) >> (L+1)], so the total work
/// is k + k/2 + k/4 + … + 1 + (D − log₂k) ≈ 2k + D
/// hashes and the same number of SSTOREs.
function insertMany(FullBinaryIMTData storage self, uint256[] memory leaves) internal returns (uint256) {
function insertMany(FullBinaryIMTData storage self, uint256[] memory leaves) public returns (uint256) {
uint256 k = leaves.length;
if (k == 0) return self.root;

Expand Down Expand Up @@ -253,7 +253,7 @@ library FullStorageBinaryIMT {
/// @dev Updates a leaf in the tree. No caller-supplied proof needed.
/// Reads siblings from storage, verifies the old leaf, writes the new
/// path, and updates the root.
function update(FullBinaryIMTData storage self, uint256 index, uint256 oldLeaf, uint256 newLeaf) internal {
function update(FullBinaryIMTData storage self, uint256 index, uint256 oldLeaf, uint256 newLeaf) public {
if (newLeaf == oldLeaf) {
revert NewLeafCannotEqualOldLeaf();
}
Expand Down
156 changes: 156 additions & 0 deletions contracts/src/core/libraries/WorldIDRegistryV1Tree.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Poseidon2T2V1 as Poseidon2T2} from "../hash/Poseidon2V1.sol";
import {
FullBinaryIMTData,
FullStorageBinaryIMT,
LeafDoesNotExist,
LeafIndexOutOfRange,
NewLeafCannotEqualOldLeaf,
SNARK_SCALAR_FIELD,
TreeIsFull,
ValueGreaterThanSnarkScalarField
} from "./FullStorageBinaryIMT.sol";

/// @dev V1 registry write paths copied from FullStorageBinaryIMT, with Poseidon2T2 aliased to the V1 public wrapper.
library WorldIDRegistryV1Tree {
function insert(FullBinaryIMTData storage self, uint256 leaf) internal returns (uint256) {
uint256 depth = self.depth;

if (leaf >= SNARK_SCALAR_FIELD) {
revert ValueGreaterThanSnarkScalarField();
}
if (self.numberOfLeaves >= uint256(1) << depth) {
revert TreeIsFull();
}

uint256 numLeaves = self.numberOfLeaves;
uint256 idx = numLeaves;

self.nodes[FullStorageBinaryIMT._key(0, idx)] = leaf;

uint256 hash = leaf;
for (uint256 level = 0; level < depth;) {
uint256 siblingIdx = idx ^ 1;
uint256 sibling = FullStorageBinaryIMT._getNode(self, level, siblingIdx, numLeaves);

if ((idx & 1) == 0) {
hash = Poseidon2T2.compress([hash, sibling]);
} else {
hash = Poseidon2T2.compress([sibling, hash]);
}

idx >>= 1;
unchecked {
++level;
}

// Write the parent node
self.nodes[FullStorageBinaryIMT._key(level, idx)] = hash;
}

self.root = hash;
self.numberOfLeaves += 1;
return hash;
}

function insertMany(FullBinaryIMTData storage self, uint256[] memory leaves) internal returns (uint256) {
uint256 k = leaves.length;
if (k == 0) return self.root;

uint256 depth = self.depth;
uint256 start = self.numberOfLeaves;
uint256 cap = uint256(1) << depth;
if (start >= cap || k > cap - start) revert TreeIsFull();

for (uint256 i = 0; i < k;) {
uint256 leaf = leaves[i];
if (leaf >= SNARK_SCALAR_FIELD) {
revert ValueGreaterThanSnarkScalarField();
}
self.nodes[FullStorageBinaryIMT._key(0, start + i)] = leaf;
unchecked {
++i;
}
}

uint256 levelStart = start;
uint256 levelEnd = start + k - 1;
uint256 effectiveLeaves = start + k;

for (uint256 level = 0; level < depth;) {
uint256 parentStart = levelStart >> 1;
uint256 parentEnd = levelEnd >> 1;
uint256 parentLevel;
unchecked {
parentLevel = level + 1;
}

for (uint256 p = parentStart; p <= parentEnd;) {
uint256 leftChild = p << 1;
uint256 left = FullStorageBinaryIMT._getNode(self, level, leftChild, effectiveLeaves);
uint256 right = FullStorageBinaryIMT._getNode(self, level, leftChild | 1, effectiveLeaves);

self.nodes[FullStorageBinaryIMT._key(parentLevel, p)] = Poseidon2T2.compress([left, right]);
unchecked {
++p;
}
}

levelStart = parentStart;
levelEnd = parentEnd;
unchecked {
++level;
}
}

uint256 newRoot = self.nodes[FullStorageBinaryIMT._key(depth, 0)];
self.root = newRoot;
self.numberOfLeaves = start + k;
return newRoot;
}

function update(FullBinaryIMTData storage self, uint256 index, uint256 oldLeaf, uint256 newLeaf) internal {
if (newLeaf == oldLeaf) {
revert NewLeafCannotEqualOldLeaf();
}
if (newLeaf >= SNARK_SCALAR_FIELD) {
revert ValueGreaterThanSnarkScalarField();
}
uint256 numLeaves = self.numberOfLeaves;
if (index >= numLeaves) {
revert LeafIndexOutOfRange();
}

uint256 stored = FullStorageBinaryIMT._getNode(self, 0, index, numLeaves);
if (stored != oldLeaf) {
revert LeafDoesNotExist();
}

uint256 depth = self.depth;
uint256 idx = index;

self.nodes[FullStorageBinaryIMT._key(0, idx)] = newLeaf;

uint256 hash = newLeaf;
for (uint256 level = 0; level < depth;) {
uint256 siblingIdx = idx ^ 1;
uint256 sibling = FullStorageBinaryIMT._getNode(self, level, siblingIdx, numLeaves);

if ((idx & 1) == 0) {
hash = Poseidon2T2.compress([hash, sibling]);
} else {
hash = Poseidon2T2.compress([sibling, hash]);
}

idx >>= 1;
unchecked {
++level;
}
self.nodes[FullStorageBinaryIMT._key(level, idx)] = hash;
}

self.root = hash;
}
}
20 changes: 0 additions & 20 deletions crates/registries/abi/WorldIDRegistryV2Abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1799,11 +1799,6 @@
"name": "InvalidSignature",
"inputs": []
},
{
"type": "error",
"name": "LeafDoesNotExist",
"inputs": []
},
{
"type": "error",
"name": "LeafIndexOutOfRange",
Expand Down Expand Up @@ -1909,11 +1904,6 @@
"name": "MismatchingArrayLengths",
"inputs": []
},
{
"type": "error",
"name": "NewLeafCannotEqualOldLeaf",
"inputs": []
},
{
"type": "error",
"name": "NoActiveRecoveryAgentUpdate",
Expand Down Expand Up @@ -2079,11 +2069,6 @@
}
]
},
{
"type": "error",
"name": "TreeIsFull",
"inputs": []
},
{
"type": "error",
"name": "UUPSUnauthorizedCallContext",
Expand Down Expand Up @@ -2116,11 +2101,6 @@
"name": "UnmanageableNotAllowed",
"inputs": []
},
{
"type": "error",
"name": "ValueGreaterThanSnarkScalarField",
"inputs": []
},
{
"type": "error",
"name": "WrongDefaultZeroIndex",
Expand Down
Loading
Loading