├── .dockerignore ├── .gas-snapshot ├── .github └── workflows │ ├── run_tests.yml │ └── slither.yml ├── .gitignore ├── .gitmodules ├── Dockerfile.deploy ├── LICENSE.txt ├── README.md ├── differential_testing ├── README.md ├── scripts │ ├── generate_complete_proof.ts │ ├── generate_complete_root.ts │ ├── generate_root.ts │ ├── generate_root_cli.ts │ ├── merkle-tree.ts │ ├── package-lock.json │ └── package.json └── test │ ├── CompleteDifferentialTests.t.sol │ ├── DifferentialTests.t.sol │ └── utils │ └── Strings2.sol ├── foundry.toml ├── package.json ├── remappings.txt ├── reports └── murky_gas_report.png ├── script ├── Merkle.s.sol ├── common │ └── ScriptHelper.sol ├── target │ ├── input.json │ └── output.json └── test │ └── Merkle.s.t.sol └── src ├── CompleteMerkle.sol ├── Merkle.sol ├── Xorkle.sol ├── common └── MurkyBase.sol └── test ├── CompleteMerkle.t.sol ├── Merkle.t.sol ├── MurkyBase.t.sol ├── StandardInput.t.sol ├── Xorkle.t.sol └── standard_data ├── README.md ├── StandardInput.old.txt └── StandardInput.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | out 2 | cache 3 | *.txt -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | StandardizedInputTest:testMerkleGenerateProofStandard() (gas: 751849) 2 | StandardizedInputTest:testMerkleVerifyProofStandard() (gas: 846894) 3 | StandardizedInputTest:testXorkleGenerateProofStandard() (gas: 695233) 4 | StandardizedInputTest:testXorkleVerifyProofStandard() (gas: 779721) 5 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | name: tests 8 | 9 | jobs: 10 | check: 11 | name: Run All Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: recursive 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 16.15.1 20 | 21 | - name: Install Foundry 22 | uses: foundry-rs/foundry-toolchain@v1 23 | with: 24 | version: nightly 25 | 26 | - name: Run Fuzzed Unit Tests 27 | run: forge test --no-match-path src/test/StandardInput.t.sol --fuzz-runs 10000 28 | 29 | - name: Run Murky Differential Tests 30 | run: | 31 | npm --prefix differential_testing/scripts/ install 32 | npm --prefix differential_testing/scripts/ run compile 33 | forge test --ffi --contracts differential_testing/test/DifferentialTests.t.sol --fuzz-runs 512 34 | 35 | - name: Run Complete Differential Tests 36 | run: | 37 | npm --prefix differential_testing/scripts/ install 38 | npm --prefix differential_testing/scripts/ run compile 39 | forge test --ffi --contracts differential_testing/test/CompleteDifferentialTests.t.sol --fuzz-runs 512 40 | 41 | - name: Run Standard Gas Snapshotting 42 | run: forge snapshot --gas-report --ffi --match-path src/test/StandardInput.t.sol -------------------------------------------------------------------------------- /.github/workflows/slither.yml: -------------------------------------------------------------------------------- 1 | name: Slither Analysis 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | analyze: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: crytic/slither-action@v0.3.1 13 | with: 14 | slither-args: '--exclude-informational --checklist --show-ignored-findings' 15 | fail-on: 'low' 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | *.txt 4 | differential_testing/**/node_modules 5 | differential_testing/**/*.js 6 | differential_testing/**/*.json 7 | differential_testing/data/input 8 | 9 | !remappings.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/openzeppelin-contracts"] 2 | path = lib/openzeppelin-contracts 3 | url = https://github.com/openzeppelin/openzeppelin-contracts 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | -------------------------------------------------------------------------------- /Dockerfile.deploy: -------------------------------------------------------------------------------- 1 | from ghcr.io/gakonst/foundry:nightly 2 | 3 | WORKDIR /murky 4 | COPY . . 5 | RUN forge build 6 | RUN forge test 7 | ENTRYPOINT ["forge", "create"] -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 dmfxyz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Merkle Generator and Prover in Solidity 2 | ![Tests](https://github.com/dmfxyz/murky/actions/workflows/run_tests.yml/badge.svg?event=push) 3 | ![Slither](https://github.com/dmfxyz/murky/actions/workflows/slither.yml/badge.svg?event=push) 4 | 5 | 6 | ### Overview 7 | Murky contains contracts that can generate merkle roots and proofs. Murky also performs inclusion verification. A couple of default implementations are available out-of-the-box: 8 | 9 | 1. [`Merkle.sol`](./src/Merkle.sol) is the original Murky implementation. It implements the tree as a [Full Binary Tree](https://xlinux.nist.gov/dads/HTML/fullBinaryTree.html). 10 | 11 | 2. [`CompleteMerkle.sol`](./src/CompleteMerkle.sol) is a merkle tree implementation using [Complete Binary Trees](https://xlinux.nist.gov/dads/HTML/completeBinaryTree.html). Some external libraries, particulary front-end or off-chain ones, use this type of tree. 12 | 13 | By default, both trees use sorted concatentation based hashing; you can also "bring your own" hashing function by inherting from [`MurkyBase.sol`](./src/common/MurkyBase.sol). 14 | 15 | The root generation, proof generation, and verification functions are all fuzz tested (configured 10,000 runs by default) using arbitrary bytes32 arrays and uint leaves. See [testing](#testing). 16 | 17 | > Note: Code is not audited (yet). Please do your own due dilligence testing if you are planning to use this code! 18 | 19 | You can currently see Murky in action in the [Seaport](https://github.com/ProjectOpenSea/Seaport) test suite. 20 | 21 | ### Building Locally 22 | You can run the repo using [Foundry](https://github.com/gakonst/foundry). 23 | 1. clone the repo 24 | 2. `forge install` 25 | 3. `forge test` 26 | 27 | ### Example Usage 28 | ```solidity 29 | // Initialize 30 | Merkle m = new Merkle(); 31 | // Toy Data 32 | bytes32[] memory data = new bytes32[](4); 33 | data[0] = bytes32("0x0"); 34 | data[1] = bytes32("0x1"); 35 | data[2] = bytes32("0x2"); 36 | data[3] = bytes32("0x3"); 37 | // Get Root, Proof, and Verify 38 | bytes32 root = m.getRoot(data); 39 | bytes32[] memory proof = m.getProof(data, 2); // will get proof for 0x2 value 40 | bool verified = m.verifyProof(root, proof, data[2]); // true! 41 | assertTrue(verified); 42 | ``` 43 | 44 | ### Script 45 | `Merkle.s.sol` is implemented using `forge-std` for quick and simple interaction with the core contracts. The script reads from `script/target/input.json`, generates merkle proof using `Merkle.sol` and then outputs at `script/target/output.json`. 46 | 47 | The hashes of the leafs are generated using `keccak256(bytes.concat(keccak256(abi.encode(...))))`, which is the same as [`OpenZeppelin/merkle-tree`](https://github.com/OpenZeppelin/merkle-tree#validating-a-proof-in-solidity). 48 | 49 | ```bash 50 | forge script script/Merkle.s.sol 51 | ``` 52 | 53 | ### Testing 54 | The code is both "fuzz" tested and tested with standardized data. [Standard data info](./src/test/standard_data/). 55 | 56 | When measuring a change's performance impact, please ensure you are benchmarking using standardized data only*: 57 | 58 | ```sh 59 | forge snapshot --ffi --match-path src/test/StandardInput.t.sol 60 | ``` 61 | 62 | Passing just standardized tests is not sufficient for implementation changes. All changes must pass all tests, preferably with 10,000 fuzz runs. Slither analysis must also pass. 63 | 64 | > * It's possible that an improvement is not adequetly revealed by the current standardized data. If that is the case, new standard data should be provided with an accompanying description/justification. 65 | 66 | There is also [differential testing](./differential_testing/). 67 | 68 | #### Latest Gas 69 | ![gas report](./reports/murky_gas_report.png) 70 | --- -------------------------------------------------------------------------------- /differential_testing/README.md: -------------------------------------------------------------------------------- 1 | ## Differential Testing 2 | Differential testing is used to compare Murky's solidity implementation to reference implementations in other languages. This directory contains the scripts needed to support this testing, as well as the differential tests themselves. 3 | 4 | Currently two reference implementations are tested. The first is adapted from [Uniswap/merkle-distributor](https://github.com/uniswap/merkle-distributor), and the second is from [OpenZeppelin/merkle-tree](https://github.com/OpenZeppelin/merkle-tree). Both are written in Javascript. 5 | 6 | 7 | ### Setup 8 | From the [scripts directory](./scripts/), run 9 | ```sh 10 | npm install 11 | npm run compile 12 | ``` 13 | 14 | 15 | ### Test the javascript implementation 16 | From the scripts directory: 17 | 1. To test that the Uniswap/merkle-distributor differential test will work: 18 | ```sh 19 | npm run generate-root 20 | ``` 21 | 22 | 2. To test that the OpenZeppelin/merkle-tree differential test will work: 23 | ```sh 24 | npm run generate-complete-root ../data/complete_root_input.json 25 | npm run generate-complete-proof ../data/complete_proof_input.json 26 | ``` 27 | 28 | If all commands output data without exception, then you are ready to run the differential tests. 29 | ### Run the differential test using foundry 30 | Now you can run the tests. 31 | From the **root** of the Murky repo, run: 32 | ```sh 33 | forge test --ffi --contracts differential_testing/test/ 34 | ``` 35 | > Note: The differential tests can take some time to run. An extended period of time without output does not necessarily indicate a problem. 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /differential_testing/scripts/generate_complete_proof.ts: -------------------------------------------------------------------------------- 1 | import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; 2 | import { ethers } from 'ethers'; 3 | 4 | const fs = require('fs'); 5 | const input_file = process.argv[2]; 6 | const input = JSON.parse(fs.readFileSync(input_file, 'utf8')); 7 | const one_word = "0x0000000000000000000000000000000000000000000000000000000000000001" 8 | const leafs = input['leafs'].map((bytes32) => [bytes32, one_word]); 9 | const indexToProve = input['index']; 10 | const tree = StandardMerkleTree.of(leafs, ["bytes32", "bytes32"], { sortLeaves: false }); // NO DEFAULT SORTING LEAVES 11 | const proof = tree.getProof(indexToProve); 12 | process.stdout.write(ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [proof])); 13 | -------------------------------------------------------------------------------- /differential_testing/scripts/generate_complete_root.ts: -------------------------------------------------------------------------------- 1 | import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; 2 | const fs = require('fs'); 3 | const input_file = process.argv[2]; 4 | const input = JSON.parse(fs.readFileSync(input_file, 'utf8')); 5 | const one_word = "0x0000000000000000000000000000000000000000000000000000000000000001" 6 | const leafs = input['leafs'].map((bytes32) => [bytes32, one_word]); 7 | const tree = StandardMerkleTree.of(leafs, ["bytes32", "bytes32"], { sortLeaves: false }); // NO DEFAULT SORTING LEAVES 8 | process.stdout.write(tree.root); 9 | 10 | -------------------------------------------------------------------------------- /differential_testing/scripts/generate_root.ts: -------------------------------------------------------------------------------- 1 | import MerkleTree from './merkle-tree'; 2 | import * as fs from 'fs'; 3 | import { ethers } from 'ethers'; 4 | import { toBuffer } from 'ethereumjs-util'; 5 | import crypto from 'crypto'; 6 | 7 | var data = []; 8 | for (var i = 0; i < 129; ++i) { 9 | data.push("0x" + crypto.randomBytes(32).toString('hex')); 10 | } 11 | var dataAsBuffer = data.map(b => toBuffer(b)); 12 | 13 | const tree = new MerkleTree(dataAsBuffer); 14 | process.stdout.write(ethers.utils.defaultAbiCoder.encode(['bytes32'], [tree.getRoot()])); 15 | const encodedData = ethers.utils.defaultAbiCoder.encode(["bytes32[129]"], [data]); 16 | if (!fs.existsSync("../data/")) { 17 | fs.mkdirSync("../data/"); 18 | } 19 | fs.writeFileSync("../data/merkle_input.txt", encodedData); 20 | 21 | -------------------------------------------------------------------------------- /differential_testing/scripts/generate_root_cli.ts: -------------------------------------------------------------------------------- 1 | import MerkleTree from './merkle-tree'; 2 | import { ethers } from 'ethers'; 3 | import { toBuffer } from 'ethereumjs-util'; 4 | 5 | const encoder = ethers.utils.defaultAbiCoder; 6 | const num_leaves = process.argv[2]; 7 | const encoded_leaves = process.argv[3]; 8 | const decoded_data = encoder.decode([`bytes32[${num_leaves}]`], encoded_leaves)[0] 9 | var dataAsBuffer = decoded_data.map(b => toBuffer(b)); 10 | 11 | const tree = new MerkleTree(dataAsBuffer); 12 | process.stdout.write(ethers.utils.defaultAbiCoder.encode(['bytes32'], [tree.getRoot()])); 13 | 14 | 15 | -------------------------------------------------------------------------------- /differential_testing/scripts/merkle-tree.ts: -------------------------------------------------------------------------------- 1 | import { bufferToHex, keccak256, toBuffer } from 'ethereumjs-util' 2 | import { ethers } from 'ethers' 3 | 4 | export default class MerkleTree { 5 | private readonly elements: Buffer[] 6 | private readonly bufferElementPositionIndex: { [hexElement: string]: number } 7 | private readonly layers: Buffer[][] 8 | 9 | constructor(elements: Buffer[]) { 10 | this.elements = [...elements] 11 | // Sort elements 12 | //this.elements.sort(Buffer.compare) !!!!! MURKY::: WE ASSUME ELEMENTS ARE PRE-SORTED BY USER 13 | // Deduplicate elements 14 | //this.elements = MerkleTree.bufDedup(this.elements) !!!!! MURKY::: GENERIC TREE 15 | 16 | this.bufferElementPositionIndex = this.elements.reduce<{ [hexElement: string]: number }>((memo, el, index) => { 17 | memo[bufferToHex(el)] = index 18 | return memo 19 | }, {}) 20 | 21 | // Create layers 22 | this.layers = this.getLayers(this.elements) 23 | } 24 | 25 | getLayers(elements: Buffer[]): Buffer[][] { 26 | if (elements.length === 0) { 27 | throw new Error('empty tree') 28 | } 29 | 30 | const layers = [] 31 | layers.push(elements) 32 | 33 | // Get next layer until we reach the root 34 | while (layers[layers.length - 1].length > 1) { 35 | layers.push(this.getNextLayer(layers[layers.length - 1])) 36 | } 37 | 38 | return layers 39 | } 40 | 41 | getNextLayer(elements: Buffer[]): Buffer[] { 42 | return elements.reduce((layer, el, idx, arr) => { 43 | if (idx % 2 === 0) { 44 | // Hash the current element with its pair element 45 | layer.push(MerkleTree.combinedHash(el, arr[idx + 1])) 46 | } 47 | 48 | return layer 49 | }, []) 50 | } 51 | 52 | static combinedHash(first: Buffer, second: Buffer): Buffer { 53 | if (!first) { 54 | first = toBuffer("0x0000000000000000000000000000000000000000000000000000000000000000") //!!!!! MURKY::: ALWAYS NEED TO HASH EACH LAYER 55 | } 56 | if (!second) { 57 | second = toBuffer("0x0000000000000000000000000000000000000000000000000000000000000000") //!!!!! MURKY::: ALWAYS NEED TO HASH EACH LAYER 58 | } 59 | 60 | return keccak256(MerkleTree.sortAndConcat(first, second)) 61 | } 62 | 63 | getRoot(): Buffer { 64 | return this.layers[this.layers.length - 1][0] 65 | } 66 | 67 | getHexRoot(): string { 68 | return bufferToHex(this.getRoot()) 69 | } 70 | 71 | getProof(el: Buffer) { 72 | let idx = this.bufferElementPositionIndex[bufferToHex(el)] 73 | 74 | if (typeof idx !== 'number') { 75 | throw new Error('Element does not exist in Merkle tree') 76 | } 77 | 78 | return this.layers.reduce((proof, layer) => { 79 | const pairElement = MerkleTree.getPairElement(idx, layer) 80 | 81 | if (pairElement) { 82 | proof.push(pairElement) 83 | } 84 | 85 | idx = Math.floor(idx / 2) 86 | 87 | return proof 88 | }, []) 89 | } 90 | 91 | getHexProof(el: Buffer): string[] { 92 | const proof = this.getProof(el) 93 | 94 | return MerkleTree.bufArrToHexArr(proof) 95 | } 96 | 97 | private static getPairElement(idx: number, layer: Buffer[]): Buffer | null { 98 | const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1 99 | 100 | if (pairIdx < layer.length) { 101 | return layer[pairIdx] 102 | } else { 103 | return null 104 | } 105 | } 106 | 107 | private static bufDedup(elements: Buffer[]): Buffer[] { 108 | return elements.filter((el, idx) => { 109 | return idx === 0 || !elements[idx - 1].equals(el) 110 | }) 111 | } 112 | 113 | private static bufArrToHexArr(arr: Buffer[]): string[] { 114 | if (arr.some((el) => !Buffer.isBuffer(el))) { 115 | throw new Error('Array is not an array of buffers') 116 | } 117 | 118 | return arr.map((el) => '0x' + el.toString('hex')) 119 | } 120 | 121 | public static sortAndConcat(...args: Buffer[]): Buffer { 122 | return Buffer.concat([...args].sort(Buffer.compare)); 123 | } 124 | } -------------------------------------------------------------------------------- /differential_testing/scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "murky-differential", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "murky-differential", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@openzeppelin/merkle-tree": "^1.0.6", 13 | "ethereumjs-util": "^7.1.4", 14 | "ethers": "^5.6.4", 15 | "rlp": "^3.0.0" 16 | }, 17 | "devDependencies": { 18 | "typescript": "^4.6.3" 19 | } 20 | }, 21 | "node_modules/@ethersproject/abi": { 22 | "version": "5.6.1", 23 | "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.1.tgz", 24 | "integrity": "sha512-0cqssYh6FXjlwKWBmLm3+zH2BNARoS5u/hxbz+LpQmcDB3w0W553h2btWui1/uZp2GBM/SI3KniTuMcYyHpA5w==", 25 | "funding": [ 26 | { 27 | "type": "individual", 28 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 29 | }, 30 | { 31 | "type": "individual", 32 | "url": "https://www.buymeacoffee.com/ricmoo" 33 | } 34 | ], 35 | "dependencies": { 36 | "@ethersproject/address": "^5.6.0", 37 | "@ethersproject/bignumber": "^5.6.0", 38 | "@ethersproject/bytes": "^5.6.0", 39 | "@ethersproject/constants": "^5.6.0", 40 | "@ethersproject/hash": "^5.6.0", 41 | "@ethersproject/keccak256": "^5.6.0", 42 | "@ethersproject/logger": "^5.6.0", 43 | "@ethersproject/properties": "^5.6.0", 44 | "@ethersproject/strings": "^5.6.0" 45 | } 46 | }, 47 | "node_modules/@ethersproject/abstract-provider": { 48 | "version": "5.6.0", 49 | "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.6.0.tgz", 50 | "integrity": "sha512-oPMFlKLN+g+y7a79cLK3WiLcjWFnZQtXWgnLAbHZcN3s7L4v90UHpTOrLk+m3yr0gt+/h9STTM6zrr7PM8uoRw==", 51 | "funding": [ 52 | { 53 | "type": "individual", 54 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 55 | }, 56 | { 57 | "type": "individual", 58 | "url": "https://www.buymeacoffee.com/ricmoo" 59 | } 60 | ], 61 | "dependencies": { 62 | "@ethersproject/bignumber": "^5.6.0", 63 | "@ethersproject/bytes": "^5.6.0", 64 | "@ethersproject/logger": "^5.6.0", 65 | "@ethersproject/networks": "^5.6.0", 66 | "@ethersproject/properties": "^5.6.0", 67 | "@ethersproject/transactions": "^5.6.0", 68 | "@ethersproject/web": "^5.6.0" 69 | } 70 | }, 71 | "node_modules/@ethersproject/abstract-signer": { 72 | "version": "5.6.0", 73 | "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.6.0.tgz", 74 | "integrity": "sha512-WOqnG0NJKtI8n0wWZPReHtaLkDByPL67tn4nBaDAhmVq8sjHTPbCdz4DRhVu/cfTOvfy9w3iq5QZ7BX7zw56BQ==", 75 | "funding": [ 76 | { 77 | "type": "individual", 78 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 79 | }, 80 | { 81 | "type": "individual", 82 | "url": "https://www.buymeacoffee.com/ricmoo" 83 | } 84 | ], 85 | "dependencies": { 86 | "@ethersproject/abstract-provider": "^5.6.0", 87 | "@ethersproject/bignumber": "^5.6.0", 88 | "@ethersproject/bytes": "^5.6.0", 89 | "@ethersproject/logger": "^5.6.0", 90 | "@ethersproject/properties": "^5.6.0" 91 | } 92 | }, 93 | "node_modules/@ethersproject/address": { 94 | "version": "5.6.0", 95 | "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.0.tgz", 96 | "integrity": "sha512-6nvhYXjbXsHPS+30sHZ+U4VMagFC/9zAk6Gd/h3S21YW4+yfb0WfRtaAIZ4kfM4rrVwqiy284LP0GtL5HXGLxQ==", 97 | "funding": [ 98 | { 99 | "type": "individual", 100 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 101 | }, 102 | { 103 | "type": "individual", 104 | "url": "https://www.buymeacoffee.com/ricmoo" 105 | } 106 | ], 107 | "dependencies": { 108 | "@ethersproject/bignumber": "^5.6.0", 109 | "@ethersproject/bytes": "^5.6.0", 110 | "@ethersproject/keccak256": "^5.6.0", 111 | "@ethersproject/logger": "^5.6.0", 112 | "@ethersproject/rlp": "^5.6.0" 113 | } 114 | }, 115 | "node_modules/@ethersproject/base64": { 116 | "version": "5.6.0", 117 | "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.6.0.tgz", 118 | "integrity": "sha512-2Neq8wxJ9xHxCF9TUgmKeSh9BXJ6OAxWfeGWvbauPh8FuHEjamgHilllx8KkSd5ErxyHIX7Xv3Fkcud2kY9ezw==", 119 | "funding": [ 120 | { 121 | "type": "individual", 122 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 123 | }, 124 | { 125 | "type": "individual", 126 | "url": "https://www.buymeacoffee.com/ricmoo" 127 | } 128 | ], 129 | "dependencies": { 130 | "@ethersproject/bytes": "^5.6.0" 131 | } 132 | }, 133 | "node_modules/@ethersproject/basex": { 134 | "version": "5.6.0", 135 | "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.6.0.tgz", 136 | "integrity": "sha512-qN4T+hQd/Md32MoJpc69rOwLYRUXwjTlhHDIeUkUmiN/JyWkkLLMoG0TqvSQKNqZOMgN5stbUYN6ILC+eD7MEQ==", 137 | "funding": [ 138 | { 139 | "type": "individual", 140 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 141 | }, 142 | { 143 | "type": "individual", 144 | "url": "https://www.buymeacoffee.com/ricmoo" 145 | } 146 | ], 147 | "dependencies": { 148 | "@ethersproject/bytes": "^5.6.0", 149 | "@ethersproject/properties": "^5.6.0" 150 | } 151 | }, 152 | "node_modules/@ethersproject/bignumber": { 153 | "version": "5.6.0", 154 | "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.6.0.tgz", 155 | "integrity": "sha512-VziMaXIUHQlHJmkv1dlcd6GY2PmT0khtAqaMctCIDogxkrarMzA9L94KN1NeXqqOfFD6r0sJT3vCTOFSmZ07DA==", 156 | "funding": [ 157 | { 158 | "type": "individual", 159 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 160 | }, 161 | { 162 | "type": "individual", 163 | "url": "https://www.buymeacoffee.com/ricmoo" 164 | } 165 | ], 166 | "dependencies": { 167 | "@ethersproject/bytes": "^5.6.0", 168 | "@ethersproject/logger": "^5.6.0", 169 | "bn.js": "^4.11.9" 170 | } 171 | }, 172 | "node_modules/@ethersproject/bignumber/node_modules/bn.js": { 173 | "version": "4.12.0", 174 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 175 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" 176 | }, 177 | "node_modules/@ethersproject/bytes": { 178 | "version": "5.6.1", 179 | "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz", 180 | "integrity": "sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g==", 181 | "funding": [ 182 | { 183 | "type": "individual", 184 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 185 | }, 186 | { 187 | "type": "individual", 188 | "url": "https://www.buymeacoffee.com/ricmoo" 189 | } 190 | ], 191 | "dependencies": { 192 | "@ethersproject/logger": "^5.6.0" 193 | } 194 | }, 195 | "node_modules/@ethersproject/constants": { 196 | "version": "5.6.0", 197 | "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.6.0.tgz", 198 | "integrity": "sha512-SrdaJx2bK0WQl23nSpV/b1aq293Lh0sUaZT/yYKPDKn4tlAbkH96SPJwIhwSwTsoQQZxuh1jnqsKwyymoiBdWA==", 199 | "funding": [ 200 | { 201 | "type": "individual", 202 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 203 | }, 204 | { 205 | "type": "individual", 206 | "url": "https://www.buymeacoffee.com/ricmoo" 207 | } 208 | ], 209 | "dependencies": { 210 | "@ethersproject/bignumber": "^5.6.0" 211 | } 212 | }, 213 | "node_modules/@ethersproject/contracts": { 214 | "version": "5.6.0", 215 | "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.6.0.tgz", 216 | "integrity": "sha512-74Ge7iqTDom0NX+mux8KbRUeJgu1eHZ3iv6utv++sLJG80FVuU9HnHeKVPfjd9s3woFhaFoQGf3B3iH/FrQmgw==", 217 | "funding": [ 218 | { 219 | "type": "individual", 220 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 221 | }, 222 | { 223 | "type": "individual", 224 | "url": "https://www.buymeacoffee.com/ricmoo" 225 | } 226 | ], 227 | "dependencies": { 228 | "@ethersproject/abi": "^5.6.0", 229 | "@ethersproject/abstract-provider": "^5.6.0", 230 | "@ethersproject/abstract-signer": "^5.6.0", 231 | "@ethersproject/address": "^5.6.0", 232 | "@ethersproject/bignumber": "^5.6.0", 233 | "@ethersproject/bytes": "^5.6.0", 234 | "@ethersproject/constants": "^5.6.0", 235 | "@ethersproject/logger": "^5.6.0", 236 | "@ethersproject/properties": "^5.6.0", 237 | "@ethersproject/transactions": "^5.6.0" 238 | } 239 | }, 240 | "node_modules/@ethersproject/hash": { 241 | "version": "5.6.0", 242 | "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.6.0.tgz", 243 | "integrity": "sha512-fFd+k9gtczqlr0/BruWLAu7UAOas1uRRJvOR84uDf4lNZ+bTkGl366qvniUZHKtlqxBRU65MkOobkmvmpHU+jA==", 244 | "funding": [ 245 | { 246 | "type": "individual", 247 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 248 | }, 249 | { 250 | "type": "individual", 251 | "url": "https://www.buymeacoffee.com/ricmoo" 252 | } 253 | ], 254 | "dependencies": { 255 | "@ethersproject/abstract-signer": "^5.6.0", 256 | "@ethersproject/address": "^5.6.0", 257 | "@ethersproject/bignumber": "^5.6.0", 258 | "@ethersproject/bytes": "^5.6.0", 259 | "@ethersproject/keccak256": "^5.6.0", 260 | "@ethersproject/logger": "^5.6.0", 261 | "@ethersproject/properties": "^5.6.0", 262 | "@ethersproject/strings": "^5.6.0" 263 | } 264 | }, 265 | "node_modules/@ethersproject/hdnode": { 266 | "version": "5.6.0", 267 | "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.6.0.tgz", 268 | "integrity": "sha512-61g3Jp3nwDqJcL/p4nugSyLrpl/+ChXIOtCEM8UDmWeB3JCAt5FoLdOMXQc3WWkc0oM2C0aAn6GFqqMcS/mHTw==", 269 | "funding": [ 270 | { 271 | "type": "individual", 272 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 273 | }, 274 | { 275 | "type": "individual", 276 | "url": "https://www.buymeacoffee.com/ricmoo" 277 | } 278 | ], 279 | "dependencies": { 280 | "@ethersproject/abstract-signer": "^5.6.0", 281 | "@ethersproject/basex": "^5.6.0", 282 | "@ethersproject/bignumber": "^5.6.0", 283 | "@ethersproject/bytes": "^5.6.0", 284 | "@ethersproject/logger": "^5.6.0", 285 | "@ethersproject/pbkdf2": "^5.6.0", 286 | "@ethersproject/properties": "^5.6.0", 287 | "@ethersproject/sha2": "^5.6.0", 288 | "@ethersproject/signing-key": "^5.6.0", 289 | "@ethersproject/strings": "^5.6.0", 290 | "@ethersproject/transactions": "^5.6.0", 291 | "@ethersproject/wordlists": "^5.6.0" 292 | } 293 | }, 294 | "node_modules/@ethersproject/json-wallets": { 295 | "version": "5.6.0", 296 | "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.6.0.tgz", 297 | "integrity": "sha512-fmh86jViB9r0ibWXTQipxpAGMiuxoqUf78oqJDlCAJXgnJF024hOOX7qVgqsjtbeoxmcLwpPsXNU0WEe/16qPQ==", 298 | "funding": [ 299 | { 300 | "type": "individual", 301 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 302 | }, 303 | { 304 | "type": "individual", 305 | "url": "https://www.buymeacoffee.com/ricmoo" 306 | } 307 | ], 308 | "dependencies": { 309 | "@ethersproject/abstract-signer": "^5.6.0", 310 | "@ethersproject/address": "^5.6.0", 311 | "@ethersproject/bytes": "^5.6.0", 312 | "@ethersproject/hdnode": "^5.6.0", 313 | "@ethersproject/keccak256": "^5.6.0", 314 | "@ethersproject/logger": "^5.6.0", 315 | "@ethersproject/pbkdf2": "^5.6.0", 316 | "@ethersproject/properties": "^5.6.0", 317 | "@ethersproject/random": "^5.6.0", 318 | "@ethersproject/strings": "^5.6.0", 319 | "@ethersproject/transactions": "^5.6.0", 320 | "aes-js": "3.0.0", 321 | "scrypt-js": "3.0.1" 322 | } 323 | }, 324 | "node_modules/@ethersproject/keccak256": { 325 | "version": "5.6.0", 326 | "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.6.0.tgz", 327 | "integrity": "sha512-tk56BJ96mdj/ksi7HWZVWGjCq0WVl/QvfhFQNeL8fxhBlGoP+L80uDCiQcpJPd+2XxkivS3lwRm3E0CXTfol0w==", 328 | "funding": [ 329 | { 330 | "type": "individual", 331 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 332 | }, 333 | { 334 | "type": "individual", 335 | "url": "https://www.buymeacoffee.com/ricmoo" 336 | } 337 | ], 338 | "dependencies": { 339 | "@ethersproject/bytes": "^5.6.0", 340 | "js-sha3": "0.8.0" 341 | } 342 | }, 343 | "node_modules/@ethersproject/logger": { 344 | "version": "5.6.0", 345 | "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.6.0.tgz", 346 | "integrity": "sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==", 347 | "funding": [ 348 | { 349 | "type": "individual", 350 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 351 | }, 352 | { 353 | "type": "individual", 354 | "url": "https://www.buymeacoffee.com/ricmoo" 355 | } 356 | ] 357 | }, 358 | "node_modules/@ethersproject/networks": { 359 | "version": "5.6.2", 360 | "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.6.2.tgz", 361 | "integrity": "sha512-9uEzaJY7j5wpYGTojGp8U89mSsgQLc40PCMJLMCnFXTs7nhBveZ0t7dbqWUNrepWTszDbFkYD6WlL8DKx5huHA==", 362 | "funding": [ 363 | { 364 | "type": "individual", 365 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 366 | }, 367 | { 368 | "type": "individual", 369 | "url": "https://www.buymeacoffee.com/ricmoo" 370 | } 371 | ], 372 | "dependencies": { 373 | "@ethersproject/logger": "^5.6.0" 374 | } 375 | }, 376 | "node_modules/@ethersproject/pbkdf2": { 377 | "version": "5.6.0", 378 | "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.6.0.tgz", 379 | "integrity": "sha512-Wu1AxTgJo3T3H6MIu/eejLFok9TYoSdgwRr5oGY1LTLfmGesDoSx05pemsbrPT2gG4cQME+baTSCp5sEo2erZQ==", 380 | "funding": [ 381 | { 382 | "type": "individual", 383 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 384 | }, 385 | { 386 | "type": "individual", 387 | "url": "https://www.buymeacoffee.com/ricmoo" 388 | } 389 | ], 390 | "dependencies": { 391 | "@ethersproject/bytes": "^5.6.0", 392 | "@ethersproject/sha2": "^5.6.0" 393 | } 394 | }, 395 | "node_modules/@ethersproject/properties": { 396 | "version": "5.6.0", 397 | "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.6.0.tgz", 398 | "integrity": "sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==", 399 | "funding": [ 400 | { 401 | "type": "individual", 402 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 403 | }, 404 | { 405 | "type": "individual", 406 | "url": "https://www.buymeacoffee.com/ricmoo" 407 | } 408 | ], 409 | "dependencies": { 410 | "@ethersproject/logger": "^5.6.0" 411 | } 412 | }, 413 | "node_modules/@ethersproject/providers": { 414 | "version": "5.6.4", 415 | "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.4.tgz", 416 | "integrity": "sha512-WAdknnaZ52hpHV3qPiJmKx401BLpup47h36Axxgre9zT+doa/4GC/Ne48ICPxTm0BqndpToHjpLP1ZnaxyE+vw==", 417 | "funding": [ 418 | { 419 | "type": "individual", 420 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 421 | }, 422 | { 423 | "type": "individual", 424 | "url": "https://www.buymeacoffee.com/ricmoo" 425 | } 426 | ], 427 | "dependencies": { 428 | "@ethersproject/abstract-provider": "^5.6.0", 429 | "@ethersproject/abstract-signer": "^5.6.0", 430 | "@ethersproject/address": "^5.6.0", 431 | "@ethersproject/basex": "^5.6.0", 432 | "@ethersproject/bignumber": "^5.6.0", 433 | "@ethersproject/bytes": "^5.6.0", 434 | "@ethersproject/constants": "^5.6.0", 435 | "@ethersproject/hash": "^5.6.0", 436 | "@ethersproject/logger": "^5.6.0", 437 | "@ethersproject/networks": "^5.6.0", 438 | "@ethersproject/properties": "^5.6.0", 439 | "@ethersproject/random": "^5.6.0", 440 | "@ethersproject/rlp": "^5.6.0", 441 | "@ethersproject/sha2": "^5.6.0", 442 | "@ethersproject/strings": "^5.6.0", 443 | "@ethersproject/transactions": "^5.6.0", 444 | "@ethersproject/web": "^5.6.0", 445 | "bech32": "1.1.4", 446 | "ws": "7.4.6" 447 | } 448 | }, 449 | "node_modules/@ethersproject/random": { 450 | "version": "5.6.0", 451 | "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.0.tgz", 452 | "integrity": "sha512-si0PLcLjq+NG/XHSZz90asNf+YfKEqJGVdxoEkSukzbnBgC8rydbgbUgBbBGLeHN4kAJwUFEKsu3sCXT93YMsw==", 453 | "funding": [ 454 | { 455 | "type": "individual", 456 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 457 | }, 458 | { 459 | "type": "individual", 460 | "url": "https://www.buymeacoffee.com/ricmoo" 461 | } 462 | ], 463 | "dependencies": { 464 | "@ethersproject/bytes": "^5.6.0", 465 | "@ethersproject/logger": "^5.6.0" 466 | } 467 | }, 468 | "node_modules/@ethersproject/rlp": { 469 | "version": "5.6.0", 470 | "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.6.0.tgz", 471 | "integrity": "sha512-dz9WR1xpcTL+9DtOT/aDO+YyxSSdO8YIS0jyZwHHSlAmnxA6cKU3TrTd4Xc/bHayctxTgGLYNuVVoiXE4tTq1g==", 472 | "funding": [ 473 | { 474 | "type": "individual", 475 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 476 | }, 477 | { 478 | "type": "individual", 479 | "url": "https://www.buymeacoffee.com/ricmoo" 480 | } 481 | ], 482 | "dependencies": { 483 | "@ethersproject/bytes": "^5.6.0", 484 | "@ethersproject/logger": "^5.6.0" 485 | } 486 | }, 487 | "node_modules/@ethersproject/sha2": { 488 | "version": "5.6.0", 489 | "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.6.0.tgz", 490 | "integrity": "sha512-1tNWCPFLu1n3JM9t4/kytz35DkuF9MxqkGGEHNauEbaARdm2fafnOyw1s0tIQDPKF/7bkP1u3dbrmjpn5CelyA==", 491 | "funding": [ 492 | { 493 | "type": "individual", 494 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 495 | }, 496 | { 497 | "type": "individual", 498 | "url": "https://www.buymeacoffee.com/ricmoo" 499 | } 500 | ], 501 | "dependencies": { 502 | "@ethersproject/bytes": "^5.6.0", 503 | "@ethersproject/logger": "^5.6.0", 504 | "hash.js": "1.1.7" 505 | } 506 | }, 507 | "node_modules/@ethersproject/signing-key": { 508 | "version": "5.6.0", 509 | "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.6.0.tgz", 510 | "integrity": "sha512-S+njkhowmLeUu/r7ir8n78OUKx63kBdMCPssePS89So1TH4hZqnWFsThEd/GiXYp9qMxVrydf7KdM9MTGPFukA==", 511 | "funding": [ 512 | { 513 | "type": "individual", 514 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 515 | }, 516 | { 517 | "type": "individual", 518 | "url": "https://www.buymeacoffee.com/ricmoo" 519 | } 520 | ], 521 | "dependencies": { 522 | "@ethersproject/bytes": "^5.6.0", 523 | "@ethersproject/logger": "^5.6.0", 524 | "@ethersproject/properties": "^5.6.0", 525 | "bn.js": "^4.11.9", 526 | "elliptic": "6.5.4", 527 | "hash.js": "1.1.7" 528 | } 529 | }, 530 | "node_modules/@ethersproject/signing-key/node_modules/bn.js": { 531 | "version": "4.12.0", 532 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 533 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" 534 | }, 535 | "node_modules/@ethersproject/solidity": { 536 | "version": "5.6.0", 537 | "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.6.0.tgz", 538 | "integrity": "sha512-YwF52vTNd50kjDzqKaoNNbC/r9kMDPq3YzDWmsjFTRBcIF1y4JCQJ8gB30wsTfHbaxgxelI5BfxQSxD/PbJOww==", 539 | "funding": [ 540 | { 541 | "type": "individual", 542 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 543 | }, 544 | { 545 | "type": "individual", 546 | "url": "https://www.buymeacoffee.com/ricmoo" 547 | } 548 | ], 549 | "dependencies": { 550 | "@ethersproject/bignumber": "^5.6.0", 551 | "@ethersproject/bytes": "^5.6.0", 552 | "@ethersproject/keccak256": "^5.6.0", 553 | "@ethersproject/logger": "^5.6.0", 554 | "@ethersproject/sha2": "^5.6.0", 555 | "@ethersproject/strings": "^5.6.0" 556 | } 557 | }, 558 | "node_modules/@ethersproject/strings": { 559 | "version": "5.6.0", 560 | "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.6.0.tgz", 561 | "integrity": "sha512-uv10vTtLTZqrJuqBZR862ZQjTIa724wGPWQqZrofaPI/kUsf53TBG0I0D+hQ1qyNtllbNzaW+PDPHHUI6/65Mg==", 562 | "funding": [ 563 | { 564 | "type": "individual", 565 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 566 | }, 567 | { 568 | "type": "individual", 569 | "url": "https://www.buymeacoffee.com/ricmoo" 570 | } 571 | ], 572 | "dependencies": { 573 | "@ethersproject/bytes": "^5.6.0", 574 | "@ethersproject/constants": "^5.6.0", 575 | "@ethersproject/logger": "^5.6.0" 576 | } 577 | }, 578 | "node_modules/@ethersproject/transactions": { 579 | "version": "5.6.0", 580 | "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.6.0.tgz", 581 | "integrity": "sha512-4HX+VOhNjXHZyGzER6E/LVI2i6lf9ejYeWD6l4g50AdmimyuStKc39kvKf1bXWQMg7QNVh+uC7dYwtaZ02IXeg==", 582 | "funding": [ 583 | { 584 | "type": "individual", 585 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 586 | }, 587 | { 588 | "type": "individual", 589 | "url": "https://www.buymeacoffee.com/ricmoo" 590 | } 591 | ], 592 | "dependencies": { 593 | "@ethersproject/address": "^5.6.0", 594 | "@ethersproject/bignumber": "^5.6.0", 595 | "@ethersproject/bytes": "^5.6.0", 596 | "@ethersproject/constants": "^5.6.0", 597 | "@ethersproject/keccak256": "^5.6.0", 598 | "@ethersproject/logger": "^5.6.0", 599 | "@ethersproject/properties": "^5.6.0", 600 | "@ethersproject/rlp": "^5.6.0", 601 | "@ethersproject/signing-key": "^5.6.0" 602 | } 603 | }, 604 | "node_modules/@ethersproject/units": { 605 | "version": "5.6.0", 606 | "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.6.0.tgz", 607 | "integrity": "sha512-tig9x0Qmh8qbo1w8/6tmtyrm/QQRviBh389EQ+d8fP4wDsBrJBf08oZfoiz1/uenKK9M78yAP4PoR7SsVoTjsw==", 608 | "funding": [ 609 | { 610 | "type": "individual", 611 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 612 | }, 613 | { 614 | "type": "individual", 615 | "url": "https://www.buymeacoffee.com/ricmoo" 616 | } 617 | ], 618 | "dependencies": { 619 | "@ethersproject/bignumber": "^5.6.0", 620 | "@ethersproject/constants": "^5.6.0", 621 | "@ethersproject/logger": "^5.6.0" 622 | } 623 | }, 624 | "node_modules/@ethersproject/wallet": { 625 | "version": "5.6.0", 626 | "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.6.0.tgz", 627 | "integrity": "sha512-qMlSdOSTyp0MBeE+r7SUhr1jjDlC1zAXB8VD84hCnpijPQiSNbxr6GdiLXxpUs8UKzkDiNYYC5DRI3MZr+n+tg==", 628 | "funding": [ 629 | { 630 | "type": "individual", 631 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 632 | }, 633 | { 634 | "type": "individual", 635 | "url": "https://www.buymeacoffee.com/ricmoo" 636 | } 637 | ], 638 | "dependencies": { 639 | "@ethersproject/abstract-provider": "^5.6.0", 640 | "@ethersproject/abstract-signer": "^5.6.0", 641 | "@ethersproject/address": "^5.6.0", 642 | "@ethersproject/bignumber": "^5.6.0", 643 | "@ethersproject/bytes": "^5.6.0", 644 | "@ethersproject/hash": "^5.6.0", 645 | "@ethersproject/hdnode": "^5.6.0", 646 | "@ethersproject/json-wallets": "^5.6.0", 647 | "@ethersproject/keccak256": "^5.6.0", 648 | "@ethersproject/logger": "^5.6.0", 649 | "@ethersproject/properties": "^5.6.0", 650 | "@ethersproject/random": "^5.6.0", 651 | "@ethersproject/signing-key": "^5.6.0", 652 | "@ethersproject/transactions": "^5.6.0", 653 | "@ethersproject/wordlists": "^5.6.0" 654 | } 655 | }, 656 | "node_modules/@ethersproject/web": { 657 | "version": "5.6.0", 658 | "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.6.0.tgz", 659 | "integrity": "sha512-G/XHj0hV1FxI2teHRfCGvfBUHFmU+YOSbCxlAMqJklxSa7QMiHFQfAxvwY2PFqgvdkxEKwRNr/eCjfAPEm2Ctg==", 660 | "funding": [ 661 | { 662 | "type": "individual", 663 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 664 | }, 665 | { 666 | "type": "individual", 667 | "url": "https://www.buymeacoffee.com/ricmoo" 668 | } 669 | ], 670 | "dependencies": { 671 | "@ethersproject/base64": "^5.6.0", 672 | "@ethersproject/bytes": "^5.6.0", 673 | "@ethersproject/logger": "^5.6.0", 674 | "@ethersproject/properties": "^5.6.0", 675 | "@ethersproject/strings": "^5.6.0" 676 | } 677 | }, 678 | "node_modules/@ethersproject/wordlists": { 679 | "version": "5.6.0", 680 | "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.6.0.tgz", 681 | "integrity": "sha512-q0bxNBfIX3fUuAo9OmjlEYxP40IB8ABgb7HjEZCL5IKubzV3j30CWi2rqQbjTS2HfoyQbfINoKcTVWP4ejwR7Q==", 682 | "funding": [ 683 | { 684 | "type": "individual", 685 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 686 | }, 687 | { 688 | "type": "individual", 689 | "url": "https://www.buymeacoffee.com/ricmoo" 690 | } 691 | ], 692 | "dependencies": { 693 | "@ethersproject/bytes": "^5.6.0", 694 | "@ethersproject/hash": "^5.6.0", 695 | "@ethersproject/logger": "^5.6.0", 696 | "@ethersproject/properties": "^5.6.0", 697 | "@ethersproject/strings": "^5.6.0" 698 | } 699 | }, 700 | "node_modules/@noble/hashes": { 701 | "version": "1.2.0", 702 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", 703 | "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", 704 | "funding": [ 705 | { 706 | "type": "individual", 707 | "url": "https://paulmillr.com/funding/" 708 | } 709 | ] 710 | }, 711 | "node_modules/@noble/secp256k1": { 712 | "version": "1.7.1", 713 | "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", 714 | "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", 715 | "funding": [ 716 | { 717 | "type": "individual", 718 | "url": "https://paulmillr.com/funding/" 719 | } 720 | ] 721 | }, 722 | "node_modules/@openzeppelin/merkle-tree": { 723 | "version": "1.0.6", 724 | "resolved": "https://registry.npmjs.org/@openzeppelin/merkle-tree/-/merkle-tree-1.0.6.tgz", 725 | "integrity": "sha512-cGWOb2WBWbJhqvupzxjnKAwGLxxAEYPg51sk76yZ5nVe5D03mw7Vx5yo8llaIEqYhP5O39M8QlrNWclgLfKVrA==", 726 | "dependencies": { 727 | "@ethersproject/abi": "^5.7.0", 728 | "ethereum-cryptography": "^1.1.2" 729 | } 730 | }, 731 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/abi": { 732 | "version": "5.7.0", 733 | "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", 734 | "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", 735 | "funding": [ 736 | { 737 | "type": "individual", 738 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 739 | }, 740 | { 741 | "type": "individual", 742 | "url": "https://www.buymeacoffee.com/ricmoo" 743 | } 744 | ], 745 | "dependencies": { 746 | "@ethersproject/address": "^5.7.0", 747 | "@ethersproject/bignumber": "^5.7.0", 748 | "@ethersproject/bytes": "^5.7.0", 749 | "@ethersproject/constants": "^5.7.0", 750 | "@ethersproject/hash": "^5.7.0", 751 | "@ethersproject/keccak256": "^5.7.0", 752 | "@ethersproject/logger": "^5.7.0", 753 | "@ethersproject/properties": "^5.7.0", 754 | "@ethersproject/strings": "^5.7.0" 755 | } 756 | }, 757 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/abstract-provider": { 758 | "version": "5.7.0", 759 | "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", 760 | "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", 761 | "funding": [ 762 | { 763 | "type": "individual", 764 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 765 | }, 766 | { 767 | "type": "individual", 768 | "url": "https://www.buymeacoffee.com/ricmoo" 769 | } 770 | ], 771 | "dependencies": { 772 | "@ethersproject/bignumber": "^5.7.0", 773 | "@ethersproject/bytes": "^5.7.0", 774 | "@ethersproject/logger": "^5.7.0", 775 | "@ethersproject/networks": "^5.7.0", 776 | "@ethersproject/properties": "^5.7.0", 777 | "@ethersproject/transactions": "^5.7.0", 778 | "@ethersproject/web": "^5.7.0" 779 | } 780 | }, 781 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/abstract-signer": { 782 | "version": "5.7.0", 783 | "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", 784 | "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", 785 | "funding": [ 786 | { 787 | "type": "individual", 788 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 789 | }, 790 | { 791 | "type": "individual", 792 | "url": "https://www.buymeacoffee.com/ricmoo" 793 | } 794 | ], 795 | "dependencies": { 796 | "@ethersproject/abstract-provider": "^5.7.0", 797 | "@ethersproject/bignumber": "^5.7.0", 798 | "@ethersproject/bytes": "^5.7.0", 799 | "@ethersproject/logger": "^5.7.0", 800 | "@ethersproject/properties": "^5.7.0" 801 | } 802 | }, 803 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/address": { 804 | "version": "5.7.0", 805 | "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", 806 | "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", 807 | "funding": [ 808 | { 809 | "type": "individual", 810 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 811 | }, 812 | { 813 | "type": "individual", 814 | "url": "https://www.buymeacoffee.com/ricmoo" 815 | } 816 | ], 817 | "dependencies": { 818 | "@ethersproject/bignumber": "^5.7.0", 819 | "@ethersproject/bytes": "^5.7.0", 820 | "@ethersproject/keccak256": "^5.7.0", 821 | "@ethersproject/logger": "^5.7.0", 822 | "@ethersproject/rlp": "^5.7.0" 823 | } 824 | }, 825 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/base64": { 826 | "version": "5.7.0", 827 | "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", 828 | "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", 829 | "funding": [ 830 | { 831 | "type": "individual", 832 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 833 | }, 834 | { 835 | "type": "individual", 836 | "url": "https://www.buymeacoffee.com/ricmoo" 837 | } 838 | ], 839 | "dependencies": { 840 | "@ethersproject/bytes": "^5.7.0" 841 | } 842 | }, 843 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/bignumber": { 844 | "version": "5.7.0", 845 | "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", 846 | "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", 847 | "funding": [ 848 | { 849 | "type": "individual", 850 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 851 | }, 852 | { 853 | "type": "individual", 854 | "url": "https://www.buymeacoffee.com/ricmoo" 855 | } 856 | ], 857 | "dependencies": { 858 | "@ethersproject/bytes": "^5.7.0", 859 | "@ethersproject/logger": "^5.7.0", 860 | "bn.js": "^5.2.1" 861 | } 862 | }, 863 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/bytes": { 864 | "version": "5.7.0", 865 | "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", 866 | "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", 867 | "funding": [ 868 | { 869 | "type": "individual", 870 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 871 | }, 872 | { 873 | "type": "individual", 874 | "url": "https://www.buymeacoffee.com/ricmoo" 875 | } 876 | ], 877 | "dependencies": { 878 | "@ethersproject/logger": "^5.7.0" 879 | } 880 | }, 881 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/constants": { 882 | "version": "5.7.0", 883 | "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", 884 | "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", 885 | "funding": [ 886 | { 887 | "type": "individual", 888 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 889 | }, 890 | { 891 | "type": "individual", 892 | "url": "https://www.buymeacoffee.com/ricmoo" 893 | } 894 | ], 895 | "dependencies": { 896 | "@ethersproject/bignumber": "^5.7.0" 897 | } 898 | }, 899 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/hash": { 900 | "version": "5.7.0", 901 | "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", 902 | "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", 903 | "funding": [ 904 | { 905 | "type": "individual", 906 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 907 | }, 908 | { 909 | "type": "individual", 910 | "url": "https://www.buymeacoffee.com/ricmoo" 911 | } 912 | ], 913 | "dependencies": { 914 | "@ethersproject/abstract-signer": "^5.7.0", 915 | "@ethersproject/address": "^5.7.0", 916 | "@ethersproject/base64": "^5.7.0", 917 | "@ethersproject/bignumber": "^5.7.0", 918 | "@ethersproject/bytes": "^5.7.0", 919 | "@ethersproject/keccak256": "^5.7.0", 920 | "@ethersproject/logger": "^5.7.0", 921 | "@ethersproject/properties": "^5.7.0", 922 | "@ethersproject/strings": "^5.7.0" 923 | } 924 | }, 925 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/keccak256": { 926 | "version": "5.7.0", 927 | "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", 928 | "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", 929 | "funding": [ 930 | { 931 | "type": "individual", 932 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 933 | }, 934 | { 935 | "type": "individual", 936 | "url": "https://www.buymeacoffee.com/ricmoo" 937 | } 938 | ], 939 | "dependencies": { 940 | "@ethersproject/bytes": "^5.7.0", 941 | "js-sha3": "0.8.0" 942 | } 943 | }, 944 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/logger": { 945 | "version": "5.7.0", 946 | "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", 947 | "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", 948 | "funding": [ 949 | { 950 | "type": "individual", 951 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 952 | }, 953 | { 954 | "type": "individual", 955 | "url": "https://www.buymeacoffee.com/ricmoo" 956 | } 957 | ] 958 | }, 959 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/networks": { 960 | "version": "5.7.1", 961 | "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", 962 | "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", 963 | "funding": [ 964 | { 965 | "type": "individual", 966 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 967 | }, 968 | { 969 | "type": "individual", 970 | "url": "https://www.buymeacoffee.com/ricmoo" 971 | } 972 | ], 973 | "dependencies": { 974 | "@ethersproject/logger": "^5.7.0" 975 | } 976 | }, 977 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/properties": { 978 | "version": "5.7.0", 979 | "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", 980 | "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", 981 | "funding": [ 982 | { 983 | "type": "individual", 984 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 985 | }, 986 | { 987 | "type": "individual", 988 | "url": "https://www.buymeacoffee.com/ricmoo" 989 | } 990 | ], 991 | "dependencies": { 992 | "@ethersproject/logger": "^5.7.0" 993 | } 994 | }, 995 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/rlp": { 996 | "version": "5.7.0", 997 | "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", 998 | "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", 999 | "funding": [ 1000 | { 1001 | "type": "individual", 1002 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 1003 | }, 1004 | { 1005 | "type": "individual", 1006 | "url": "https://www.buymeacoffee.com/ricmoo" 1007 | } 1008 | ], 1009 | "dependencies": { 1010 | "@ethersproject/bytes": "^5.7.0", 1011 | "@ethersproject/logger": "^5.7.0" 1012 | } 1013 | }, 1014 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/signing-key": { 1015 | "version": "5.7.0", 1016 | "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", 1017 | "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", 1018 | "funding": [ 1019 | { 1020 | "type": "individual", 1021 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 1022 | }, 1023 | { 1024 | "type": "individual", 1025 | "url": "https://www.buymeacoffee.com/ricmoo" 1026 | } 1027 | ], 1028 | "dependencies": { 1029 | "@ethersproject/bytes": "^5.7.0", 1030 | "@ethersproject/logger": "^5.7.0", 1031 | "@ethersproject/properties": "^5.7.0", 1032 | "bn.js": "^5.2.1", 1033 | "elliptic": "6.5.4", 1034 | "hash.js": "1.1.7" 1035 | } 1036 | }, 1037 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/strings": { 1038 | "version": "5.7.0", 1039 | "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", 1040 | "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", 1041 | "funding": [ 1042 | { 1043 | "type": "individual", 1044 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 1045 | }, 1046 | { 1047 | "type": "individual", 1048 | "url": "https://www.buymeacoffee.com/ricmoo" 1049 | } 1050 | ], 1051 | "dependencies": { 1052 | "@ethersproject/bytes": "^5.7.0", 1053 | "@ethersproject/constants": "^5.7.0", 1054 | "@ethersproject/logger": "^5.7.0" 1055 | } 1056 | }, 1057 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/transactions": { 1058 | "version": "5.7.0", 1059 | "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", 1060 | "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", 1061 | "funding": [ 1062 | { 1063 | "type": "individual", 1064 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 1065 | }, 1066 | { 1067 | "type": "individual", 1068 | "url": "https://www.buymeacoffee.com/ricmoo" 1069 | } 1070 | ], 1071 | "dependencies": { 1072 | "@ethersproject/address": "^5.7.0", 1073 | "@ethersproject/bignumber": "^5.7.0", 1074 | "@ethersproject/bytes": "^5.7.0", 1075 | "@ethersproject/constants": "^5.7.0", 1076 | "@ethersproject/keccak256": "^5.7.0", 1077 | "@ethersproject/logger": "^5.7.0", 1078 | "@ethersproject/properties": "^5.7.0", 1079 | "@ethersproject/rlp": "^5.7.0", 1080 | "@ethersproject/signing-key": "^5.7.0" 1081 | } 1082 | }, 1083 | "node_modules/@openzeppelin/merkle-tree/node_modules/@ethersproject/web": { 1084 | "version": "5.7.1", 1085 | "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", 1086 | "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", 1087 | "funding": [ 1088 | { 1089 | "type": "individual", 1090 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 1091 | }, 1092 | { 1093 | "type": "individual", 1094 | "url": "https://www.buymeacoffee.com/ricmoo" 1095 | } 1096 | ], 1097 | "dependencies": { 1098 | "@ethersproject/base64": "^5.7.0", 1099 | "@ethersproject/bytes": "^5.7.0", 1100 | "@ethersproject/logger": "^5.7.0", 1101 | "@ethersproject/properties": "^5.7.0", 1102 | "@ethersproject/strings": "^5.7.0" 1103 | } 1104 | }, 1105 | "node_modules/@openzeppelin/merkle-tree/node_modules/ethereum-cryptography": { 1106 | "version": "1.2.0", 1107 | "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", 1108 | "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", 1109 | "dependencies": { 1110 | "@noble/hashes": "1.2.0", 1111 | "@noble/secp256k1": "1.7.1", 1112 | "@scure/bip32": "1.1.5", 1113 | "@scure/bip39": "1.1.1" 1114 | } 1115 | }, 1116 | "node_modules/@scure/base": { 1117 | "version": "1.1.6", 1118 | "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", 1119 | "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", 1120 | "funding": { 1121 | "url": "https://paulmillr.com/funding/" 1122 | } 1123 | }, 1124 | "node_modules/@scure/bip32": { 1125 | "version": "1.1.5", 1126 | "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", 1127 | "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", 1128 | "funding": [ 1129 | { 1130 | "type": "individual", 1131 | "url": "https://paulmillr.com/funding/" 1132 | } 1133 | ], 1134 | "dependencies": { 1135 | "@noble/hashes": "~1.2.0", 1136 | "@noble/secp256k1": "~1.7.0", 1137 | "@scure/base": "~1.1.0" 1138 | } 1139 | }, 1140 | "node_modules/@scure/bip39": { 1141 | "version": "1.1.1", 1142 | "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", 1143 | "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", 1144 | "funding": [ 1145 | { 1146 | "type": "individual", 1147 | "url": "https://paulmillr.com/funding/" 1148 | } 1149 | ], 1150 | "dependencies": { 1151 | "@noble/hashes": "~1.2.0", 1152 | "@scure/base": "~1.1.0" 1153 | } 1154 | }, 1155 | "node_modules/@types/bn.js": { 1156 | "version": "5.1.0", 1157 | "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", 1158 | "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", 1159 | "dependencies": { 1160 | "@types/node": "*" 1161 | } 1162 | }, 1163 | "node_modules/@types/node": { 1164 | "version": "17.0.25", 1165 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", 1166 | "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==" 1167 | }, 1168 | "node_modules/@types/pbkdf2": { 1169 | "version": "3.1.0", 1170 | "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", 1171 | "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", 1172 | "dependencies": { 1173 | "@types/node": "*" 1174 | } 1175 | }, 1176 | "node_modules/@types/secp256k1": { 1177 | "version": "4.0.3", 1178 | "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", 1179 | "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", 1180 | "dependencies": { 1181 | "@types/node": "*" 1182 | } 1183 | }, 1184 | "node_modules/aes-js": { 1185 | "version": "3.0.0", 1186 | "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", 1187 | "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" 1188 | }, 1189 | "node_modules/base-x": { 1190 | "version": "3.0.9", 1191 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", 1192 | "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", 1193 | "dependencies": { 1194 | "safe-buffer": "^5.0.1" 1195 | } 1196 | }, 1197 | "node_modules/bech32": { 1198 | "version": "1.1.4", 1199 | "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", 1200 | "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" 1201 | }, 1202 | "node_modules/blakejs": { 1203 | "version": "1.2.1", 1204 | "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", 1205 | "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" 1206 | }, 1207 | "node_modules/bn.js": { 1208 | "version": "5.2.1", 1209 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", 1210 | "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" 1211 | }, 1212 | "node_modules/brorand": { 1213 | "version": "1.1.0", 1214 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", 1215 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" 1216 | }, 1217 | "node_modules/browserify-aes": { 1218 | "version": "1.2.0", 1219 | "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", 1220 | "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", 1221 | "dependencies": { 1222 | "buffer-xor": "^1.0.3", 1223 | "cipher-base": "^1.0.0", 1224 | "create-hash": "^1.1.0", 1225 | "evp_bytestokey": "^1.0.3", 1226 | "inherits": "^2.0.1", 1227 | "safe-buffer": "^5.0.1" 1228 | } 1229 | }, 1230 | "node_modules/bs58": { 1231 | "version": "4.0.1", 1232 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 1233 | "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", 1234 | "dependencies": { 1235 | "base-x": "^3.0.2" 1236 | } 1237 | }, 1238 | "node_modules/bs58check": { 1239 | "version": "2.1.2", 1240 | "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", 1241 | "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", 1242 | "dependencies": { 1243 | "bs58": "^4.0.0", 1244 | "create-hash": "^1.1.0", 1245 | "safe-buffer": "^5.1.2" 1246 | } 1247 | }, 1248 | "node_modules/buffer-xor": { 1249 | "version": "1.0.3", 1250 | "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", 1251 | "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" 1252 | }, 1253 | "node_modules/cipher-base": { 1254 | "version": "1.0.4", 1255 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 1256 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 1257 | "dependencies": { 1258 | "inherits": "^2.0.1", 1259 | "safe-buffer": "^5.0.1" 1260 | } 1261 | }, 1262 | "node_modules/create-hash": { 1263 | "version": "1.2.0", 1264 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 1265 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 1266 | "dependencies": { 1267 | "cipher-base": "^1.0.1", 1268 | "inherits": "^2.0.1", 1269 | "md5.js": "^1.3.4", 1270 | "ripemd160": "^2.0.1", 1271 | "sha.js": "^2.4.0" 1272 | } 1273 | }, 1274 | "node_modules/create-hmac": { 1275 | "version": "1.1.7", 1276 | "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", 1277 | "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", 1278 | "dependencies": { 1279 | "cipher-base": "^1.0.3", 1280 | "create-hash": "^1.1.0", 1281 | "inherits": "^2.0.1", 1282 | "ripemd160": "^2.0.0", 1283 | "safe-buffer": "^5.0.1", 1284 | "sha.js": "^2.4.8" 1285 | } 1286 | }, 1287 | "node_modules/elliptic": { 1288 | "version": "6.5.4", 1289 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", 1290 | "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", 1291 | "dependencies": { 1292 | "bn.js": "^4.11.9", 1293 | "brorand": "^1.1.0", 1294 | "hash.js": "^1.0.0", 1295 | "hmac-drbg": "^1.0.1", 1296 | "inherits": "^2.0.4", 1297 | "minimalistic-assert": "^1.0.1", 1298 | "minimalistic-crypto-utils": "^1.0.1" 1299 | } 1300 | }, 1301 | "node_modules/elliptic/node_modules/bn.js": { 1302 | "version": "4.12.0", 1303 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 1304 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" 1305 | }, 1306 | "node_modules/ethereum-cryptography": { 1307 | "version": "0.1.3", 1308 | "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", 1309 | "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", 1310 | "dependencies": { 1311 | "@types/pbkdf2": "^3.0.0", 1312 | "@types/secp256k1": "^4.0.1", 1313 | "blakejs": "^1.1.0", 1314 | "browserify-aes": "^1.2.0", 1315 | "bs58check": "^2.1.2", 1316 | "create-hash": "^1.2.0", 1317 | "create-hmac": "^1.1.7", 1318 | "hash.js": "^1.1.7", 1319 | "keccak": "^3.0.0", 1320 | "pbkdf2": "^3.0.17", 1321 | "randombytes": "^2.1.0", 1322 | "safe-buffer": "^5.1.2", 1323 | "scrypt-js": "^3.0.0", 1324 | "secp256k1": "^4.0.1", 1325 | "setimmediate": "^1.0.5" 1326 | } 1327 | }, 1328 | "node_modules/ethereumjs-util": { 1329 | "version": "7.1.4", 1330 | "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.4.tgz", 1331 | "integrity": "sha512-p6KmuPCX4mZIqsQzXfmSx9Y0l2hqf+VkAiwSisW3UKUFdk8ZkAt+AYaor83z2nSi6CU2zSsXMlD80hAbNEGM0A==", 1332 | "dependencies": { 1333 | "@types/bn.js": "^5.1.0", 1334 | "bn.js": "^5.1.2", 1335 | "create-hash": "^1.1.2", 1336 | "ethereum-cryptography": "^0.1.3", 1337 | "rlp": "^2.2.4" 1338 | }, 1339 | "engines": { 1340 | "node": ">=10.0.0" 1341 | } 1342 | }, 1343 | "node_modules/ethereumjs-util/node_modules/rlp": { 1344 | "version": "2.2.7", 1345 | "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", 1346 | "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", 1347 | "dependencies": { 1348 | "bn.js": "^5.2.0" 1349 | }, 1350 | "bin": { 1351 | "rlp": "bin/rlp" 1352 | } 1353 | }, 1354 | "node_modules/ethers": { 1355 | "version": "5.6.4", 1356 | "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.6.4.tgz", 1357 | "integrity": "sha512-62UIfxAQXdf67TeeOaoOoPctm5hUlYgfd0iW3wxfj7qRYKDcvvy0f+sJ3W2/Pyx77R8dblvejA8jokj+lS+ATQ==", 1358 | "funding": [ 1359 | { 1360 | "type": "individual", 1361 | "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" 1362 | }, 1363 | { 1364 | "type": "individual", 1365 | "url": "https://www.buymeacoffee.com/ricmoo" 1366 | } 1367 | ], 1368 | "dependencies": { 1369 | "@ethersproject/abi": "5.6.1", 1370 | "@ethersproject/abstract-provider": "5.6.0", 1371 | "@ethersproject/abstract-signer": "5.6.0", 1372 | "@ethersproject/address": "5.6.0", 1373 | "@ethersproject/base64": "5.6.0", 1374 | "@ethersproject/basex": "5.6.0", 1375 | "@ethersproject/bignumber": "5.6.0", 1376 | "@ethersproject/bytes": "5.6.1", 1377 | "@ethersproject/constants": "5.6.0", 1378 | "@ethersproject/contracts": "5.6.0", 1379 | "@ethersproject/hash": "5.6.0", 1380 | "@ethersproject/hdnode": "5.6.0", 1381 | "@ethersproject/json-wallets": "5.6.0", 1382 | "@ethersproject/keccak256": "5.6.0", 1383 | "@ethersproject/logger": "5.6.0", 1384 | "@ethersproject/networks": "5.6.2", 1385 | "@ethersproject/pbkdf2": "5.6.0", 1386 | "@ethersproject/properties": "5.6.0", 1387 | "@ethersproject/providers": "5.6.4", 1388 | "@ethersproject/random": "5.6.0", 1389 | "@ethersproject/rlp": "5.6.0", 1390 | "@ethersproject/sha2": "5.6.0", 1391 | "@ethersproject/signing-key": "5.6.0", 1392 | "@ethersproject/solidity": "5.6.0", 1393 | "@ethersproject/strings": "5.6.0", 1394 | "@ethersproject/transactions": "5.6.0", 1395 | "@ethersproject/units": "5.6.0", 1396 | "@ethersproject/wallet": "5.6.0", 1397 | "@ethersproject/web": "5.6.0", 1398 | "@ethersproject/wordlists": "5.6.0" 1399 | } 1400 | }, 1401 | "node_modules/evp_bytestokey": { 1402 | "version": "1.0.3", 1403 | "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", 1404 | "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", 1405 | "dependencies": { 1406 | "md5.js": "^1.3.4", 1407 | "safe-buffer": "^5.1.1" 1408 | } 1409 | }, 1410 | "node_modules/hash-base": { 1411 | "version": "3.1.0", 1412 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 1413 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 1414 | "dependencies": { 1415 | "inherits": "^2.0.4", 1416 | "readable-stream": "^3.6.0", 1417 | "safe-buffer": "^5.2.0" 1418 | }, 1419 | "engines": { 1420 | "node": ">=4" 1421 | } 1422 | }, 1423 | "node_modules/hash.js": { 1424 | "version": "1.1.7", 1425 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", 1426 | "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", 1427 | "dependencies": { 1428 | "inherits": "^2.0.3", 1429 | "minimalistic-assert": "^1.0.1" 1430 | } 1431 | }, 1432 | "node_modules/hmac-drbg": { 1433 | "version": "1.0.1", 1434 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", 1435 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", 1436 | "dependencies": { 1437 | "hash.js": "^1.0.3", 1438 | "minimalistic-assert": "^1.0.0", 1439 | "minimalistic-crypto-utils": "^1.0.1" 1440 | } 1441 | }, 1442 | "node_modules/inherits": { 1443 | "version": "2.0.4", 1444 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1445 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1446 | }, 1447 | "node_modules/js-sha3": { 1448 | "version": "0.8.0", 1449 | "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", 1450 | "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" 1451 | }, 1452 | "node_modules/keccak": { 1453 | "version": "3.0.2", 1454 | "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", 1455 | "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", 1456 | "hasInstallScript": true, 1457 | "dependencies": { 1458 | "node-addon-api": "^2.0.0", 1459 | "node-gyp-build": "^4.2.0", 1460 | "readable-stream": "^3.6.0" 1461 | }, 1462 | "engines": { 1463 | "node": ">=10.0.0" 1464 | } 1465 | }, 1466 | "node_modules/md5.js": { 1467 | "version": "1.3.5", 1468 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 1469 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 1470 | "dependencies": { 1471 | "hash-base": "^3.0.0", 1472 | "inherits": "^2.0.1", 1473 | "safe-buffer": "^5.1.2" 1474 | } 1475 | }, 1476 | "node_modules/minimalistic-assert": { 1477 | "version": "1.0.1", 1478 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", 1479 | "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" 1480 | }, 1481 | "node_modules/minimalistic-crypto-utils": { 1482 | "version": "1.0.1", 1483 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", 1484 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" 1485 | }, 1486 | "node_modules/node-addon-api": { 1487 | "version": "2.0.2", 1488 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", 1489 | "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" 1490 | }, 1491 | "node_modules/node-gyp-build": { 1492 | "version": "4.4.0", 1493 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", 1494 | "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", 1495 | "bin": { 1496 | "node-gyp-build": "bin.js", 1497 | "node-gyp-build-optional": "optional.js", 1498 | "node-gyp-build-test": "build-test.js" 1499 | } 1500 | }, 1501 | "node_modules/pbkdf2": { 1502 | "version": "3.1.2", 1503 | "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", 1504 | "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", 1505 | "dependencies": { 1506 | "create-hash": "^1.1.2", 1507 | "create-hmac": "^1.1.4", 1508 | "ripemd160": "^2.0.1", 1509 | "safe-buffer": "^5.0.1", 1510 | "sha.js": "^2.4.8" 1511 | }, 1512 | "engines": { 1513 | "node": ">=0.12" 1514 | } 1515 | }, 1516 | "node_modules/randombytes": { 1517 | "version": "2.1.0", 1518 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1519 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1520 | "dependencies": { 1521 | "safe-buffer": "^5.1.0" 1522 | } 1523 | }, 1524 | "node_modules/readable-stream": { 1525 | "version": "3.6.0", 1526 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1527 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1528 | "dependencies": { 1529 | "inherits": "^2.0.3", 1530 | "string_decoder": "^1.1.1", 1531 | "util-deprecate": "^1.0.1" 1532 | }, 1533 | "engines": { 1534 | "node": ">= 6" 1535 | } 1536 | }, 1537 | "node_modules/ripemd160": { 1538 | "version": "2.0.2", 1539 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 1540 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 1541 | "dependencies": { 1542 | "hash-base": "^3.0.0", 1543 | "inherits": "^2.0.1" 1544 | } 1545 | }, 1546 | "node_modules/rlp": { 1547 | "version": "3.0.0", 1548 | "resolved": "https://registry.npmjs.org/rlp/-/rlp-3.0.0.tgz", 1549 | "integrity": "sha512-PD6U2PGk6Vq2spfgiWZdomLvRGDreBLxi5jv5M8EpRo3pU6VEm31KO+HFxE18Q3vgqfDrQ9pZA3FP95rkijNKw==", 1550 | "bin": { 1551 | "rlp": "bin/rlp" 1552 | } 1553 | }, 1554 | "node_modules/safe-buffer": { 1555 | "version": "5.2.1", 1556 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1557 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1558 | "funding": [ 1559 | { 1560 | "type": "github", 1561 | "url": "https://github.com/sponsors/feross" 1562 | }, 1563 | { 1564 | "type": "patreon", 1565 | "url": "https://www.patreon.com/feross" 1566 | }, 1567 | { 1568 | "type": "consulting", 1569 | "url": "https://feross.org/support" 1570 | } 1571 | ] 1572 | }, 1573 | "node_modules/scrypt-js": { 1574 | "version": "3.0.1", 1575 | "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", 1576 | "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" 1577 | }, 1578 | "node_modules/secp256k1": { 1579 | "version": "4.0.3", 1580 | "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", 1581 | "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", 1582 | "hasInstallScript": true, 1583 | "dependencies": { 1584 | "elliptic": "^6.5.4", 1585 | "node-addon-api": "^2.0.0", 1586 | "node-gyp-build": "^4.2.0" 1587 | }, 1588 | "engines": { 1589 | "node": ">=10.0.0" 1590 | } 1591 | }, 1592 | "node_modules/setimmediate": { 1593 | "version": "1.0.5", 1594 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1595 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 1596 | }, 1597 | "node_modules/sha.js": { 1598 | "version": "2.4.11", 1599 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 1600 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 1601 | "dependencies": { 1602 | "inherits": "^2.0.1", 1603 | "safe-buffer": "^5.0.1" 1604 | }, 1605 | "bin": { 1606 | "sha.js": "bin.js" 1607 | } 1608 | }, 1609 | "node_modules/string_decoder": { 1610 | "version": "1.3.0", 1611 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1612 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1613 | "dependencies": { 1614 | "safe-buffer": "~5.2.0" 1615 | } 1616 | }, 1617 | "node_modules/typescript": { 1618 | "version": "4.6.3", 1619 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", 1620 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", 1621 | "dev": true, 1622 | "bin": { 1623 | "tsc": "bin/tsc", 1624 | "tsserver": "bin/tsserver" 1625 | }, 1626 | "engines": { 1627 | "node": ">=4.2.0" 1628 | } 1629 | }, 1630 | "node_modules/util-deprecate": { 1631 | "version": "1.0.2", 1632 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1633 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1634 | }, 1635 | "node_modules/ws": { 1636 | "version": "7.4.6", 1637 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", 1638 | "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", 1639 | "engines": { 1640 | "node": ">=8.3.0" 1641 | }, 1642 | "peerDependencies": { 1643 | "bufferutil": "^4.0.1", 1644 | "utf-8-validate": "^5.0.2" 1645 | }, 1646 | "peerDependenciesMeta": { 1647 | "bufferutil": { 1648 | "optional": true 1649 | }, 1650 | "utf-8-validate": { 1651 | "optional": true 1652 | } 1653 | } 1654 | } 1655 | } 1656 | } 1657 | -------------------------------------------------------------------------------- /differential_testing/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "murky-differential", 3 | "version": "1.0.0", 4 | "description": "Scripts to perform solidity/js merkle tree testing", 5 | "main": "generate.js", 6 | "author": "", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@openzeppelin/merkle-tree": "^1.0.6", 10 | "ethereumjs-util": "^7.1.4", 11 | "ethers": "^5.6.4", 12 | "rlp": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.6.3" 16 | }, 17 | "scripts": { 18 | "compile": "npx tsc --esModuleInterop --downlevelIteration ./*.ts", 19 | "generate-root": "node generate_root.js", 20 | "generate-root-129": "node generate_root.js", 21 | "generate-root-cli": "node generate_root_cli.js", 22 | "generate-complete-root": "node generate_complete_root.js", 23 | "generate-complete-proof": "node generate_complete_proof.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /differential_testing/test/CompleteDifferentialTests.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "../../src/CompleteMerkle.sol"; 4 | import "forge-std/Test.sol"; 5 | import "./utils/Strings2.sol"; 6 | 7 | 8 | contract CompleteDifferentialTests is Test { 9 | CompleteMerkle m; 10 | bytes32 oneword; 11 | using {Strings2.toHexString} for bytes; 12 | 13 | 14 | function setUp() public { 15 | m = new CompleteMerkle(); 16 | oneword = bytes32(uint256(0x1)); 17 | } 18 | 19 | function testDifferentialOZMerkleTreeRootGeneration(bytes32[] memory leafs) public { 20 | // Setup assumptions 21 | vm.assume(leafs.length > 1); 22 | 23 | // Generate the input json to be used by the JS implementation 24 | string memory obj1 = vm.serializeBytes32("input", "leafs", leafs); 25 | vm.writeJson(obj1, "./differential_testing/data/complete_root_input.json"); 26 | 27 | // Prepare the leafs. OZ JS Merkle double hashes and expects each leaf to be a len(2) array (e.g. address, amount) 28 | // For this test we use the fuzzed bytes32 and a constant bytes32 0x1 29 | bytes32[] memory compatibleLeafs = new bytes32[](leafs.length); 30 | for (uint256 i = 0; i < leafs.length; ++i) { 31 | compatibleLeafs[i] = keccak256(abi.encode(keccak256(abi.encode(leafs[i],oneword)))); 32 | } 33 | 34 | // Run ffi and read back the oz generated root 35 | string[] memory runJsInputs = new string[](7); 36 | runJsInputs[0] = 'npm'; 37 | runJsInputs[1] = '--prefix'; 38 | runJsInputs[2] = 'differential_testing/scripts/'; 39 | runJsInputs[3] = '--silent'; 40 | runJsInputs[4] = 'run'; 41 | runJsInputs[5] = 'generate-complete-root'; // Generates length 129 by default 42 | runJsInputs[6] = "../data/complete_root_input.json"; 43 | bytes memory jsResult = vm.ffi(runJsInputs); 44 | bytes32 ozGeneratedRoot = abi.decode(jsResult, (bytes32)); 45 | 46 | // generate our root and compare 47 | bytes32 root = m.getRoot(compatibleLeafs); 48 | assertEq(root, ozGeneratedRoot); 49 | } 50 | 51 | function testDifferentialOZMerkleTreeProofGeneration(bytes32[] memory leafs, uint256 index) public { 52 | // Setup assumptions 53 | vm.assume(leafs.length > 1); 54 | vm.assume(index < leafs.length); 55 | 56 | string memory obj1 = "input"; 57 | vm.serializeBytes32(obj1, "leafs", leafs); 58 | string memory obj2 = vm.serializeUint(obj1, "index", index); 59 | vm.writeJson(obj2, "./differential_testing/data/complete_proof_input.json"); 60 | 61 | // Prepare the leafs. OZ JS Merkle double hashes and expects each leaf to be a len(2) array (e.g. address, amount) 62 | // For this test we use the fuzzed bytes32 and a constant bytes32 0x1 63 | bytes32[] memory compatibleLeafs = new bytes32[](leafs.length); 64 | for (uint256 i = 0; i < leafs.length; ++i) { 65 | compatibleLeafs[i] = keccak256(abi.encode(keccak256(abi.encode(leafs[i],oneword)))); 66 | } 67 | 68 | bytes32[] memory proof = m.getProof(compatibleLeafs, index); 69 | // Run ffi and read back the oz generated root 70 | string[] memory runJsInputs = new string[](7); 71 | runJsInputs[0] = 'npm'; 72 | runJsInputs[1] = '--prefix'; 73 | runJsInputs[2] = 'differential_testing/scripts/'; 74 | runJsInputs[3] = '--silent'; 75 | runJsInputs[4] = 'run'; 76 | runJsInputs[5] = 'generate-complete-proof'; 77 | runJsInputs[6] = "../data/complete_proof_input.json"; 78 | bytes memory jsResult = vm.ffi(runJsInputs); 79 | bytes32[] memory ozGeneratedProof = abi.decode(jsResult, (bytes32[])); 80 | 81 | // assert proofs are equal 82 | assertEq(proof.length, ozGeneratedProof.length); 83 | for (uint256 i = 0; i < proof.length; ++i) { 84 | assertEq(proof[i], ozGeneratedProof[i]); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /differential_testing/test/DifferentialTests.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "../../src/Merkle.sol"; 4 | import "forge-std/Test.sol"; 5 | import "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; 6 | import "openzeppelin-contracts/contracts/utils/Strings.sol"; 7 | import "forge-std/console.sol"; 8 | import "./utils/Strings2.sol"; 9 | 10 | contract DifferentialTests is Test { 11 | using Strings for uint; 12 | using {Strings2.toHexString} for bytes; 13 | 14 | Merkle m; 15 | bytes32[129] data; 16 | 17 | function setUp() public { 18 | m = new Merkle(); 19 | } 20 | 21 | ///@dev original log2ceil implementation failed on this and other 22 | // numbers close to a power of 2. Diff tested here for sanity. 23 | function testMerkleRootMatchesJSImplementation129() public { 24 | // Run the reference implementation in javascript 25 | string[] memory runJsInputs = new string[](6); 26 | runJsInputs[0] = 'npm'; 27 | runJsInputs[1] = '--prefix'; 28 | runJsInputs[2] = 'differential_testing/scripts/'; 29 | runJsInputs[3] = '--silent'; 30 | runJsInputs[4] = 'run'; 31 | runJsInputs[5] = 'generate-root'; // Generates length 129 by default 32 | bytes memory jsResult = vm.ffi(runJsInputs); 33 | bytes32 jsGeneratedRoot = abi.decode(jsResult, (bytes32)); 34 | 35 | // Read in the file generated by the reference implementation 36 | string[] memory loadJsDataInputs = new string[](2); 37 | loadJsDataInputs[0] = "cat"; 38 | loadJsDataInputs[1] = "differential_testing/data/merkle_input.txt"; 39 | bytes memory loadResult = vm.ffi(loadJsDataInputs); 40 | data = abi.decode(loadResult, (bytes32[129])); 41 | 42 | // Calculate root using Murky 43 | bytes32 murkyGeneratedRoot = m.getRoot(_getData()); 44 | assertEq(murkyGeneratedRoot, jsGeneratedRoot); 45 | } 46 | 47 | /// @notice For questions on the argument name, see: https://en.wikipedia.org/wiki/Toronto_Maple_leaves 48 | function testMerkleRootMatchesJSImplementationFuzzed(bytes32[] memory leafs) public { 49 | vm.assume(leafs.length > 1); 50 | bytes memory packed = abi.encodePacked(leafs); 51 | string[] memory runJsInputs = new string[](8); 52 | 53 | // build ffi command string 54 | runJsInputs[0] = 'npm'; 55 | runJsInputs[1] = '--prefix'; 56 | runJsInputs[2] = 'differential_testing/scripts/'; 57 | runJsInputs[3] = '--silent'; 58 | runJsInputs[4] = 'run'; 59 | runJsInputs[5] = 'generate-root-cli'; 60 | runJsInputs[6] = leafs.length.toString(); 61 | runJsInputs[7] = packed.toHexString(); 62 | 63 | // run and captures output 64 | bytes memory jsResult = vm.ffi(runJsInputs); 65 | bytes32 jsGeneratedRoot = abi.decode(jsResult, (bytes32)); 66 | 67 | // Calculate root using Murky 68 | bytes32 murkyGeneratedRoot = m.getRoot(leafs); 69 | assertEq(murkyGeneratedRoot, jsGeneratedRoot); 70 | } 71 | 72 | function testCompatabilityOpenZeppelinProver(bytes32[] memory _data, uint256 node) public { 73 | vm.assume(_data.length > 1); 74 | vm.assume(node < _data.length); 75 | bytes32 root = m.getRoot(_data); 76 | bytes32[] memory proof = m.getProof(_data, node); 77 | bytes32 valueToProve = _data[node]; 78 | bool murkyVerified = m.verifyProof(root, proof, valueToProve); 79 | bool ozVerified = MerkleProof.verify(proof, root, valueToProve); 80 | assertTrue(murkyVerified == ozVerified); 81 | } 82 | 83 | function _getData() public view returns (bytes32[] memory) { 84 | bytes32[] memory _data = new bytes32[](data.length); 85 | uint length = data.length; 86 | for (uint i = 0; i < length; ++i) { 87 | _data[i] = data[i]; 88 | } 89 | return _data; 90 | } 91 | } -------------------------------------------------------------------------------- /differential_testing/test/utils/Strings2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | library Strings2 { 4 | 5 | ///@dev converts bytes array to its ASCII hex string representation 6 | /// TODO: Definitely more efficient way to do this by processing multiple (16?) bytes at once 7 | /// but really a helper function for the tests, efficiency not key. 8 | function toHexString(bytes memory input) public pure returns (string memory) { 9 | require(input.length < type(uint256).max / 2 - 1); 10 | bytes16 symbols = "0123456789abcdef"; 11 | bytes memory hex_buffer = new bytes(2 * input.length + 2); 12 | hex_buffer[0] = "0"; 13 | hex_buffer[1] = "x"; 14 | 15 | uint pos = 2; 16 | uint256 length = input.length; 17 | for (uint i = 0; i < length; ++i) { 18 | uint _byte = uint8(input[i]); 19 | hex_buffer[pos++] = symbols[_byte >> 4]; 20 | hex_buffer[pos++] = symbols[_byte & 0xf]; 21 | } 22 | return string(hex_buffer); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | fuzz_runs=5000 6 | fuzz.max_test_rejects=500000 7 | fs_permissions = [{ access = "read-write", path = "./"}] 8 | 9 | # See more config options https://github.com/gakonst/foundry/tree/master/config -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "murky", 3 | "version": "0.1.0", 4 | "description": "Murky. A Merkle Proof Generator and Validator in Solidity", 5 | "homepage": "https://github.com/dmfxyz/murky", 6 | "bugs": "https://github.com/dmfxyz/murky", 7 | "license": "MIT", 8 | "author": "dmfxyz", 9 | "files": [ 10 | "src/**/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/dmfxyz/murky.git" 15 | } 16 | } -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | openzeppelin-contracts/=lib/openzeppelin-contracts/ -------------------------------------------------------------------------------- /reports/murky_gas_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmfxyz/murky/991e371eb1dfa9f86701869eb08ec4e98c3cc0b0/reports/murky_gas_report.png -------------------------------------------------------------------------------- /script/Merkle.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "forge-std/StdJson.sol"; 6 | import "forge-std/console.sol"; 7 | import "../src/Merkle.sol"; 8 | import "./common/ScriptHelper.sol"; 9 | 10 | // Merkle proof generator script 11 | // To use: 12 | // 1. Update the input.json file in /script/target/input.json 13 | // 2. Run `forge script script/Merkle.s.sol` 14 | // 3. The output file will be generated in /script/target/output.json 15 | 16 | /// @notice Merkle proof generator script 17 | /// @author kootsZhin 18 | contract MerkleScript is Script, ScriptHelper { 19 | using stdJson for string; 20 | 21 | Merkle private m = new Merkle(); 22 | 23 | string private inputPath = "/script/target/input.json"; 24 | string private outputPath = "/script/target/output.json"; 25 | 26 | string private elements = vm.readFile(string.concat(vm.projectRoot(), inputPath)); 27 | string[] private types = elements.readStringArray(".types"); 28 | uint256 private count = elements.readUint(".count"); 29 | 30 | bytes32[] private leafs = new bytes32[](count); 31 | 32 | string[] private inputs = new string[](count); 33 | string[] private outputs = new string[](count); 34 | 35 | string private output; 36 | 37 | /// @dev Returns the JSON path of the input file 38 | function getValuesByIndex(uint256 i, uint256 j) internal pure returns (string memory) { 39 | return string.concat(".values.", vm.toString(i), ".", vm.toString(j)); 40 | } 41 | 42 | /// @dev Generate the JSON entries for the output file 43 | function generateJsonEntries(string memory _inputs, string memory _proof, string memory _root, string memory _leaf) 44 | internal 45 | pure 46 | returns (string memory) 47 | { 48 | string memory result = string.concat( 49 | "{", 50 | "\"inputs\":", 51 | _inputs, 52 | ",", 53 | "\"proof\":", 54 | _proof, 55 | ",", 56 | "\"root\":\"", 57 | _root, 58 | "\",", 59 | "\"leaf\":\"", 60 | _leaf, 61 | "\"", 62 | "}" 63 | ); 64 | 65 | return result; 66 | } 67 | 68 | /// @dev Read the input file and generate the Merkle proof, then write the output file 69 | function run() public { 70 | console.log("Generating Merkle Proof for %s", inputPath); 71 | 72 | for (uint256 i = 0; i < count; ++i) { 73 | string[] memory input = new string[](types.length); 74 | bytes32[] memory data = new bytes32[](types.length); 75 | 76 | for (uint256 j = 0; j < types.length; ++j) { 77 | if (compareStrings(types[j], "address")) { 78 | address value = elements.readAddress(getValuesByIndex(i, j)); 79 | data[j] = bytes32(uint256(uint160(value))); 80 | input[j] = vm.toString(value); 81 | } else if (compareStrings(types[j], "uint")) { 82 | uint256 value = vm.parseUint(elements.readString(getValuesByIndex(i, j))); 83 | data[j] = bytes32(value); 84 | input[j] = vm.toString(value); 85 | } 86 | } 87 | 88 | leafs[i] = keccak256(bytes.concat(keccak256(ltrim64(abi.encode(data))))); 89 | inputs[i] = stringArrayToString(input); 90 | } 91 | 92 | for (uint256 i = 0; i < count; ++i) { 93 | string memory proof = bytes32ArrayToString(m.getProof(leafs, i)); 94 | string memory root = vm.toString(m.getRoot(leafs)); 95 | string memory leaf = vm.toString(leafs[i]); 96 | string memory input = inputs[i]; 97 | 98 | outputs[i] = generateJsonEntries(input, proof, root, leaf); 99 | } 100 | 101 | output = stringArrayToArrayString(outputs); 102 | vm.writeFile(string.concat(vm.projectRoot(), outputPath), output); 103 | 104 | console.log("DONE: The output is found at %s", inputPath); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /script/common/ScriptHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | /// @notice Helper functions for scripts 7 | contract ScriptHelper is Script { 8 | /// @dev Compares two strings and returns true iff they are equal. 9 | function compareStrings(string memory a, string memory b) internal pure returns (bool) { 10 | return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); 11 | } 12 | 13 | /// @dev Returns a slice of `_bytes` with the first 64 bytes removed. 14 | function ltrim64(bytes memory _bytes) internal pure returns (bytes memory) { 15 | return slice(_bytes, 64, _bytes.length - 64); 16 | } 17 | 18 | /// @dev Returns a slice of `_bytes` starting at index `_start` and of length `_length`. 19 | /// referenece: https://github.com/GNSPS/solidity-bytes-utils/blob/6458fb2780a3092bc756e737f246be1de6d3d362/contracts/BytesLib.sol#L228 20 | function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { 21 | require(_length + 31 >= _length, "slice_overflow"); 22 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); 23 | 24 | bytes memory tempBytes; 25 | 26 | assembly { 27 | switch iszero(_length) 28 | case 0 { 29 | tempBytes := mload(0x40) 30 | 31 | let lengthmod := and(_length, 31) 32 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 33 | let end := add(mc, _length) 34 | 35 | for { let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { 36 | mc := add(mc, 0x20) 37 | cc := add(cc, 0x20) 38 | } { mstore(mc, mload(cc)) } 39 | 40 | mstore(tempBytes, _length) 41 | mstore(0x40, and(add(mc, 31), not(31))) 42 | } 43 | default { 44 | tempBytes := mload(0x40) 45 | 46 | mstore(tempBytes, 0) 47 | mstore(0x40, add(tempBytes, 0x20)) 48 | } 49 | } 50 | return tempBytes; 51 | } 52 | 53 | /// @dev Converts a string array into a JSON array string. 54 | function stringArrayToString(string[] memory array) internal pure returns (string memory) { 55 | string memory result = "["; 56 | 57 | for (uint256 i = 0; i < array.length; i++) { 58 | if (i != array.length - 1) { 59 | result = string.concat(result, "\"", array[i], "\","); 60 | } else { 61 | result = string.concat(result, "\"", array[i], "\""); 62 | } 63 | } 64 | 65 | return string.concat(result, "]"); 66 | } 67 | 68 | /// @dev Converts a string array into a JSON array string. Does not add quotes around each element. 69 | function stringArrayToArrayString(string[] memory array) internal pure returns (string memory) { 70 | string memory result = "["; 71 | 72 | for (uint256 i = 0; i < array.length; i++) { 73 | if (i != array.length - 1) { 74 | result = string.concat(result, array[i], ","); 75 | } else { 76 | result = string.concat(result, array[i]); 77 | } 78 | } 79 | 80 | return string.concat(result, "]"); 81 | } 82 | 83 | /// @dev Converts a bytes32 array into a JSON array string. 84 | function bytes32ArrayToString(bytes32[] memory array) internal pure returns (string memory) { 85 | string memory result = "["; 86 | 87 | for (uint256 i = 0; i < array.length; i++) { 88 | if (i != array.length - 1) { 89 | result = string.concat(result, "\"", vm.toString(array[i]), "\","); 90 | } else { 91 | result = string.concat(result, "\"", vm.toString(array[i]), "\""); 92 | } 93 | } 94 | 95 | return string.concat(result, "]"); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /script/target/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | "address", 4 | "uint", 5 | "uint" 6 | ], 7 | "count": 5, 8 | "values": { 9 | "0": { 10 | "0": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", 11 | "1": "10000", 12 | "2": "1" 13 | }, 14 | "1": { 15 | "0": "0x7c6b4bbe207d642d98d5c537142d85209e585087", 16 | "1": "20000", 17 | "2": "2" 18 | }, 19 | "2": { 20 | "0": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", 21 | "1": "30000", 22 | "2": "3" 23 | }, 24 | "3": { 25 | "0": "0x0000000000000000000000000000000000001337", 26 | "1": "40000", 27 | "2": "4" 28 | }, 29 | "4": { 30 | "0": "0x4e59b44847b379578588920ca78fbf26c0b4956c", 31 | "1": "50000", 32 | "2": "5" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /script/target/output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", 5 | "10000", 6 | "1" 7 | ], 8 | "proof": [ 9 | "0x073180b765e1cf92cb05630069c8481ec19171661317b4fab88b9c3c9835fb5b", 10 | "0x7040f273bcf51ac5aeb606be19c784802b4a81469d609428e2487c5fa23e855e", 11 | "0x00d9335e1f2b15d085d331fc240a20196e4ae5037b7d317f05d597c855a329b4" 12 | ], 13 | "root": "0xb61922e9343b32c54f90f4cca2561cc277e090b279ba4f9e75d1a9994b895123", 14 | "leaf": "0x035e33df50de019c2fdafb75e088976405fe8806b0341fa28db67c78e5e7f0e7" 15 | }, 16 | { 17 | "inputs": [ 18 | "0x7c6b4BbE207d642D98D5c537142d85209E585087", 19 | "20000", 20 | "2" 21 | ], 22 | "proof": [ 23 | "0x035e33df50de019c2fdafb75e088976405fe8806b0341fa28db67c78e5e7f0e7", 24 | "0x7040f273bcf51ac5aeb606be19c784802b4a81469d609428e2487c5fa23e855e", 25 | "0x00d9335e1f2b15d085d331fc240a20196e4ae5037b7d317f05d597c855a329b4" 26 | ], 27 | "root": "0xb61922e9343b32c54f90f4cca2561cc277e090b279ba4f9e75d1a9994b895123", 28 | "leaf": "0x073180b765e1cf92cb05630069c8481ec19171661317b4fab88b9c3c9835fb5b" 29 | }, 30 | { 31 | "inputs": [ 32 | "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", 33 | "30000", 34 | "3" 35 | ], 36 | "proof": [ 37 | "0x975e50a8393e07fe8577a1d50cea4ae8b9b43895a76664200675758f3af43486", 38 | "0x4e7362282749d862c14dcf6bd69bf478a6cc395e5d717521e9052334a2dfcfee", 39 | "0x00d9335e1f2b15d085d331fc240a20196e4ae5037b7d317f05d597c855a329b4" 40 | ], 41 | "root": "0xb61922e9343b32c54f90f4cca2561cc277e090b279ba4f9e75d1a9994b895123", 42 | "leaf": "0x7893f48fb1a6dd7466995e4116fd6c3445e07878fed74af169b349f3a0c93333" 43 | }, 44 | { 45 | "inputs": [ 46 | "0x0000000000000000000000000000000000001337", 47 | "40000", 48 | "4" 49 | ], 50 | "proof": [ 51 | "0x7893f48fb1a6dd7466995e4116fd6c3445e07878fed74af169b349f3a0c93333", 52 | "0x4e7362282749d862c14dcf6bd69bf478a6cc395e5d717521e9052334a2dfcfee", 53 | "0x00d9335e1f2b15d085d331fc240a20196e4ae5037b7d317f05d597c855a329b4" 54 | ], 55 | "root": "0xb61922e9343b32c54f90f4cca2561cc277e090b279ba4f9e75d1a9994b895123", 56 | "leaf": "0x975e50a8393e07fe8577a1d50cea4ae8b9b43895a76664200675758f3af43486" 57 | }, 58 | { 59 | "inputs": [ 60 | "0x4e59b44847b379578588920cA78FbF26c0B4956C", 61 | "50000", 62 | "5" 63 | ], 64 | "proof": [ 65 | "0x0000000000000000000000000000000000000000000000000000000000000000", 66 | "0x0000000000000000000000000000000000000000000000000000000000000000", 67 | "0xe8224361894cd8af52fc779be23f1da8e67692037cd16f7f35dcbff9678eb23e" 68 | ], 69 | "root": "0xb61922e9343b32c54f90f4cca2561cc277e090b279ba4f9e75d1a9994b895123", 70 | "leaf": "0x3ec457418b24baf8dbebe05d51f0f289919195b71df18e65c21372626282ebb5" 71 | } 72 | ] -------------------------------------------------------------------------------- /script/test/Merkle.s.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "forge-std/console.sol"; 6 | import "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; 7 | 8 | import "../common/ScriptHelper.sol"; 9 | 10 | contract MerkleScriptOutputTest is Test, ScriptHelper { 11 | // TODO: hardcoding the output from /script/target/output.json for simplicity now 12 | 13 | bytes32[] proof = [ 14 | bytes32(0x073180b765e1cf92cb05630069c8481ec19171661317b4fab88b9c3c9835fb5b), 15 | bytes32(0x7040f273bcf51ac5aeb606be19c784802b4a81469d609428e2487c5fa23e855e), 16 | bytes32(0x00d9335e1f2b15d085d331fc240a20196e4ae5037b7d317f05d597c855a329b4) 17 | ]; 18 | bytes32 root = 0xb61922e9343b32c54f90f4cca2561cc277e090b279ba4f9e75d1a9994b895123; 19 | bytes32 leaf = 0x035e33df50de019c2fdafb75e088976405fe8806b0341fa28db67c78e5e7f0e7; 20 | 21 | address addr = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; 22 | uint256 value = 10000; 23 | uint256 index = 1; 24 | 25 | function testAbiEncode(address a, uint256 b, uint256 c) public pure { 26 | vm.assume(a != address(0)); 27 | vm.assume(b != 0); 28 | vm.assume(c != 0); 29 | 30 | bytes32 a_bytes = bytes32(uint256(uint160(a))); 31 | bytes32 b_bytes = bytes32(b); 32 | bytes32 c_bytes = bytes32(c); 33 | 34 | bytes32[] memory _bytes = new bytes32[](3); 35 | _bytes[0] = a_bytes; 36 | _bytes[1] = b_bytes; 37 | _bytes[2] = c_bytes; 38 | 39 | assertEq(abi.encode(a, b, c), ltrim64(abi.encode(_bytes))); 40 | } 41 | 42 | function testComputeLeaf() public view { 43 | bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(addr, value, index)))); 44 | assertEq(computedLeaf, leaf); 45 | } 46 | 47 | function testVerify() public view { 48 | bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(addr, value, index)))); 49 | assertTrue(MerkleProof.verify(proof, root, computedLeaf)); 50 | } 51 | 52 | function testFalseAddress(address a) public view { 53 | vm.assume(a != addr); 54 | 55 | bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(a, value, index)))); 56 | assertFalse(MerkleProof.verify(proof, root, computedLeaf)); 57 | } 58 | 59 | function testFalseValue(uint256 b, uint256 c) public view { 60 | vm.assume(b != value); 61 | vm.assume(c != index); 62 | 63 | bytes32 computedLeaf = keccak256(bytes.concat(keccak256(abi.encode(addr, b, c)))); 64 | assertFalse(MerkleProof.verify(proof, root, computedLeaf)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/CompleteMerkle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./common/MurkyBase.sol"; 5 | 6 | /// @notice simple, kinda efficient (and improving!) Merkle proof generator and verifier using complete binary trees. 7 | /// @author dmfxyz 8 | /// @dev Note Merkle Tree implemented as a Complete Binary Tree. Merkle.sol uses full binary trees. 9 | contract CompleteMerkle is MurkyBase { 10 | /** 11 | * 12 | * HASHING FUNCTION * 13 | * 14 | */ 15 | /// @notice Hash function used when verifying proofs. 16 | /// @dev WARNING: This method is only used by verifyProof (inherited from MurkyBase.sol). 17 | // If modified, you **MUST** ensure the inline assembly methods in buildTree and getProof are modified to match. 18 | function hashLeafPairs(bytes32 left, bytes32 right) public pure override returns (bytes32 _hash) { 19 | assembly { 20 | switch lt(left, right) 21 | case 0 { 22 | mstore(0x0, right) 23 | mstore(0x20, left) 24 | } 25 | default { 26 | mstore(0x0, left) 27 | mstore(0x20, right) 28 | } 29 | _hash := keccak256(0x0, 0x40) 30 | } 31 | } 32 | 33 | ///@dev internal function that builds initial *sparse* array that is the shell of a complete tree 34 | function _initTree(bytes32[] memory data) internal pure returns (bytes32[] memory) { 35 | require(data.length > 1, "wont generate root for single leaf"); 36 | 37 | bytes32[] memory tree = new bytes32[](2 * data.length - 1); 38 | assembly { 39 | let dlen := mload(data) 40 | let titer := add(sub(mload(tree), dlen), 1) 41 | for { let i := add(data, mul(dlen, 0x20)) } gt(i, data) { i := sub(i, 0x20) } { 42 | mstore(add(tree, mul(titer, 0x20)), mload(i)) 43 | titer := add(titer, 1) 44 | } 45 | } 46 | return tree; 47 | } 48 | 49 | /// @notice Builds array representation of a complete binary merkle tree from given data. Tree[0] will contain the trees root. 50 | /// @dev Visibility set to private rather than internal as there is likely useful composability with this function. 51 | /// @param data The data to build the tree from. It should already be sorted if sorting is required. 52 | /// @return tree as a standard array representation of a complete binary tree. 53 | function buildTree(bytes32[] memory data) private pure returns (bytes32[] memory) { 54 | bytes32[] memory tree = _initTree(data); 55 | assembly { 56 | function hash_leafs(left, right) -> _hash { 57 | switch lt(left, right) 58 | case 0 { 59 | mstore(0x0, right) 60 | mstore(0x20, left) 61 | } 62 | default { 63 | mstore(0x0, left) 64 | mstore(0x20, right) 65 | } 66 | _hash := keccak256(0x0, 0x40) 67 | } 68 | for { let i := mload(tree) } gt(i, 1) { i := sub(i, 2) } { 69 | // TODO: clean all this up, mainly broken out for early understanding and debugging 70 | let left := mload(add(tree, mul(sub(i, 1), 0x20))) 71 | let right := mload(add(tree, mul(i, 0x20))) 72 | let posToWrite := add(tree, shr(1, mul(sub(i, 1), 0x20))) 73 | mstore(posToWrite, hash_leafs(left, right)) 74 | } 75 | } 76 | return tree; 77 | } 78 | 79 | /// @notice Calculates the merkle tree root for a given set of data. 80 | /// @param data The data to generate the root for. It should already be sorted if sorting is required. 81 | /// @return root as bytes32. 82 | function getRoot(bytes32[] memory data) public pure override returns (bytes32) { 83 | require(data.length > 1, "wont generate root for single leaf"); 84 | bytes32[] memory tree = buildTree(data); 85 | return tree[0]; 86 | } 87 | 88 | /// @notice Generates proof for given set of data and target index 89 | /// @dev Note that this returns a dynamic memory array constructed via assembly. 90 | /// This may cause issues with certain REPL interpreters (e.g. Chisel). 91 | /// @param data The data to generate the inclusion proof for. It should already be sorted if sorting is required. 92 | /// @param index The index of the value in the data that you wish to generate the inclusion proof for 93 | /// @return proof array that can subsequently be used by verifyProof or other libraries 94 | function getProof(bytes32[] memory data, uint256 index) public pure override returns (bytes32[] memory) { 95 | require(data.length > 1, "wont generate proof for single leaf"); 96 | bytes32[] memory tree = buildTree(data); 97 | 98 | assembly { 99 | let iter := sub(sub(mload(tree), index), 0x1) 100 | let ptr := mload(0x40) 101 | mstore(ptr, 0x20) 102 | let proofSizePtr := add(ptr, 0x20) 103 | let proofIndexPtr := add(ptr, 0x40) 104 | for {} 0x1 {} { 105 | // while (true) 106 | let sibling := mload(add(tree, mul(add(iter, shl(0x1, and(iter, 0x1))), 0x20))) 107 | mstore(proofIndexPtr, sibling) 108 | iter := shr(1, sub(iter, eq(and(iter, 0x1), 0x0))) 109 | if eq(iter, 0x0) { break } 110 | proofIndexPtr := add(proofIndexPtr, 0x20) 111 | } 112 | mstore(proofSizePtr, div(sub(proofIndexPtr, proofSizePtr), 0x20)) 113 | return(ptr, add(0x40, sub(proofIndexPtr, proofSizePtr))) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Merkle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./common/MurkyBase.sol"; 5 | 6 | /// @notice Nascent, simple, kinda efficient (and improving!) Merkle proof generator and verifier 7 | /// @author dmfxyz 8 | /// @dev Note Generic Merkle Tree 9 | contract Merkle is MurkyBase { 10 | /** 11 | * 12 | * HASHING FUNCTION * 13 | * 14 | */ 15 | 16 | /// ascending sort and concat prior to hashing 17 | function hashLeafPairs(bytes32 left, bytes32 right) public pure override returns (bytes32 _hash) { 18 | assembly { 19 | switch lt(left, right) 20 | case 0 { 21 | mstore(0x0, right) 22 | mstore(0x20, left) 23 | } 24 | default { 25 | mstore(0x0, left) 26 | mstore(0x20, right) 27 | } 28 | _hash := keccak256(0x0, 0x40) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Xorkle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./common/MurkyBase.sol"; 5 | 6 | /// @notice Nascent, simple, kinda efficient (and improving!) Merkle proof generator and verifier 7 | /// @author dmfxyz 8 | /// @dev Note Xor Based "Merkle" Tree 9 | contract Xorkle is MurkyBase { 10 | /** 11 | * 12 | * HASHING FUNCTION * 13 | * 14 | */ 15 | function hashLeafPairs(bytes32 left, bytes32 right) public pure override returns (bytes32 _hash) { 16 | // saves a few gas lol 17 | assembly { 18 | mstore(0x0, xor(left, right)) 19 | _hash := keccak256(0x0, 0x20) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/MurkyBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | abstract contract MurkyBase { 5 | /** 6 | * 7 | * CONSTRUCTOR * 8 | * 9 | */ 10 | constructor() {} 11 | 12 | /** 13 | * 14 | * VIRTUAL HASHING FUNCTIONS * 15 | * 16 | */ 17 | function hashLeafPairs(bytes32 left, bytes32 right) public pure virtual returns (bytes32 _hash); 18 | 19 | /** 20 | * 21 | * PROOF VERIFICATION * 22 | * 23 | */ 24 | function verifyProof(bytes32 root, bytes32[] memory proof, bytes32 valueToProve) 25 | external 26 | pure 27 | virtual 28 | returns (bool) 29 | { 30 | // proof length must be less than max array size 31 | bytes32 rollingHash = valueToProve; 32 | uint256 length = proof.length; 33 | unchecked { 34 | for (uint256 i = 0; i < length; ++i) { 35 | rollingHash = hashLeafPairs(rollingHash, proof[i]); 36 | } 37 | } 38 | return root == rollingHash; 39 | } 40 | 41 | /** 42 | * 43 | * PROOF GENERATION * 44 | * 45 | */ 46 | function getRoot(bytes32[] memory data) public pure virtual returns (bytes32) { 47 | require(data.length > 1, "won't generate root for single leaf"); 48 | while (data.length > 1) { 49 | data = hashLevel(data); 50 | } 51 | return data[0]; 52 | } 53 | 54 | function getProof(bytes32[] memory data, uint256 node) public pure virtual returns (bytes32[] memory) { 55 | require(data.length > 1, "won't generate proof for single leaf"); 56 | // The size of the proof is equal to the ceiling of log2(numLeaves) 57 | bytes32[] memory result = new bytes32[](log2ceilBitMagic(data.length)); 58 | uint256 pos = 0; 59 | 60 | // Two overflow risks: node, pos 61 | // node: max array size is 2**256-1. Largest index in the array will be 1 less than that. Also, 62 | // for dynamic arrays, size is limited to 2**64-1 63 | // pos: pos is bounded by log2(data.length), which should be less than type(uint256).max 64 | while (data.length > 1) { 65 | unchecked { 66 | if (node & 0x1 == 1) { 67 | result[pos] = data[node - 1]; 68 | } else if (node + 1 == data.length) { 69 | result[pos] = bytes32(0); 70 | } else { 71 | result[pos] = data[node + 1]; 72 | } 73 | ++pos; 74 | node /= 2; 75 | } 76 | data = hashLevel(data); 77 | } 78 | return result; 79 | } 80 | 81 | ///@dev function is private to prevent unsafe data from being passed 82 | function hashLevel(bytes32[] memory data) private pure returns (bytes32[] memory) { 83 | bytes32[] memory result; 84 | 85 | // Function is private, and all internal callers check that data.length >=2. 86 | // Underflow is not possible as lowest possible value for data/result index is 1 87 | // overflow should be safe as length is / 2 always. 88 | unchecked { 89 | uint256 length = data.length; 90 | if (length & 0x1 == 1) { 91 | result = new bytes32[](length / 2 + 1); 92 | result[result.length - 1] = hashLeafPairs(data[length - 1], bytes32(0)); 93 | } else { 94 | result = new bytes32[](length / 2); 95 | } 96 | // pos is upper bounded by data.length / 2, so safe even if array is at max size 97 | uint256 pos = 0; 98 | for (uint256 i = 0; i < length - 1; i += 2) { 99 | result[pos] = hashLeafPairs(data[i], data[i + 1]); 100 | ++pos; 101 | } 102 | } 103 | return result; 104 | } 105 | 106 | /** 107 | * 108 | * MATH "LIBRARY" * 109 | * 110 | */ 111 | 112 | /// @dev Note that x is assumed > 0 113 | function log2ceil(uint256 x) public pure returns (uint256) { 114 | uint256 ceil = 0; 115 | uint256 pOf2; 116 | // If x is a power of 2, then this function will return a ceiling 117 | // that is 1 greater than the actual ceiling. So we need to check if 118 | // x is a power of 2, and subtract one from ceil if so. 119 | assembly { 120 | // we check by seeing if x == (~x + 1) & x. This applies a mask 121 | // to find the lowest set bit of x and then checks it for equality 122 | // with x. If they are equal, then x is a power of 2. 123 | 124 | /* Example 125 | x has single bit set 126 | x := 0000_1000 127 | (~x + 1) = (1111_0111) + 1 = 1111_1000 128 | (1111_1000 & 0000_1000) = 0000_1000 == x 129 | 130 | x has multiple bits set 131 | x := 1001_0010 132 | (~x + 1) = (0110_1101 + 1) = 0110_1110 133 | (0110_1110 & x) = 0000_0010 != x 134 | */ 135 | 136 | // we do some assembly magic to treat the bool as an integer later on 137 | pOf2 := eq(and(add(not(x), 1), x), x) 138 | } 139 | 140 | // if x == type(uint256).max, than ceil is capped at 256 141 | // if x == 0, then pO2 == 0, so ceil won't underflow 142 | unchecked { 143 | while (x > 0) { 144 | x >>= 1; 145 | ceil++; 146 | } 147 | ceil -= pOf2; // see above 148 | } 149 | return ceil; 150 | } 151 | 152 | /// Original bitmagic adapted from https://github.com/paulrberg/prb-math/blob/main/contracts/PRBMath.sol 153 | /// @dev Note that x assumed > 1 154 | function log2ceilBitMagic(uint256 x) public pure returns (uint256) { 155 | if (x <= 1) { 156 | return 0; 157 | } 158 | uint256 msb = 0; 159 | uint256 _x = x; 160 | if (x >= 2 ** 128) { 161 | x >>= 128; 162 | msb += 128; 163 | } 164 | if (x >= 2 ** 64) { 165 | x >>= 64; 166 | msb += 64; 167 | } 168 | if (x >= 2 ** 32) { 169 | x >>= 32; 170 | msb += 32; 171 | } 172 | if (x >= 2 ** 16) { 173 | x >>= 16; 174 | msb += 16; 175 | } 176 | if (x >= 2 ** 8) { 177 | x >>= 8; 178 | msb += 8; 179 | } 180 | if (x >= 2 ** 4) { 181 | x >>= 4; 182 | msb += 4; 183 | } 184 | if (x >= 2 ** 2) { 185 | x >>= 2; 186 | msb += 2; 187 | } 188 | if (x >= 2 ** 1) { 189 | msb += 1; 190 | } 191 | 192 | uint256 lsb = (~_x + 1) & _x; 193 | if ((lsb == _x) && (msb > 0)) { 194 | return msb; 195 | } else { 196 | return msb + 1; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/test/CompleteMerkle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../CompleteMerkle.sol"; 5 | import "forge-std/Test.sol"; 6 | import "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; 7 | 8 | contract CompleteMerkleTest is Test { 9 | CompleteMerkle m; 10 | 11 | function setUp() public { 12 | m = new CompleteMerkle(); 13 | } 14 | 15 | function testGenerateProof(bytes32[] memory data, uint256 node) public view { 16 | vm.assume(data.length > 1); 17 | vm.assume(node < data.length); 18 | bytes32 root = m.getRoot(data); 19 | bytes32[] memory proof = m.getProof(data, node); 20 | bytes32 valueToProve = data[node]; 21 | 22 | bytes32 rollingHash = valueToProve; 23 | for (uint256 i = 0; i < proof.length; ++i) { 24 | rollingHash = m.hashLeafPairs(rollingHash, proof[i]); 25 | } 26 | assertEq(rollingHash, root); 27 | } 28 | 29 | function testVerifyProofSucceedsForGoodValue(bytes32[] memory data, uint256 node) public view { 30 | vm.assume(data.length > 1); 31 | vm.assume(node < data.length); 32 | bytes32 root = m.getRoot(data); 33 | bytes32[] memory proof = m.getProof(data, node); 34 | bytes32 valueToProve = data[node]; 35 | assertTrue(m.verifyProof(root, proof, valueToProve)); 36 | } 37 | 38 | function testVerifyProofFailsForBadValue(bytes32[] memory data, bytes32 valueToProve, uint256 node) public view { 39 | vm.assume(data.length > 1); 40 | vm.assume(node < data.length); 41 | vm.assume(valueNotInArray(data, valueToProve)); 42 | bytes32 root = m.getRoot(data); 43 | bytes32[] memory proof = m.getProof(data, node); 44 | assertFalse(m.verifyProof(root, proof, valueToProve)); 45 | } 46 | 47 | function testVerifyProofOzForGasComparison(bytes32[] memory data, uint256 node) public view { 48 | vm.assume(data.length > 1); 49 | vm.assume(node < data.length); 50 | bytes32 root = m.getRoot(data); 51 | bytes32[] memory proof = m.getProof(data, node); 52 | bytes32 valueToProve = data[node]; 53 | assertTrue(MerkleProof.verify(proof, root, valueToProve)); 54 | } 55 | 56 | function testWontGetRootSingleLeaf() public { 57 | bytes32[] memory data = new bytes32[](1); 58 | data[0] = bytes32(0x0); 59 | vm.expectRevert("wont generate root for single leaf"); 60 | m.getRoot(data); 61 | } 62 | 63 | function testWontGetProofSingleLeaf() public { 64 | bytes32[] memory data = new bytes32[](1); 65 | data[0] = bytes32(0x0); 66 | vm.expectRevert("wont generate proof for single leaf"); 67 | m.getProof(data, 0x0); 68 | } 69 | 70 | function valueNotInArray(bytes32[] memory data, bytes32 value) public pure returns (bool) { 71 | for (uint256 i = 0; i < data.length; ++i) { 72 | if (data[i] == value) return false; 73 | } 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/Merkle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../Merkle.sol"; 5 | import "forge-std/Test.sol"; 6 | import "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; 7 | 8 | contract ContractTest is Test { 9 | Merkle m; 10 | 11 | function setUp() public { 12 | m = new Merkle(); 13 | } 14 | 15 | function testHashes(bytes32 left, bytes32 right) public view { 16 | bytes32 hAssem = m.hashLeafPairs(left, right); 17 | bytes memory packed; 18 | if (left <= right) { 19 | packed = abi.encodePacked(left, right); 20 | } else { 21 | packed = abi.encodePacked(right, left); 22 | } 23 | bytes32 hNaive = keccak256(packed); 24 | assertEq(hAssem, hNaive); 25 | } 26 | 27 | function testGenerateProof(bytes32[] memory data, uint256 node) public view { 28 | vm.assume(data.length > 1); 29 | vm.assume(node < data.length); 30 | bytes32 root = m.getRoot(data); 31 | bytes32[] memory proof = m.getProof(data, node); 32 | bytes32 valueToProve = data[node]; 33 | 34 | bytes32 rollingHash = valueToProve; 35 | for (uint256 i = 0; i < proof.length; ++i) { 36 | rollingHash = m.hashLeafPairs(rollingHash, proof[i]); 37 | } 38 | assertEq(rollingHash, root); 39 | } 40 | 41 | function testVerifyProof(bytes32[] memory data, uint256 node) public view { 42 | vm.assume(data.length > 1); 43 | vm.assume(node < data.length); 44 | bytes32 root = m.getRoot(data); 45 | bytes32[] memory proof = m.getProof(data, node); 46 | bytes32 valueToProve = data[node]; 47 | assertTrue(m.verifyProof(root, proof, valueToProve)); 48 | } 49 | 50 | function testFalseWhenNotMemberProof(bytes32[] memory data, bytes32 valueToProve, uint256 node) public view { 51 | vm.assume(data.length > 1); 52 | vm.assume(node < data.length); 53 | vm.assume(valueNotInArray(data, valueToProve)); 54 | bytes32 root = m.getRoot(data); 55 | bytes32[] memory proof = m.getProof(data, node); 56 | assertFalse(m.verifyProof(root, proof, valueToProve)); 57 | } 58 | 59 | function testVerifyProofOzForGasComparison(bytes32[] memory data, uint256 node) public view { 60 | vm.assume(data.length > 1); 61 | vm.assume(node < data.length); 62 | bytes32 root = m.getRoot(data); 63 | bytes32[] memory proof = m.getProof(data, node); 64 | bytes32 valueToProve = data[node]; 65 | assertTrue(MerkleProof.verify(proof, root, valueToProve)); 66 | } 67 | 68 | function testWontGetRootSingleLeaf() public { 69 | bytes32[] memory data = new bytes32[](1); 70 | data[0] = bytes32(0x0); 71 | vm.expectRevert("won't generate root for single leaf"); 72 | m.getRoot(data); 73 | } 74 | 75 | function testWontGetProofSingleLeaf() public { 76 | bytes32[] memory data = new bytes32[](1); 77 | data[0] = bytes32(0x0); 78 | vm.expectRevert("won't generate proof for single leaf"); 79 | m.getProof(data, 0x0); 80 | } 81 | 82 | function valueNotInArray(bytes32[] memory data, bytes32 value) public pure returns (bool) { 83 | for (uint256 i = 0; i < data.length; ++i) { 84 | if (data[i] == value) return false; 85 | } 86 | return true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/MurkyBase.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "forge-std/Test.sol"; 4 | import "../common/MurkyBase.sol"; 5 | 6 | contract MurkyBaseTest is Test, MurkyBase { 7 | // Hacky way to test the base functions until transitioned to library 8 | function hashLeafPairs(bytes32, bytes32) public pure virtual override returns (bytes32) { 9 | return bytes32(0x0); 10 | } 11 | 12 | function testLogCeil(uint256 x) public view { 13 | vm.assume(x > 0); 14 | this.log2ceil(x); 15 | } 16 | 17 | function testLogCeilBitMagic(uint256 x) public view { 18 | vm.assume(x > 0); 19 | this.log2ceilBitMagic(x); 20 | } 21 | 22 | function testLogCeil_KnownPowerOf2() public view { 23 | assertEq(3, this.log2ceilBitMagic(8)); 24 | } 25 | 26 | function testLogCeil_Known() public view { 27 | assertEq(8, this.log2ceilBitMagic((129))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/StandardInput.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "../Xorkle.sol"; 4 | import "../Merkle.sol"; 5 | import "../CompleteMerkle.sol"; 6 | import "forge-std/Test.sol"; 7 | 8 | contract StandardizedInputTest is Test { 9 | Xorkle x; 10 | Merkle m; 11 | CompleteMerkle cm; 12 | bytes32[100] data; 13 | uint256[8] leaves = [4, 8, 15, 16, 23, 42, 69, 88]; 14 | 15 | function setUp() public { 16 | string[] memory inputs = new string[](2); 17 | inputs[0] = "cat"; 18 | inputs[1] = "src/test/standard_data/StandardInput.txt"; 19 | bytes memory result = vm.ffi(inputs); 20 | data = abi.decode(result, (bytes32[100])); 21 | x = new Xorkle(); 22 | m = new Merkle(); 23 | cm = new CompleteMerkle(); 24 | } 25 | 26 | function testXorkleGenerateProofStandard() public view { 27 | bytes32[] memory _data = _getData(); 28 | for (uint256 i = 0; i < leaves.length; ++i) { 29 | x.getProof(_data, leaves[i]); 30 | } 31 | } 32 | 33 | function testMerkleGenerateProofStandard() public view { 34 | bytes32[] memory _data = _getData(); 35 | for (uint256 i = 0; i < leaves.length; ++i) { 36 | m.getProof(_data, leaves[i]); 37 | } 38 | } 39 | 40 | function testXorkleVerifyProofStandard() public view { 41 | bytes32[] memory _data = _getData(); 42 | bytes32 root = x.getRoot(_data); 43 | for (uint256 i = 0; i < leaves.length; ++i) { 44 | bytes32[] memory proof = x.getProof(_data, leaves[i]); 45 | assertTrue(x.verifyProof(root, proof, _data[leaves[i]])); 46 | } 47 | } 48 | 49 | function testMerkleVerifyProofStandard() public view { 50 | bytes32[] memory _data = _getData(); 51 | bytes32 root = m.getRoot(_data); 52 | for (uint256 i = 0; i < leaves.length; ++i) { 53 | bytes32[] memory proof = m.getProof(_data, leaves[i]); 54 | assertTrue(m.verifyProof(root, proof, _data[leaves[i]])); 55 | } 56 | } 57 | 58 | function testCompleteMerkleGenerateProofStandard() public view { 59 | bytes32[] memory _data = _getData(); 60 | for (uint256 i = 0; i < leaves.length; ++i) { 61 | cm.getProof(_data, leaves[i]); 62 | } 63 | } 64 | 65 | function testCompleteMerkleVerifyProofStandard() public view { 66 | bytes32[] memory _data = _getData(); 67 | bytes32 root = cm.getRoot(_data); 68 | for (uint256 i = 0; i < leaves.length; ++i) { 69 | bytes32[] memory proof = cm.getProof(_data, leaves[i]); 70 | assertTrue(cm.verifyProof(root, proof, _data[leaves[i]])); 71 | } 72 | } 73 | 74 | function _getData() public view returns (bytes32[] memory) { 75 | bytes32[] memory _data = new bytes32[](data.length); 76 | uint256 length = data.length; 77 | for (uint256 i = 0; i < length; ++i) { 78 | _data[i] = data[i]; 79 | } 80 | return _data; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/Xorkle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.4; 3 | 4 | import "../Xorkle.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | contract ContractTest is Test { 8 | Xorkle m; 9 | 10 | function setUp() public { 11 | m = new Xorkle(); 12 | } 13 | 14 | function testHashes(bytes32 left, bytes32 right) public view { 15 | bytes32 hAssem = m.hashLeafPairs(left, right); 16 | bytes32 hNaive = keccak256(abi.encode(left ^ right)); 17 | assertEq(hAssem, hNaive); 18 | } 19 | 20 | function testGenerateProof(bytes32[] memory data, uint256 node) public view { 21 | vm.assume(data.length > 1); 22 | vm.assume(node < data.length); 23 | bytes32 root = m.getRoot(data); 24 | bytes32[] memory proof = m.getProof(data, node); 25 | bytes32 valueToProve = data[node]; 26 | 27 | bytes32 rollingHash = valueToProve; 28 | for (uint256 i = 0; i < proof.length; ++i) { 29 | rollingHash = m.hashLeafPairs(rollingHash, proof[i]); 30 | } 31 | assertEq(rollingHash, root); 32 | } 33 | 34 | function testVerifyProof(bytes32[] memory data, uint256 node) public view { 35 | vm.assume(data.length > 1); 36 | vm.assume(node < data.length); 37 | bytes32 root = m.getRoot(data); 38 | bytes32[] memory proof = m.getProof(data, node); 39 | bytes32 valueToProve = data[node]; 40 | assertTrue(m.verifyProof(root, proof, valueToProve)); 41 | } 42 | 43 | function testFalseWhenNotMemberProof(bytes32[] memory data, bytes32 valueToProve, uint256 node) public view { 44 | vm.assume(data.length > 1); 45 | vm.assume(node < data.length); 46 | vm.assume(valueNotInArray(data, valueToProve)); 47 | bytes32 root = m.getRoot(data); 48 | bytes32[] memory proof = m.getProof(data, node); 49 | assertFalse(m.verifyProof(root, proof, valueToProve)); 50 | } 51 | 52 | function testWontGetRootSingleLeaf() public { 53 | bytes32[] memory data = new bytes32[](1); 54 | data[0] = bytes32(0x0); 55 | vm.expectRevert("won't generate root for single leaf"); 56 | m.getRoot(data); 57 | } 58 | 59 | function testWontGetProofSingleLeaf() public { 60 | bytes32[] memory data = new bytes32[](1); 61 | data[0] = bytes32(0x0); 62 | vm.expectRevert("won't generate proof for single leaf"); 63 | m.getProof(data, 0x0); 64 | } 65 | 66 | function valueNotInArray(bytes32[] memory data, bytes32 value) public pure returns (bool) { 67 | for (uint256 i = 0; i < data.length; ++i) { 68 | if (data[i] == value) return false; 69 | } 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/standard_data/README.md: -------------------------------------------------------------------------------- 1 | ## Standardized Testing Data 2 | 3 | This folder contains standardized testing data for reproducible proofs, gas snapshotting, etc. This is an important addition to fuzz testing as it allows for consistent performance measures, comparison with other libraries and implementations, and probably other benefits that I'm not aware of yet. 4 | 5 | The data is currently used by [StandardInput.t.sol](../StandardInput.t.sol) and is loaded at test time using Foundry's `ffi` cheatcode. 6 | 7 | The data is encoded, and is decoded into a `bytes32[100]` array at test time. 8 | 9 | ### Files 10 | * **StandardData.txt**: the current standard testing data file. Contains 100 addresses encoded to 32 bytes each. The generation script is [dead simple](https://gist.github.com/dmfxyz/36dce9db458eea1135e93ae739197bb3). Always interested in improvements! 11 | 12 | * **StandardData.old.txt**: a prior testing file. Also contains 100 32 byte data points, but most are very sparse with only the two highest order bits set. It remains here as prior gas-snapshots relied on it. 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/standard_data/StandardInput.old.txt: -------------------------------------------------------------------------------- 1 | 98c6b6225ecabf3e62f3d99c3f2cc7e50d41a097d3fad11996ea2d2fbf5b8224460d86199978082aee3b1f551ffe261f716d5c8ffa19aa5784bd609ebeffc87363272026c7501dd99d94d143db41e9e13fd8e59fa1b0906b3eb83bf34ccc769d2acf067ac861cf3e10494f234d1746119ad829c89b35b34d683e1c85776396e3584400919ded9d1899adff062050ee2d0070346f290ae1e8a1e7d34f2af5e4830ca5a5193cd51a5830145bba57bc62b8e468c9c74570c679849e2b91166d9f60568a94acde693814dd7daf2a435a51d39f87c11b2fdfda65201ae74099dea53192834e574ec8ee19c894da69830a60adffc30d1277b0ec780d4402ec2949da4bc4ec995816d2509c993f4bf87be55c9cb53a99a3f7cfbc16a0343cade8c8fefc3df14a0202e058b82e5275076ce1ff10d0a492eeb545b92f96110a6bf706afb8d1a80a6b7a3600a84c466f49344a3b0067109fd502448c46ba8968b748988bd28a235fdcc4565b6a4097708585aecccd4ed85d9d093faa4fea37eca2c23b396eea1bd8eca3a5fbce56b11ccb80f5cf70ab5386ac93170ad249264e0d6f03f336496d6c84c4d9099cc02043cda2c642e31d184d1fbc1ea86acf48eded9274bc47206ba3f1ecfc2666442de2a1c754ff128202cb12e414a0be423295af0ed4ba05e169f10e78458e11029a222eb045d01c5faca61f94dc4ca81d6211f2675127fc8e45c85fe1bea862a583401bb4a6ac461ef003409c8dd490f62258b8540b2f1fd09c876e243ef2cc5cac23da8e8205f44d707b26520aa9ae0dc27633af0a332bb1f4f10abadb4bcdb34011e599737eb946e089dfddc8e7974ab99c80fb59cb956d51d7e863eba9f638aa828a325b134a9f6c8c6ca3877179ac4ba40ecc1571d284727b956f693d1cf187fc091183e727ae9deddc62db607322143c64aa4150e99b1470209411134d50dbc24d6ee0678b2a81154744266e93488b1c4e178397706776bfd91cbe7775853b881094fc86774de9c719902421cebfad9a0e1ca5d44ff5fee59eed0294ea4b39e27fca9395e2e5ec527f5cb94353bb2992d43a2cefd258ca054ab929503fc75abe08db69f40462e7a76990d7a80d7b0d0c3ad3d43c6cbaa2bb2029614ab31fe26483a5a7879ca24a0f266e9680562c2195bb9f5cd6aadae91073cdd49de339dd553cdd6b182c32bcb3201c003b75eaa21b6de2cb6253b6c60e3fe986dfb74f330df433e1b81bf1107659384ba69508d17c8f46a75af415d068045ddc5a814a9f669fcf2847772048e1ec1483b0ed61ec82adf5caaacc9cb75e05e2b9e51be03829d8d0c68e2bf42f4687572b6036db76f5c9f41cd7fc58cfeccd710586898eba192b564a375be81fd86eb01e6e60cc125bcb2b238a83367d0e5068540d93eca893d962566a6eae39ba72e94a103e1b5dd54cecdd81a05435e2dcc6ea87b6bab2749e9278626d8c94d5ca2db7a480cb5bea809653d4d1c4c1eaf128cfd2ab621053932d866833a82e0385e59bbafc7ed55fe3d9fc9cf3f4a3d3e6a29bc7119543111e6246871121b25fbd767fcc794a747e917bf576afed7da6ab5d06fdcf86e23c36b93231955be6ae34d669f683211163d58a3756abde8e766a39cfe89b95c765906f369fed01770499d641be42bfa243f4634afc332aeba31295a97f6973db6f291c3d6cac520c07375201b100bc629ac3fcb95de3378233b9b6b1339ae7c98d97910f9c0ab122e3888353eaf2cc426e07c8578a4419a78778343a41c0e294010f0d31543f5277fd9ab9230f9d5f5e47e2e7a3c40ee20e4c18ab93b0510908ba5d81c0c104805c8f8e462c731d6ecd5b8caca573ac656e4f0d7e18c7547693ef0a1150ecbb666098ab1974f2215ecfbe010ca74518bc4acb8079a381b016dd0bb623e1c075a1fcf80e2e7b4327760290f1e74e8a322fadbb69cefc54471e8002ff5ff954a52236031416aed06d9f2fc901901300416e1ed520fdb0fbb3f2b674a278c16939d1f0667570fed969e2a64f90d86d369ce38493d648484904c018738dc33dd6d959e20c768b692655c45014c9490d34aebc931133126a8b564c1a6d858ab3aabfa3066fe89f766a0400feca0d942e431bb6660fe9354c54d1e80194eb3a9fd6402861fbc81b49cd7d8b72a8a2e463b6813609da92d1fb749cd7fb2a03e878e5306c195a523f6f5debf9221ede92dde0c9529cda37727eb91681ee08ae8ed861e9f0355fd76002af2fb3ee9f160ded7449620ebce2f0f1f38cc06f84f841c7544104b5cb55719d2630512238b7f2177e87a8eef70356c9de9e36279a7db46a0d010b0b4643d788b1abcc9db74bdd9358047fa94f72c8148d0dfabbee18f5c756addbe303ad2f8e004beb2f0fe87acb72266286debbaf6c3912d753373b35920c783a9d7c9f190a2b990dba22165c617b5384de5d4b5372c23f3ef29824e5324f80babc77dfc70cfc507dbd07e81c16d7d66bad90d8fbb4fa0ba56e11a6bc9d8e0c6dc17fee076605524c8a7935f08d885ac8138eabdc3f291ab2adfff43ab40abb8aec9b652d1866fc748e91b6600cbf4daa9538cf12e96ee0e2f5010da2357ccb18a3f7b7f7c0e15d224e1169ae58c35ed673da9b7dff01c91fb5a79f492a6387eb11d8d0b9dfcb01c24723bb8eace99788381ac2e3b1d6e2b44da480a7bf1cb889e746a0ad862511f6ec22b7eaa7bb8f357034e685a8ed6a921bad6280beda3846ebd8a3abc6917eadb88646d213150b7505a5458b9ad8333a58dd28f3605ec02e55b60e33e27a84f3723c074609c02129aeb3c7cb96d155a559fe7383dd464a3d12b3361859b834cdb18a4b2e1790fb48391c8e03a2c1affdc9bc350e6c1705c5c691b4114cce56d2c3622a8af341afabae39a788877ad3e630eeaeeb9fd09a7be66d4f4f465fee847c46d8b64b190350068fbc4d58ac9ca89bf64ab5ee15f41272c2566b6668f8588faf2b0e195bf301c3d3de69b62f75c6342db0f266923149d857df9d2cd60c65994c041bb50883d0769c9204494929527fd61f0468500f25329128b563f5eda75f452d7f0a6a571e2d1f21be3adca75d8b63635214ef1c037d95647daba5aec0a2d84c81457592c1b8700bf9c926bdebc81c14527c46e25d546b818b1f8714f2367ff49443fb3fbd5a9df6b0ad99e67dc5897f387481e01bbae6894f0f2ad806e26902a7697c534c2917c60321cc228c286abf752c454eb5578a6315714e875f431ffdc0f56a0860ab1c0a227f424abfe680881b61c4e0d08ac45398638c6355702abdc33a6e2bbc72e49003a1b064b1ef47f93e10441718a4a9dfa8187302dcdf4c559dc9eebfc5e1605db96555496ca4b63bb26dfdbd1fd7ee9fec2950f9e2516d1ffffad60c4c9214f3e763800f3b21376cd2e12248362c958825717440b676d02a2ac8051e0d051f3b94f003a3ab9c49f6e0e41da784a912c235234e7f42200a5414485ffd33d202fa17082a7400e9e529f31eed90256316df39616e843f505d03cd2e15c2eb95a1551ceb052cc12c155ae21c61ae270362db32bfdd4bc1557bcef10a96138fbb2d8e216b939210e13f61935c5c44a871ad8ab743b91c27679444891ef8e63246d51e1e9d29e4d806a111266003401485dc65c6bcf8e4fc452306e907b2d4ace5c291dc8bd5e81dd9ff18989db3268ff6942817c1aef327ac070f47eba4668a66022aaadba2d2bcd54f99061d8d1bfeffcc1d127ea8b6080d887b09196db8aa110e024b10aecfb70a6b661feb5f65abedb4c6ffdb37b1742d676baa8e94d1c64e38840fb7f1cd32bc126d99cf7c2e0107598455590cfd7fc8e81e59bdf3bd21b1bcf0029cca4ec49046a8ae90c52200763edaed204413112c34057895f9cfa727b0760ab8c73f785756d0a5e91ac30d3c2d16f1bf8324f882e242042e52becc0f4ec81b84230096b1d9cc7c2da5f92ba6e9bfce3d64bfcac4e01eb62ecf4c945a804b6645389702c045b40050c7dd19e49a1b2aa2aa1e2da7122149db41341f94d2088f7f4d4dbc511a9862a78240518320078128b64e97d93aa4504d20b3dbce45362999484445079dde78a4c338b4b279d9cd72f69798c2889a14bcc41febbd47303a805379186dcfa6d1b0d6c6b83af257729c24e1cde76fbcd7677dbce090d1f641d95584777a705fce56d1530eb40346d461f69f1fd1b5653207222c73c3732cb63fc3c1f548a91ef9c55ec49fbbd5465c5279318d33190caac594a65ee10485485d74d82526fc1cee38ac01e0826d6c65399afc3b88843837baecd64c07c77edfc02b99e5b6c0413221dc10de97124c6a59f7a2951823eb9f055f68421c28ab66f5d363778c87994e6ae314a651e7fb20630f344a1d647f0358e495d8da948c6e7093b85187c685fc4e1cef306c90c0dba8539909d26cea05d037e693b1f3750cf0fda53cc84cac535bc33c7db9e9848f7480a79a72b32cc7b769efefb906222c604aa9ab0ff014b275370d3e4425389ad7b4cb27edb45512b8b84b -------------------------------------------------------------------------------- /src/test/standard_data/StandardInput.txt: -------------------------------------------------------------------------------- 1 | f910ccaa307836354233316666386231414464306335333243453944383735313530636239613337633333624633aa307831336341393634344241453063343638333062353035363532373237463131413066303361623033aa307836316433454436366136343633613745324536623337303139346235383831363632623964303563aa307836363139423438644334436262383143633139343864443632633732443035313643463162343231aa307862623038384337373630414365623138313536423830616341343432633738313631303663413545aa307832433334423933633839364463354362396439443465336665343532334341326532373046363544aa307841383641353734313562316346336235453239453536443539306346323433344366433363616661aa307833373846614534373233346263456241336230313142386431393163353841613136443637303937aa307830383438343037393566453464633362373633453339384436313937384532664432466434313243aa307842383430333130393434346237633033664434333338354563433236416239654630356638343063aa307865323437324235323535644235393762316361313837623145334262323330666635636534663736aa307865373845353531396238634134306131633335444463424436313239423134363831316564646232aa307831314231426264316530336566383638343031324262433566353432453737444435353446443633aa307835383433373043316133423546343531344146313146316244634164353462354531323430653561aa307837334643386662666638393562343237344446456234393231343266303046663962443439393346aa307837383645363535363432653235463931316331303837343239364446466434343032356136633837aa307831323066653637644533363763413165644143374639616231354632666232354135446644456262aa307862363166613644393035324130324245614335643743394231383836333031303335663742643936aa307835343432383632664233344442633245413945653942306530306639313565353533313334334435aa307866314266663135663639396539433131453530634132623536443035623643423364303938623063aa307862376333353032373564354462423837346234363061353864343738343864433733663232373836aa307866324530323234453839323832396131334246356131463731363964423535393435374435364361aa307845423239656143643736414331453132343942354333373638323135664236373534326136323244aa307830624442453538373635333938314638303336383936653838306236634535633535384537313530aa307837333931326631643438313344343838366431366163424361413237393442643836354331623238aa307864316633333830343330384437396266393939633133653330333866313130343366363330373136aa307835653165373161366146613336354235363242393835353534634232456137393961386444304544aa307861333245623139424130384331434564443263343464426566316136643464363338393735303135aa307832623031413737383638416334356631304345386230663361373539463737456338383936413141aa307865354437623246663838443534324233423732353631424231393165463736333931366563433142aa307862354330434433333545323031304443633242346134304631394339614539363438316166306438aa307834393062386637314464356534363234364431326237353838663035366333393945646539303431aa307835366630464146333336613446446162313546323242333545426536394333306245363336434238aa307834324131394139304335333630334639313937456444334145346445393935393135373535633936aa307862644543623444446542644536626534656231443833443539303430463135353936394341613234aa307830466544306641383332454542303439614631363638663465384635433930433931333666383539aa307862616336463637333236354162413366373046443736463835334533634234316662363835613144aa307833313546316541413342333830364264336132463336343135303465336362333731416233663736aa307861466531323834366436356533443236393632393564303536376444373335433430443632313746aa307834334662366132393337316544433642643166423466443565643565454541304443413939433146aa307839383939313162346131343845386334303633423042393337326543323332326241334232364136aa307837326533343937376632413166643632363762393131614530663437433436653763363936443163aa307830326446393536384362384636413539453238463231633746373144613733324432323337344634aa307837313764373331323534423964333631333433426534663539356344463037393844386264343932aa307832434130323836314441303536353933663731656663366430343464436532333461363663346533aa307838354243386231383530643033353845374136626235663836313963624563624235354531653935aa307835336234373535443033343230643931643941373444314532393537303741354135653133303864aa307838646462334543314635383532373038336336363164303342363837396432383231353632463037aa307862614439386331344564333832313763426632324466326539633835333532396637383530453738aa307865413862393736306662424633663266373264323344463638383565356233313236653334643036aa307832336337366664394335443844336630434330323645623235463236393833334434344264416232aa307843654334623938333342464334423136343438643744313134666437326242306233373339373430aa307831616463443965393946333045426146393265383765443437343439423543663632323164313144aa307865396430466137456536336463313632326143393336443031433843383037373442663941373765aa307866633732634242383466643034653934353434384438353338444432663145443265394238303839aa307838633633654242366537366241333162333438344530383341386139373638356663384231616533aa307834423963623431394132443634334231323436414235383537633938363731323242354230444666aa307833454445373045353737374231303061326264664243323565343934433341396435343541356131aa307844393666366432633065383735416130323930366261383246356164356437384164353843613834aa307833343331313631333334343361433366336430343130373964636230334361366635353631313245aa307834334243323531323544346333653535643238624661626630434136336331456338426238393446aa307831624132653866616639326531393136344132646342384331633735316362353030334642454442aa307842663943636435623634383838653036336337393930394665433935663634393432463130664163aa307835354534383138373645623236643731323232363832433031334166363435456139616638463866aa307863353238423463376645313366323235353133364364343139333533353434303035616330306462aa307866413837613161303933443145386139363832346237623265634431303743333632453535663430aa307842393030636266643139393941463733386238623932664331324342313042373041443566423266aa307831373733354164303837363437414233384533656137453944326337613131613035336432393141aa307831453462354233613841303734623166336435333336373732623232343935316237364239413533aa307837424141386339394566366330374566356346373641646443433031373831373945304632613634aa307832393864443637346539364444344636356363353632443339363861353442366262663339613339aa307866373839623339363036323642663566333830306130346631306643456265333361363831334136aa307830393835334335323336363933663531454233453765413535433561623563313634356130443063aa307837654135334339324541316134463164653036643138344465443131334333316538374463313031aa307832663564653638333735633239373236323345374261453439366331646364306441463443643264aa307834623533446131383061374534453730324244316337613332376332393434363430326130663937aa307832653336464541303931363030446437353636393339346234343734443862364137644336376641aa307833343033373042343045333434363438633139333642333237353536396138343432664443396245aa307837353737336631363736374139374434414431444566313831343536316135313235614133396245aa307843306244643346413844313236314433373439383244623944353762466334386242303146334262aa307841373335346162434230613336373331396338353963323661383336343961373035314338653636aa307832354230353142333944364444344446643137463735433838303139323835376238363138374235aa307843443333393336373437423532433132336242323731343441356339663635463861323744464663aa307830306232353566653131303944656443376333433137626131323032333534344334373933426146aa307838376131386361413364423734633765363439344563384137433962323839303166304162463932aa307835376233353343376231353638303646303943356545313562323935333939346541383932353441aa307839383030633136304630446137363336393730353142313736433933453236314564383636343630aa307831333842344344353746314364343739363031434341446641313745633065314331624633306335aa307862656438616331426331613961363431383038394564456146386239394363343144413735663934aa307838303430653266393238366634453432624464326344336562333945323261633131364262393533aa307837363437323045443565626435633046344330394431456334633661623132643738423138313339aa307834343630323630383030633842433239353843464344323635423031663038623630384337373564aa307844633635306632393046643241353343303739313636304233393431396642663562663445363735aa307865464535323633393843363839393961383262373932453839303165323334383338373864306538aa307832396641653534343735373533303738444263353530326563323530463346636532364337383733aa307837313633653137376439344130613763383432313332353362356244626636343139653766424139aa307830374438393230663466444133433837363131326462613730353634373635444565434541643045aa307831443333633244383633333344644646323461373666623363333839324434623046366434663842aa307833323042373335623332363836386131373837633132373138384165323335316438333739386465aa307861383444363146314242333638393338633830363335446245613936456145326333353562423532 --------------------------------------------------------------------------------