├── .nvmrc ├── .solcover.js ├── .env.example ├── .eslintignore ├── .prettierignore ├── .gitignore ├── contracts ├── tree │ ├── binary │ │ ├── Branch.sol │ │ ├── BinaryMerkleProof.sol │ │ ├── Node.sol │ │ ├── TreeHasher.sol │ │ ├── BinaryMerkleTree.sol │ │ └── BinaryMerkleTreeUtils.sol │ ├── sparse │ │ ├── Branch.sol │ │ ├── Node.sol │ │ ├── TreeHasher.sol │ │ ├── Proofs.sol │ │ └── SparseMerkleTree.sol │ ├── sum │ │ ├── SumMerkleProof.sol │ │ ├── TreeHasher.sol │ │ └── SumMerkleTree.sol │ ├── Cryptography.sol │ ├── Constants.sol │ └── Utils.sol └── mocks │ ├── MockMerkleSumTree.sol │ ├── MockSparseMerkleTree.sol │ └── MockBinaryMerkleTree.sol ├── test ├── utils │ ├── cryptography.ts │ ├── constants.ts │ ├── proofTest.ts │ ├── encodedValue.ts │ └── utils.ts ├── test_vectors │ └── binary_proofs │ │ ├── Test 1 Leaf Index 0.yaml │ │ ├── Test 0 Leaves.yaml │ │ ├── Test 1 Leaf Invalid Root.yaml │ │ ├── Test 1 Leaf Invalid Proof Index.yaml │ │ ├── Test 10 Leaves Index 4.yaml │ │ ├── Test 100 Leaves Index 10.yaml │ │ ├── Test 1024 Leaves Index 512.yaml │ │ └── Test 1024 Leaves Invalid Root.yaml ├── test_helpers │ ├── sumMerkleTree.ts │ └── binaryMerkleTree.ts ├── merkleTreeSum.ts ├── merkleTreeBinary.ts └── merkleTreeSparse.ts ├── .solhint.json ├── tsconfig.json ├── .github └── workflows │ ├── lint.yaml │ ├── npm-publish.yml │ └── ci.yaml ├── .prettierrc.json ├── .eslintrc.json ├── README.md ├── hardhat.config.ts ├── package.json └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/fermium 2 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ROPSTEN_PRIVATE_KEY= 2 | INFURA_API_KEY= 3 | ETHERSCAN_API_KEY= 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/coverage/ 3 | **/build/ 4 | **/typechain/ 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | .coverage_* 3 | coverage 4 | **/typechain/**/* 5 | node_modules 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .vscode 4 | 5 | coverage 6 | coverage.json 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | typechain 13 | yarn-error.log 14 | -------------------------------------------------------------------------------- /contracts/tree/binary/Branch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | struct MerkleBranch { 5 | bytes32[] proof; 6 | bytes32 key; 7 | bytes value; 8 | } 9 | -------------------------------------------------------------------------------- /test/utils/cryptography.ts: -------------------------------------------------------------------------------- 1 | import { utils, BytesLike } from 'ethers'; 2 | 3 | // The hash function for the merkle trees 4 | export default function hash(data: BytesLike): string { 5 | return utils.sha256(data); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/tree/binary/BinaryMerkleProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Merkle Tree Node structure. 5 | struct BinaryMerkleProof { 6 | bytes32[] proof; 7 | uint256 key; 8 | uint256 numLeaves; 9 | } 10 | -------------------------------------------------------------------------------- /contracts/tree/sparse/Branch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {SparseCompactMerkleProof} from "./Proofs.sol"; 5 | 6 | struct MerkleBranch { 7 | SparseCompactMerkleProof proof; 8 | bytes32 key; 9 | bytes value; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/tree/binary/Node.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Merkle Tree Node structure. 5 | struct Node { 6 | bytes32 digest; 7 | // Left child. 8 | bytes32 leftChildPtr; 9 | // Right child. 10 | bytes32 rightChildPtr; 11 | } 12 | -------------------------------------------------------------------------------- /test/utils/constants.ts: -------------------------------------------------------------------------------- 1 | // Useful constants for constucting Merkle trees 2 | 3 | export const EMPTY = '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; 4 | 5 | export const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; 6 | 7 | export const MAX_HEIGHT = 256; 8 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["solhint:recommended"], 3 | "plugins": [], 4 | "rules": { 5 | "compiler-version": ["error", "^0.8.0"], 6 | "var-name-mixedcase": null, 7 | "no-inline-assembly": null, 8 | "avoid-tx-origin": null, 9 | "func-visibility": ["error", { "ignoreConstructors": true }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true 9 | }, 10 | "include": ["./scripts", "./protocol", "./test", "./typechain"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /test/utils/proofTest.ts: -------------------------------------------------------------------------------- 1 | import { EncodedValueInput } from './encodedValue'; 2 | 3 | export default interface ProofTest { 4 | name: string; 5 | root: EncodedValueInput; 6 | data: EncodedValueInput; 7 | proof_set: EncodedValueInput[]; 8 | proof_index: number; 9 | num_leaves: number; 10 | expected_verification: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /contracts/tree/sum/SumMerkleProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Sum Merkle Tree Proof structure. 5 | struct SumMerkleProof { 6 | // List of side nodes to verify and calculate tree. 7 | bytes32[] sideNodes; 8 | // Node substree sums. 9 | uint256[] nodeSums; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/tree/sparse/Node.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | struct Node { 5 | bytes32 digest; 6 | bytes1 prefix; 7 | bytes32 leftChildPtr; // Zero if node is leaf 8 | bytes32 rightChildPtr; // Zero if node is leaf 9 | bytes32 key; // Zero if node is not leaf 10 | bytes leafData; // Zero if node is not leaf 11 | } 12 | -------------------------------------------------------------------------------- /contracts/tree/Cryptography.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice This library abstracts the hashing function used for the merkle tree implementation 5 | library CryptographyLib { 6 | /// @notice The hash method 7 | /// @param data The bytes input data. 8 | /// @return The returned hash result. 9 | function hash(bytes memory data) internal pure returns (bytes32) { 10 | return sha256(data); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 1 Leaf Index 0.yaml: -------------------------------------------------------------------------------- 1 | name: Test 1 Leaf Index 0 2 | function_name: generate_test_1_leaf_index_0 3 | description: Build a proof from a binary Merkle tree consisting of 1 leaf and leaf index 0. This proof is valid and verification is expected to pass. 4 | root: 5 | value: 40561de71f9ab9b37d9bb1194f96e73d9b24170d13e8fbe62216a5eddea86412 6 | encoding: hex 7 | data: 8 | value: 573e3ac3738932ec6c1bcb33981470f1cdcd34d0cc64f5b7168ec6f0b4a66295 9 | encoding: hex 10 | proof_set: [] 11 | proof_index: 0 12 | num_leaves: 1 13 | expected_verification: true 14 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 0 Leaves.yaml: -------------------------------------------------------------------------------- 1 | name: Test 0 Leaves 2 | function_name: generate_test_0_leaves 3 | description: Build a proof from a binary Merkle tree and manual set the number of leaves to 0. Setting the number of leaves to 0 implies that the source tree is empty. This proof is invalid because empty trees cannot produce a proof. Verification is expected to fail. 4 | root: 5 | value: ce04537ec4b4b84cf02fa5c50c7810e2f6592e3b00de7423d7c3e7e7d8689a67 6 | encoding: hex 7 | data: 8 | value: 6ccf3bf0187426970d45aefb2e4b86bda1bc1769d69c3f48a3c64766669abf12 9 | encoding: hex 10 | proof_set: [] 11 | proof_index: 0 12 | num_leaves: 0 13 | expected_verification: false 14 | -------------------------------------------------------------------------------- /test/utils/encodedValue.ts: -------------------------------------------------------------------------------- 1 | interface EncodedValueInput { 2 | value: string; 3 | encoding: BufferEncoding; 4 | } 5 | 6 | class EncodedValue { 7 | /// The encoded value 8 | value: string; 9 | 10 | /// The encoding type 11 | encoding: BufferEncoding; 12 | 13 | constructor(item: EncodedValueInput) { 14 | this.value = item.value; 15 | this.encoding = item.encoding; 16 | } 17 | 18 | toString(): string { 19 | if (this.encoding.toLowerCase() === 'hex') { 20 | return `0x${this.value}`; 21 | } 22 | 23 | return this.value; 24 | } 25 | 26 | toBuffer(): Uint8Array { 27 | return Buffer.from(this.value, this.encoding); 28 | } 29 | } 30 | 31 | export { EncodedValueInput, EncodedValue }; 32 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - "**" # matches every branch 8 | name: "Linting and Formatting" 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | persist-credentials: false 16 | - name: Reconfigure git to use HTTP authentication 17 | run: > 18 | git config --global url."https://github.com/".insteadOf 19 | ssh://git@github.com/ 20 | - uses: actions/setup-node@v1 21 | with: 22 | node-version: 14.x 23 | - run: npm ci 24 | - run: npm run lint 25 | - run: npm run format-check 26 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 1 Leaf Invalid Root.yaml: -------------------------------------------------------------------------------- 1 | name: Test 1 Leaf Invalid Root 2 | function_name: generate_test_1_leaf_invalid_root 3 | description: Build a proof from a binary Merkle tree consisting of 1 leaf and manually set the root. The root is manually set to the SHA256 hash of the string "invalid". This proof is invalid because root is not generated from canonical Merkle tree construction. Verification is expected to fail. 4 | root: 5 | value: f1234d75178d892a133a410355a5a990cf75d2f33eba25d575943d4df632f3a4 6 | encoding: hex 7 | data: 8 | value: 3ddceee04f31f8ba8ed2f2193157310529a8f9ba9427c3e8f1720d3a28ee49ed 9 | encoding: hex 10 | proof_set: [] 11 | proof_index: 0 12 | num_leaves: 1 13 | expected_verification: false 14 | -------------------------------------------------------------------------------- /contracts/tree/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | library Constants { 5 | /////////////// 6 | // Constants // 7 | /////////////// 8 | 9 | /// @dev Maximum tree height 10 | uint256 internal constant MAX_HEIGHT = 256; 11 | 12 | /// @dev Empty node hash 13 | bytes32 internal constant EMPTY = sha256(""); 14 | 15 | /// @dev Default value for sparse Merkle tree node 16 | bytes32 internal constant ZERO = bytes32(0); 17 | 18 | /// @dev The null pointer 19 | bytes32 internal constant NULL = bytes32(0); 20 | 21 | /// @dev The prefixes of leaves and nodes 22 | bytes1 internal constant LEAF_PREFIX = 0x00; 23 | bytes1 internal constant NODE_PREFIX = 0x01; 24 | } 25 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 1 Leaf Invalid Proof Index.yaml: -------------------------------------------------------------------------------- 1 | name: Test 1 Leaf Invalid Proof Index 2 | function_name: generate_test_1_leaf_invalid_proof_index 3 | description: Build a proof from a binary Merkle tree consisting of 1 leaf and manually set the leaf index to 1. Because the leaf index is zero-based, leaf index 1 refers to a position outside the range of the source tree. This proof is invalid because the leaf index is out of range. Verification is expected to fail. 4 | root: 5 | value: bcd692b474ff28ad1b46171b907170afffe213d0a999d37172016371f89b1d50 6 | encoding: hex 7 | data: 8 | value: c5211bc07d69b2071acd2315cb80b0e39352140a7f8b2b8f4216a455435153c5 9 | encoding: hex 10 | proof_set: [] 11 | proof_index: 1 12 | num_leaves: 1 13 | expected_verification: false 14 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Node.js Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 16 15 | - run: npm ci 16 | - run: npm test 17 | 18 | publish-npm: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 16 26 | registry-url: https://registry.npmjs.org/ 27 | - run: npm ci 28 | - run: npm publish --access public 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 100, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "preserve" 12 | } 13 | }, 14 | { 15 | "files": ["*.js", "*.ts"], 16 | "options": { 17 | "printWidth": 100, 18 | "semi": true, 19 | "tabWidth": 4, 20 | "useTabs": false, 21 | "singleQuote": true, 22 | "bracketSpacing": true, 23 | "trailingComma": "es5" 24 | } 25 | }, 26 | { 27 | "files": ["*.js", "*.ts", "*.json"], 28 | "options": { 29 | "useTabs": true 30 | } 31 | }, 32 | { 33 | "files": "*.md", 34 | "options": { 35 | "useTabs": false 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /contracts/mocks/MockMerkleSumTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {MerkleSumTree, SumMerkleProof} from "../tree/sum/SumMerkleTree.sol"; 5 | 6 | contract MockMerkleSumTree { 7 | bool public verified; 8 | bytes32 public root; 9 | uint256 public rootSum; 10 | 11 | function verify( 12 | bytes32 _root, 13 | uint256 _rootSum, 14 | bytes memory data, 15 | uint256 sum, 16 | SumMerkleProof memory proof, 17 | uint256 key, 18 | uint256 numLeaves 19 | ) public { 20 | bool result = MerkleSumTree.verify(_root, _rootSum, data, sum, proof, key, numLeaves); 21 | verified = result; 22 | } 23 | 24 | function computeRoot(bytes[] memory data, uint256[] memory values) public { 25 | (bytes32 resultRoot, uint256 resultSum) = MerkleSumTree.computeRoot(data, values); 26 | root = resultRoot; 27 | rootSum = resultSum; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 10 Leaves Index 4.yaml: -------------------------------------------------------------------------------- 1 | name: Test 10 Leaves Index 4 2 | function_name: generate_test_10_leaves_index_4 3 | description: Build a proof from a binary Merkle tree consisting of 10 leaves and leaf index 4. This proof is valid and verification is expected to pass. 4 | root: 5 | value: dfdf69ca3d8bbe42effc15304a76d95fc7e2dbf54cbc185a0f0157afb47c27e8 6 | encoding: hex 7 | data: 8 | value: 3d22b3c3a953a9f18ed12e1b28768132f84c6c69d18d88cfe71de0636a8b5c50 9 | encoding: hex 10 | proof_set: 11 | - value: 390af00738648704dbee5791e5a0dd83a0cf8e59901a519a2af4630ed9c2147b 12 | encoding: hex 13 | - value: 085c73fb213d3c8b32d104ca2236bc342eac5c204730cd2115f64e8cb754e66e 14 | encoding: hex 15 | - value: 01d58263228eac906f24a2b5f7aa53a59cd70b5895a262e8eb8151e3d571f694 16 | encoding: hex 17 | - value: 3462cc7c1d1bb60c889a6a43a71a4c23c7f4a9ab5c917a63170c182bc57b74e1 18 | encoding: hex 19 | proof_index: 4 20 | num_leaves: 10 21 | expected_verification: true 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | }, 8 | "extends": [ 9 | "airbnb-typescript", 10 | "plugin:@typescript-eslint/recommended", 11 | "prettier" 12 | ], 13 | "rules": { 14 | // Disable error on devDependencies importing since this isn't a TS library 15 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], 16 | "no-await-in-loop": 0, 17 | "prefer-destructuring": 0, 18 | "no-bitwise": 0, 19 | "import/extensions": [ 20 | "error", 21 | "ignorePackages", 22 | { 23 | "ts": "never" 24 | } 25 | ] 26 | }, 27 | // Disable no-unused-expressions to allow chai 'expect' expressions in testing 28 | "overrides": [ 29 | { 30 | "files": ["test/*.ts", "test/*/*.ts", "*test.ts"], 31 | "rules": { 32 | "@typescript-eslint/no-unused-expressions": "off" 33 | } 34 | } 35 | ] 36 | 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - "**" # matches every branch 8 | name: "Node.js Tests and Coverage" 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | persist-credentials: false 16 | - name: Reconfigure git to use HTTP authentication 17 | run: > 18 | git config --global url."https://github.com/".insteadOf 19 | ssh://git@github.com/ 20 | - uses: actions/setup-node@v1 21 | with: 22 | node-version: 14.x 23 | - run: npm ci 24 | - run: npm run build 25 | - run: npm test 26 | - run: npm run coverage 27 | 28 | # - name: Upload coverage to Codecov 29 | # uses: codecov/codecov-action@v1 30 | # with: 31 | # token: ${{ secrets.CODECOV_TOKEN }} 32 | # file: ./coverage.json 33 | # flags: unittests 34 | # name: codecov-umbrella 35 | # fail_ci_if_error: true 36 | -------------------------------------------------------------------------------- /contracts/tree/sum/TreeHasher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {CryptographyLib} from "../Cryptography.sol"; 5 | import {Constants} from "../Constants.sol"; 6 | 7 | /// @notice Hash a leaf node. 8 | /// @param value fee of the leaf. 9 | /// @param data, raw data of the leaf. 10 | // solhint-disable-next-line func-visibility 11 | function leafDigest(uint256 value, bytes memory data) pure returns (bytes32) { 12 | return CryptographyLib.hash(abi.encodePacked(Constants.LEAF_PREFIX, value, data)); 13 | } 14 | 15 | /// @notice Hash a node, which is not a leaf. 16 | /// @param leftValue, sum of fees in left subtree. 17 | /// @param left, left child hash. 18 | /// @param right, right child hash. 19 | // solhint-disable-next-line func-visibility 20 | function nodeDigest( 21 | uint256 leftValue, 22 | bytes32 left, 23 | uint256 rightValue, 24 | bytes32 right 25 | ) pure returns (bytes32) { 26 | return 27 | CryptographyLib.hash( 28 | abi.encodePacked(Constants.NODE_PREFIX, leftValue, left, rightValue, right) 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /contracts/mocks/MockSparseMerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {SparseMerkleTree, SparseCompactMerkleProof, MerkleBranch} from "../tree/sparse/SparseMerkleTree.sol"; 5 | 6 | contract MockSparseMerkleTree { 7 | bool public verified; 8 | bytes32 public root; 9 | 10 | function verifyCompact( 11 | SparseCompactMerkleProof memory proof, 12 | bytes32 key, 13 | bytes memory value, 14 | bytes32 _root 15 | ) public { 16 | verified = SparseMerkleTree.verifyCompact(proof, key, value, _root); 17 | } 18 | 19 | function addBranchesAndUpdate( 20 | MerkleBranch[] memory branches, 21 | bytes32 _root, 22 | bytes32 key, 23 | bytes memory value 24 | ) public { 25 | root = SparseMerkleTree.addBranchesAndUpdate(branches, _root, key, value); 26 | } 27 | 28 | function addBranchesAndDelete( 29 | MerkleBranch[] memory branches, 30 | bytes32 _root, 31 | bytes32 key 32 | ) public { 33 | root = SparseMerkleTree.addBranchesAndDelete(branches, _root, key); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/utils/utils.ts: -------------------------------------------------------------------------------- 1 | // Helper functions for testing 2 | import { BigNumber as BN } from 'ethers'; 3 | import hash from './cryptography'; 4 | 5 | export function randomAddress(): string { 6 | return hash(BN.from(Math.floor(Math.random() * 1_000_000)).toHexString()).slice(0, 42); 7 | } 8 | 9 | export function randomInt(max: number): number { 10 | return Math.floor(Math.random() * max); 11 | } 12 | 13 | export function uintToBytes32(i: number): string { 14 | const value = BN.from(i).toHexString(); 15 | let trimmedValue = value.slice(2); 16 | trimmedValue = '0'.repeat(64 - trimmedValue.length).concat(trimmedValue); 17 | return '0x'.concat(trimmedValue); 18 | } 19 | 20 | export function padUint(value: BN): string { 21 | // uint256 is encoded as 32 bytes, so pad that string. 22 | let trimmedValue = value.toHexString().slice(2); 23 | trimmedValue = '0'.repeat(64 - trimmedValue.length).concat(trimmedValue); 24 | return '0x'.concat(trimmedValue); 25 | } 26 | 27 | // Does a util exist for this in ethers.js ? 28 | export function padBytes(value: string): string { 29 | let trimmedValue = value.slice(2); 30 | trimmedValue = '0'.repeat(64 - trimmedValue.length).concat(trimmedValue); 31 | return '0x'.concat(trimmedValue); 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fuel Solidity Merkle Trees 2 | 3 | 4 | 5 | 6 | ![ci](https://github.com/fuellabs/fuel-v2-contracts/workflows/Node.js%20Tests%20and%20Coverage/badge.svg?branch=master) 7 | [![NPM Package](https://img.shields.io/npm/v/fuel-merkle-sol)](https://www.npmjs.org/package/fuel-merkle-sol) 8 | 9 | A Solidity implementation of a binary Merkle tree (specifically, a Merkle Mountain Range), a sparse Merkle tree, and a Merkle sum tree. 10 | 11 | ## Building From Source 12 | 13 | ### Dependencies 14 | 15 | | dep | version | 16 | | ------- | -------------------------------------------------------- | 17 | | Node.js | [>=v14.0.0](https://nodejs.org/en/blog/release/v14.0.0/) | 18 | 19 | ### Building 20 | 21 | Install dependencies: 22 | 23 | ```sh 24 | npm ci 25 | ``` 26 | 27 | Build and run tests: 28 | 29 | ```sh 30 | npm run build 31 | npm test 32 | ``` 33 | 34 | ## Contributing 35 | 36 | Code must be formatted and linted. 37 | 38 | ```sh 39 | npm run format 40 | npm run lint 41 | ``` 42 | 43 | ## License 44 | 45 | The primary license for this repo is `Apache-2.0`, see [`LICENSE`](./LICENSE). 46 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from 'hardhat/types'; 2 | import '@nomiclabs/hardhat-waffle'; 3 | import '@nomiclabs/hardhat-etherscan'; 4 | import 'hardhat-typechain'; 5 | import 'hardhat-deploy'; 6 | import 'solidity-coverage'; 7 | import 'hardhat-gas-reporter'; 8 | import { config as dotEnvConfig } from 'dotenv'; 9 | 10 | dotEnvConfig(); 11 | 12 | const INFURA_API_KEY = process.env.INFURA_API_KEY || ''; 13 | const ROPSTEN_PRIVATE_KEY = 14 | process.env.ROPSTEN_PRIVATE_KEY || 15 | '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'; // well known private key 16 | const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY; 17 | 18 | const config: HardhatUserConfig = { 19 | defaultNetwork: 'hardhat', 20 | solidity: { 21 | compilers: [ 22 | { 23 | version: '0.8.4', 24 | }, 25 | ], 26 | }, 27 | mocha: { 28 | timeout: 180_000, 29 | }, 30 | networks: { 31 | hardhat: { 32 | accounts: { 33 | count: 128, 34 | }, 35 | }, 36 | ropsten: { 37 | url: `https://ropsten.infura.io/v3/${INFURA_API_KEY}`, 38 | accounts: [ROPSTEN_PRIVATE_KEY], 39 | }, 40 | coverage: { 41 | url: 'http://127.0.0.1:8555', 42 | }, 43 | }, 44 | etherscan: { 45 | apiKey: ETHERSCAN_API_KEY, 46 | }, 47 | }; 48 | 49 | export default config; 50 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 100 Leaves Index 10.yaml: -------------------------------------------------------------------------------- 1 | name: Test 100 Leaves Index 10 2 | function_name: generate_test_100_leaves_index_10 3 | description: Build a proof from a binary Merkle tree consisting of 100 leaves and leaf index 10. This proof is valid and verification is expected to pass. 4 | root: 5 | value: a9b6d97f45ed55e2242fa995c5d412a46a2e57a5a3f676aa9e60d973479c2446 6 | encoding: hex 7 | data: 8 | value: 8ac6d60867dc72a799812086355af4ed38f68642e3857aea2cc48c94586179d1 9 | encoding: hex 10 | proof_set: 11 | - value: 51159906d4d62672664766ed8757915c7c925cfee778b8b3359df37a73ebeca7 12 | encoding: hex 13 | - value: 2b4f180744f30b41daad98a0d9f80fd8302de65ef33487e75b3eec6122c8e3f3 14 | encoding: hex 15 | - value: 59c518c8c13721afd212b11680680ed64270e79b503f5e7e342b9b201e7fa4f2 16 | encoding: hex 17 | - value: 4175b79f36911a6c629de2e70b03ff9cbfc51867ad71ef6462a8999c65783ef1 18 | encoding: hex 19 | - value: 8cdc18e4cb221ba071cd4804726794719e9202b8b95d8148e4492d3d76a4e9aa 20 | encoding: hex 21 | - value: 2434ae470b4d9f05943e65b63236ae7888281d06f9412068f602ca2662e73471 22 | encoding: hex 23 | - value: 1b7cc44323e578aa1380f7b5012ede68663732413625a0028c1c0d3e7652baa9 24 | encoding: hex 25 | proof_index: 10 26 | num_leaves: 100 27 | expected_verification: true 28 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 1024 Leaves Index 512.yaml: -------------------------------------------------------------------------------- 1 | name: Test 1024 Leaves Index 512 2 | function_name: generate_test_1024_leaves_index_512 3 | description: Build a proof from a binary Merkle tree consisting of 1024 leaves and leaf index 512. This proof is valid and verification is expected to pass. 4 | root: 5 | value: 1a57447fae64c1b78d02a05ac5255b2d1b350b8ce10c1180002288a9a49cf6fb 6 | encoding: hex 7 | data: 8 | value: 424c66d6981633789ccbd0a8e142ccff24208943cd143f0e2dc5d84542cb2ead 9 | encoding: hex 10 | proof_set: 11 | - value: 5f7a260b5b5dc663ef6a338ba673c71ef611e4a5e52dc41e9bd82dd9a9507c30 12 | encoding: hex 13 | - value: 51dbb7b62080c64fbc976425d0032933d1126df6f223420b332297c8ca133cee 14 | encoding: hex 15 | - value: 52c32361aabbcdb8a2d7fa6ede781716cfcb7e2708a7d1cae69b242154ba4d65 16 | encoding: hex 17 | - value: ef41093d90dd8ece225c19f2073dfea48890f10087dcc6451c21d76a91feecfb 18 | encoding: hex 19 | - value: d306531e7c69cb02b7b4008281007360d00af0f640d80d44ac9730a263582967 20 | encoding: hex 21 | - value: 200bf4ae7f3d12cfa374d72d8d73cf320b3f38bfb3dde85793a8d06f040da181 22 | encoding: hex 23 | - value: c9b8de8d214f5cf0ca3aeae52def4456268f3c1f06e905ed741bc18b10844f8f 24 | encoding: hex 25 | - value: a09d8a561023e032a8b560f1e85d300a3122b0cdf83b47798802d25d4db1083c 26 | encoding: hex 27 | - value: 782b6c333967ceb052cc479f83da4cd6d9e3b75c963cd4f4c4e8f1d7dbcb3436 28 | encoding: hex 29 | - value: 854f3ff8757852014af53efa1e8b88622281141ea5ad8f1bd21a133f467af2c4 30 | encoding: hex 31 | proof_index: 512 32 | num_leaves: 1024 33 | expected_verification: true 34 | -------------------------------------------------------------------------------- /test/test_helpers/sumMerkleTree.ts: -------------------------------------------------------------------------------- 1 | /// Some useful helper methods for testing Merkle trees. 2 | import { ethers } from 'hardhat'; 3 | import { BigNumber as BN, Contract } from 'ethers'; 4 | import { constructTree, getProof } from '@fuel-ts/merklesum'; 5 | import { padBytes } from '../utils/utils'; 6 | 7 | // Build a tree, generate a proof for a given leaf (with optional tampering), and verify using contract 8 | async function checkVerify( 9 | msto: Contract, 10 | numLeaves: number, 11 | leafNumber: number, 12 | tamper_data: boolean, 13 | tamper_sum: boolean 14 | ): Promise { 15 | const data = []; 16 | const keys = []; 17 | const sums = []; 18 | const size = numLeaves; 19 | for (let i = 0; i < size; i += 1) { 20 | data.push(BN.from(i).toHexString()); 21 | keys.push(BN.from(i).toHexString()); 22 | sums.push(BN.from(i)); 23 | } 24 | 25 | const nodeToProve = leafNumber - 1; 26 | const nodes = constructTree(sums, data); 27 | const proof = getProof(nodes, nodeToProve); 28 | const root = nodes[nodes.length - 1]; 29 | 30 | let dataToProve = data[nodeToProve]; 31 | let sumToProve = sums[nodeToProve]; 32 | 33 | if (tamper_data) { 34 | // Introduce bad data: 35 | const badData = ethers.utils.formatBytes32String('badData'); 36 | dataToProve = badData; 37 | } 38 | 39 | if (tamper_sum) { 40 | // Introduce bad data: 41 | const badSum = BN.from(42); 42 | sumToProve = badSum; 43 | } 44 | 45 | await msto.verify( 46 | root.hash, 47 | root.sum, 48 | dataToProve, 49 | sumToProve, 50 | proof, 51 | padBytes(keys[nodeToProve]), 52 | keys.length 53 | ); 54 | 55 | const result = await msto.verified(); 56 | 57 | return result; 58 | } 59 | 60 | export default checkVerify; 61 | -------------------------------------------------------------------------------- /test/test_vectors/binary_proofs/Test 1024 Leaves Invalid Root.yaml: -------------------------------------------------------------------------------- 1 | name: Test 1024 Leaves Invalid Root 2 | function_name: generate_test_1024_leaves_invalid_root 3 | description: Build a proof from a binary Merkle tree consisting of 1024 leaves and manually set the root. The root is manually set to the SHA256 hash of the string "invalid". This proof is invalid because root is not generated from canonical Merkle tree construction. Verification is expected to fail. 4 | root: 5 | value: f1234d75178d892a133a410355a5a990cf75d2f33eba25d575943d4df632f3a4 6 | encoding: hex 7 | data: 8 | value: 8711188204125cd3bde90be717dc0a106935559431c2bdc6d6d114cf71f46b4a 9 | encoding: hex 10 | proof_set: 11 | - value: bc6abd3c380c4d69085c2f879e784f096fcd0a9e921b359fecf87ef76ca4bc67 12 | encoding: hex 13 | - value: c82561818abd7843b694824f21024c134158ce89445e7dc4a4aebebdb5a7dd5e 14 | encoding: hex 15 | - value: f94219552c116bad634d8f9c98157ee414741ec053ea7f1ff3412df4a7b2899f 16 | encoding: hex 17 | - value: 617e973c2ac3dd9adfeb944ca1ee4d54053e2520c7d7d435872dd60f6c45c1bb 18 | encoding: hex 19 | - value: 3fe1e227f51971bff35c83338f48282c81f94edb7577cce7bb56661e14aab740 20 | encoding: hex 21 | - value: cbabcf9e2f7ef7a85eb98e49380236c767ec32cfac1c878a03b22492f84916a6 22 | encoding: hex 23 | - value: 2689ef456cd69c7af07084ab16922331127544f038627bfa04f8d95effd1150c 24 | encoding: hex 25 | - value: 8007f304566161b05a29f4380dee1b5f4178b1e0a5dfadcf6613f6e15a0ec761 26 | encoding: hex 27 | - value: 6d8b4dd3156d16df1883acf6d6eb22f27635b44459b09a5a77a7bda6fb919d13 28 | encoding: hex 29 | - value: c2a27c22ec6bfd35c83dcdf5379539c0865c23341ef8abba1248aa44fe36d23f 30 | encoding: hex 31 | proof_index: 512 32 | num_leaves: 1024 33 | expected_verification: false 34 | -------------------------------------------------------------------------------- /contracts/mocks/MockBinaryMerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {BinaryMerkleTree, MerkleBranch} from "../tree/binary/BinaryMerkleTree.sol"; 5 | 6 | contract MockBinaryMerkleTree { 7 | bool public verified; 8 | bytes32 public root; 9 | 10 | function verify( 11 | bytes32 _root, 12 | bytes memory data, 13 | bytes32[] memory proof, 14 | uint256 key, 15 | uint256 numLeaves 16 | ) public returns (bool) { 17 | bool result = BinaryMerkleTree.verify(_root, data, proof, key, numLeaves); 18 | verified = result; 19 | return result; 20 | } 21 | 22 | function verifyDigest( 23 | bytes32 _root, 24 | bytes32 digest, 25 | bytes32[] memory proof, 26 | uint256 key, 27 | uint256 numLeaves 28 | ) public pure returns (bool) { 29 | return BinaryMerkleTree.verifyDigest(_root, digest, proof, key, numLeaves); 30 | } 31 | 32 | function computeRoot(bytes[] memory data) public returns (bytes32) { 33 | bytes32 result = BinaryMerkleTree.computeRoot(data); 34 | root = result; 35 | return root; 36 | } 37 | 38 | function append( 39 | uint256 numLeaves, 40 | bytes memory data, 41 | bytes32[] memory proof 42 | ) public returns (bytes32, bool) { 43 | (root, verified) = BinaryMerkleTree.append(numLeaves, data, proof); 44 | return (root, verified); 45 | } 46 | 47 | function addBranchesAndUpdate( 48 | MerkleBranch[] memory branches, 49 | bytes32 _root, 50 | bytes32 key, 51 | bytes memory value, 52 | uint256 numLeaves 53 | ) public returns (bytes32) { 54 | root = BinaryMerkleTree.addBranchesAndUpdate(branches, _root, key, value, numLeaves); 55 | return root; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/test_helpers/binaryMerkleTree.ts: -------------------------------------------------------------------------------- 1 | /// Some useful helper methods for testing binary Merkle trees. 2 | import { ethers } from 'hardhat'; 3 | import { BigNumber as BN, Contract } from 'ethers'; 4 | import { constructTree, getProof, calcRoot } from '@fuel-ts/merkle'; 5 | import { padBytes } from '../utils/utils'; 6 | 7 | // Build a tree, generate a proof for a given leaf (with optional tampering), and verify using contract 8 | export async function checkVerify( 9 | bmto: Contract, 10 | numLeaves: number, 11 | leafNumber: number, 12 | tamper: boolean 13 | ): Promise { 14 | const data = []; 15 | const keys = []; 16 | for (let i = 0; i < numLeaves; i += 1) { 17 | data.push(BN.from(i).toHexString()); 18 | keys.push(BN.from(i).toHexString()); 19 | } 20 | const leafToProve = leafNumber - 1; 21 | const nodes = constructTree(data); 22 | const root = nodes[nodes.length - 1]; 23 | let dataToProve = data[leafToProve]; 24 | const proof = getProof(nodes, leafToProve); 25 | 26 | if (tamper) { 27 | // Introduce bad data: 28 | const badData = ethers.utils.formatBytes32String('badData'); 29 | dataToProve = badData; 30 | } 31 | 32 | await bmto.verify(root.hash, dataToProve, proof, padBytes(keys[leafToProve]), keys.length); 33 | 34 | const result = await bmto.verified(); 35 | 36 | return result; 37 | } 38 | 39 | export async function checkAppend( 40 | bmto: Contract, 41 | numLeaves: number, 42 | badProof: boolean 43 | ): Promise { 44 | const data = []; 45 | const size = numLeaves; 46 | for (let i = 0; i < size; i += 1) { 47 | data.push(BN.from(i).toHexString()); 48 | } 49 | 50 | const leafToAppend = BN.from(42).toHexString(); 51 | data.push(leafToAppend); 52 | const nodes = constructTree(data); 53 | 54 | const proof = getProof(nodes, numLeaves); 55 | 56 | if (badProof) { 57 | proof.push(ethers.constants.HashZero); 58 | } 59 | 60 | await bmto.append(numLeaves, leafToAppend, proof); 61 | 62 | const root = await bmto.root(); 63 | return root === calcRoot(data); 64 | } 65 | -------------------------------------------------------------------------------- /test/merkleTreeSum.ts: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import { solidity } from 'ethereum-waffle'; 3 | import { ethers } from 'hardhat'; 4 | import { BigNumber as BN, Contract } from 'ethers'; 5 | import { calcRoot } from '@fuel-ts/merklesum'; 6 | import checkVerify from './test_helpers/sumMerkleTree'; 7 | import hash from './utils/cryptography'; 8 | 9 | chai.use(solidity); 10 | const { expect } = chai; 11 | 12 | describe('sum Merkle tree', async () => { 13 | let mstlib: Contract; 14 | let msto: Contract; 15 | 16 | before(async () => { 17 | const merkleSumFactory = await ethers.getContractFactory('MerkleSumTree'); 18 | mstlib = await merkleSumFactory.deploy(); 19 | await mstlib.deployed(); 20 | }); 21 | 22 | beforeEach(async () => { 23 | const merkleSumTreeFactory = await ethers.getContractFactory('MockMerkleSumTree', { 24 | libraries: { MerkleSumTree: mstlib.address }, 25 | }); 26 | msto = await merkleSumTreeFactory.deploy(); 27 | await msto.deployed(); 28 | }); 29 | 30 | it('Compute root', async () => { 31 | const data = []; 32 | const values = []; 33 | const valuesBN = []; 34 | const size = 100; 35 | for (let i = 0; i < size; i += 1) { 36 | data.push(hash('0xabde')); 37 | values.push(BN.from(1).toHexString()); 38 | valuesBN.push(BN.from(1)); 39 | } 40 | await msto.computeRoot(data, values); 41 | const res = calcRoot(valuesBN, data); 42 | 43 | // Compare results 44 | expect(res.sum).to.be.equal(100); // True answer 45 | expect(await msto.root()).to.be.equal(res.hash); 46 | expect(await msto.rootSum()).to.be.equal(res.sum); 47 | }); 48 | 49 | it('Verifications', async () => { 50 | const testCases = [ 51 | { numLeaves: 100, proveLeaf: 100 }, 52 | { numLeaves: 100, proveLeaf: 99 }, 53 | { numLeaves: 99, proveLeaf: 42 }, 54 | { numLeaves: 1, proveLeaf: 1 }, 55 | ]; 56 | 57 | for (let i = 0; i < testCases.length; i += 1) { 58 | // Expect success 59 | expect( 60 | await checkVerify( 61 | msto, 62 | testCases[i].numLeaves, 63 | testCases[i].proveLeaf, 64 | false, 65 | false 66 | ) 67 | ).to.equal(true); 68 | 69 | // Tamper with data 70 | expect( 71 | await checkVerify(msto, testCases[i].numLeaves, testCases[i].proveLeaf, false, true) 72 | ).to.equal(false); 73 | 74 | // Tamper with sums 75 | expect( 76 | await checkVerify(msto, testCases[i].numLeaves, testCases[i].proveLeaf, true, false) 77 | ).to.equal(false); 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fuel-contracts/merkle-sol", 3 | "version": "0.1.4", 4 | "description": "The Solidity implemention of the Merkle trees used in Fuel v2.", 5 | "main": "test/index.js", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "build": "npm run clean && npm run compile", 9 | "clean": "npx hardhat clean", 10 | "compile": "npx hardhat compile --show-stack-traces", 11 | "coverage": "npm run build && npx hardhat coverage --temp artifacts --network coverage", 12 | "format": "prettier --write \"contracts/**/*.sol\" \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.yaml\"", 13 | "format-check": "prettier --check \"contracts/**/*.sol\" \"**/*.ts\" \"**/*.js\" \"**/*.md\" \"**/*.yaml\"", 14 | "lint": "npx solhint \"contracts/**/*.sol\" && npx eslint . && npx markdownlint --ignore node_modules **/*.md", 15 | "test": "npx hardhat test", 16 | "test-no-compile": "npx hardhat test --no-compile", 17 | "test-parallel": "npx mocha 'test/**/*.ts' --recursive --parallel --require hardhat/register" 18 | }, 19 | "devDependencies": { 20 | "@ethersproject/abstract-provider": "^5.1.0", 21 | "@ethersproject/hardware-wallets": "^5.0.14", 22 | "@fuel-ts/merkle": "^0.5.0", 23 | "@fuel-ts/merklesum": "^0.5.0", 24 | "@fuel-ts/sparsemerkle": "^0.5.0", 25 | "@nomiclabs/hardhat-ethers": "^2.0.2", 26 | "@nomiclabs/hardhat-etherscan": "^2.1.2", 27 | "@nomiclabs/hardhat-waffle": "^2.0.1", 28 | "@typechain/ethers-v5": "^6.0.5", 29 | "@types/chai": "^4.2.18", 30 | "@types/js-yaml": "^4.0.5", 31 | "@types/mocha": "^8.2.2", 32 | "@types/node": "^15.3.0", 33 | "@typescript-eslint/eslint-plugin": "^4.23.0", 34 | "@typescript-eslint/parser": "^4.23.0", 35 | "chai": "^4.3.4", 36 | "dotenv": "^9.0.2", 37 | "eslint": "^7.26.0", 38 | "eslint-config-airbnb-typescript": "^12.3.1", 39 | "eslint-config-prettier": "^8.3.0", 40 | "eslint-plugin-import": "^2.23.2", 41 | "eslint-plugin-jsx-a11y": "^6.4.1", 42 | "eslint-plugin-prettier": "^3.3.0", 43 | "eslint-plugin-react": "^7.23.2", 44 | "eslint-plugin-react-hooks": "^4.2.0", 45 | "ethereum-waffle": "^3.3.0", 46 | "ethers": "^5.1.4", 47 | "hardhat": "^2.3.0", 48 | "hardhat-deploy": "^0.7.5", 49 | "hardhat-gas-reporter": "^1.0.4", 50 | "hardhat-typechain": "^0.3.5", 51 | "js-yaml": "^4.1.0", 52 | "markdownlint": "^0.23.1", 53 | "markdownlint-cli": "^0.27.1", 54 | "prettier": "^2.3.0", 55 | "prettier-plugin-solidity": "^1.0.0-beta.10", 56 | "solc": "^0.8.4", 57 | "solhint": "3.3.4", 58 | "solidity-coverage": "^0.7.20", 59 | "ts-generator": "^0.1.1", 60 | "ts-node": "^9.1.1", 61 | "typechain": "^4.0.3", 62 | "typescript": "^4.2.4" 63 | }, 64 | "dependencies": { 65 | "ganache-cli": "^6.12.2", 66 | "mocha": "^10.0.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/tree/binary/TreeHasher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {CryptographyLib} from "../Cryptography.sol"; 5 | import {Constants} from "../Constants.sol"; 6 | import {Node} from "./Node.sol"; 7 | 8 | /// @notice hash some data 9 | /// @param data: The data to be hashed 10 | // solhint-disable-next-line func-visibility 11 | function hash(bytes memory data) pure returns (bytes32) { 12 | return CryptographyLib.hash(data); 13 | } 14 | 15 | /// @notice Calculate the digest of a node 16 | /// @param left : The left child 17 | /// @param right: The right child 18 | /// @return digest : The node digest 19 | // solhint-disable-next-line func-visibility 20 | function nodeDigest(bytes32 left, bytes32 right) pure returns (bytes32 digest) { 21 | digest = hash(abi.encodePacked(Constants.NODE_PREFIX, left, right)); 22 | } 23 | 24 | /// @notice Calculate the digest of a leaf 25 | /// @param data : The data of the leaf 26 | /// @return digest : The leaf digest 27 | // solhint-disable-next-line func-visibility 28 | function leafDigest(bytes memory data) pure returns (bytes32 digest) { 29 | digest = hash(abi.encodePacked(Constants.LEAF_PREFIX, data)); 30 | } 31 | 32 | /// @notice Hash a leaf node. 33 | /// @param data, raw data of the leaf. 34 | /// @return The leaf represented as a Node struct 35 | // solhint-disable-next-line func-visibility 36 | function hashLeaf(bytes memory data) pure returns (Node memory) { 37 | bytes32 digest = leafDigest(data); 38 | return Node(digest, Constants.NULL, Constants.NULL); 39 | } 40 | 41 | /// @notice Hash a node, which is not a leaf. 42 | /// @param left, left child hash. 43 | /// @param right, right child hash. 44 | /// @param leftPtr, the pointer to the left child 45 | /// @param rightPtr, the pointer to the right child 46 | /// @return : The new Node object 47 | // solhint-disable-next-line func-visibility 48 | function hashNode( 49 | bytes32 leftPtr, 50 | bytes32 rightPtr, 51 | bytes32 left, 52 | bytes32 right 53 | ) pure returns (Node memory) { 54 | bytes32 digest = nodeDigest(left, right); 55 | return Node(digest, leftPtr, rightPtr); 56 | } 57 | 58 | /// @notice Parse a node's data into its left and right children 59 | /// @param node: The node to be parsed 60 | /// @return : Pointers to the left and right children 61 | // solhint-disable-next-line func-visibility 62 | function parseNode(Node memory node) pure returns (bytes32, bytes32) { 63 | return (node.leftChildPtr, node.rightChildPtr); 64 | } 65 | 66 | /// @notice See if node has children, otherwise it is a leaf 67 | /// @param node: The node to be parsed 68 | /// @return : Whether the node is a leaf. 69 | // solhint-disable-next-line func-visibility 70 | function isLeaf(Node memory node) pure returns (bool) { 71 | return (node.leftChildPtr == Constants.ZERO || node.rightChildPtr == Constants.ZERO); 72 | } 73 | -------------------------------------------------------------------------------- /contracts/tree/sparse/TreeHasher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {CryptographyLib} from "../Cryptography.sol"; 5 | import {Constants} from "../Constants.sol"; 6 | import {Node} from "./Node.sol"; 7 | 8 | /// @notice Contains functions for hashing leaves and nodes, and parsing their data 9 | 10 | /// @notice hash some data 11 | /// @param data: The data to be hashed 12 | // solhint-disable-next-line func-visibility 13 | function hash(bytes memory data) pure returns (bytes32) { 14 | return CryptographyLib.hash(data); 15 | } 16 | 17 | // solhint-disable-next-line func-visibility 18 | function nodeDigest(bytes32 left, bytes32 right) pure returns (bytes32 digest) { 19 | digest = hash(abi.encodePacked(Constants.NODE_PREFIX, left, right)); 20 | } 21 | 22 | // solhint-disable-next-line func-visibility 23 | function leafDigest(bytes32 key, bytes memory value) pure returns (bytes32 digest) { 24 | digest = hash(abi.encodePacked(Constants.LEAF_PREFIX, key, hash(value))); 25 | } 26 | 27 | /// @notice Hash a leaf node. 28 | /// @param key: The key of the leaf 29 | /// @param data, raw data of the leaf. 30 | /// @return The leaf represented as a Node struct 31 | // solhint-disable-next-line func-visibility 32 | function hashLeaf(bytes32 key, bytes memory data) pure returns (Node memory) { 33 | bytes32 digest = leafDigest(key, data); 34 | return Node(digest, Constants.LEAF_PREFIX, Constants.NULL, Constants.NULL, key, data); 35 | } 36 | 37 | /// @notice Hash a node, which is not a leaf. 38 | /// @param left, left child hash. 39 | /// @param right, right child hash. 40 | /// @param leftPtr, the pointer to the left child 41 | /// @param rightPtr, the pointer to the right child 42 | // solhint-disable-next-line func-visibility 43 | function hashNode( 44 | bytes32 leftPtr, 45 | bytes32 rightPtr, 46 | bytes32 left, 47 | bytes32 right 48 | ) pure returns (Node memory) { 49 | bytes32 digest = nodeDigest(left, right); 50 | return Node(digest, Constants.NODE_PREFIX, leftPtr, rightPtr, Constants.NULL, ""); 51 | } 52 | 53 | /// @notice Parse a node's data into its left and right children 54 | /// @param node: The node to be parsed 55 | // solhint-disable-next-line func-visibility 56 | function parseNode(Node memory node) pure returns (bytes32, bytes32) { 57 | return (node.leftChildPtr, node.rightChildPtr); 58 | } 59 | 60 | /// @notice Parse a leaf's data into its key and data 61 | /// @param leaf: The leaf to be parsed 62 | // solhint-disable-next-line func-visibility 63 | function parseLeaf(Node memory leaf) pure returns (bytes32, bytes memory) { 64 | return (leaf.key, leaf.leafData); 65 | } 66 | 67 | /// @notice Inspect the prefix of a node's data to determine if it is a leaf 68 | /// @param node: The node to be parsed 69 | // solhint-disable-next-line func-visibility 70 | function isLeaf(Node memory node) pure returns (bool) { 71 | return (node.prefix == Constants.LEAF_PREFIX); 72 | } 73 | -------------------------------------------------------------------------------- /contracts/tree/sparse/Proofs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {parseLeaf, leafDigest, nodeDigest} from "./TreeHasher.sol"; 5 | import {Node} from "./Node.sol"; 6 | import {isDefaultValue, getBitAtFromMSB, shrinkBytes32Array} from "../Utils.sol"; 7 | import {Constants} from "../Constants.sol"; 8 | 9 | /// @notice A full (non-compact) sparse Merkle proof 10 | struct SparseMerkleProof { 11 | bytes32[] SideNodes; 12 | Node NonMembershipLeaf; 13 | Node Sibling; 14 | } 15 | 16 | /// @notice A sparse Merkle proof 17 | /// @dev Minimal sidenodes are provided, with a bitmask indicating at which height they occur 18 | struct SparseCompactMerkleProof { 19 | bytes32[] SideNodes; 20 | Node NonMembershipLeaf; 21 | uint256[] BitMask; 22 | uint256 NumSideNodes; 23 | Node Sibling; 24 | } 25 | 26 | /// @notice Verify a proof is valid 27 | /// @param proof: A decompacted sparse Merkle proof 28 | /// @param root: The root of the SMT containing the leaf to be proved 29 | /// @param key: The key of the leave being proved 30 | /// @param value: The value of the leaf 31 | /// @return result : Whether the proof is valid or not 32 | // solhint-disable-next-line func-visibility 33 | function verifyProof( 34 | SparseMerkleProof memory proof, 35 | bytes32 root, 36 | bytes32 key, 37 | bytes memory value 38 | ) pure returns (bool) { 39 | bytes32 currentHash; 40 | bytes32 actualPath; 41 | bytes memory data; 42 | 43 | /// @dev No bytes comparison in solidity, compare hashes instead 44 | if (isDefaultValue(value)) { 45 | // Non-membership proof 46 | if (proof.NonMembershipLeaf.digest == Constants.ZERO) { 47 | currentHash = Constants.ZERO; 48 | } else { 49 | // leaf is an unrelated leaf 50 | (actualPath, data) = parseLeaf(proof.NonMembershipLeaf); 51 | if (actualPath == key) { 52 | // Leaf does exist: non-membership proof failed 53 | return false; 54 | } 55 | currentHash = leafDigest(actualPath, data); 56 | } 57 | } else { 58 | // Membership proof 59 | currentHash = leafDigest(key, value); 60 | } 61 | 62 | // Recompute root. 63 | for (uint256 i = 0; i < proof.SideNodes.length; i += 1) { 64 | if (getBitAtFromMSB(key, proof.SideNodes.length - 1 - i) == 1) { 65 | currentHash = nodeDigest(proof.SideNodes[i], currentHash); 66 | } else { 67 | currentHash = nodeDigest(currentHash, proof.SideNodes[i]); 68 | } 69 | } 70 | return (currentHash == root); 71 | } 72 | 73 | /// @notice Turn a sparse Merkle proof into a compact sparse Merkle proof 74 | /// @param proof: The sparse Merkle proof 75 | // solhint-disable-next-line func-visibility 76 | function compactProof(SparseMerkleProof memory proof) 77 | pure 78 | returns (SparseCompactMerkleProof memory) 79 | { 80 | uint256[] memory bitMask = new uint256[](proof.SideNodes.length); 81 | // Create a large-enough dynamic array for the sidenodes 82 | bytes32[] memory compactedSideNodes = new bytes32[](256); 83 | bytes32 node; 84 | uint256 sideNodesCount = 0; 85 | 86 | /// Compact proof into array of non-zero sidenodes and a bitmask 87 | for (uint256 i = 0; i < proof.SideNodes.length; i += 1) { 88 | node = proof.SideNodes[i]; 89 | if (node == Constants.ZERO) { 90 | bitMask[i] = 0; 91 | } else { 92 | compactedSideNodes[sideNodesCount++] = node; 93 | bitMask[i] = 1; 94 | } 95 | } 96 | 97 | /// Shrink the array of sidenodes to its final size 98 | bytes32[] memory finalCompactedSideNodes = shrinkBytes32Array( 99 | compactedSideNodes, 100 | sideNodesCount 101 | ); 102 | 103 | return 104 | SparseCompactMerkleProof( 105 | finalCompactedSideNodes, 106 | proof.NonMembershipLeaf, 107 | bitMask, 108 | proof.SideNodes.length, 109 | proof.Sibling 110 | ); 111 | } 112 | 113 | /// @notice Turns a Compact sparse Merkle proof into a full sparse Merkle proof 114 | /// @param proof: The Compact proof to be decompacted 115 | // solhint-disable-next-line func-visibility 116 | function decompactProof(SparseCompactMerkleProof memory proof) 117 | pure 118 | returns (SparseMerkleProof memory) 119 | { 120 | bytes32[] memory decompactedSideNodes = new bytes32[](proof.NumSideNodes); 121 | uint256 position = 0; 122 | 123 | for (uint256 i = 0; i < proof.NumSideNodes; i += 1) { 124 | if (proof.BitMask[i] == 0) { 125 | decompactedSideNodes[i] = Constants.ZERO; 126 | } else { 127 | decompactedSideNodes[i] = proof.SideNodes[position]; 128 | position += 1; 129 | } 130 | } 131 | 132 | return SparseMerkleProof(decompactedSideNodes, proof.NonMembershipLeaf, proof.Sibling); 133 | } 134 | -------------------------------------------------------------------------------- /contracts/tree/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {Constants} from "./Constants.sol"; 5 | 6 | /// @notice Calculate the starting bit of the path to a leaf 7 | /// @param numLeaves : The total number of leaves in the tree 8 | /// @return startingBit : The starting bit of the path 9 | // solhint-disable-next-line func-visibility 10 | function getStartingBit(uint256 numLeaves) pure returns (uint256 startingBit) { 11 | // Determine height of the left subtree. This is the maximum path length, so all paths start at this offset from the right-most bit 12 | startingBit = 0; 13 | while ((1 << startingBit) < numLeaves) { 14 | startingBit += 1; 15 | } 16 | return Constants.MAX_HEIGHT - startingBit; 17 | } 18 | 19 | /// @notice Calculate the length of the path to a leaf 20 | /// @param key: The key of the leaf 21 | /// @param numLeaves: The total number of leaves in the tree 22 | /// @return pathLength : The length of the path to the leaf 23 | /// @dev A precondition to this function is that `numLeaves > 1`, so that `(pathLength - 1)` does not cause an underflow when pathLength = 0. 24 | // solhint-disable-next-line func-visibility 25 | function pathLengthFromKey(uint256 key, uint256 numLeaves) pure returns (uint256 pathLength) { 26 | // Get the height of the left subtree. This is equal to the offset of the starting bit of the path 27 | pathLength = 256 - getStartingBit(numLeaves); 28 | 29 | // Determine the number of leaves in the left subtree 30 | uint256 numLeavesLeftSubTree = (1 << (pathLength - 1)); 31 | 32 | // If leaf is in left subtree, path length is full height of left subtree 33 | if (key <= numLeavesLeftSubTree - 1) { 34 | return pathLength; 35 | } 36 | // Otherwise, if left sub tree has only one leaf, path has one additional step 37 | else if (numLeavesLeftSubTree == 1) { 38 | return 1; 39 | } 40 | // Otherwise, if right sub tree has only one leaf, path has one additional step 41 | else if (numLeaves - numLeavesLeftSubTree <= 1) { 42 | return 1; 43 | } 44 | // Otherwise, add 1 to height and recurse into right subtree 45 | else { 46 | return 1 + pathLengthFromKey(key - numLeavesLeftSubTree, numLeaves - numLeavesLeftSubTree); 47 | } 48 | } 49 | 50 | /// @notice Gets the bit at an offset from the most significant bit 51 | /// @param data: The data to check the bit 52 | /// @param position: The position of the bit to check 53 | // solhint-disable-next-line func-visibility 54 | function getBitAtFromMSB(bytes32 data, uint256 position) pure returns (uint256) { 55 | if (uint8(data[position / 8]) & (1 << (8 - 1 - (position % 8))) > 0) { 56 | return 1; 57 | } else { 58 | return 0; 59 | } 60 | } 61 | 62 | /// @notice Reverses an array 63 | /// @param sideNodes: The array of sidenodes to be reversed 64 | /// @return The reversed array 65 | // solhint-disable-next-line func-visibility 66 | function reverseSideNodes(bytes32[] memory sideNodes) pure returns (bytes32[] memory) { 67 | uint256 left = 0; 68 | uint256 right = sideNodes.length - 1; 69 | 70 | while (left < right) { 71 | (sideNodes[left], sideNodes[right]) = (sideNodes[right], sideNodes[left]); 72 | left = left + 1; 73 | right = right - 1; 74 | } 75 | return sideNodes; 76 | } 77 | 78 | /// @notice Counts the number of leading bits two bytes32 have in common 79 | /// @param data1: The first piece of data to compare 80 | /// @param data2: The second piece of data to compare 81 | /// @return The number of shared leading bits 82 | // solhint-disable-next-line func-visibility 83 | function countCommonPrefix(bytes32 data1, bytes32 data2) pure returns (uint256) { 84 | uint256 count = 0; 85 | 86 | for (uint256 i = 0; i < Constants.MAX_HEIGHT; i++) { 87 | if (getBitAtFromMSB(data1, i) == getBitAtFromMSB(data2, i)) { 88 | count += 1; 89 | } else { 90 | break; 91 | } 92 | } 93 | return count; 94 | } 95 | 96 | /// @notice Shrinks an over-allocated dynamic array of bytes32 to the correct size 97 | /// @param inputArray: The bytes32 array to be shrunk 98 | /// @param length: The length to shrink to 99 | /// @return finalArray : The full array of bytes32 100 | /// @dev Needed where an unknown number of elements are to be pushed to a dynamic array 101 | /// @dev We fist allocate a large-enough array, and then shrink once we're done populating it 102 | // solhint-disable-next-line func-visibility 103 | function shrinkBytes32Array(bytes32[] memory inputArray, uint256 length) 104 | pure 105 | returns (bytes32[] memory finalArray) 106 | { 107 | finalArray = new bytes32[](length); 108 | for (uint256 i = 0; i < length; i++) { 109 | finalArray[i] = inputArray[i]; 110 | } 111 | return finalArray; 112 | } 113 | 114 | /// @notice compares a byte array to the (bytes32) default (ZERO) value 115 | /// @param value : The bytes to compare 116 | /// @dev No byte array comparison in solidity, so compare keccak hashes 117 | // solhint-disable-next-line func-visibility 118 | function isDefaultValue(bytes memory value) pure returns (bool) { 119 | return keccak256(value) == keccak256(abi.encodePacked(Constants.ZERO)); 120 | } 121 | -------------------------------------------------------------------------------- /test/merkleTreeBinary.ts: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import { ethers } from 'hardhat'; 3 | import { solidity } from 'ethereum-waffle'; 4 | import { BigNumber as BN, Contract } from 'ethers'; 5 | import { calcRoot, constructTree, getProof, hashLeaf } from '@fuel-ts/merkle'; 6 | import BinaryMerkleBranch from '@fuel-ts/merkle/dist/types/branch'; 7 | import yaml from 'js-yaml'; 8 | import fs from 'fs'; 9 | import { checkAppend, checkVerify } from './test_helpers/binaryMerkleTree'; 10 | import { ZERO } from './utils/constants'; 11 | import { uintToBytes32 } from './utils/utils'; 12 | import { EncodedValue, EncodedValueInput } from './utils/encodedValue'; 13 | import ProofTest from './utils/proofTest'; 14 | 15 | chai.use(solidity); 16 | const { expect } = chai; 17 | 18 | describe('binary Merkle tree', async () => { 19 | let bmtlib: Contract; 20 | let bmto: Contract; 21 | 22 | before(async () => { 23 | const merkleFactory = await ethers.getContractFactory('BinaryMerkleTree'); 24 | bmtlib = await merkleFactory.deploy(); 25 | await bmtlib.deployed(); 26 | }); 27 | 28 | beforeEach(async () => { 29 | // Deploy mock contract and link BMT library 30 | const mockMerkle = await ethers.getContractFactory('MockBinaryMerkleTree', { 31 | libraries: { BinaryMerkleTree: bmtlib.address }, 32 | }); 33 | bmto = await mockMerkle.deploy(); 34 | await bmto.deployed(); 35 | }); 36 | 37 | it('Compute root', async () => { 38 | const data = []; 39 | const size = 20; 40 | for (let i = 0; i < size; i += 1) { 41 | data.push(BN.from(i).toHexString()); 42 | } 43 | await bmto.computeRoot(data); 44 | const result = await bmto.root(); 45 | const res = calcRoot(data); 46 | 47 | // Compare results 48 | expect(result).to.be.equal(res); 49 | }); 50 | 51 | it('Verifications', async () => { 52 | const testCases = [ 53 | { numLeaves: 100, proveLeaf: 100 }, 54 | { numLeaves: 100, proveLeaf: 99 }, 55 | { numLeaves: 99, proveLeaf: 42 }, 56 | { numLeaves: 1, proveLeaf: 1 }, 57 | ]; 58 | 59 | for (let i = 0; i < testCases.length; i += 1) { 60 | // Expect success 61 | expect( 62 | await checkVerify(bmto, testCases[i].numLeaves, testCases[i].proveLeaf, false) 63 | ).to.equal(true); 64 | 65 | // Tamper with data 66 | expect( 67 | await checkVerify(bmto, testCases[i].numLeaves, testCases[i].proveLeaf, true) 68 | ).to.equal(false); 69 | } 70 | }); 71 | 72 | it('Data-driven proofs produce expected verifications', async () => { 73 | const dir = './test/test_vectors/binary_proofs'; 74 | 75 | const executeTest = async (file: string) => { 76 | const fileData = fs.readFileSync(`${dir}/${file}`, 'utf8'); 77 | const test = yaml.load(fileData) as ProofTest; 78 | 79 | const root: EncodedValue = new EncodedValue(test.root); 80 | const data: EncodedValue = new EncodedValue(test.data); 81 | const proofSet: EncodedValue[] = test.proof_set.map( 82 | (item: EncodedValueInput) => new EncodedValue(item) 83 | ); 84 | const key: number = +test.proof_index; 85 | const count: number = +test.num_leaves; 86 | 87 | const verification = await bmto.callStatic.verify( 88 | root.toString(), 89 | data.toBuffer(), 90 | proofSet.map((item) => item.toBuffer()), 91 | key, 92 | count 93 | ); 94 | const expectedVerification: boolean = test.expected_verification; 95 | expect(verification).to.equal(expectedVerification); 96 | }; 97 | 98 | fs.readdir(dir, (err: Error | null, files: string[]) => { 99 | files.forEach((file) => executeTest(file)); 100 | }); 101 | }); 102 | 103 | it('Append', async () => { 104 | const testCases = [1, 5, 100]; 105 | for (let i = 0; i < testCases.length; i += 1) { 106 | // Correct proof should succees 107 | expect(await checkAppend(bmto, testCases[i], false)).to.equal(true); 108 | // Incorrect proof should fail 109 | expect(await checkAppend(bmto, testCases[i], true)).to.equal(false); 110 | } 111 | }); 112 | 113 | it('AddBranches and update', async () => { 114 | // First build a full tree in TS 115 | const numLeaves = 100; 116 | const data = []; 117 | const keys = []; 118 | const size = numLeaves; 119 | 120 | for (let i = 0; i < size; i += 1) { 121 | data.push(BN.from(i).toHexString()); 122 | keys.push(BN.from(i).toHexString()); 123 | } 124 | 125 | let nodes = constructTree(data); 126 | 127 | // Build branches for a selection of keys 128 | const branches: BinaryMerkleBranch[] = []; 129 | const keyNumbers = [4, 8, 15, 16, 23, 42]; 130 | const keysToAdd: string[] = []; 131 | for (let i = 0; i < keyNumbers.length; i += 1) { 132 | keysToAdd.push(uintToBytes32(keyNumbers[i])); 133 | } 134 | 135 | for (let i = 0; i < keysToAdd.length; i += 1) { 136 | const keyToAdd = keysToAdd[i]; 137 | const valueToAdd = data[BN.from(keysToAdd[i]).toNumber()]; 138 | const proof = getProof(nodes, BN.from(keysToAdd[i]).toNumber()); 139 | branches.push(new BinaryMerkleBranch(proof, keyToAdd, valueToAdd)); 140 | } 141 | 142 | // Add branches and update a key 143 | const keyToUpdate = keysToAdd[4]; // Index into 'keyNumbers', not 'keys' 144 | const newData = BN.from(9999).toHexString(); 145 | await bmto.addBranchesAndUpdate( 146 | branches, 147 | nodes[nodes.length - 1].hash, 148 | keyToUpdate, 149 | newData, 150 | numLeaves 151 | ); 152 | let newSolRoot = await bmto.root(); 153 | 154 | // Change data and rebuild tree (ts) 155 | data[parseInt(keyToUpdate, 16)] = newData; 156 | nodes = constructTree(data); 157 | const newTSRoot = nodes[nodes.length - 1].hash; 158 | 159 | // Check roots are equal 160 | expect(newSolRoot).to.equal(newTSRoot); 161 | 162 | // Trivial cases 163 | // Tree is empty 164 | await bmto.addBranchesAndUpdate([], ZERO, ZERO, newData, 0); 165 | newSolRoot = await bmto.root(); 166 | expect(newSolRoot).to.equal(hashLeaf(newData)); 167 | 168 | // Tree has only one leaf 169 | await bmto.addBranchesAndUpdate( 170 | [new BinaryMerkleBranch([], ZERO, uintToBytes32(42))], 171 | hashLeaf(uintToBytes32(42)), 172 | ZERO, 173 | uintToBytes32(43), 174 | 1 175 | ); 176 | newSolRoot = await bmto.root(); 177 | expect(newSolRoot).to.equal(hashLeaf(uintToBytes32(43))); 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /test/merkleTreeSparse.ts: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import { solidity } from 'ethereum-waffle'; 3 | import { ethers } from 'hardhat'; 4 | import { Contract } from 'ethers'; 5 | import SparseMerkleTree from '@fuel-ts/sparsemerkle'; 6 | import DeepSparseMerkleSubTree from '@fuel-ts/sparsemerkle/dist/deepSparseMerkleSubTree'; 7 | import SparseCompactMerkleSolidityProof from '@fuel-ts/sparsemerkle/dist/types/sparseCompactMerkleSolidityProof'; 8 | import SparseMerkleSolidityNode from '@fuel-ts/sparsemerkle/dist/types/sparseMerkleSolidityNode'; 9 | import SparseCompactMerkleBranch from '@fuel-ts/sparsemerkle/dist/types/sparseCompactMerkleBranch'; 10 | import { uintToBytes32 } from './utils/utils'; 11 | import hash from './utils/cryptography'; 12 | import { ZERO } from './utils/constants'; 13 | 14 | chai.use(solidity); 15 | const { expect } = chai; 16 | 17 | describe('Sparse Merkle Tree', async () => { 18 | let sbmtlib: Contract; 19 | 20 | before(async () => { 21 | const sparseMerkleFactory = await ethers.getContractFactory('SparseMerkleTree'); 22 | sbmtlib = await sparseMerkleFactory.deploy(); 23 | await sbmtlib.deployed(); 24 | }); 25 | 26 | let dsmsto: Contract; 27 | 28 | it('Proof verification', async () => { 29 | // Create a SMT 30 | const smt = new SparseMerkleTree(); 31 | const data = uintToBytes32(42); 32 | 33 | // Add some leaves 34 | for (let i = 0; i < 100; i += 1) { 35 | const key = hash(uintToBytes32(i)); 36 | smt.update(key, data); 37 | } 38 | const sparseMerkleTreeFactory = await ethers.getContractFactory('MockSparseMerkleTree', { 39 | libraries: { SparseMerkleTree: sbmtlib.address }, 40 | }); 41 | dsmsto = await sparseMerkleTreeFactory.deploy(); 42 | await dsmsto.deployed(); 43 | 44 | const indexToProve = 51; 45 | const keyToProve = hash(uintToBytes32(indexToProve)); 46 | let compactMembershipProof = smt.proveCompacted(keyToProve); 47 | 48 | // Need to convert typescript proof (with raw data) into solidity proof (with nodes): 49 | let proofSideNodes = compactMembershipProof.SideNodes; 50 | let nonMembershipLeaf = new SparseMerkleSolidityNode( 51 | compactMembershipProof.NonMembershipLeafData 52 | ); 53 | let bitmask = compactMembershipProof.BitMask; 54 | let numSideNodes = compactMembershipProof.NumSideNodes; 55 | let sibling = new SparseMerkleSolidityNode(compactMembershipProof.SiblingData); 56 | 57 | let solidityProof = new SparseCompactMerkleSolidityProof( 58 | proofSideNodes, 59 | nonMembershipLeaf, 60 | bitmask, 61 | numSideNodes, 62 | sibling 63 | ); 64 | 65 | const badData = uintToBytes32(999); 66 | 67 | // Valid membership proof 68 | // eslint-disable-next-line no-unused-expressions 69 | await dsmsto.verifyCompact(solidityProof, keyToProve, data, smt.root); 70 | expect(await dsmsto.verified()).to.be.true; 71 | // Invalid membership proof 72 | // eslint-disable-next-line no-unused-expressions 73 | await dsmsto.verifyCompact(solidityProof, keyToProve, badData, smt.root); 74 | expect(await dsmsto.verified()).to.be.false; 75 | 76 | const nonMembershipIndex = 200; 77 | const nonMembershipKey = hash(uintToBytes32(nonMembershipIndex)); 78 | 79 | compactMembershipProof = smt.proveCompacted(nonMembershipKey); 80 | 81 | // Need to convert typescript proof (with raw data) into solidity proof (with nodes): 82 | proofSideNodes = compactMembershipProof.SideNodes; 83 | nonMembershipLeaf = new SparseMerkleSolidityNode( 84 | compactMembershipProof.NonMembershipLeafData 85 | ); 86 | bitmask = compactMembershipProof.BitMask; 87 | numSideNodes = compactMembershipProof.NumSideNodes; 88 | sibling = new SparseMerkleSolidityNode(compactMembershipProof.SiblingData); 89 | 90 | solidityProof = new SparseCompactMerkleSolidityProof( 91 | proofSideNodes, 92 | nonMembershipLeaf, 93 | bitmask, 94 | numSideNodes, 95 | sibling 96 | ); 97 | 98 | // Valid Non-membership proof 99 | // eslint-disable-next-line no-unused-expressions 100 | await dsmsto.verifyCompact(solidityProof, nonMembershipKey, ZERO, smt.root); 101 | expect(await dsmsto.verified()).to.be.true; 102 | 103 | // Invalid Non-membership proof 104 | // eslint-disable-next-line no-unused-expressions 105 | await dsmsto.verifyCompact(solidityProof, keyToProve, ZERO, smt.root); 106 | expect(await dsmsto.verified()).to.be.false; 107 | }); 108 | 109 | it('add branches and update', async () => { 110 | // Create a SMT 111 | const smt = new SparseMerkleTree(); 112 | const data = uintToBytes32(42); 113 | const newData = uintToBytes32(43); 114 | 115 | // Add some leaves 116 | for (let i = 0; i < 100; i += 1) { 117 | const key = hash(uintToBytes32(i)); 118 | smt.update(key, data); 119 | } 120 | 121 | // Create DSMST (ts) and add some branches from the full SMT using compact proofs: 122 | const dsmst = new DeepSparseMerkleSubTree(smt.root); 123 | 124 | const branches: SparseCompactMerkleBranch[] = []; 125 | 126 | const keyNumbers = [4, 8, 15, 16, 23, 42]; 127 | const keys: string[] = []; 128 | for (let i = 0; i < keyNumbers.length; i += 1) { 129 | keys.push(hash(uintToBytes32(keyNumbers[i]))); 130 | } 131 | 132 | for (let i = 0; i < keys.length; i += 1) { 133 | const keyToAdd = keys[i]; 134 | const valueToAdd = data; 135 | const compactMembershipProof = smt.proveCompacted(keyToAdd); 136 | const res = dsmst.addBranchCompact(compactMembershipProof, keyToAdd, valueToAdd); 137 | 138 | // Need to convert typescript proof (with raw data) into solidity proof (with nodes): 139 | 140 | const proofSideNodes = compactMembershipProof.SideNodes; 141 | const nonMembershipLeaf = new SparseMerkleSolidityNode( 142 | compactMembershipProof.NonMembershipLeafData 143 | ); 144 | const bitmask = compactMembershipProof.BitMask; 145 | const numSideNodes = compactMembershipProof.NumSideNodes; 146 | const sibling = new SparseMerkleSolidityNode(compactMembershipProof.SiblingData); 147 | 148 | const solidityProof = new SparseCompactMerkleSolidityProof( 149 | proofSideNodes, 150 | nonMembershipLeaf, 151 | bitmask, 152 | numSideNodes, 153 | sibling 154 | ); 155 | 156 | branches.push(new SparseCompactMerkleBranch(solidityProof, keyToAdd, valueToAdd)); 157 | 158 | // Check proof is valid and branch was successfully added for typescript 159 | expect(res); 160 | } 161 | 162 | // UPDATE 163 | const keyToUpdate = keys[3]; 164 | // Add branches and update on the DSMST (solidity) 165 | await dsmsto.addBranchesAndUpdate(branches, smt.root, keyToUpdate, newData); 166 | let solRoot = await dsmsto.root(); 167 | // Update a leaf on the full SMT 168 | smt.update(keyToUpdate, newData); 169 | 170 | // Update same leaf on the DSMST (ts) 171 | dsmst.update(keyToUpdate, newData); 172 | 173 | // Check roots are equal 174 | expect(dsmst.root).to.equal(smt.root); 175 | expect(solRoot).to.equal(dsmst.root); 176 | 177 | // DELETION 178 | // Delete the key we just updated 179 | const keyToDelete = keyToUpdate; 180 | 181 | // Delete a leaf on the full SMT 182 | smt.delete(keyToDelete); 183 | 184 | // Delete same leaf on the DSMST (ts) 185 | dsmst.delete(keyToDelete); 186 | 187 | // Add branches and delete on the DSMST (solidity) 188 | await dsmsto.addBranchesAndDelete(branches, smt.root, keyToDelete); 189 | solRoot = await dsmsto.root(); 190 | 191 | // Check roots are equal 192 | expect(dsmst.root).to.equal(smt.root); 193 | expect(solRoot).to.equal(dsmst.root); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /contracts/tree/sum/SumMerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {CryptographyLib} from "../Cryptography.sol"; 5 | import {SumMerkleProof} from "./SumMerkleProof.sol"; 6 | import {Constants} from "../Constants.sol"; 7 | import {pathLengthFromKey} from "../Utils.sol"; 8 | import {leafDigest, nodeDigest} from "./TreeHasher.sol"; 9 | 10 | /// @title Sum Merkle Tree. 11 | /// @notice spec can be found at https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/cryptographic_primitives.md#binary-merkle-sum-tree. 12 | library MerkleSumTree { 13 | /// @notice Verify if element (key, data) exists in Merkle tree, decompacts proof, goes through side nodes and calculates hashes up to the root, compares roots. 14 | /// @param root: The root of the tree in which verify the given leaf 15 | /// @param key: The key of the leaf to verify. 16 | /// @param proof: Binary Merkle Proof for the leaf. 17 | /// @param numLeaves: The number of leaves in the tree 18 | function verify( 19 | bytes32 root, 20 | uint256 rootSum, 21 | bytes memory data, 22 | uint256 _sum, 23 | SumMerkleProof memory proof, 24 | uint256 key, 25 | uint256 numLeaves 26 | ) public pure returns (bool) { 27 | // Check proof is correct length for the key it is proving 28 | if (numLeaves <= 1) { 29 | if (proof.sideNodes.length != 0) { 30 | return false; 31 | } 32 | } else if (proof.sideNodes.length != pathLengthFromKey(key, numLeaves)) { 33 | return false; 34 | } 35 | 36 | // Check key is in tree 37 | if (key >= numLeaves) { 38 | return false; 39 | } 40 | 41 | // Check proof has valid format 42 | if (proof.nodeSums.length != proof.sideNodes.length) { 43 | return false; 44 | } 45 | 46 | // A sibling at height 1 is created by getting the LeafSum of the original data. 47 | bytes32 digest = leafDigest(_sum, data); 48 | uint256 sum = _sum; 49 | 50 | // Handle case where proof is empty: i.e, only one leaf exists, so verify hash(data) is root 51 | if (proof.sideNodes.length == 0) { 52 | if (numLeaves == 1) { 53 | return (digest == root) && (sum == rootSum); 54 | } else { 55 | return false; 56 | } 57 | } 58 | 59 | uint256 height = 1; 60 | uint256 stableEnd = key; 61 | 62 | // While the current subtree (of height 'height') is complete, determine 63 | // the position of the next sibling using the complete subtree algorithm. 64 | // 'stableEnd' tells us the ending index of the last full subtree. It gets 65 | // initialized to 'key' because the first full subtree was the 66 | // subtree of height 1, created above (and had an ending index of 67 | // 'key'). 68 | 69 | while (true) { 70 | // Determine if the subtree is complete. This is accomplished by 71 | // rounding down the key to the nearest 1 << 'height', adding 1 72 | // << 'height', and comparing the result to the number of leaves in the 73 | // Merkle tree. 74 | 75 | uint256 subTreeStartIndex = (key / (1 << height)) * (1 << height); 76 | uint256 subTreeEndIndex = subTreeStartIndex + (1 << height) - 1; 77 | 78 | // If the Merkle tree does not have a leaf at index 79 | // 'subTreeEndIndex', then the subtree of the current height is not 80 | // a complete subtree. 81 | if (subTreeEndIndex >= numLeaves) { 82 | break; 83 | } 84 | stableEnd = subTreeEndIndex; 85 | 86 | // Determine if the key is in the first or the second half of 87 | // the subtree. 88 | if (proof.sideNodes.length <= height - 1) { 89 | return false; 90 | } 91 | if (key - subTreeStartIndex < (1 << (height - 1))) { 92 | digest = nodeDigest( 93 | sum, 94 | digest, 95 | proof.nodeSums[height - 1], 96 | proof.sideNodes[height - 1] 97 | ); 98 | } else { 99 | digest = nodeDigest( 100 | proof.nodeSums[height - 1], 101 | proof.sideNodes[height - 1], 102 | sum, 103 | digest 104 | ); 105 | } 106 | sum += proof.nodeSums[height - 1]; 107 | 108 | height += 1; 109 | } 110 | 111 | // Determine if the next hash belongs to an orphan that was elevated. This 112 | // is the case IFF 'stableEnd' (the last index of the largest full subtree) 113 | // is equal to the number of leaves in the Merkle tree. 114 | if (stableEnd != numLeaves - 1) { 115 | if (proof.sideNodes.length <= height - 1) { 116 | return false; 117 | } 118 | digest = nodeDigest( 119 | sum, 120 | digest, 121 | proof.nodeSums[height - 1], 122 | proof.sideNodes[height - 1] 123 | ); 124 | sum += proof.nodeSums[height - 1]; 125 | height += 1; 126 | } 127 | 128 | // All remaining elements in the proof set will belong to a left sibling. 129 | while (height - 1 < proof.sideNodes.length) { 130 | digest = nodeDigest( 131 | proof.nodeSums[height - 1], 132 | proof.sideNodes[height - 1], 133 | sum, 134 | digest 135 | ); 136 | sum += proof.nodeSums[height - 1]; 137 | height += 1; 138 | } 139 | 140 | return (digest == root) && (sum == rootSum); 141 | } 142 | 143 | /// @notice Computes sparse Merkle tree root from leaves. 144 | /// @param data, list of leaves' data in ascending order of leaves. 145 | /// @param values, list of leaves' values in ascending order of leaves. 146 | function computeRoot(bytes[] memory data, uint256[] memory values) 147 | public 148 | pure 149 | returns (bytes32, uint256) 150 | { 151 | bytes32[] memory nodes = new bytes32[](data.length); 152 | for (uint256 i = 0; i < data.length; ++i) { 153 | nodes[i] = leafDigest(values[i], data[i]); 154 | } 155 | uint256 odd = nodes.length & 1; 156 | uint256 size = (nodes.length + 1) >> 1; 157 | uint256[] memory sums = new uint256[](size); 158 | // pNodes are nodes in previous level. 159 | // We use pNodes to avoid damaging the input leaves. 160 | bytes32[] memory pNodes = nodes; 161 | uint256[] memory pSums = values; 162 | while (true) { 163 | uint256 i = 0; 164 | for (; i < size - odd; ++i) { 165 | uint256 j = i << 1; 166 | nodes[i] = nodeDigest(pSums[j], pNodes[j], pSums[j + 1], pNodes[j + 1]); 167 | sums[i] = pSums[j] + pSums[j + 1]; 168 | } 169 | if (odd == 1) { 170 | nodes[i] = pNodes[i << 1]; 171 | sums[i] = pSums[i << 1]; 172 | } 173 | if (size == 1) { 174 | break; 175 | } 176 | odd = (size & 1); 177 | size = (size + 1) >> 1; 178 | pNodes = nodes; 179 | pSums = sums; 180 | } 181 | return (nodes[0], sums[0]); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /contracts/tree/binary/BinaryMerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {Node} from "./Node.sol"; 5 | import {nodeDigest, leafDigest, hashNode} from "./TreeHasher.sol"; 6 | import {hashLeaf} from "./TreeHasher.sol"; 7 | import {MerkleBranch} from "./Branch.sol"; 8 | import {BinaryMerkleProof} from "./BinaryMerkleProof.sol"; 9 | import {Constants} from "../Constants.sol"; 10 | import {pathLengthFromKey, getStartingBit} from "../Utils.sol"; 11 | import {getBitAtFromMSB} from "../Utils.sol"; 12 | import {verifyBinaryTree, verifyBinaryTreeDigest} from "./BinaryMerkleTreeUtils.sol"; 13 | import {computeBinaryTreeRoot} from "./BinaryMerkleTreeUtils.sol"; 14 | import {getPtrToNode, getNodeAtPtr} from "./BinaryMerkleTreeUtils.sol"; 15 | import {addBranch, sideNodesForRoot} from "./BinaryMerkleTreeUtils.sol"; 16 | 17 | /// @title Binary Merkle Tree. 18 | /// @notice spec can be found at https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/cryptographicprimitives.md#binary-merkle-tree. 19 | library BinaryMerkleTree { 20 | /// @notice Verify if element (key, data) exists in Merkle tree, given data, proof, and root. 21 | /// @param root: The root of the tree in which verify the given leaf 22 | /// @param data: The data of the leaf to verify 23 | /// @param key: The key of the leaf to verify. 24 | /// @param proof: Binary Merkle Proof for the leaf. 25 | /// @param numLeaves: The number of leaves in the tree 26 | /// @return : Whether the proof is valid 27 | /// @dev numLeaves is necessary to determine height of sub-tree containing the data to prove 28 | function verify( 29 | bytes32 root, 30 | bytes memory data, 31 | bytes32[] memory proof, 32 | uint256 key, 33 | uint256 numLeaves 34 | ) public pure returns (bool) { 35 | return verifyBinaryTree(root, data, proof, key, numLeaves); 36 | } 37 | 38 | /// @notice Verify if element (key, digest) exists in Merkle tree, given digest, proof, and root. 39 | /// @param root: The root of the tree in which verify the given leaf 40 | /// @param digest: The digest of the data of the leaf to verify 41 | /// @param key: The key of the leaf to verify. 42 | /// @param proof: Binary Merkle Proof for the leaf. 43 | /// @param numLeaves: The number of leaves in the tree 44 | /// @return : Whether the proof is valid 45 | /// @dev numLeaves is necessary to determine height of sub-tree containing the data to prove 46 | function verifyDigest( 47 | bytes32 root, 48 | bytes32 digest, 49 | bytes32[] memory proof, 50 | uint256 key, 51 | uint256 numLeaves 52 | ) public pure returns (bool) { 53 | return verifyBinaryTreeDigest(root, digest, proof, key, numLeaves); 54 | } 55 | 56 | /// @notice Computes Merkle tree root from leaves. 57 | /// @param data: list of leaves' data in ascending for leaves order. 58 | /// @return : The root of the tree 59 | function computeRoot(bytes[] memory data) public pure returns (bytes32) { 60 | return computeBinaryTreeRoot(data); 61 | } 62 | 63 | /// @notice Appends a new element by calculating new root, returns new root and if successful, pure function. 64 | /// @param numLeaves, number of leaves in the tree currently. 65 | /// @param data, The data of the leaf to append. 66 | /// @param proof, Binary Merkle Proof to use for the leaf. 67 | /// @return : The root of the new tree 68 | /// @return : Whether the proof is valid 69 | function append( 70 | uint256 numLeaves, 71 | bytes memory data, 72 | bytes32[] memory proof 73 | ) public pure returns (bytes32, bool) { 74 | bytes32 digest = leafDigest(data); 75 | 76 | // Since appended leaf is last leaf in tree by definition, its path consists only of set bits 77 | // (because all side nodes will be on its left) 78 | // Therefore, the number of steps in the proof should equal number of bits set in the key 79 | // E.g. If appending the 7th leaf, key = 0b110 => proofLength = 2. 80 | 81 | uint256 proofLength = 0; 82 | while (numLeaves > 0) { 83 | proofLength += numLeaves & 1; 84 | numLeaves = numLeaves >> 1; 85 | } 86 | 87 | if (proof.length != proofLength) { 88 | return (Constants.NULL, false); 89 | } 90 | 91 | // If proof length is correctly 0, tree is empty, and we are appending the first leaf 92 | if (proofLength == 0) { 93 | digest = leafDigest(data); 94 | } 95 | // Otherwise tree non-empty so we calculate nodes up to root 96 | else { 97 | for (uint256 i = 0; i < proofLength; ++i) { 98 | digest = nodeDigest(proof[i], digest); 99 | } 100 | } 101 | 102 | return (digest, true); 103 | } 104 | 105 | /// @notice Update a given leaf 106 | /// @param key: The key of the leaf to be added 107 | /// @param value: The data to update the leaf with 108 | /// @param sideNodes: The sideNodes from the leaf to the root 109 | /// @param numLeaves: The total number of leaves in the tree 110 | /// @return currentPtr : The pointer to the root of the tree 111 | function updateWithSideNodes( 112 | bytes32 key, 113 | bytes memory value, 114 | bytes32[] memory sideNodes, 115 | uint256 numLeaves 116 | ) public pure returns (bytes32 currentPtr) { 117 | Node memory currentNode = hashLeaf(value); 118 | currentPtr = getPtrToNode(currentNode); 119 | 120 | // If numleaves <= 1, then the root is just the leaf hash (or ZERO) 121 | if (numLeaves > 1) { 122 | uint256 startingBit = getStartingBit(numLeaves); 123 | uint256 pathLength = pathLengthFromKey(uint256(key), numLeaves); 124 | 125 | for (uint256 i = 0; i < pathLength; i += 1) { 126 | if (getBitAtFromMSB(key, startingBit + pathLength - 1 - i) == 1) { 127 | currentNode = hashNode( 128 | sideNodes[i], 129 | currentPtr, 130 | getNodeAtPtr(sideNodes[i]).digest, 131 | currentNode.digest 132 | ); 133 | } else { 134 | currentNode = hashNode( 135 | currentPtr, 136 | sideNodes[i], 137 | currentNode.digest, 138 | getNodeAtPtr(sideNodes[i]).digest 139 | ); 140 | } 141 | 142 | currentPtr = getPtrToNode(currentNode); 143 | } 144 | } 145 | } 146 | 147 | /// @notice Add an array of branches and update one of them 148 | /// @param branches: The array of branches to add 149 | /// @param root: The root of the tree 150 | /// @param key: The key of the leaf to be added 151 | /// @param value: The data to update the leaf with 152 | /// @param numLeaves: The total number of leaves in the tree 153 | /// @return newRoot : The new root of the tree 154 | function addBranchesAndUpdate( 155 | MerkleBranch[] memory branches, 156 | bytes32 root, 157 | bytes32 key, 158 | bytes memory value, 159 | uint256 numLeaves 160 | ) public pure returns (bytes32 newRoot) { 161 | bytes32 rootPtr = Constants.ZERO; 162 | for (uint256 i = 0; i < branches.length; i++) { 163 | rootPtr = addBranch( 164 | branches[i].key, 165 | branches[i].value, 166 | branches[i].proof, 167 | root, 168 | rootPtr, 169 | numLeaves 170 | ); 171 | } 172 | 173 | bytes32[] memory sideNodes = sideNodesForRoot(key, rootPtr, numLeaves); 174 | bytes32 newRootPtr = updateWithSideNodes(key, value, sideNodes, numLeaves); 175 | 176 | return getNodeAtPtr(newRootPtr).digest; 177 | } 178 | 179 | /// @notice Derive the proof for a new appended leaf from the proof for the last appended leaf 180 | /// @param oldProof: The proof to the last appeneded leaf 181 | /// @param lastLeaf: The last leaf hash 182 | /// @param key: The key of the new leaf 183 | /// @return : The proof for the appending of the new leaf 184 | /// @dev This function assumes that oldProof has been verified in position (key - 1) 185 | function deriveAppendProofFromLastProof( 186 | bytes32[] memory oldProof, 187 | bytes32 lastLeaf, 188 | uint256 key 189 | ) public pure returns (bytes32[] memory) { 190 | // First prepend last leaf to its proof. 191 | bytes32[] memory newProofBasis = new bytes32[](oldProof.length + 1); 192 | newProofBasis[0] = leafDigest(abi.encodePacked(lastLeaf)); 193 | for (uint256 i = 0; i < oldProof.length; i += 1) { 194 | newProofBasis[i + 1] = oldProof[i]; 195 | } 196 | 197 | // If the new leaf is "even", this will already be the new proof 198 | if (key & 1 == 1) { 199 | return newProofBasis; 200 | } 201 | 202 | // Otherwise, get the expected length of the new proof (it's the last leaf by definition, so numLeaves = key + 1) 203 | // Assuming old proof was valid, this will always be shorter than the old proof. 204 | uint256 expectedProofLength = pathLengthFromKey(key, key + 1); 205 | 206 | bytes32[] memory newProof = new bytes32[](expectedProofLength); 207 | 208 | // "Hash up" through old proof until we have the correct first sidenode 209 | bytes32 firstSideNode = newProofBasis[0]; 210 | uint256 hashedUpIndex = 0; 211 | while (hashedUpIndex < (newProofBasis.length - expectedProofLength)) { 212 | firstSideNode = nodeDigest(newProofBasis[hashedUpIndex + 1], firstSideNode); 213 | hashedUpIndex += 1; 214 | } 215 | 216 | // Set the calculated first side node as the first element in the proof 217 | newProof[0] = firstSideNode; 218 | 219 | // Then append the remaining (unchanged) sidenodes, if any 220 | for (uint256 j = 1; j < expectedProofLength; j += 1) { 221 | newProof[j] = newProofBasis[hashedUpIndex + j]; 222 | } 223 | 224 | return newProof; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /contracts/tree/binary/BinaryMerkleTreeUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {Node} from "./Node.sol"; 5 | import {nodeDigest, leafDigest} from "./TreeHasher.sol"; 6 | import {parseNode, isLeaf} from "./TreeHasher.sol"; 7 | import {BinaryMerkleProof} from "./BinaryMerkleProof.sol"; 8 | import {Constants} from "../Constants.sol"; 9 | import {pathLengthFromKey, getStartingBit} from "../Utils.sol"; 10 | import {getBitAtFromMSB, reverseSideNodes} from "../Utils.sol"; 11 | import {shrinkBytes32Array} from "../Utils.sol"; 12 | 13 | /// @notice Get the pointer to a node in memory 14 | /// @param node: The node to get the pointer to 15 | /// @return ptr : The pointer to the node 16 | // solhint-disable-next-line func-visibility 17 | function getPtrToNode(Node memory node) pure returns (bytes32 ptr) { 18 | assembly { 19 | ptr := node 20 | } 21 | } 22 | 23 | /// @notice Get a node at a given pointer 24 | /// @param ptr: The pointer to the node 25 | /// @return node : The node 26 | // solhint-disable-next-line func-visibility 27 | function getNodeAtPtr(bytes32 ptr) pure returns (Node memory node) { 28 | assembly { 29 | node := ptr 30 | } 31 | } 32 | 33 | /// @notice Verify if element (key, data) exists in Merkle tree, given data, proof, and root. 34 | /// @param root: The root of the tree in which verify the given leaf 35 | /// @param data: The data of the leaf to verify 36 | /// @param key: The key of the leaf to verify. 37 | /// @param proof: Binary Merkle Proof for the leaf. 38 | /// @param numLeaves: The number of leaves in the tree 39 | /// @return : Whether the proof is valid 40 | /// @dev numLeaves is necessary to determine height of sub-tree containing the data to prove 41 | // solhint-disable-next-line func-visibility 42 | function verifyBinaryTree( 43 | bytes32 root, 44 | bytes memory data, 45 | bytes32[] memory proof, 46 | uint256 key, 47 | uint256 numLeaves 48 | ) pure returns (bool) { 49 | // A sibling at height 1 is created by getting the hash of the data to prove. 50 | return verifyBinaryTreeDigest(root, leafDigest(data), proof, key, numLeaves); 51 | } 52 | 53 | /// @notice Verify if element (key, digest) exists in Merkle tree, given digest, proof, and root. 54 | /// @param root: The root of the tree in which verify the given leaf 55 | /// @param digest: The digest of the data of the leaf to verify 56 | /// @param key: The key of the leaf to verify. 57 | /// @param proof: Binary Merkle Proof for the leaf. 58 | /// @param numLeaves: The number of leaves in the tree 59 | /// @return : Whether the proof is valid 60 | /// @dev numLeaves is necessary to determine height of sub-tree containing the data to prove 61 | // solhint-disable-next-line func-visibility 62 | function verifyBinaryTreeDigest( 63 | bytes32 root, 64 | bytes32 digest, 65 | bytes32[] memory proof, 66 | uint256 key, 67 | uint256 numLeaves 68 | ) pure returns (bool) { 69 | // Check proof is correct length for the key it is proving 70 | if (numLeaves <= 1) { 71 | if (proof.length != 0) { 72 | return false; 73 | } 74 | } else if (proof.length != pathLengthFromKey(key, numLeaves)) { 75 | return false; 76 | } 77 | 78 | // Check key is in tree 79 | if (key >= numLeaves) { 80 | return false; 81 | } 82 | 83 | // Null proof is only valid if numLeaves = 1 84 | // If so, just verify digest is root 85 | if (proof.length == 0) { 86 | if (numLeaves == 1) { 87 | return (root == digest); 88 | } else { 89 | return false; 90 | } 91 | } 92 | 93 | uint256 height = 1; 94 | uint256 stableEnd = key; 95 | 96 | // While the current subtree (of height 'height') is complete, determine 97 | // the position of the next sibling using the complete subtree algorithm. 98 | // 'stableEnd' tells us the ending index of the last full subtree. It gets 99 | // initialized to 'key' because the first full subtree was the 100 | // subtree of height 1, created above (and had an ending index of 101 | // 'key'). 102 | 103 | while (true) { 104 | // Determine if the subtree is complete. This is accomplished by 105 | // rounding down the key to the nearest 1 << 'height', adding 1 106 | // << 'height', and comparing the result to the number of leaves in the 107 | // Merkle tree. 108 | 109 | uint256 subTreeStartIndex = (key / (1 << height)) * (1 << height); 110 | uint256 subTreeEndIndex = subTreeStartIndex + (1 << height) - 1; 111 | 112 | // If the Merkle tree does not have a leaf at index 113 | // 'subTreeEndIndex', then the subtree of the current height is not 114 | // a complete subtree. 115 | if (subTreeEndIndex >= numLeaves) { 116 | break; 117 | } 118 | stableEnd = subTreeEndIndex; 119 | 120 | // Determine if the key is in the first or the second half of 121 | // the subtree. 122 | if (proof.length <= height - 1) { 123 | return false; 124 | } 125 | if (key - subTreeStartIndex < (1 << (height - 1))) { 126 | digest = nodeDigest(digest, proof[height - 1]); 127 | } else { 128 | digest = nodeDigest(proof[height - 1], digest); 129 | } 130 | 131 | height += 1; 132 | } 133 | 134 | // Determine if the next hash belongs to an orphan that was elevated. This 135 | // is the case IFF 'stableEnd' (the last index of the largest full subtree) 136 | // is equal to the number of leaves in the Merkle tree. 137 | if (stableEnd != numLeaves - 1) { 138 | if (proof.length <= height - 1) { 139 | return false; 140 | } 141 | digest = nodeDigest(digest, proof[height - 1]); 142 | height += 1; 143 | } 144 | 145 | // All remaining elements in the proof set will belong to a left sibling\ 146 | // i.e proof sideNodes are hashed in "from the left" 147 | while (height - 1 < proof.length) { 148 | digest = nodeDigest(proof[height - 1], digest); 149 | height += 1; 150 | } 151 | 152 | return (digest == root); 153 | } 154 | 155 | /// @notice Computes Merkle tree root from leaves. 156 | /// @param data: list of leaves' data in ascending for leaves order. 157 | /// @return : The root of the tree 158 | // solhint-disable-next-line func-visibility 159 | function computeBinaryTreeRoot(bytes[] memory data) pure returns (bytes32) { 160 | if (data.length == 0) { 161 | return Constants.EMPTY; 162 | } 163 | bytes32[] memory nodes = new bytes32[](data.length); 164 | for (uint256 i = 0; i < data.length; ++i) { 165 | nodes[i] = leafDigest(data[i]); 166 | } 167 | uint256 size = (nodes.length + 1) >> 1; 168 | uint256 odd = nodes.length & 1; 169 | // pNodes are nodes in previous level. 170 | // We use pNodes to avoid damaging the input leaves. 171 | bytes32[] memory pNodes = nodes; 172 | while (true) { 173 | uint256 i = 0; 174 | for (; i < size - odd; ++i) { 175 | uint256 j = i << 1; 176 | nodes[i] = nodeDigest(pNodes[j], pNodes[j + 1]); 177 | } 178 | if (odd == 1) { 179 | nodes[i] = pNodes[i << 1]; 180 | } 181 | if (size == 1) { 182 | break; 183 | } 184 | odd = (size & 1); 185 | size = (size + 1) >> 1; 186 | pNodes = nodes; 187 | } 188 | return nodes[0]; 189 | } 190 | 191 | /// @notice Appends a new element by calculating new root, returns new root and if successful, pure function. 192 | /// @param numLeaves, number of leaves in the tree currently. 193 | /// @param data, The data of the leaf to append. 194 | /// @param proof, Binary Merkle Proof to use for the leaf. 195 | /// @return : The root of the new tree 196 | /// @return : Whether the proof is valid 197 | // solhint-disable-next-line func-visibility 198 | function appendBinaryTree( 199 | uint256 numLeaves, 200 | bytes memory data, 201 | bytes32[] memory proof 202 | ) pure returns (bytes32, bool) { 203 | bytes32 digest = leafDigest(data); 204 | 205 | // Since appended leaf is last leaf in tree by definition, its path consists only of set bits 206 | // (because all side nodes will be on its left) 207 | // Therefore, the number of steps in the proof should equal number of bits set in the key 208 | // E.g. If appending the 7th leaf, key = 0b110 => proofLength = 2. 209 | 210 | uint256 proofLength = 0; 211 | while (numLeaves > 0) { 212 | proofLength += numLeaves & 1; 213 | numLeaves = numLeaves >> 1; 214 | } 215 | 216 | if (proof.length != proofLength) { 217 | return (Constants.NULL, false); 218 | } 219 | 220 | // If proof length is correctly 0, tree is empty, and we are appending the first leaf 221 | if (proofLength == 0) { 222 | digest = leafDigest(data); 223 | } 224 | // Otherwise tree non-empty so we calculate nodes up to root 225 | else { 226 | for (uint256 i = 0; i < proofLength; ++i) { 227 | digest = nodeDigest(proof[i], digest); 228 | } 229 | } 230 | 231 | return (digest, true); 232 | } 233 | 234 | /// @notice Adds a branch to the in-storage sparse representation of tree 235 | /// @dev We store the minimum subset of nodes necessary to calculate the root 236 | /// @param key: The key of the leaf 237 | /// @param value : The data of the leaf 238 | /// @param root : The root of the tree containing the added branch 239 | /// @param rootPtr : The pointer to the root node 240 | /// @param proof: The proof (assumed valid) of the leaf up to the root 241 | /// @param numLeaves: The total number of leaves in the tree 242 | /// @return : The pointer to the root node 243 | // solhint-disable-next-line func-visibility 244 | function addBranch( 245 | bytes32 key, 246 | bytes memory value, 247 | bytes32[] memory proof, 248 | bytes32 root, 249 | bytes32 rootPtr, 250 | uint256 numLeaves 251 | ) pure returns (bytes32) { 252 | // Handle case where tree has only one leaf (so it is the root) 253 | if (numLeaves == 1) { 254 | Node memory rootNode = Node(root, Constants.NULL, Constants.NULL); 255 | rootPtr = getPtrToNode(rootNode); 256 | return rootPtr; 257 | } 258 | uint256 startingBit = getStartingBit(numLeaves); 259 | 260 | AddBranchVariables memory variables; 261 | 262 | bytes32[] memory sideNodePtrs = new bytes32[](proof.length); 263 | bytes32[] memory nodePtrs = new bytes32[](proof.length); 264 | 265 | // Set root 266 | // When adding the first branch, rootPtr will not be set yet, set it here. 267 | if (rootPtr == Constants.NULL) { 268 | // Set the new root 269 | Node memory rootNode = Node(root, Constants.NULL, Constants.NULL); 270 | rootPtr = getPtrToNode(rootNode); 271 | variables.parent = rootNode; 272 | } 273 | // On subsequent branches, we need to retrieve root 274 | else { 275 | variables.parent = getNodeAtPtr(rootPtr); 276 | } 277 | 278 | // Step backwards through proof (from root down to leaf), getting pointers to the nodes/sideNodes 279 | // If node is not yet added, set digest to NULL (we'll set it when we hash back up the branch) 280 | for (uint256 i = proof.length; i > 0; i -= 1) { 281 | uint256 j = i - 1; 282 | 283 | // Descend into left or right subtree depending on key 284 | // If leaf is in the right subtree: 285 | if (getBitAtFromMSB(key, startingBit + proof.length - i) == 1) { 286 | // Subtree is on the right, so sidenode is on the left. 287 | // Check to see if sidenode already exists. If not, create it. and associate with parent 288 | if (variables.parent.leftChildPtr == Constants.NULL) { 289 | variables.sideNode = Node(proof[j], Constants.NULL, Constants.NULL); 290 | variables.sideNodePtr = getPtrToNode(variables.sideNode); 291 | variables.parent.leftChildPtr = variables.sideNodePtr; 292 | } else { 293 | variables.sideNodePtr = variables.parent.leftChildPtr; 294 | } 295 | 296 | // Check to see if node already exists. If not, create it. and associate with parent 297 | // Its digest is initially null. We calculate and set it when we climb back up the tree 298 | if (variables.parent.rightChildPtr == Constants.NULL) { 299 | variables.node = Node(Constants.NULL, Constants.NULL, Constants.NULL); 300 | variables.nodePtr = getPtrToNode(variables.node); 301 | variables.parent.rightChildPtr = variables.nodePtr; 302 | } else { 303 | variables.nodePtr = variables.parent.rightChildPtr; 304 | variables.node = getNodeAtPtr(variables.nodePtr); 305 | } 306 | 307 | // Mirror image of preceding code block, for when leaf is in the left subtree 308 | // If subtree is on the left, sideNode is on the right 309 | } else { 310 | if (variables.parent.rightChildPtr == Constants.NULL) { 311 | variables.sideNode = Node(proof[j], Constants.NULL, Constants.NULL); 312 | variables.sideNodePtr = getPtrToNode(variables.sideNode); 313 | variables.parent.rightChildPtr = variables.sideNodePtr; 314 | } else { 315 | variables.sideNodePtr = variables.parent.rightChildPtr; 316 | } 317 | 318 | if (variables.parent.leftChildPtr == Constants.NULL) { 319 | variables.node = Node(Constants.NULL, Constants.NULL, Constants.NULL); 320 | variables.nodePtr = getPtrToNode(variables.node); 321 | variables.parent.leftChildPtr = variables.nodePtr; 322 | } else { 323 | variables.nodePtr = variables.parent.leftChildPtr; 324 | variables.node = getNodeAtPtr(variables.nodePtr); 325 | } 326 | } 327 | 328 | // Keep pointers to sideNode and node 329 | sideNodePtrs[j] = variables.sideNodePtr; 330 | nodePtrs[j] = variables.nodePtr; 331 | 332 | variables.parent = variables.node; 333 | } 334 | 335 | // Set leaf digest 336 | Node memory leaf = getNodeAtPtr(nodePtrs[0]); 337 | leaf.digest = leafDigest(value); 338 | 339 | if (proof.length == 0) { 340 | return rootPtr; 341 | } 342 | 343 | // Go back up the tree, setting the digests of nodes on the branch 344 | for (uint256 i = 1; i < nodePtrs.length; i += 1) { 345 | variables.node = getNodeAtPtr(nodePtrs[i]); 346 | variables.node.digest = nodeDigest( 347 | getNodeAtPtr(variables.node.leftChildPtr).digest, 348 | getNodeAtPtr(variables.node.rightChildPtr).digest 349 | ); 350 | } 351 | 352 | return rootPtr; 353 | } 354 | 355 | /// @notice Get the sidenodes for a given leaf key up to the root 356 | /// @param key: The key for which to find the sidenodes 357 | /// @param rootPtr: The memory pointer to the root of the tree 358 | /// @param numLeaves : The total number of leaves in the tree 359 | /// @return The sidenodes up to the root. 360 | // solhint-disable-next-line func-visibility 361 | function sideNodesForRoot( 362 | bytes32 key, 363 | bytes32 rootPtr, 364 | uint256 numLeaves 365 | ) pure returns (bytes32[] memory) { 366 | // Allocate a large enough array for the sidenodes (we'll shrink it later) 367 | bytes32[] memory sideNodes = new bytes32[](256); 368 | 369 | Node memory currentNode = getNodeAtPtr(rootPtr); 370 | 371 | // If the root is a placeholder, the tree is empty, so there are no sidenodes to return. 372 | // The leaf pointer is the root pointer 373 | if (currentNode.digest == Constants.ZERO) { 374 | bytes32[] memory emptySideNodes; 375 | return emptySideNodes; 376 | } 377 | 378 | // If the root is a leaf, the tree has only one leaf, so there are also no sidenodes to return. 379 | // The leaf pointer is the root pointer 380 | if (isLeaf(currentNode)) { 381 | bytes32[] memory emptySideNodes; 382 | return emptySideNodes; 383 | } 384 | 385 | // Tree has at least 2 leaves 386 | SideNodesFunctionVariables memory variables; 387 | 388 | variables.sideNodeCount = 0; 389 | 390 | uint256 startingBit = getStartingBit(numLeaves); 391 | uint256 pathLength = pathLengthFromKey(uint256(key), numLeaves); 392 | 393 | // Descend the tree from the root according to the key, collecting side nodes 394 | for (uint256 i = startingBit; i < startingBit + pathLength; i++) { 395 | (variables.leftNodePtr, variables.rightNodePtr) = parseNode(currentNode); 396 | // Bifurcate left or right depending on bit in key 397 | if (getBitAtFromMSB(key, i) == 1) { 398 | (variables.nodePtr, variables.sideNodePtr) = ( 399 | variables.rightNodePtr, 400 | variables.leftNodePtr 401 | ); 402 | } else { 403 | (variables.nodePtr, variables.sideNodePtr) = ( 404 | variables.leftNodePtr, 405 | variables.rightNodePtr 406 | ); 407 | } 408 | 409 | sideNodes[variables.sideNodeCount] = variables.sideNodePtr; 410 | variables.sideNodeCount += 1; 411 | 412 | currentNode = getNodeAtPtr(variables.nodePtr); 413 | } 414 | 415 | return reverseSideNodes(shrinkBytes32Array(sideNodes, variables.sideNodeCount)); 416 | } 417 | 418 | struct AddBranchVariables { 419 | bytes32 nodePtr; 420 | bytes32 sideNodePtr; 421 | Node node; 422 | Node parent; 423 | Node sideNode; 424 | } 425 | 426 | /// @notice A struct to hold variables of the sidenodes function in memory 427 | /// @dev Necessary to circumvent stack-too-deep errors caused by too many 428 | /// @dev variables on the stack. 429 | struct SideNodesFunctionVariables { 430 | bytes32 leftNodePtr; 431 | bytes32 rightNodePtr; 432 | bytes32 nodePtr; 433 | bytes32 sideNodePtr; 434 | uint256 sideNodeCount; 435 | } 436 | -------------------------------------------------------------------------------- /contracts/tree/sparse/SparseMerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.4; 3 | 4 | import {SparseMerkleProof, SparseCompactMerkleProof} from "./Proofs.sol"; 5 | import {verifyProof, decompactProof} from "./Proofs.sol"; 6 | import {Node} from "./Node.sol"; 7 | import {MerkleBranch} from "./Branch.sol"; 8 | import {isLeaf, parseNode, hashLeaf, hashNode} from "./TreeHasher.sol"; 9 | import {parseLeaf, leafDigest, nodeDigest} from "./TreeHasher.sol"; 10 | import {Constants} from "../Constants.sol"; 11 | import {reverseSideNodes, countCommonPrefix} from "../Utils.sol"; 12 | import {getBitAtFromMSB, shrinkBytes32Array, isDefaultValue} from "../Utils.sol"; 13 | 14 | /// @notice A storage backed deep sparse Merkle subtree 15 | /// @dev A DSMST is a SMT which can be minimally populated with branches 16 | /// @dev This allows updating with only a minimal subset of leaves, assuming 17 | /// @dev valid proofs are provided for those leaves 18 | library SparseMerkleTree { 19 | /// @notice Get the pointer to a node in memory 20 | /// @param node: The node to get the pointer to 21 | /// @return ptr : The pointer to the node 22 | // solhint-disable-next-line func-visibility 23 | function getPtrToNode(Node memory node) internal pure returns (bytes32 ptr) { 24 | assembly { 25 | ptr := node 26 | } 27 | } 28 | 29 | /// @notice Get a node at a given pointer 30 | /// @param ptr: The pointer to the node 31 | /// @return node : The node 32 | // solhint-disable-next-line func-visibility 33 | function getNodeAtPtr(bytes32 ptr) internal pure returns (Node memory node) { 34 | assembly { 35 | node := ptr 36 | } 37 | } 38 | 39 | /// @notice Get the sidenodes for a given leaf key up to the root 40 | /// @param key: The key for which to find the sidenodes 41 | /// @param rootPtr: The memory pointer to the root of the tree 42 | /// @return The sidenodes up to the root, leaf hash, leaf data, and sibling data of the specified key 43 | // solhint-disable-next-line func-visibility 44 | function sideNodesForRoot(bytes32 key, bytes32 rootPtr) 45 | internal 46 | pure 47 | returns ( 48 | bytes32[] memory, 49 | bytes32, 50 | Node memory, 51 | Node memory 52 | ) 53 | { 54 | Node memory nullNode = Node( 55 | Constants.NULL, 56 | bytes1(0), 57 | Constants.NULL, 58 | Constants.NULL, 59 | Constants.NULL, 60 | "" 61 | ); 62 | 63 | // Allocate a large enough array for the sidenodes (we'll shrink it later) 64 | bytes32[] memory sideNodes = new bytes32[](256); 65 | bytes32[] memory emptySideNodes; 66 | 67 | Node memory currentNode = getNodeAtPtr(rootPtr); 68 | 69 | // If the root is a placeholder, there are no sidenodes to return. 70 | // The currentNode is the root, and the sibling is null 71 | if (currentNode.digest == Constants.ZERO) { 72 | return (emptySideNodes, rootPtr, currentNode, nullNode); 73 | } 74 | 75 | // If the root is a leaf, there are also no sidenodes to return. 76 | // The data is the leaf data, and the sibling is null 77 | if (isLeaf(currentNode)) { 78 | return (emptySideNodes, rootPtr, currentNode, nullNode); 79 | } 80 | 81 | SideNodesFunctionVariables memory variables; 82 | variables.sideNodeCount = 0; 83 | 84 | // Descend the tree from the root according to the key, collecting side nodes 85 | for (uint256 i = 0; i < Constants.MAX_HEIGHT; i++) { 86 | (variables.leftNodePtr, variables.rightNodePtr) = parseNode(currentNode); 87 | // Bifurcate left or right depending on bit in key at this height 88 | if (getBitAtFromMSB(key, i) == 1) { 89 | (variables.nodePtr, variables.sideNodePtr) = ( 90 | variables.rightNodePtr, 91 | variables.leftNodePtr 92 | ); 93 | } else { 94 | (variables.nodePtr, variables.sideNodePtr) = ( 95 | variables.leftNodePtr, 96 | variables.rightNodePtr 97 | ); 98 | } 99 | 100 | sideNodes[variables.sideNodeCount] = variables.sideNodePtr; 101 | variables.sideNodeCount += 1; 102 | 103 | // If pointer iz zero, we have reached the end (Leaf not yet in tree) 104 | if (variables.nodePtr == Constants.NULL) { 105 | currentNode = nullNode; 106 | break; 107 | } 108 | 109 | currentNode = getNodeAtPtr(variables.nodePtr); 110 | 111 | // If the node is a leaf, we've reached the end. (Leaf already in tree) 112 | if (isLeaf(currentNode)) { 113 | break; 114 | } 115 | } 116 | 117 | return ( 118 | reverseSideNodes(shrinkBytes32Array(sideNodes, variables.sideNodeCount)), 119 | variables.nodePtr, 120 | currentNode, 121 | getNodeAtPtr(variables.sideNodePtr) 122 | ); 123 | } 124 | 125 | /// @notice Update a key, given the sidenodes to the root 126 | /// @param key: The key of the leaf to be updated 127 | /// @param value: The new value 128 | /// @param sideNodes: The sidenodes between the leaf and the root 129 | /// @param oldLeafPtr: The hash of the leaf before the update 130 | /// @param oldLeafNode: The data of the leaf before the update 131 | /// @return The updated root of the tree 132 | // solhint-disable-next-line func-visibility 133 | function updateWithSideNodes( 134 | bytes32 key, 135 | bytes memory value, 136 | bytes32[] memory sideNodes, 137 | bytes32 oldLeafPtr, 138 | Node memory oldLeafNode 139 | ) internal pure returns (bytes32) { 140 | bytes32 currentPtr; 141 | Node memory currentNode; 142 | bytes32 actualPath; 143 | 144 | currentNode = hashLeaf(key, value); 145 | currentPtr = getPtrToNode(currentNode); 146 | 147 | // If the leaf node that sibling nodes lead to has a different actual path 148 | // than the leaf node being updated, we need to create an intermediate node 149 | // with this leaf node and the new leaf node as children. 150 | // 151 | // First, get the number of bits that the paths of the two leaf nodes share 152 | // in common as a prefix. 153 | 154 | uint256 commonPrefixCount; 155 | if (oldLeafPtr == Constants.NULL) { 156 | commonPrefixCount = Constants.MAX_HEIGHT; 157 | } else { 158 | (actualPath, ) = parseLeaf(oldLeafNode); 159 | commonPrefixCount = countCommonPrefix(key, actualPath); 160 | } 161 | 162 | if (commonPrefixCount != Constants.MAX_HEIGHT) { 163 | if (getBitAtFromMSB(key, commonPrefixCount) == 1) { 164 | currentNode = hashNode( 165 | oldLeafPtr, 166 | currentPtr, 167 | getNodeAtPtr(oldLeafPtr).digest, 168 | getNodeAtPtr(currentPtr).digest 169 | ); 170 | } else { 171 | currentNode = hashNode( 172 | currentPtr, 173 | oldLeafPtr, 174 | getNodeAtPtr(currentPtr).digest, 175 | getNodeAtPtr(oldLeafPtr).digest 176 | ); 177 | } 178 | currentPtr = getPtrToNode(currentNode); 179 | } 180 | 181 | for (uint256 i = 0; i < Constants.MAX_HEIGHT; i++) { 182 | bytes32 sideNodePtr; 183 | uint256 offsetOfSideNodes = Constants.MAX_HEIGHT - sideNodes.length; 184 | 185 | // If there are no sidenodes at this height, but the number of 186 | // bits that the paths of the two leaf nodes share in common is 187 | // greater than this height, then we need to build up the tree 188 | // to this height with placeholder values at siblings. 189 | 190 | // Removed 2nd condition here => || (sideNodes[i - offsetOfSideNodes] == ""/null) 191 | // as it can never be reached (?) 192 | if (i < offsetOfSideNodes) { 193 | if ( 194 | (commonPrefixCount != Constants.MAX_HEIGHT) && 195 | (commonPrefixCount > Constants.MAX_HEIGHT - 1 - i) 196 | ) { 197 | sideNodePtr = Constants.NULL; 198 | } else { 199 | continue; 200 | } 201 | } else { 202 | sideNodePtr = sideNodes[i - offsetOfSideNodes]; 203 | } 204 | 205 | if (getBitAtFromMSB(key, Constants.MAX_HEIGHT - 1 - i) == 1) { 206 | currentNode = hashNode( 207 | sideNodePtr, 208 | currentPtr, 209 | getNodeAtPtr(sideNodePtr).digest, 210 | getNodeAtPtr(currentPtr).digest 211 | ); 212 | } else { 213 | currentNode = hashNode( 214 | currentPtr, 215 | sideNodePtr, 216 | getNodeAtPtr(currentPtr).digest, 217 | getNodeAtPtr(sideNodePtr).digest 218 | ); 219 | } 220 | 221 | currentPtr = getPtrToNode(currentNode); 222 | } 223 | 224 | return currentPtr; 225 | } 226 | 227 | /// @notice Delete a key, given the sidenodes to the root 228 | /// @param key: The key of the leaf to be deleted 229 | /// @param sideNodes: The sidenodes between the leaf and the root 230 | /// @param oldLeafNode: The data of the leaf before the update 231 | /// @return The updated root of the tree 232 | /// @dev The leaf is updated to ZERO, and its sibling is percolated up the tree until it has a sibling 233 | // solhint-disable-next-line func-visibility 234 | function deleteWithSideNodes( 235 | bytes32 key, 236 | bytes32[] memory sideNodes, 237 | Node memory oldLeafNode, 238 | bytes32 rootPtr 239 | ) internal pure returns (bytes32) { 240 | // If value already zero, deletion changes nothing. Just return current root 241 | if (oldLeafNode.digest == Constants.ZERO) { 242 | return rootPtr; 243 | } 244 | 245 | // If key is already empty (different key found in its place), deletion changed nothing. Just return current root 246 | bytes32 actualPath; 247 | (actualPath, ) = parseLeaf(oldLeafNode); 248 | 249 | if (actualPath != key) { 250 | return rootPtr; 251 | } 252 | 253 | DeleteFunctionVariables memory variables; 254 | variables.nonPlaceholderReached = false; 255 | 256 | for (uint256 i = 0; i < sideNodes.length; i += 1) { 257 | if (sideNodes[i] == Constants.NULL) { 258 | continue; 259 | } 260 | variables.sideNodePtr = sideNodes[i]; 261 | 262 | if ( 263 | variables.currentNode.leftChildPtr == Constants.NULL && 264 | variables.currentNode.rightChildPtr == Constants.NULL 265 | ) { 266 | variables.sideNode = getNodeAtPtr(variables.sideNodePtr); 267 | 268 | if (isLeaf(variables.sideNode)) { 269 | // Sibling is a leaf: needs to be percolated up the tree 270 | variables.currentPtr = variables.sideNodePtr; 271 | variables.currentNode = getNodeAtPtr(variables.sideNodePtr); 272 | continue; 273 | } else { 274 | // Sibling is a node: needs to be left in its place. 275 | variables.nonPlaceholderReached = true; 276 | } 277 | } 278 | 279 | if ( 280 | !variables.nonPlaceholderReached && 281 | getNodeAtPtr(variables.sideNodePtr).digest == Constants.NULL 282 | ) { 283 | // We found another placeholder sibling node, keep going up the 284 | // tree until we find the first sibling that is not a placeholder. 285 | continue; 286 | } else if (!variables.nonPlaceholderReached) { 287 | // We found the first sibling node that is not a placeholder, it is 288 | // time to insert our leaf sibling node here. 289 | variables.nonPlaceholderReached = true; 290 | } 291 | 292 | if (getBitAtFromMSB(key, sideNodes.length - 1 - i) == 1) { 293 | variables.currentNode = hashNode( 294 | variables.sideNodePtr, 295 | variables.currentPtr, 296 | getNodeAtPtr(variables.sideNodePtr).digest, 297 | getNodeAtPtr(variables.currentPtr).digest 298 | ); 299 | } else { 300 | variables.currentNode = hashNode( 301 | variables.currentPtr, 302 | variables.sideNodePtr, 303 | getNodeAtPtr(variables.currentPtr).digest, 304 | getNodeAtPtr(variables.sideNodePtr).digest 305 | ); 306 | } 307 | 308 | variables.currentPtr = getPtrToNode(variables.currentNode); 309 | } 310 | 311 | return variables.currentPtr; 312 | } 313 | 314 | /// @notice Update the value of a leaf 315 | /// @param key: The key of the leaf to be updates 316 | /// @param value: The new value 317 | // solhint-disable-next-line func-visibility 318 | function update( 319 | bytes32 key, 320 | bytes memory value, 321 | bytes32 rootPtr 322 | ) internal pure returns (bytes32) { 323 | bytes32[] memory sideNodes; 324 | bytes32 oldLeafPtr; 325 | Node memory oldLeafNode; 326 | Node memory siblingNode; 327 | 328 | (sideNodes, oldLeafPtr, oldLeafNode, siblingNode) = sideNodesForRoot(key, rootPtr); 329 | 330 | bytes32 newRootPtr; 331 | if (isDefaultValue(value)) { 332 | newRootPtr = deleteWithSideNodes(key, sideNodes, oldLeafNode, rootPtr); 333 | } else { 334 | newRootPtr = updateWithSideNodes(key, value, sideNodes, oldLeafPtr, oldLeafNode); 335 | } 336 | 337 | return getNodeAtPtr(newRootPtr).digest; 338 | } 339 | 340 | /// @notice A struct to hold variables of the delete function in memory 341 | /// @dev Necessary to circumvent stack-too-deep errors caused by too many 342 | /// @dev variables on the stack. 343 | struct DeleteFunctionVariables { 344 | bytes32 currentPtr; 345 | Node currentNode; 346 | bytes32 sideNodePtr; 347 | Node sideNode; 348 | bool nonPlaceholderReached; 349 | } 350 | 351 | /// @notice A struct to hold variables of the sidenodes function in memory 352 | /// @dev Necessary to circumvent stack-too-deep errors caused by too many 353 | /// @dev variables on the stack. 354 | struct SideNodesFunctionVariables { 355 | bytes32 leftNodePtr; 356 | bytes32 rightNodePtr; 357 | bytes32 nodePtr; 358 | bytes32 sideNodePtr; 359 | uint256 sideNodeCount; 360 | bytes leafData; 361 | } 362 | 363 | /// @notice Verify a proof is valid 364 | /// @param proof: A decompacted sparse Merkle proof 365 | /// @param key: The key of the leave being proved 366 | /// @param value: The value of the leaf 367 | /// @return result : Whether the proof is valid or not 368 | function verify( 369 | SparseMerkleProof memory proof, 370 | bytes32 key, 371 | bytes memory value, 372 | bytes32 root 373 | ) public pure returns (bool result) { 374 | result = verifyProof(proof, root, key, value); 375 | } 376 | 377 | /// @notice Adds a branch to the DSMST 378 | /// @param proof: The proof of the leaf to be added 379 | /// @param key: The key of the leaf 380 | /// @param value: The value of the leaf 381 | /// @param root: The root of the branch 382 | function addBranch( 383 | SparseMerkleProof memory proof, 384 | bytes32 key, 385 | bytes memory value, 386 | bytes32 root, 387 | bytes32 rootPtr 388 | ) public pure returns (bytes32) { 389 | AddBranchVariables memory variables; 390 | 391 | bytes32[] memory sideNodePtrs = new bytes32[](proof.SideNodes.length); 392 | bytes32[] memory nodePtrs = new bytes32[](proof.SideNodes.length); 393 | 394 | // Set root 395 | // When adding the first branch, rootPtr will not be set yet, set it here. 396 | if (rootPtr == Constants.NULL) { 397 | // Set the new root 398 | Node memory rootNode = Node( 399 | root, 400 | Constants.LEAF_PREFIX, 401 | Constants.NULL, 402 | Constants.NULL, 403 | Constants.ZERO, 404 | "" 405 | ); 406 | rootPtr = getPtrToNode(rootNode); 407 | variables.parent = rootNode; 408 | } 409 | // On subsequent branches, we need to retrieve root 410 | else { 411 | variables.parent = getNodeAtPtr(rootPtr); 412 | } 413 | 414 | // Step backwards through proof (from root down to leaf), getting pointers to the nodes/sideNodes 415 | // If node is not yet added, set digest to NULL (we'll set it when we hash back up the branch) 416 | for (uint256 i = proof.SideNodes.length; i > 0; i -= 1) { 417 | uint256 j = i - 1; 418 | 419 | // Parent has a child, so is a node 420 | variables.parent.prefix = Constants.NODE_PREFIX; 421 | 422 | // Descend into left or right subtree depending on key 423 | // If leaf is in the right subtree: 424 | if (getBitAtFromMSB(key, proof.SideNodes.length - i) == 1) { 425 | // Subtree is on the right, so sidenode is on the left. 426 | // Check to see if sidenode already exists. If not, create it. and associate with parent 427 | if (variables.parent.leftChildPtr == Constants.NULL) { 428 | variables.sideNode = Node( 429 | proof.SideNodes[j], 430 | Constants.NODE_PREFIX, 431 | Constants.NULL, 432 | Constants.NULL, 433 | Constants.ZERO, 434 | "" 435 | ); 436 | variables.sideNodePtr = getPtrToNode(variables.sideNode); 437 | variables.parent.leftChildPtr = variables.sideNodePtr; 438 | } else { 439 | variables.sideNodePtr = variables.parent.leftChildPtr; 440 | } 441 | 442 | // Check to see if node already exists. If not, create it. and associate with parent 443 | // Its digest is initially null. We calculate and set it when we climb back up the tree 444 | if (variables.parent.rightChildPtr == Constants.NULL) { 445 | variables.node = Node( 446 | Constants.NULL, 447 | Constants.LEAF_PREFIX, 448 | Constants.NULL, 449 | Constants.NULL, 450 | Constants.ZERO, 451 | "" 452 | ); 453 | variables.nodePtr = getPtrToNode(variables.node); 454 | variables.parent.rightChildPtr = variables.nodePtr; 455 | } else { 456 | variables.nodePtr = variables.parent.rightChildPtr; 457 | variables.node = getNodeAtPtr(variables.nodePtr); 458 | } 459 | 460 | // Mirror image of preceding code block, for when leaf is in the left subtree 461 | // If subtree is on the left, sideNode is on the right 462 | } else { 463 | if (variables.parent.rightChildPtr == Constants.NULL) { 464 | variables.sideNode = Node( 465 | proof.SideNodes[j], 466 | Constants.NODE_PREFIX, 467 | Constants.NULL, 468 | Constants.NULL, 469 | Constants.ZERO, 470 | "" 471 | ); 472 | variables.sideNodePtr = getPtrToNode(variables.sideNode); 473 | variables.parent.rightChildPtr = variables.sideNodePtr; 474 | } else { 475 | variables.sideNodePtr = variables.parent.rightChildPtr; 476 | } 477 | 478 | if (variables.parent.leftChildPtr == Constants.NULL) { 479 | variables.node = Node( 480 | Constants.NULL, 481 | Constants.LEAF_PREFIX, 482 | Constants.NULL, 483 | Constants.NULL, 484 | Constants.ZERO, 485 | "" 486 | ); 487 | variables.nodePtr = getPtrToNode(variables.node); 488 | variables.parent.leftChildPtr = variables.nodePtr; 489 | } else { 490 | variables.nodePtr = variables.parent.leftChildPtr; 491 | variables.node = getNodeAtPtr(variables.nodePtr); 492 | } 493 | } 494 | 495 | // Keep pointers to sideNode and node 496 | sideNodePtrs[j] = variables.sideNodePtr; 497 | nodePtrs[j] = variables.nodePtr; 498 | 499 | variables.parent = variables.node; 500 | } 501 | 502 | // Set leaf digest 503 | Node memory leaf = getNodeAtPtr(nodePtrs[0]); 504 | leaf.digest = leafDigest(key, value); 505 | leaf.key = key; 506 | leaf.leafData = value; 507 | 508 | if (proof.SideNodes.length == 0) { 509 | return rootPtr; 510 | } 511 | 512 | // If sibling was a leaf, set its prefix to indicate that 513 | if (isLeaf(proof.Sibling)) { 514 | variables.node = getNodeAtPtr(sideNodePtrs[0]); 515 | variables.node.prefix = Constants.LEAF_PREFIX; 516 | } 517 | 518 | // Go back up the tree, setting the digests of nodes on the branch 519 | for (uint256 i = 1; i < nodePtrs.length; i += 1) { 520 | variables.node = getNodeAtPtr(nodePtrs[i]); 521 | variables.node.digest = nodeDigest( 522 | getNodeAtPtr(variables.node.leftChildPtr).digest, 523 | getNodeAtPtr(variables.node.rightChildPtr).digest 524 | ); 525 | } 526 | 527 | return rootPtr; 528 | } 529 | 530 | /// @notice Verify a compact proof 531 | /// @param proof The Compact proof 532 | /// @param key: The key of the leaf to be proved 533 | /// @param value: The value of the leaf 534 | /// @return Whether the proof is valid or not 535 | function verifyCompact( 536 | SparseCompactMerkleProof memory proof, 537 | bytes32 key, 538 | bytes memory value, 539 | bytes32 root 540 | ) public pure returns (bool) { 541 | // Decompact the proof 542 | SparseMerkleProof memory decompactedProof = decompactProof(proof); 543 | 544 | // Verify the decompacted proof 545 | return verify(decompactedProof, key, value, root); 546 | } 547 | 548 | /// @notice Add a branch using a compact proof 549 | /// @param proof: The compact Merkle proof 550 | /// @param key: The key of the leaf to be proved 551 | /// @param value: The value of the leaf 552 | /// @return Whether the addition was a success 553 | function addBranchCompact( 554 | SparseCompactMerkleProof memory proof, 555 | bytes32 key, 556 | bytes memory value, 557 | bytes32 root, 558 | bytes32 rootPtr 559 | ) public pure returns (bytes32) { 560 | SparseMerkleProof memory decompactedProof = decompactProof(proof); 561 | return addBranch(decompactedProof, key, value, root, rootPtr); 562 | } 563 | 564 | function addBranchesAndUpdate( 565 | MerkleBranch[] memory branches, 566 | bytes32 root, 567 | bytes32 key, 568 | bytes memory value 569 | ) public pure returns (bytes32 newRoot) { 570 | bytes32 rootPtr = Constants.ZERO; 571 | for (uint256 i = 0; i < branches.length; i++) { 572 | rootPtr = addBranchCompact( 573 | branches[i].proof, 574 | branches[i].key, 575 | branches[i].value, 576 | root, 577 | rootPtr 578 | ); 579 | } 580 | 581 | newRoot = update(key, value, rootPtr); 582 | } 583 | 584 | function addBranchesAndDelete( 585 | MerkleBranch[] memory branches, 586 | bytes32 root, 587 | bytes32 key 588 | ) public pure returns (bytes32 newRoot) { 589 | newRoot = addBranchesAndUpdate(branches, root, key, abi.encodePacked(Constants.ZERO)); 590 | } 591 | 592 | struct AddBranchVariables { 593 | bytes32 nodePtr; 594 | bytes32 sideNodePtr; 595 | Node node; 596 | Node parent; 597 | Node sideNode; 598 | } 599 | } 600 | --------------------------------------------------------------------------------