From 0c10c76d62d23f0a0b0ac672a221ec2ad217e747 Mon Sep 17 00:00:00 2001 From: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 07:46:21 +0000 Subject: [PATCH] Complete porting for MACI gatekeepers (#31) ## Description This PR adds the `GitcoinPassportExcubia`, `ZKEdDSAEventTicketPCDExcubia` (prev Zupass) and, `HatsExcubia`. During the porting process, certain interfaces were extended and controls and methods generalised. The code coverage is 100%. Also, this PR introduces the concept of `trait` aka the specific type of an Excubia contract. For example, `SemaphoreExcubia` has trait `Semaphore` and so on. This will make easy to discriminate and query multiple Excubiae sharing the same characteristics. ## Related Issue(s) closes #18 ## Checklist - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] My changes generate no new warnings - [x] I have run `yarn format` and `yarn compile` without getting any errors - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes --- excubiae/README.md | 85 ----- excubiae/src/Excubia.sol | 59 --- excubiae/src/IExcubia.sol | 46 --- excubiae/src/extensions/EASExcubia.sol | 80 ---- excubiae/src/extensions/ERC721Excubia.sol | 57 --- excubiae/src/extensions/FreeForAllExcubia.sol | 37 -- excubiae/src/extensions/SemaphoreExcubia.sol | 75 ---- imt/README.md | 60 --- imt/src/BinaryIMT.sol | 52 --- imt/src/Constants.sol | 5 - imt/src/InternalBinaryIMT.sol | 285 --------------- imt/src/InternalQuinaryIMT.sol | 241 ------------ imt/src/LICENSE | 21 -- imt/src/QuinaryIMT.sol | 44 --- lazy-imt/README.md | 60 --- lazy-imt/src/Constants.sol | 5 - lazy-imt/src/InternalLazyIMT.sol | 265 -------------- lazy-imt/src/LazyIMT.sol | 52 --- lazytower/README.md | 138 ------- lazytower/src/LazyTowerHashChain.sol | 100 ----- lean-imt/README.md | 59 --- lean-imt/src/Constants.sol | 4 - lean-imt/src/InternalLeanIMT.sol | 342 ------------------ lean-imt/src/LeanIMT.sol | 42 --- 24 files changed, 2214 deletions(-) delete mode 100644 excubiae/README.md delete mode 100644 excubiae/src/Excubia.sol delete mode 100644 excubiae/src/IExcubia.sol delete mode 100644 excubiae/src/extensions/EASExcubia.sol delete mode 100644 excubiae/src/extensions/ERC721Excubia.sol delete mode 100644 excubiae/src/extensions/FreeForAllExcubia.sol delete mode 100644 excubiae/src/extensions/SemaphoreExcubia.sol delete mode 100644 imt/README.md delete mode 100644 imt/src/BinaryIMT.sol delete mode 100644 imt/src/Constants.sol delete mode 100644 imt/src/InternalBinaryIMT.sol delete mode 100644 imt/src/InternalQuinaryIMT.sol delete mode 100644 imt/src/LICENSE delete mode 100644 imt/src/QuinaryIMT.sol delete mode 100644 lazy-imt/README.md delete mode 100644 lazy-imt/src/Constants.sol delete mode 100644 lazy-imt/src/InternalLazyIMT.sol delete mode 100644 lazy-imt/src/LazyIMT.sol delete mode 100644 lazytower/README.md delete mode 100644 lazytower/src/LazyTowerHashChain.sol delete mode 100644 lean-imt/README.md delete mode 100644 lean-imt/src/Constants.sol delete mode 100644 lean-imt/src/InternalLeanIMT.sol delete mode 100644 lean-imt/src/LeanIMT.sol diff --git a/excubiae/README.md b/excubiae/README.md deleted file mode 100644 index 71a4bac..0000000 --- a/excubiae/README.md +++ /dev/null @@ -1,85 +0,0 @@ -

-

- Excubiae -

-

A flexible and modular framework for general-purpose on-chain gatekeepers.

-

- -

- - - - - NPM license - - - NPM version - - - Downloads - - - Code style prettier - -

- -
-

- - 🗣️ Chat & Support - -

-
- -> [!NOTE] -> This library is experimental and untested yet - use at your own discretion... - -Excubiae is a generalized framework for on-chain gatekeepers that allows developers to define custom access control mechanisms using different on-chain credentials. By abstracting the gatekeeper logic, excubiae provides a reusable and composable solution for securing decentralised applications. This package provides a pre-defined set of specific excubia (_extensions_) for credentials based on different protocols. - -## 🛠 Install - -### npm or yarn - -Install the ` @zk-kit/excubiae` package with npm: - -```bash -npm i @zk-kit/excubiae --save -``` - -or yarn: - -```bash -yarn add @zk-kit/excubiae -``` - -## 📜 Usage - -To build your own Excubia: - -1. Inherit from the [Excubia](./Excubia.sol) abstract contract that conforms to the [IExcubia](./IExcubia.sol) interface. -2. Implement the `_check()` and `_pass()` methods logic defining your own checks to prevent unwanted access as sybils or avoid to pass the gate twice with the same data / identity. - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import { Excubia } from "excubiae/contracts/Excubia.sol"; - -contract MyExcubia is Excubia { - // ... - - function _pass(address passerby, bytes calldata data) internal override { - // Implement your logic to prevent unwanted access here. - } - - function _check(address passerby, bytes calldata data) internal view override returns (bool) { - // Implement custom access control logic here. - - return true; - } - - // ... -} -``` - -Please see the [extensions](./extensions/) folder for more complex reference implementations and the [test contracts](./test) folder for guidance on using the libraries. diff --git a/excubiae/src/Excubia.sol b/excubiae/src/Excubia.sol deleted file mode 100644 index a91a55a..0000000 --- a/excubiae/src/Excubia.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IExcubia} from "./IExcubia.sol"; - -/// @title Excubia. -/// @notice Abstract base contract which can be extended to implement a specific excubia. -/// @dev Inherit from this contract and implement the `_pass` & `_check` methods to define -/// your custom gatekeeping logic. -abstract contract Excubia is IExcubia, Ownable(msg.sender) { - /// @notice The excubia-protected contract address. - /// @dev The gate can be any contract address that requires a prior check to enable logic. - /// For example, the gate is a Semaphore group that requires the passerby - /// to meet certain criteria before joining. - address public gate; - - /// @dev Modifier to restrict function calls to only from the gate address. - modifier onlyGate() { - if (msg.sender != gate) revert GateOnly(); - _; - } - - /// @inheritdoc IExcubia - function setGate(address _gate) public virtual onlyOwner { - if (_gate == address(0)) revert ZeroAddress(); - if (gate != address(0)) revert GateAlreadySet(); - - gate = _gate; - - emit GateSet(_gate); - } - - /// @inheritdoc IExcubia - function pass(address passerby, bytes calldata data) external onlyGate { - _pass(passerby, data); - } - - /// @inheritdoc IExcubia - function check(address passerby, bytes calldata data) external view { - _check(passerby, data); - } - - /// @notice Internal function to enforce the custom gate passing logic. - /// @dev Calls the `_check` internal logic and emits the relative event if successful. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded token identifier). - function _pass(address passerby, bytes calldata data) internal virtual { - _check(passerby, data); - - emit GatePassed(passerby, gate); - } - - /// @notice Internal function to define the custom gate protection logic. - /// @dev Custom logic to determine if the passerby can pass the gate. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data that may be required for the check. - function _check(address passerby, bytes calldata data) internal view virtual {} -} diff --git a/excubiae/src/IExcubia.sol b/excubiae/src/IExcubia.sol deleted file mode 100644 index 86a4a80..0000000 --- a/excubiae/src/IExcubia.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -/// @title IExcubia. -/// @notice Excubia contract interface. -interface IExcubia { - /// @notice Event emitted when someone passes the gate check. - /// @param passerby The address of those who have successfully passed the check. - /// @param gate The address of the excubia-protected contract address. - event GatePassed(address indexed passerby, address indexed gate); - - /// @notice Event emitted when the gate address is set. - /// @param gate The address of the contract set as the gate. - event GateSet(address indexed gate); - - /// @notice Error thrown when an address equal to zero is given. - error ZeroAddress(); - - /// @notice Error thrown when the gate address is not set. - error GateNotSet(); - - /// @notice Error thrown when the callee is not the gate contract. - error GateOnly(); - - /// @notice Error thrown when the gate address has been already set. - error GateAlreadySet(); - - /// @notice Error thrown when the passerby has already passed the gate. - error AlreadyPassed(); - - /// @notice Sets the gate address. - /// @dev Only the owner can set the destination gate address. - /// @param _gate The address of the contract to be set as the gate. - function setGate(address _gate) external; - - /// @notice Enforces the custom gate passing logic. - /// @dev Must call the `check` to handle the logic of checking passerby for specific gate. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded token identifier). - function pass(address passerby, bytes calldata data) external; - - /// @dev Defines the custom gate protection logic. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data that may be required for the check. - function check(address passerby, bytes calldata data) external view; -} diff --git a/excubiae/src/extensions/EASExcubia.sol b/excubiae/src/extensions/EASExcubia.sol deleted file mode 100644 index afe5692..0000000 --- a/excubiae/src/extensions/EASExcubia.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import {Excubia} from "../Excubia.sol"; -import {IEAS} from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol"; -import {Attestation} from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol"; - -/// @title EAS Excubia Contract. -/// @notice This contract extends the Excubia contract to integrate with the Ethereum Attestation Service (EAS). -/// This contract checks an EAS attestation to permit access through the gate. -/// @dev The contract uses a specific attestation schema & attester to admit the recipient of the attestation. -contract EASExcubia is Excubia { - /// @notice The Ethereum Attestation Service contract interface. - IEAS public immutable EAS; - /// @notice The specific schema ID that attestations must match to pass the gate. - bytes32 public immutable SCHEMA; - /// @notice The trusted attester address whose attestations are considered - /// the only ones valid to pass the gate. - address public immutable ATTESTER; - - /// @notice Mapping to track which attestations have been registered by the contract to - /// avoid pass the gate twice with the same attestation. - mapping(bytes32 => bool) public registeredAttestations; - - /// @notice Error thrown when the attestation does not match the designed schema. - error UnexpectedSchema(); - - /// @notice Error thrown when the attestation does not match the designed trusted attester. - error UnexpectedAttester(); - - /// @notice Error thrown when the attestation does not match the passerby as recipient. - error UnexpectedRecipient(); - - /// @notice Error thrown when the attestation has been revoked. - error RevokedAttestation(); - - /// @notice Constructor to initialize with target EAS contract with specific attester and schema. - /// @param _eas The address of the EAS contract. - /// @param _attester The address of the trusted attester. - /// @param _schema The schema ID that attestations must match. - constructor(address _eas, address _attester, bytes32 _schema) { - if (_eas == address(0) || _attester == address(0)) revert ZeroAddress(); - - EAS = IEAS(_eas); - ATTESTER = _attester; - SCHEMA = _schema; - } - - /// @notice Internal function to handle the passing logic with check. - /// @dev Calls the parent `_pass` function and registers the attestation to avoid pass the gate twice. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded attestation ID). - function _pass(address passerby, bytes calldata data) internal override { - bytes32 attestationId = abi.decode(data, (bytes32)); - - // Avoiding passing the gate twice using the same attestation. - if (registeredAttestations[attestationId]) revert AlreadyPassed(); - - super._pass(passerby, data); - - registeredAttestations[attestationId] = true; - } - - /// @notice Internal function to handle the gate protection (attestation check) logic. - /// @dev Checks if the attestation matches the schema, attester, recipient, and is not revoked. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded attestation ID). - function _check(address passerby, bytes calldata data) internal view override { - super._check(passerby, data); - - bytes32 attestationId = abi.decode(data, (bytes32)); - - Attestation memory attestation = EAS.getAttestation(attestationId); - - if (attestation.schema != SCHEMA) revert UnexpectedSchema(); - if (attestation.attester != ATTESTER) revert UnexpectedAttester(); - if (attestation.recipient != passerby) revert UnexpectedRecipient(); - if (attestation.revocationTime != 0) revert RevokedAttestation(); - } -} diff --git a/excubiae/src/extensions/ERC721Excubia.sol b/excubiae/src/extensions/ERC721Excubia.sol deleted file mode 100644 index 8e93dc7..0000000 --- a/excubiae/src/extensions/ERC721Excubia.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import {Excubia} from "../Excubia.sol"; -import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol"; - -/// @title ERC721 Excubia Contract. -/// @notice This contract extends the Excubia contract to integrate with an ERC721 token. -/// This contract checks the ownership of an ERC721 token to permit access through the gate. -/// @dev The contract refers to a contract implementing the ERC721 standard to admit the owner of the token. -contract ERC721Excubia is Excubia { - /// @notice The ERC721 token contract interface. - IERC721 public immutable NFT; - - /// @notice Mapping to track which token IDs have been registered by the contract to - /// avoid passing the gate twice with the same token ID. - mapping(uint256 => bool) public registeredTokenIds; - - /// @notice Error thrown when the passerby is not the owner of the token. - error UnexpectedTokenOwner(); - - /// @notice Constructor to initialize with target ERC721 contract. - /// @param _erc721 The address of the ERC721 contract. - constructor(address _erc721) { - if (_erc721 == address(0)) revert ZeroAddress(); - - NFT = IERC721(_erc721); - } - - /// @notice Internal function to handle the passing logic with check. - /// @dev Calls the parent `_pass` function and registers the NFT ID to avoid passing the gate twice. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded token ID). - function _pass(address passerby, bytes calldata data) internal override { - uint256 tokenId = abi.decode(data, (uint256)); - - // Avoiding passing the gate twice with the same token ID. - if (registeredTokenIds[tokenId]) revert AlreadyPassed(); - - super._pass(passerby, data); - - registeredTokenIds[tokenId] = true; - } - - /// @notice Internal function to handle the gate protection (token ownership check) logic. - /// @dev Checks if the passerby is the owner of the token. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded token ID). - function _check(address passerby, bytes calldata data) internal view override { - super._check(passerby, data); - - uint256 tokenId = abi.decode(data, (uint256)); - - // Check if the user owns the token. - if (!(NFT.ownerOf(tokenId) == passerby)) revert UnexpectedTokenOwner(); - } -} diff --git a/excubiae/src/extensions/FreeForAllExcubia.sol b/excubiae/src/extensions/FreeForAllExcubia.sol deleted file mode 100644 index 936566d..0000000 --- a/excubiae/src/extensions/FreeForAllExcubia.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import {Excubia} from "../Excubia.sol"; - -/// @title FreeForAll Excubia Contract. -/// @notice This contract extends the Excubia contract to allow free access through the gate. -/// This contract does not perform any checks and allows any passerby to pass the gate. -/// @dev The contract overrides the `_check` function to always return true. -contract FreeForAllExcubia is Excubia { - /// @notice Constructor for the FreeForAllExcubia contract. - constructor() {} - - /// @notice Mapping to track already registered passersby. - mapping(address => bool) public registeredPassersby; - - /// @notice Internal function to handle the gate passing logic. - /// @dev This function calls the parent `_pass` function and then tracks the passerby. - /// @param passerby The address of the entity passing the gate. - /// @param data Additional data required for the pass (not used in this implementation). - function _pass(address passerby, bytes calldata data) internal override { - // Avoiding passing the gate twice with the same address. - if (registeredPassersby[passerby]) revert AlreadyPassed(); - - super._pass(passerby, data); - - registeredPassersby[passerby] = true; - } - - /// @notice Internal function to handle the gate protection logic. - /// @dev This function always returns true, signaling that any passerby is able to pass the gate. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (e.g., encoded attestation ID). - function _check(address passerby, bytes calldata data) internal view override { - super._check(passerby, data); - } -} diff --git a/excubiae/src/extensions/SemaphoreExcubia.sol b/excubiae/src/extensions/SemaphoreExcubia.sol deleted file mode 100644 index bd43112..0000000 --- a/excubiae/src/extensions/SemaphoreExcubia.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import {Excubia} from "../Excubia.sol"; -import {ISemaphore} from "@semaphore-protocol/contracts/interfaces/ISemaphore.sol"; - -/// @title Semaphore Excubia Contract -/// @notice This contract extends the Excubia contract to integrate with the Semaphore protocol. -/// It verifies the passerby Semaphore group membership proofs to grant access through the gate. -/// @dev To allow only specific Semaphore identities from a group, the contract stores the specific group identifier. -/// To avoid identities from passing twice, nullifiers are stored upon successful verification of the proofs. -contract SemaphoreExcubia is Excubia { - /// @notice The Semaphore contract interface. - ISemaphore public immutable SEMAPHORE; - /// @notice The specific group identifier that proofs must match to pass the gate. - /// @dev Used as a `scope` to ensure consistency during proof membership verification. - uint256 public immutable GROUP_ID; - - /// @notice Mapping to track which nullifiers have been used to avoid passing the - /// gate twice using the same Semaphore identity. - /// @dev The nullifier is derived from the hash of the secret and group identifier, - /// ensuring that the same identity cannot be registered twice for the same group. - mapping(uint256 => bool) public passedNullifiers; - - /// @notice Error thrown when the group identifier does not match the expected one. - error InvalidGroup(); - - /// @notice Error thrown when the proof is invalid. - error InvalidProof(); - - /// @notice Error thrown when the proof scope does not match the expected group identifier. - error UnexpectedScope(); - - /// @notice Constructor to initialize with target Semaphore contract and specific group identifier. - /// @param _semaphore The address of the Semaphore contract. - /// @param _groupId The group identifier that proofs must match. - constructor(address _semaphore, uint256 _groupId) { - if (_semaphore == address(0)) revert ZeroAddress(); - - SEMAPHORE = ISemaphore(_semaphore); - - if (ISemaphore(_semaphore).groupCounter() <= _groupId) revert InvalidGroup(); - - GROUP_ID = _groupId; - } - - /// @notice Internal function to handle the passing logic with check. - /// @dev Calls the parent `_pass` function and registers the nullifier to avoid passing the gate twice. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (ie., encoded Semaphore proof). - function _pass(address passerby, bytes calldata data) internal override { - ISemaphore.SemaphoreProof memory proof = abi.decode(data, (ISemaphore.SemaphoreProof)); - - // Avoiding passing the gate twice using the same nullifier. - if (passedNullifiers[proof.nullifier]) revert AlreadyPassed(); - - super._pass(passerby, data); - - passedNullifiers[proof.nullifier] = true; - } - - /// @notice Internal function to handle the gate protection (proof check) logic. - /// @dev Checks if the proof matches the group ID, scope, and is valid. - /// @param passerby The address of the entity attempting to pass the gate. - /// @param data Additional data required for the check (i.e., encoded Semaphore proof). - function _check(address passerby, bytes calldata data) internal view override { - super._check(passerby, data); - - ISemaphore.SemaphoreProof memory proof = abi.decode(data, (ISemaphore.SemaphoreProof)); - - if (GROUP_ID != proof.scope) revert UnexpectedScope(); - - if (!SEMAPHORE.verifyProof(GROUP_ID, proof)) revert InvalidProof(); - } -} diff --git a/imt/README.md b/imt/README.md deleted file mode 100644 index 737ec58..0000000 --- a/imt/README.md +++ /dev/null @@ -1,60 +0,0 @@ -

-

- Incremental Merkle Tree (Solidity) -

-

Incremental Merkle tree implementation in Solidity.

-

- -

- - - - - NPM license - - - NPM version - - - Downloads - - - Code style prettier - -

- -
-

- - 🗣️ Chat & Support - -

-
- -> [!WARNING] -> These library has **not** been audited. - -> [!WARNING] -> If you are looking for the first version of this package, please visit this [link](https://github.com/privacy-scaling-explorations/zk-kit/tree/imt-v1/packages/incremental-merkle-tree.sol). - ---- - -## 🛠 Install - -### npm or yarn - -Install the `@zk-kit/imt.sol` package with npm: - -```bash -npm i @zk-kit/imt.sol --save -``` - -or yarn: - -```bash -yarn add @zk-kit/imt.sol -``` - -## 📜 Usage - -Please, see the [test contracts](./test) for guidance on utilizing the libraries. diff --git a/imt/src/BinaryIMT.sol b/imt/src/BinaryIMT.sol deleted file mode 100644 index e14a16f..0000000 --- a/imt/src/BinaryIMT.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {InternalBinaryIMT, BinaryIMTData} from "./InternalBinaryIMT.sol"; - -library BinaryIMT { - using InternalBinaryIMT for *; - - function defaultZero(uint256 index) public pure returns (uint256) { - return InternalBinaryIMT._defaultZero(index); - } - - function init(BinaryIMTData storage self, uint256 depth, uint256 zero) public { - InternalBinaryIMT._init(self, depth, zero); - } - - function initWithDefaultZeroes(BinaryIMTData storage self, uint256 depth) public { - InternalBinaryIMT._initWithDefaultZeroes(self, depth); - } - - function insert(BinaryIMTData storage self, uint256 leaf) public returns (uint256) { - return InternalBinaryIMT._insert(self, leaf); - } - - function update( - BinaryIMTData storage self, - uint256 leaf, - uint256 newLeaf, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) public { - InternalBinaryIMT._update(self, leaf, newLeaf, proofSiblings, proofPathIndices); - } - - function remove( - BinaryIMTData storage self, - uint256 leaf, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) public { - InternalBinaryIMT._remove(self, leaf, proofSiblings, proofPathIndices); - } - - function verify( - BinaryIMTData storage self, - uint256 leaf, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) private view returns (bool) { - return InternalBinaryIMT._verify(self, leaf, proofSiblings, proofPathIndices); - } -} diff --git a/imt/src/Constants.sol b/imt/src/Constants.sol deleted file mode 100644 index 9f3c455..0000000 --- a/imt/src/Constants.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.4; - -uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; -uint8 constant MAX_DEPTH = 32; diff --git a/imt/src/InternalBinaryIMT.sol b/imt/src/InternalBinaryIMT.sol deleted file mode 100644 index 98f4512..0000000 --- a/imt/src/InternalBinaryIMT.sol +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {PoseidonT3} from "poseidon-solidity/PoseidonT3.sol"; -import {SNARK_SCALAR_FIELD, MAX_DEPTH} from "./Constants.sol"; - -// Each incremental tree has certain properties and data that will -// be used to add new leaves. -struct BinaryIMTData { - uint256 depth; // Depth of the tree (levels - 1). - uint256 root; // Root hash of the tree. - uint256 numberOfLeaves; // Number of leaves of the tree. - mapping(uint256 => uint256) zeroes; // Zero hashes used for empty nodes (level -> zero hash). - // The nodes of the subtrees used in the last addition of a leaf (level -> [left node, right node]). - mapping(uint256 => uint256[2]) lastSubtrees; // Caching these values is essential to efficient appends. - bool useDefaultZeroes; -} - -error ValueGreaterThanSnarkScalarField(); -error DepthNotSupported(); -error WrongDefaultZeroIndex(); -error TreeIsFull(); -error NewLeafCannotEqualOldLeaf(); -error LeafDoesNotExist(); -error LeafIndexOutOfRange(); -error WrongMerkleProofPath(); - -/// @title Incremental binary Merkle tree. -/// @dev The incremental tree allows to calculate the root hash each time a leaf is added, ensuring -/// the integrity of the tree. -library InternalBinaryIMT { - uint256 internal constant Z_0 = 0; - uint256 internal constant Z_1 = 14744269619966411208579211824598458697587494354926760081771325075741142829156; - uint256 internal constant Z_2 = 7423237065226347324353380772367382631490014989348495481811164164159255474657; - uint256 internal constant Z_3 = 11286972368698509976183087595462810875513684078608517520839298933882497716792; - uint256 internal constant Z_4 = 3607627140608796879659380071776844901612302623152076817094415224584923813162; - uint256 internal constant Z_5 = 19712377064642672829441595136074946683621277828620209496774504837737984048981; - uint256 internal constant Z_6 = 20775607673010627194014556968476266066927294572720319469184847051418138353016; - uint256 internal constant Z_7 = 3396914609616007258851405644437304192397291162432396347162513310381425243293; - uint256 internal constant Z_8 = 21551820661461729022865262380882070649935529853313286572328683688269863701601; - uint256 internal constant Z_9 = 6573136701248752079028194407151022595060682063033565181951145966236778420039; - uint256 internal constant Z_10 = 12413880268183407374852357075976609371175688755676981206018884971008854919922; - uint256 internal constant Z_11 = 14271763308400718165336499097156975241954733520325982997864342600795471836726; - uint256 internal constant Z_12 = 20066985985293572387227381049700832219069292839614107140851619262827735677018; - uint256 internal constant Z_13 = 9394776414966240069580838672673694685292165040808226440647796406499139370960; - uint256 internal constant Z_14 = 11331146992410411304059858900317123658895005918277453009197229807340014528524; - uint256 internal constant Z_15 = 15819538789928229930262697811477882737253464456578333862691129291651619515538; - uint256 internal constant Z_16 = 19217088683336594659449020493828377907203207941212636669271704950158751593251; - uint256 internal constant Z_17 = 21035245323335827719745544373081896983162834604456827698288649288827293579666; - uint256 internal constant Z_18 = 6939770416153240137322503476966641397417391950902474480970945462551409848591; - uint256 internal constant Z_19 = 10941962436777715901943463195175331263348098796018438960955633645115732864202; - uint256 internal constant Z_20 = 15019797232609675441998260052101280400536945603062888308240081994073687793470; - uint256 internal constant Z_21 = 11702828337982203149177882813338547876343922920234831094975924378932809409969; - uint256 internal constant Z_22 = 11217067736778784455593535811108456786943573747466706329920902520905755780395; - uint256 internal constant Z_23 = 16072238744996205792852194127671441602062027943016727953216607508365787157389; - uint256 internal constant Z_24 = 17681057402012993898104192736393849603097507831571622013521167331642182653248; - uint256 internal constant Z_25 = 21694045479371014653083846597424257852691458318143380497809004364947786214945; - uint256 internal constant Z_26 = 8163447297445169709687354538480474434591144168767135863541048304198280615192; - uint256 internal constant Z_27 = 14081762237856300239452543304351251708585712948734528663957353575674639038357; - uint256 internal constant Z_28 = 16619959921569409661790279042024627172199214148318086837362003702249041851090; - uint256 internal constant Z_29 = 7022159125197495734384997711896547675021391130223237843255817587255104160365; - uint256 internal constant Z_30 = 4114686047564160449611603615418567457008101555090703535405891656262658644463; - uint256 internal constant Z_31 = 12549363297364877722388257367377629555213421373705596078299904496781819142130; - uint256 internal constant Z_32 = 21443572485391568159800782191812935835534334817699172242223315142338162256601; - - function _defaultZero(uint256 index) internal pure returns (uint256) { - if (index == 0) return Z_0; - if (index == 1) return Z_1; - if (index == 2) return Z_2; - if (index == 3) return Z_3; - if (index == 4) return Z_4; - if (index == 5) return Z_5; - if (index == 6) return Z_6; - if (index == 7) return Z_7; - if (index == 8) return Z_8; - if (index == 9) return Z_9; - if (index == 10) return Z_10; - if (index == 11) return Z_11; - if (index == 12) return Z_12; - if (index == 13) return Z_13; - if (index == 14) return Z_14; - if (index == 15) return Z_15; - if (index == 16) return Z_16; - if (index == 17) return Z_17; - if (index == 18) return Z_18; - if (index == 19) return Z_19; - if (index == 20) return Z_20; - if (index == 21) return Z_21; - if (index == 22) return Z_22; - if (index == 23) return Z_23; - if (index == 24) return Z_24; - if (index == 25) return Z_25; - if (index == 26) return Z_26; - if (index == 27) return Z_27; - if (index == 28) return Z_28; - if (index == 29) return Z_29; - if (index == 30) return Z_30; - if (index == 31) return Z_31; - if (index == 32) return Z_32; - revert WrongDefaultZeroIndex(); - } - - /// @dev Initializes a tree. - /// @param self: Tree data. - /// @param depth: Depth of the tree. - /// @param zero: Zero value to be used. - function _init(BinaryIMTData storage self, uint256 depth, uint256 zero) internal { - if (zero >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (depth <= 0 || depth > MAX_DEPTH) { - revert DepthNotSupported(); - } - - self.depth = depth; - - for (uint8 i = 0; i < depth;) { - self.zeroes[i] = zero; - zero = PoseidonT3.hash([zero, zero]); - - unchecked { - ++i; - } - } - - self.root = zero; - } - - function _initWithDefaultZeroes(BinaryIMTData storage self, uint256 depth) internal { - if (depth <= 0 || depth > MAX_DEPTH) { - revert DepthNotSupported(); - } - - self.depth = depth; - self.useDefaultZeroes = true; - - self.root = _defaultZero(depth); - } - - /// @dev Inserts a leaf in the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be inserted. - function _insert(BinaryIMTData storage self, uint256 leaf) internal returns (uint256) { - uint256 depth = self.depth; - - if (leaf >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (self.numberOfLeaves >= 2 ** depth) { - revert TreeIsFull(); - } - - uint256 index = self.numberOfLeaves; - uint256 hash = leaf; - bool useDefaultZeroes = self.useDefaultZeroes; - - for (uint8 i = 0; i < depth;) { - if (index & 1 == 0) { - self.lastSubtrees[i] = [hash, useDefaultZeroes ? _defaultZero(i) : self.zeroes[i]]; - } else { - self.lastSubtrees[i][1] = hash; - } - - hash = PoseidonT3.hash(self.lastSubtrees[i]); - index >>= 1; - - unchecked { - ++i; - } - } - - self.root = hash; - self.numberOfLeaves += 1; - return hash; - } - - /// @dev Updates a leaf in the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be updated. - /// @param newLeaf: New leaf. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function _update( - BinaryIMTData storage self, - uint256 leaf, - uint256 newLeaf, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal { - if (newLeaf == leaf) { - revert NewLeafCannotEqualOldLeaf(); - } else if (newLeaf >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (!_verify(self, leaf, proofSiblings, proofPathIndices)) { - revert LeafDoesNotExist(); - } - - uint256 depth = self.depth; - uint256 hash = newLeaf; - uint256 updateIndex; - - for (uint8 i = 0; i < depth;) { - updateIndex |= uint256(proofPathIndices[i]) << uint256(i); - - if (proofPathIndices[i] == 0) { - if (proofSiblings[i] == self.lastSubtrees[i][1]) { - self.lastSubtrees[i][0] = hash; - } - - hash = PoseidonT3.hash([hash, proofSiblings[i]]); - } else { - if (proofSiblings[i] == self.lastSubtrees[i][0]) { - self.lastSubtrees[i][1] = hash; - } - - hash = PoseidonT3.hash([proofSiblings[i], hash]); - } - - unchecked { - ++i; - } - } - - if (updateIndex >= self.numberOfLeaves) { - revert LeafIndexOutOfRange(); - } - - self.root = hash; - } - - /// @dev Removes a leaf from the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be removed. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function _remove( - BinaryIMTData storage self, - uint256 leaf, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal { - _update(self, leaf, self.useDefaultZeroes ? Z_0 : self.zeroes[0], proofSiblings, proofPathIndices); - } - - /// @dev Verify if the path is correct and the leaf is part of the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be removed. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - /// @return True or false. - function _verify( - BinaryIMTData storage self, - uint256 leaf, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal view returns (bool) { - uint256 depth = self.depth; - - if (leaf >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (proofPathIndices.length != depth || proofSiblings.length != depth) { - revert WrongMerkleProofPath(); - } - - uint256 hash = leaf; - - for (uint8 i = 0; i < depth;) { - if (proofSiblings[i] >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (proofPathIndices[i] != 1 && proofPathIndices[i] != 0) { - revert WrongMerkleProofPath(); - } - - if (proofPathIndices[i] == 0) { - hash = PoseidonT3.hash([hash, proofSiblings[i]]); - } else { - hash = PoseidonT3.hash([proofSiblings[i], hash]); - } - - unchecked { - ++i; - } - } - - return hash == self.root; - } -} diff --git a/imt/src/InternalQuinaryIMT.sol b/imt/src/InternalQuinaryIMT.sol deleted file mode 100644 index 5376702..0000000 --- a/imt/src/InternalQuinaryIMT.sol +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {PoseidonT6} from "poseidon-solidity/PoseidonT6.sol"; -import {SNARK_SCALAR_FIELD, MAX_DEPTH} from "./Constants.sol"; - -// Each incremental tree has certain properties and data that will -// be used to add new leaves. -struct QuinaryIMTData { - uint256 depth; // Depth of the tree (levels - 1). - uint256 root; // Root hash of the tree. - uint256 numberOfLeaves; // Number of leaves of the tree. - mapping(uint256 => uint256) zeroes; // Zero hashes used for empty nodes (level -> zero hash). - // The nodes of the subtrees used in the last addition of a leaf (level -> [nodes]). - mapping(uint256 => uint256[5]) lastSubtrees; // Caching these values is essential to efficient appends. -} - -error ValueGreaterThanSnarkScalarField(); -error DepthNotSupported(); -error TreeIsFull(); -error NewLeafCannotEqualOldLeaf(); -error LeafDoesNotExist(); -error LeafIndexOutOfRange(); -error WrongMerkleProofPath(); - -/// @title Incremental quinary Merkle tree. -/// @dev The incremental tree allows to calculate the root hash each time a leaf is added, ensuring -/// the integrity of the tree. -library InternalQuinaryIMT { - /// @dev Initializes a tree. - /// @param self: Tree data. - /// @param depth: Depth of the tree. - /// @param zero: Zero value to be used. - function _init(QuinaryIMTData storage self, uint256 depth, uint256 zero) internal { - if (zero >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (depth <= 0 || depth > MAX_DEPTH) { - revert DepthNotSupported(); - } - - self.depth = depth; - - for (uint8 i = 0; i < depth;) { - self.zeroes[i] = zero; - uint256[5] memory zeroChildren; - - for (uint8 j = 0; j < 5;) { - zeroChildren[j] = zero; - unchecked { - ++j; - } - } - - zero = PoseidonT6.hash(zeroChildren); - - unchecked { - ++i; - } - } - - self.root = zero; - } - - /// @dev Inserts a leaf in the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be inserted. - function _insert(QuinaryIMTData storage self, uint256 leaf) internal { - uint256 depth = self.depth; - - if (leaf >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (self.numberOfLeaves >= 5 ** depth) { - revert TreeIsFull(); - } - - uint256 index = self.numberOfLeaves; - uint256 hash = leaf; - - for (uint8 i = 0; i < depth;) { - uint8 position = uint8(index % 5); - - self.lastSubtrees[i][position] = hash; - - if (position == 0) { - for (uint8 j = 1; j < 5;) { - self.lastSubtrees[i][j] = self.zeroes[i]; - unchecked { - ++j; - } - } - } - - hash = PoseidonT6.hash(self.lastSubtrees[i]); - index /= 5; - - unchecked { - ++i; - } - } - - self.root = hash; - self.numberOfLeaves += 1; - } - - /// @dev Updates a leaf in the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be updated. - /// @param newLeaf: New leaf. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function _update( - QuinaryIMTData storage self, - uint256 leaf, - uint256 newLeaf, - uint256[4][] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal { - if (newLeaf == leaf) { - revert NewLeafCannotEqualOldLeaf(); - } else if (newLeaf >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (!_verify(self, leaf, proofSiblings, proofPathIndices)) { - revert LeafDoesNotExist(); - } - - uint256 depth = self.depth; - uint256 hash = newLeaf; - uint256 updateIndex; - - for (uint8 i = 0; i < depth;) { - uint256[5] memory nodes; - updateIndex += proofPathIndices[i] * 5 ** i; - - for (uint8 j = 0; j < 5;) { - if (j < proofPathIndices[i]) { - nodes[j] = proofSiblings[i][j]; - } else if (j == proofPathIndices[i]) { - nodes[j] = hash; - } else { - nodes[j] = proofSiblings[i][j - 1]; - } - unchecked { - ++j; - } - } - - if (nodes[0] == self.lastSubtrees[i][0] || nodes[4] == self.lastSubtrees[i][4]) { - self.lastSubtrees[i][proofPathIndices[i]] = hash; - } - - hash = PoseidonT6.hash(nodes); - - unchecked { - ++i; - } - } - - if (updateIndex >= self.numberOfLeaves) { - revert LeafIndexOutOfRange(); - } - - self.root = hash; - } - - /// @dev Removes a leaf from the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be removed. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function _remove( - QuinaryIMTData storage self, - uint256 leaf, - uint256[4][] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal { - _update(self, leaf, self.zeroes[0], proofSiblings, proofPathIndices); - } - - /// @dev Verify if the path is correct and the leaf is part of the tree. - /// @param self: Tree data. - /// @param leaf: Leaf to be removed. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - /// @return True or false. - function _verify( - QuinaryIMTData storage self, - uint256 leaf, - uint256[4][] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal view returns (bool) { - uint256 depth = self.depth; - - if (leaf >= SNARK_SCALAR_FIELD) { - revert ValueGreaterThanSnarkScalarField(); - } else if (proofPathIndices.length != depth || proofSiblings.length != depth) { - revert WrongMerkleProofPath(); - } - - uint256 hash = leaf; - - for (uint8 i = 0; i < depth;) { - uint256[5] memory nodes; - - if (proofPathIndices[i] < 0 || proofPathIndices[i] >= 5) { - revert WrongMerkleProofPath(); - } - - for (uint8 j = 0; j < 5;) { - if (j < proofPathIndices[i]) { - require( - proofSiblings[i][j] < SNARK_SCALAR_FIELD, - "QuinaryIMT: sibling node must be < SNARK_SCALAR_FIELD" - ); - - nodes[j] = proofSiblings[i][j]; - } else if (j == proofPathIndices[i]) { - nodes[j] = hash; - } else { - require( - proofSiblings[i][j - 1] < SNARK_SCALAR_FIELD, - "QuinaryIMT: sibling node must be < SNARK_SCALAR_FIELD" - ); - - nodes[j] = proofSiblings[i][j - 1]; - } - - unchecked { - ++j; - } - } - - hash = PoseidonT6.hash(nodes); - - unchecked { - ++i; - } - } - - return hash == self.root; - } -} diff --git a/imt/src/LICENSE b/imt/src/LICENSE deleted file mode 100644 index 8ef16f7..0000000 --- a/imt/src/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Ethereum 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/imt/src/QuinaryIMT.sol b/imt/src/QuinaryIMT.sol deleted file mode 100644 index 41f028f..0000000 --- a/imt/src/QuinaryIMT.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {InternalQuinaryIMT, QuinaryIMTData} from "./InternalQuinaryIMT.sol"; - -library QuinaryIMT { - using InternalQuinaryIMT for *; - - function init(QuinaryIMTData storage self, uint256 depth, uint256 zero) public { - InternalQuinaryIMT._init(self, depth, zero); - } - - function insert(QuinaryIMTData storage self, uint256 leaf) public { - InternalQuinaryIMT._insert(self, leaf); - } - - function update( - QuinaryIMTData storage self, - uint256 leaf, - uint256 newLeaf, - uint256[4][] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) public { - InternalQuinaryIMT._update(self, leaf, newLeaf, proofSiblings, proofPathIndices); - } - - function remove( - QuinaryIMTData storage self, - uint256 leaf, - uint256[4][] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) public { - InternalQuinaryIMT._remove(self, leaf, proofSiblings, proofPathIndices); - } - - function verify( - QuinaryIMTData storage self, - uint256 leaf, - uint256[4][] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) private view returns (bool) { - return InternalQuinaryIMT._verify(self, leaf, proofSiblings, proofPathIndices); - } -} diff --git a/lazy-imt/README.md b/lazy-imt/README.md deleted file mode 100644 index 8073eaa..0000000 --- a/lazy-imt/README.md +++ /dev/null @@ -1,60 +0,0 @@ -

-

- Lazy Incremental Merkle Tree (Solidity) -

-

Lazy Incremental Merkle tree implementation in Solidity.

-

- -

- - - - - NPM license - - - NPM version - - - Downloads - - - Code style prettier - -

- -
-

- - 🗣️ Chat & Support - -

-
- -> [!WARNING] -> These library has **not** been audited. - -> [!WARNING] -> If you are looking for the first version of this package, please visit this [link](https://github.com/privacy-scaling-explorations/zk-kit/tree/imt-v1/packages/incremental-merkle-tree.sol). - ---- - -## 🛠 Install - -### npm or yarn - -Install the `@zk-kit/lazy-imt.sol` package with npm: - -```bash -npm i @zk-kit/lazy-imt.sol --save -``` - -or yarn: - -```bash -yarn add @zk-kit/lazy-imt.sol -``` - -## 📜 Usage - -Please, see the [test contracts](./test) for guidance on utilizing the libraries. diff --git a/lazy-imt/src/Constants.sol b/lazy-imt/src/Constants.sol deleted file mode 100644 index 9f3c455..0000000 --- a/lazy-imt/src/Constants.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.4; - -uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; -uint8 constant MAX_DEPTH = 32; diff --git a/lazy-imt/src/InternalLazyIMT.sol b/lazy-imt/src/InternalLazyIMT.sol deleted file mode 100644 index 15ba265..0000000 --- a/lazy-imt/src/InternalLazyIMT.sol +++ /dev/null @@ -1,265 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {PoseidonT3} from "poseidon-solidity/PoseidonT3.sol"; -import {SNARK_SCALAR_FIELD, MAX_DEPTH} from "./Constants.sol"; - -struct LazyIMTData { - uint40 maxIndex; - uint40 numberOfLeaves; - mapping(uint256 => uint256) elements; -} - -library InternalLazyIMT { - uint40 internal constant MAX_INDEX = (1 << 32) - 1; - - uint256 internal constant Z_0 = 0; - uint256 internal constant Z_1 = 14744269619966411208579211824598458697587494354926760081771325075741142829156; - uint256 internal constant Z_2 = 7423237065226347324353380772367382631490014989348495481811164164159255474657; - uint256 internal constant Z_3 = 11286972368698509976183087595462810875513684078608517520839298933882497716792; - uint256 internal constant Z_4 = 3607627140608796879659380071776844901612302623152076817094415224584923813162; - uint256 internal constant Z_5 = 19712377064642672829441595136074946683621277828620209496774504837737984048981; - uint256 internal constant Z_6 = 20775607673010627194014556968476266066927294572720319469184847051418138353016; - uint256 internal constant Z_7 = 3396914609616007258851405644437304192397291162432396347162513310381425243293; - uint256 internal constant Z_8 = 21551820661461729022865262380882070649935529853313286572328683688269863701601; - uint256 internal constant Z_9 = 6573136701248752079028194407151022595060682063033565181951145966236778420039; - uint256 internal constant Z_10 = 12413880268183407374852357075976609371175688755676981206018884971008854919922; - uint256 internal constant Z_11 = 14271763308400718165336499097156975241954733520325982997864342600795471836726; - uint256 internal constant Z_12 = 20066985985293572387227381049700832219069292839614107140851619262827735677018; - uint256 internal constant Z_13 = 9394776414966240069580838672673694685292165040808226440647796406499139370960; - uint256 internal constant Z_14 = 11331146992410411304059858900317123658895005918277453009197229807340014528524; - uint256 internal constant Z_15 = 15819538789928229930262697811477882737253464456578333862691129291651619515538; - uint256 internal constant Z_16 = 19217088683336594659449020493828377907203207941212636669271704950158751593251; - uint256 internal constant Z_17 = 21035245323335827719745544373081896983162834604456827698288649288827293579666; - uint256 internal constant Z_18 = 6939770416153240137322503476966641397417391950902474480970945462551409848591; - uint256 internal constant Z_19 = 10941962436777715901943463195175331263348098796018438960955633645115732864202; - uint256 internal constant Z_20 = 15019797232609675441998260052101280400536945603062888308240081994073687793470; - uint256 internal constant Z_21 = 11702828337982203149177882813338547876343922920234831094975924378932809409969; - uint256 internal constant Z_22 = 11217067736778784455593535811108456786943573747466706329920902520905755780395; - uint256 internal constant Z_23 = 16072238744996205792852194127671441602062027943016727953216607508365787157389; - uint256 internal constant Z_24 = 17681057402012993898104192736393849603097507831571622013521167331642182653248; - uint256 internal constant Z_25 = 21694045479371014653083846597424257852691458318143380497809004364947786214945; - uint256 internal constant Z_26 = 8163447297445169709687354538480474434591144168767135863541048304198280615192; - uint256 internal constant Z_27 = 14081762237856300239452543304351251708585712948734528663957353575674639038357; - uint256 internal constant Z_28 = 16619959921569409661790279042024627172199214148318086837362003702249041851090; - uint256 internal constant Z_29 = 7022159125197495734384997711896547675021391130223237843255817587255104160365; - uint256 internal constant Z_30 = 4114686047564160449611603615418567457008101555090703535405891656262658644463; - uint256 internal constant Z_31 = 12549363297364877722388257367377629555213421373705596078299904496781819142130; - uint256 internal constant Z_32 = 21443572485391568159800782191812935835534334817699172242223315142338162256601; - - function _defaultZero(uint8 index) internal pure returns (uint256) { - if (index == 0) return Z_0; - if (index == 1) return Z_1; - if (index == 2) return Z_2; - if (index == 3) return Z_3; - if (index == 4) return Z_4; - if (index == 5) return Z_5; - if (index == 6) return Z_6; - if (index == 7) return Z_7; - if (index == 8) return Z_8; - if (index == 9) return Z_9; - if (index == 10) return Z_10; - if (index == 11) return Z_11; - if (index == 12) return Z_12; - if (index == 13) return Z_13; - if (index == 14) return Z_14; - if (index == 15) return Z_15; - if (index == 16) return Z_16; - if (index == 17) return Z_17; - if (index == 18) return Z_18; - if (index == 19) return Z_19; - if (index == 20) return Z_20; - if (index == 21) return Z_21; - if (index == 22) return Z_22; - if (index == 23) return Z_23; - if (index == 24) return Z_24; - if (index == 25) return Z_25; - if (index == 26) return Z_26; - if (index == 27) return Z_27; - if (index == 28) return Z_28; - if (index == 29) return Z_29; - if (index == 30) return Z_30; - if (index == 31) return Z_31; - if (index == 32) return Z_32; - revert("LazyIMT: defaultZero bad index"); - } - - function _init(LazyIMTData storage self, uint8 depth) internal { - require(depth <= MAX_DEPTH, "LazyIMT: Tree too large"); - self.maxIndex = uint40((1 << depth) - 1); - self.numberOfLeaves = 0; - } - - function _reset(LazyIMTData storage self) internal { - self.numberOfLeaves = 0; - } - - function _indexForElement(uint8 level, uint40 index) internal pure returns (uint40) { - // store the elements sparsely - return MAX_INDEX * level + index; - } - - function _insert(LazyIMTData storage self, uint256 leaf) internal { - uint40 index = self.numberOfLeaves; - require(leaf < SNARK_SCALAR_FIELD, "LazyIMT: leaf must be < SNARK_SCALAR_FIELD"); - require(index < self.maxIndex, "LazyIMT: tree is full"); - - self.numberOfLeaves = index + 1; - - uint256 hash = leaf; - - for (uint8 i = 0;;) { - self.elements[_indexForElement(i, index)] = hash; - // it's a left element so we don't hash until there's a right element - if (index & 1 == 0) break; - uint40 elementIndex = _indexForElement(i, index - 1); - hash = PoseidonT3.hash([self.elements[elementIndex], hash]); - unchecked { - index >>= 1; - i++; - } - } - } - - function _update(LazyIMTData storage self, uint256 leaf, uint40 index) internal { - require(leaf < SNARK_SCALAR_FIELD, "LazyIMT: leaf must be < SNARK_SCALAR_FIELD"); - uint40 numberOfLeaves = self.numberOfLeaves; - require(index < numberOfLeaves, "LazyIMT: leaf must exist"); - - uint256 hash = leaf; - - for (uint8 i = 0; true;) { - self.elements[_indexForElement(i, index)] = hash; - uint256 levelCount = numberOfLeaves >> (i + 1); - if (levelCount <= index >> 1) break; - if (index & 1 == 0) { - uint40 elementIndex = _indexForElement(i, index + 1); - hash = PoseidonT3.hash([hash, self.elements[elementIndex]]); - } else { - uint40 elementIndex = _indexForElement(i, index - 1); - hash = PoseidonT3.hash([self.elements[elementIndex], hash]); - } - unchecked { - index >>= 1; - i++; - } - } - } - - function _root(LazyIMTData storage self) internal view returns (uint256) { - // this will always short circuit if self.numberOfLeaves == 0 - uint40 numberOfLeaves = self.numberOfLeaves; - // dynamically determine a depth - uint8 depth = 1; - while (uint40(2) ** uint40(depth) < numberOfLeaves) { - depth++; - } - return _root(self, numberOfLeaves, depth); - } - - function _root(LazyIMTData storage self, uint8 depth) internal view returns (uint256) { - require(depth > 0, "LazyIMT: depth must be > 0"); - require(depth <= MAX_DEPTH, "LazyIMT: depth must be <= MAX_DEPTH"); - uint40 numberOfLeaves = self.numberOfLeaves; - require(uint40(2) ** uint40(depth) >= numberOfLeaves, "LazyIMT: ambiguous depth"); - return _root(self, numberOfLeaves, depth); - } - - // Here it's assumed that the depth value is valid. If it is either 0 or > 2^8-1 - // this function will panic. - function _root(LazyIMTData storage self, uint40 numberOfLeaves, uint8 depth) internal view returns (uint256) { - require(depth <= MAX_DEPTH, "LazyIMT: depth must be <= MAX_DEPTH"); - // this should always short circuit if self.numberOfLeaves == 0 - if (numberOfLeaves == 0) return _defaultZero(depth); - uint256[] memory levels = new uint256[](depth + 1); - _levels(self, numberOfLeaves, depth, levels); - return levels[depth]; - } - - function _levels(LazyIMTData storage self, uint40 numberOfLeaves, uint8 depth, uint256[] memory levels) - internal - view - { - require(depth <= MAX_DEPTH, "LazyIMT: depth must be <= MAX_DEPTH"); - require(numberOfLeaves > 0, "LazyIMT: number of leaves must be > 0"); - // this should always short circuit if self.numberOfLeaves == 0 - uint40 index = numberOfLeaves - 1; - - if (index & 1 == 0) { - levels[0] = self.elements[_indexForElement(0, index)]; - } else { - levels[0] = _defaultZero(0); - } - - for (uint8 i = 0; i < depth;) { - if (index & 1 == 0) { - levels[i + 1] = PoseidonT3.hash([levels[i], _defaultZero(i)]); - } else { - uint256 levelCount = (numberOfLeaves) >> (i + 1); - if (levelCount > index >> 1) { - uint256 parent = self.elements[_indexForElement(i + 1, index >> 1)]; - levels[i + 1] = parent; - } else { - uint256 sibling = self.elements[_indexForElement(i, index - 1)]; - levels[i + 1] = PoseidonT3.hash([sibling, levels[i]]); - } - } - unchecked { - index >>= 1; - i++; - } - } - } - - function _merkleProofElements(LazyIMTData storage self, uint40 index, uint8 depth) - internal - view - returns (uint256[] memory) - { - uint40 numberOfLeaves = self.numberOfLeaves; - require(index < numberOfLeaves, "LazyIMT: leaf must exist"); - - uint8 targetDepth = 1; - while (uint40(2) ** uint40(targetDepth) < numberOfLeaves) { - targetDepth++; - } - require(depth >= targetDepth, "LazyIMT: proof depth"); - // pass depth -1 because we don't need the root value - uint256[] memory _elements = new uint256[](depth); - _levels(self, numberOfLeaves, targetDepth - 1, _elements); - - // unroll the bottom entry of the tree because it will never need to - // be pulled from _levels - if (index & 1 == 0) { - if (index + 1 >= numberOfLeaves) { - _elements[0] = _defaultZero(0); - } else { - _elements[0] = self.elements[_indexForElement(0, index + 1)]; - } - } else { - _elements[0] = self.elements[_indexForElement(0, index - 1)]; - } - index >>= 1; - - for (uint8 i = 1; i < depth;) { - uint256 currentLevelCount = numberOfLeaves >> i; - if (index & 1 == 0) { - // if the element is an uncomputed edge node we'll use the value set - // from _levels above - // otherwise set as usual below - if (index + 1 < currentLevelCount) { - _elements[i] = self.elements[_indexForElement(i, index + 1)]; - } else if (((numberOfLeaves - 1) >> i) <= index) { - _elements[i] = _defaultZero(i); - } - } else { - _elements[i] = self.elements[_indexForElement(i, index - 1)]; - } - unchecked { - index >>= 1; - i++; - } - } - return _elements; - } -} diff --git a/lazy-imt/src/LazyIMT.sol b/lazy-imt/src/LazyIMT.sol deleted file mode 100644 index 48a2da8..0000000 --- a/lazy-imt/src/LazyIMT.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {InternalLazyIMT, LazyIMTData} from "./InternalLazyIMT.sol"; - -library LazyIMT { - using InternalLazyIMT for *; - - function init(LazyIMTData storage self, uint8 depth) public { - InternalLazyIMT._init(self, depth); - } - - function defaultZero(uint8 index) public pure returns (uint256) { - return InternalLazyIMT._defaultZero(index); - } - - function reset(LazyIMTData storage self) public { - InternalLazyIMT._reset(self); - } - - function indexForElement(uint8 level, uint40 index) public pure returns (uint40) { - return InternalLazyIMT._indexForElement(level, index); - } - - function insert(LazyIMTData storage self, uint256 leaf) public { - InternalLazyIMT._insert(self, leaf); - } - - function update(LazyIMTData storage self, uint256 leaf, uint40 index) public { - InternalLazyIMT._update(self, leaf, index); - } - - function root(LazyIMTData storage self) public view returns (uint256) { - return InternalLazyIMT._root(self); - } - - function root(LazyIMTData storage self, uint8 depth) public view returns (uint256) { - return InternalLazyIMT._root(self, depth); - } - - function merkleProofElements(LazyIMTData storage self, uint40 index, uint8 depth) - public - view - returns (uint256[] memory) - { - return InternalLazyIMT._merkleProofElements(self, index, depth); - } - - function _root(LazyIMTData storage self, uint40 numberOfLeaves, uint8 depth) internal view returns (uint256) { - return InternalLazyIMT._root(self, numberOfLeaves, depth); - } -} diff --git a/lazytower/README.md b/lazytower/README.md deleted file mode 100644 index dcaa8a5..0000000 --- a/lazytower/README.md +++ /dev/null @@ -1,138 +0,0 @@ -

-

- LazyTower (Solidity) -

-

LazyTower Solidity library.

-

- -

- - - - - NPM license - - - NPM version - - - Downloads - - - Code style prettier - -

- -
-

- - 🗣️ Chat & Support - -

-
- -> [!WARNING] -> These library has **not** been audited. - ---- - -## 🛠 Install - -### npm or yarn - -Install the `@zk-kit/lazytower.sol` package with npm: - -```bash -npm i @zk-kit/lazytower.sol --save -``` - -or yarn: - -```bash -yarn add @zk-kit/lazytower.sol -``` - -## 📜 Usage - -### Importing and using the library - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import "../LazyTowerHashChain.sol"; - -contract LazyTowerHashChainTest { - using LazyTowerHashChain for LazyTowerHashChainData; - - event Add(uint256 item); - - // map for multiple test cases - mapping(bytes32 => LazyTowerHashChainData) public towers; - - function add(bytes32 _towerId, uint256 _item) external { - towers[_towerId].add(_item); - emit Add(_item); - } - - function getDataForProving(bytes32 _towerId) external view returns (uint256, uint256[] memory, uint256) { - return towers[_towerId].getDataForProving(); - } -} -``` - -### Creating an Hardhat task to deploy the contract - -```typescript -import { Contract } from "ethers" -import { task, types } from "hardhat/config" - -task("deploy:lazytower-test", "Deploy a LazyTowerHashChainTest contract") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ logs }, { ethers }): Promise => { - const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3") - const PoseidonT3 = await PoseidonT3Factory.deploy() - - if (logs) { - console.info(`PoseidonT3 library has been deployed to: ${PoseidonT3.address}`) - } - - const LazyTowerLibFactory = await ethers.getContractFactory("LazyTowerHashChain", { - libraries: { - PoseidonT3: PoseidonT3.address - } - }) - const lazyTowerLib = await LazyTowerLibFactory.deploy() - - await lazyTowerLib.deployed() - - if (logs) { - console.info(`LazyTowerHashChain library has been deployed to: ${lazyTowerLib.address}`) - } - - const ContractFactory = await ethers.getContractFactory("LazyTowerHashChainTest", { - libraries: { - LazyTowerHashChain: lazyTowerLib.address - } - }) - - const contract = await ContractFactory.deploy() - - await contract.deployed() - - if (logs) { - console.info(`Test contract has been deployed to: ${contract.address}`) - } - - return contract - }) -``` - -## Contacts - -### Developers - -- e-mail : lcamel@gmail.com -- github : [@LCamel](https://github.com/LCamel) -- website : https://www.facebook.com/LCamel diff --git a/lazytower/src/LazyTowerHashChain.sol b/lazytower/src/LazyTowerHashChain.sol deleted file mode 100644 index a0fd6d1..0000000 --- a/lazytower/src/LazyTowerHashChain.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {PoseidonT3} from "poseidon-solidity/PoseidonT3.sol"; -// CAPACITY = W * (W**0 + W**1 + ... + W**(H - 1)) = W * (W**H - 1) / (W - 1) -// 4 * (4**24 - 1) / (4 - 1) = 375_299_968_947_540; - -uint256 constant H = 24; -uint256 constant W = 4; - -uint256 constant bitsPerLevel = 4; -uint256 constant levelBitmask = 15; // (1 << bitsPerLevel) - 1 -uint256 constant ones = 0x111111111111111111111111; // H ones - -// Each LazyTower has certain properties and data that will -// be used to add new items. -struct LazyTowerHashChainData { - uint256 levelLengths; // length of each level - uint256[H] digests; // digest of each level - uint256[H] digestOfDigests; // digest of digests -} - -/// @title LazyTower. -/// @dev The LazyTower allows to calculate the digest of digests each time an item is added, ensuring -/// the integrity of the LazyTower. -library LazyTowerHashChain { - uint256 internal constant SNARK_SCALAR_FIELD = - 21888242871839275222246405745257275088548364400416034343698204186575808495617; - - function findLowestNonFullLevelThenInc(uint256 levelLengths) - internal - pure - returns (uint256 level, bool isHead, bool isTop, uint256 newLevelLengths) - { - // find the lowest non-full level - uint256 levelLength; - while (true) { - levelLength = levelLengths & levelBitmask; - if (levelLength < W) break; - level++; - levelLengths >>= bitsPerLevel; - } - - isHead = (levelLength == 0); - isTop = ((levelLengths >> bitsPerLevel) == 0); - - // increment the non-full levelLength(s) by one - // all full levels below become ones - uint256 fullLevelBits = level * bitsPerLevel; - uint256 onesMask = (1 << fullLevelBits) - 1; - newLevelLengths = ((levelLengths + 1) << fullLevelBits) + (onesMask & ones); - } - - /// @dev Add an item. - /// @param self: LazyTower data - /// @param item: item to be added - function add(LazyTowerHashChainData storage self, uint256 item) public { - require(item < SNARK_SCALAR_FIELD, "LazyTower: item must be < SNARK_SCALAR_FIELD"); - - uint256 level; - bool isHead; - bool isTop; - (level, isHead, isTop, self.levelLengths) = findLowestNonFullLevelThenInc(self.levelLengths); - - uint256 digest; - uint256 digestOfDigests; - uint256 toAdd; - - // append at the first non-full level - toAdd = (level == 0) ? item : self.digests[level - 1]; - digest = isHead ? toAdd : PoseidonT3.hash([self.digests[level], toAdd]); - digestOfDigests = isTop ? digest : PoseidonT3.hash([self.digestOfDigests[level + 1], digest]); - self.digests[level] = digest; - self.digestOfDigests[level] = digestOfDigests; - - // the rest of levels are all full - while (level != 0) { - level--; - - toAdd = (level == 0) ? item : self.digests[level - 1]; - digest = toAdd; - digestOfDigests = PoseidonT3.hash([digestOfDigests, digest]); // top-down - self.digests[level] = digest; - self.digestOfDigests[level] = digestOfDigests; - } - } - - function getDataForProving(LazyTowerHashChainData storage self) - external - view - returns (uint256, uint256[] memory, uint256) - { - uint256 len = self.digests.length; - uint256[] memory digests = new uint256[](len); // for returning a dynamic array - for (uint256 i = 0; i < len; i++) { - digests[i] = self.digests[i]; - } - return (self.levelLengths, digests, self.digestOfDigests[0]); - } -} diff --git a/lean-imt/README.md b/lean-imt/README.md deleted file mode 100644 index 8c5e1cb..0000000 --- a/lean-imt/README.md +++ /dev/null @@ -1,59 +0,0 @@ -

-

- Lean Incremental Merkle Tree (Solidity) -

-

Lean Incremental Merkle tree implementation in Solidity.

-

- -

- - - - - NPM license - - - NPM version - - - Downloads - - - Code style prettier - -

- -
-

- - 🗣️ Chat & Support - -

-
- -> [!NOTE] -> This library has been audited as part of the Semaphore V4 PSE audit: https://semaphore.pse.dev/Semaphore_4.0.0_Audit.pdf. - -The LeanIMT is an optimized binary version of the [IMT](https://github.com/privacy-scaling-explorations/zk-kit.solidity/tree/main/packages/imt) into binary-focused model, eliminating the need for zero values and allowing dynamic depth adjustment. Unlike the IMT, which uses a zero hash for incomplete nodes, the LeanIMT directly adopts the left child's value when a node lacks a right counterpart. The tree's depth dynamically adjusts to the count of leaves, enhancing efficiency by reducing the number of required hash calculations. To understand more about the LeanIMT, take a look at this [visual explanation](https://hackmd.io/@vplasencia/S1whLBN16). - ---- - -## 🛠 Install - -### npm or yarn - -Install the `@zk-kit/lean-imt.sol` package with npm: - -```bash -npm i @zk-kit/lean-imt.sol --save -``` - -or yarn: - -```bash -yarn add @zk-kit/lean-imt.sol -``` - -## 📜 Usage - -Please, see the [test contracts](./test) for guidance on utilizing the libraries. diff --git a/lean-imt/src/Constants.sol b/lean-imt/src/Constants.sol deleted file mode 100644 index 4e86b6e..0000000 --- a/lean-imt/src/Constants.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.4; - -uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; diff --git a/lean-imt/src/InternalLeanIMT.sol b/lean-imt/src/InternalLeanIMT.sol deleted file mode 100644 index 8f5c593..0000000 --- a/lean-imt/src/InternalLeanIMT.sol +++ /dev/null @@ -1,342 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {PoseidonT3} from "poseidon-solidity/PoseidonT3.sol"; -import {SNARK_SCALAR_FIELD} from "./Constants.sol"; - -struct LeanIMTData { - // Tracks the current number of leaves in the tree. - uint256 size; - // Represents the current depth of the tree, which can increase as new leaves are inserted. - uint256 depth; - // A mapping from each level of the tree to the node value of the last even position at that level. - // Used for efficient inserts, updates and root calculations. - mapping(uint256 => uint256) sideNodes; - // A mapping from leaf values to their respective indices in the tree. - // This facilitates checks for leaf existence and retrieval of leaf positions. - mapping(uint256 => uint256) leaves; -} - -error WrongSiblingNodes(); -error LeafGreaterThanSnarkScalarField(); -error LeafCannotBeZero(); -error LeafAlreadyExists(); -error LeafDoesNotExist(); - -/// @title Lean Incremental binary Merkle tree. -/// @dev The LeanIMT is an optimized version of the BinaryIMT. -/// This implementation eliminates the use of zeroes, and make the tree depth dynamic. -/// When a node doesn't have the right child, instead of using a zero hash as in the BinaryIMT, -/// the node's value becomes that of its left child. Furthermore, rather than utilizing a static tree depth, -/// it is updated based on the number of leaves in the tree. This approach -/// results in the calculation of significantly fewer hashes, making the tree more efficient. -library InternalLeanIMT { - /// @dev Inserts a new leaf into the incremental merkle tree. - /// The function ensures that the leaf is valid according to the - /// constraints of the tree and then updates the tree's structure accordingly. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @param leaf: The value of the new leaf to be inserted into the tree. - /// @return The new hash of the node after the leaf has been inserted. - function _insert(LeanIMTData storage self, uint256 leaf) internal returns (uint256) { - if (leaf >= SNARK_SCALAR_FIELD) { - revert LeafGreaterThanSnarkScalarField(); - } else if (leaf == 0) { - revert LeafCannotBeZero(); - } else if (_has(self, leaf)) { - revert LeafAlreadyExists(); - } - - uint256 index = self.size; - - // Cache tree depth to optimize gas - uint256 treeDepth = self.depth; - - // A new insertion can increase a tree's depth by at most 1, - // and only if the number of leaves supported by the current - // depth is less than the number of leaves to be supported after insertion. - if (2 ** treeDepth < index + 1) { - ++treeDepth; - } - - self.depth = treeDepth; - - uint256 node = leaf; - - for (uint256 level = 0; level < treeDepth;) { - if ((index >> level) & 1 == 1) { - node = PoseidonT3.hash([self.sideNodes[level], node]); - } else { - self.sideNodes[level] = node; - } - - unchecked { - ++level; - } - } - - self.size = ++index; - - self.sideNodes[treeDepth] = node; - self.leaves[leaf] = index; - - return node; - } - - /// @dev Inserts many leaves into the incremental merkle tree. - /// The function ensures that the leaves are valid according to the - /// constraints of the tree and then updates the tree's structure accordingly. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @param leaves: The values of the new leaves to be inserted into the tree. - /// @return The root after the leaves have been inserted. - function _insertMany(LeanIMTData storage self, uint256[] calldata leaves) internal returns (uint256) { - // Cache tree size to optimize gas - uint256 treeSize = self.size; - - // Check that all the new values are correct to be added. - for (uint256 i = 0; i < leaves.length;) { - if (leaves[i] >= SNARK_SCALAR_FIELD) { - revert LeafGreaterThanSnarkScalarField(); - } else if (leaves[i] == 0) { - revert LeafCannotBeZero(); - } else if (_has(self, leaves[i])) { - revert LeafAlreadyExists(); - } - - self.leaves[leaves[i]] = treeSize + 1 + i; - - unchecked { - ++i; - } - } - - // Array to save the nodes that will be used to create the next level of the tree. - uint256[] memory currentLevelNewNodes; - - currentLevelNewNodes = leaves; - - // Cache tree depth to optimize gas - uint256 treeDepth = self.depth; - - // Calculate the depth of the tree after adding the new values. - // Unlike the 'insert' function, we need a while here as - // N insertions can increase the tree's depth more than once. - while (2 ** treeDepth < treeSize + leaves.length) { - ++treeDepth; - } - - self.depth = treeDepth; - - // First index to change in every level. - uint256 currentLevelStartIndex = treeSize; - - // Size of the level used to create the next level. - uint256 currentLevelSize = treeSize + leaves.length; - - // The index where changes begin at the next level. - uint256 nextLevelStartIndex = currentLevelStartIndex >> 1; - - // The size of the next level. - uint256 nextLevelSize = ((currentLevelSize - 1) >> 1) + 1; - - for (uint256 level = 0; level < treeDepth;) { - // The number of nodes for the new level that will be created, - // only the new values, not the entire level. - uint256 numberOfNewNodes = nextLevelSize - nextLevelStartIndex; - uint256[] memory nextLevelNewNodes = new uint256[](numberOfNewNodes); - for (uint256 i = 0; i < numberOfNewNodes;) { - uint256 leftNode; - - // Assign the left node using the saved path or the position in the array. - if ((i + nextLevelStartIndex) * 2 < currentLevelStartIndex) { - leftNode = self.sideNodes[level]; - } else { - leftNode = currentLevelNewNodes[(i + nextLevelStartIndex) * 2 - currentLevelStartIndex]; - } - - uint256 rightNode; - - // Assign the right node if the value exists. - if ((i + nextLevelStartIndex) * 2 + 1 < currentLevelSize) { - rightNode = currentLevelNewNodes[(i + nextLevelStartIndex) * 2 + 1 - currentLevelStartIndex]; - } - - uint256 parentNode; - - // Assign the parent node. - // If it has a right child the result will be the hash(leftNode, rightNode) if not, - // it will be the leftNode. - if (rightNode != 0) { - parentNode = PoseidonT3.hash([leftNode, rightNode]); - } else { - parentNode = leftNode; - } - - nextLevelNewNodes[i] = parentNode; - - unchecked { - ++i; - } - } - - // Update the `sideNodes` variable. - // If `currentLevelSize` is odd, the saved value will be the last value of the array - // if it is even and there are more than 1 element in `currentLevelNewNodes`, the saved value - // will be the value before the last one. - // If it is even and there is only one element, there is no need to save anything because - // the correct value for this level was already saved before. - if (currentLevelSize & 1 == 1) { - self.sideNodes[level] = currentLevelNewNodes[currentLevelNewNodes.length - 1]; - } else if (currentLevelNewNodes.length > 1) { - self.sideNodes[level] = currentLevelNewNodes[currentLevelNewNodes.length - 2]; - } - - currentLevelStartIndex = nextLevelStartIndex; - - // Calculate the next level startIndex value. - // It is the position of the parent node which is pos/2. - nextLevelStartIndex >>= 1; - - // Update the next array that will be used to calculate the next level. - currentLevelNewNodes = nextLevelNewNodes; - - currentLevelSize = nextLevelSize; - - // Calculate the size of the next level. - // The size of the next level is (currentLevelSize - 1) / 2 + 1. - nextLevelSize = ((nextLevelSize - 1) >> 1) + 1; - - unchecked { - ++level; - } - } - - // Update tree size - self.size = treeSize + leaves.length; - - // Update tree root - self.sideNodes[treeDepth] = currentLevelNewNodes[0]; - - return currentLevelNewNodes[0]; - } - - /// @dev Updates the value of an existing leaf and recalculates hashes - /// to maintain tree integrity. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @param oldLeaf: The value of the leaf that is to be updated. - /// @param newLeaf: The new value that will replace the oldLeaf in the tree. - /// @param siblingNodes: An array of sibling nodes that are necessary to recalculate the path to the root. - /// @return The new hash of the updated node after the leaf has been updated. - function _update(LeanIMTData storage self, uint256 oldLeaf, uint256 newLeaf, uint256[] calldata siblingNodes) - internal - returns (uint256) - { - if (newLeaf >= SNARK_SCALAR_FIELD) { - revert LeafGreaterThanSnarkScalarField(); - } else if (!_has(self, oldLeaf)) { - revert LeafDoesNotExist(); - } else if (_has(self, newLeaf)) { - revert LeafAlreadyExists(); - } - - uint256 index = _indexOf(self, oldLeaf); - uint256 node = newLeaf; - uint256 oldRoot = oldLeaf; - - uint256 lastIndex = self.size - 1; - uint256 i = 0; - - // Cache tree depth to optimize gas - uint256 treeDepth = self.depth; - - for (uint256 level = 0; level < treeDepth;) { - if ((index >> level) & 1 == 1) { - if (siblingNodes[i] >= SNARK_SCALAR_FIELD) { - revert LeafGreaterThanSnarkScalarField(); - } - - node = PoseidonT3.hash([siblingNodes[i], node]); - oldRoot = PoseidonT3.hash([siblingNodes[i], oldRoot]); - - unchecked { - ++i; - } - } else { - if (index >> level != lastIndex >> level) { - if (siblingNodes[i] >= SNARK_SCALAR_FIELD) { - revert LeafGreaterThanSnarkScalarField(); - } - - node = PoseidonT3.hash([node, siblingNodes[i]]); - oldRoot = PoseidonT3.hash([oldRoot, siblingNodes[i]]); - - unchecked { - ++i; - } - } else { - self.sideNodes[i] = node; - } - } - - unchecked { - ++level; - } - } - - if (oldRoot != _root(self)) { - revert WrongSiblingNodes(); - } - - self.sideNodes[treeDepth] = node; - - if (newLeaf != 0) { - self.leaves[newLeaf] = self.leaves[oldLeaf]; - } - - self.leaves[oldLeaf] = 0; - - return node; - } - - /// @dev Removes a leaf from the tree by setting its value to zero. - /// This function utilizes the update function to set the leaf's value - /// to zero and update the tree's state accordingly. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @param oldLeaf: The value of the leaf to be removed. - /// @param siblingNodes: An array of sibling nodes required for updating the path to the root after removal. - /// @return The new root hash of the tree after the leaf has been removed. - function _remove(LeanIMTData storage self, uint256 oldLeaf, uint256[] calldata siblingNodes) - internal - returns (uint256) - { - return _update(self, oldLeaf, 0, siblingNodes); - } - - /// @dev Checks if a leaf exists in the tree. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @param leaf: The value of the leaf to check for existence. - /// @return A boolean value indicating whether the leaf exists in the tree. - function _has(LeanIMTData storage self, uint256 leaf) internal view returns (bool) { - return self.leaves[leaf] != 0; - } - - /// @dev Retrieves the index of a given leaf in the tree. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @param leaf: The value of the leaf whose index is to be found. - /// @return The index of the specified leaf within the tree. If the leaf is not present, the function - /// reverts with a custom error. - function _indexOf(LeanIMTData storage self, uint256 leaf) internal view returns (uint256) { - if (self.leaves[leaf] == 0) { - revert LeafDoesNotExist(); - } - - return self.leaves[leaf] - 1; - } - - /// @dev Retrieves the root of the tree from the 'sideNodes' mapping using the - /// current tree depth. - /// @param self: A storage reference to the 'LeanIMTData' struct. - /// @return The root hash of the tree. - function _root(LeanIMTData storage self) internal view returns (uint256) { - return self.sideNodes[self.depth]; - } -} diff --git a/lean-imt/src/LeanIMT.sol b/lean-imt/src/LeanIMT.sol deleted file mode 100644 index 552c88e..0000000 --- a/lean-imt/src/LeanIMT.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {InternalLeanIMT, LeanIMTData} from "./InternalLeanIMT.sol"; - -library LeanIMT { - using InternalLeanIMT for *; - - function insert(LeanIMTData storage self, uint256 leaf) public returns (uint256) { - return InternalLeanIMT._insert(self, leaf); - } - - function insertMany(LeanIMTData storage self, uint256[] calldata leaves) public returns (uint256) { - return InternalLeanIMT._insertMany(self, leaves); - } - - function update(LeanIMTData storage self, uint256 oldLeaf, uint256 newLeaf, uint256[] calldata siblingNodes) - public - returns (uint256) - { - return InternalLeanIMT._update(self, oldLeaf, newLeaf, siblingNodes); - } - - function remove(LeanIMTData storage self, uint256 oldLeaf, uint256[] calldata siblingNodes) - public - returns (uint256) - { - return InternalLeanIMT._remove(self, oldLeaf, siblingNodes); - } - - function has(LeanIMTData storage self, uint256 leaf) public view returns (bool) { - return InternalLeanIMT._has(self, leaf); - } - - function indexOf(LeanIMTData storage self, uint256 leaf) public view returns (uint256) { - return InternalLeanIMT._indexOf(self, leaf); - } - - function root(LeanIMTData storage self) public view returns (uint256) { - return InternalLeanIMT._root(self); - } -}