├── .clusterfuzzlite ├── Dockerfile ├── build.sh └── project.yaml ├── .github ├── dependabot.yml └── workflows │ ├── cflite_build.yml │ ├── cflite_pr.yml │ ├── codeql-analysis.yml │ ├── go_test.yml │ ├── golangci-lint.yml │ ├── govulncheck.yml │ └── scorecard.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cmd └── proofgen │ └── main.go ├── compact ├── node_fuzz_test.go ├── nodes.go ├── nodes_test.go ├── range.go ├── range_internal_test.go ├── range_test.go └── testdata │ └── fuzz │ └── FuzzRangeNodes │ ├── 03167a8f4de6b14a237e918bd96fea8dd45076bd458c8bcf96b09a61df6846ae │ ├── 0e0f7b76aefbacfec1b9d14fb1cf6ddfccbe6772ff60f2ba617fb1b7884df19f │ ├── 13cdadf28017adae88657be24090473325f4db10ab8fa3f22541b7cb10b683b1 │ ├── 13dddce380497392cc2a016ad6b881063b6bd99abfd4ffca8ce2781524e43a44 │ ├── 2d382dbaf5238a4066cd46a22d5e3d37deeefd23087f7b46aa0352efe2de13a1 │ ├── 2e26d182e06aac76b42a5fa546fa288652501b7a8384489e7d2ebbe43290b8b2 │ ├── 4502438e2e714833404e371f4ccdffdb472d8ece05d9cd70685d4911671d73a5 │ ├── 4b5b8deb6dc0a2aabf73efc8f107f9241a5504cc069224dc666bc2d5ab576f58 │ ├── 629ea6f8a4d39bad9b1d45832a75a9b5d81814269fa9ca20e9f9c77e83d78548 │ ├── 6365559667f7be67023423270b88fa3cbeb746c81f784f15382a2f66f3a6a151 │ ├── 74bfce2710370711bdd53cb9450cb8b84b542b6c0a04b6a02e0cbbdb49e6888e │ ├── 8196e8e5d77a138305dac92e1dae4e651c5da36949f7f9164e26d07d7372bc28 │ ├── 88904c8350b849ec832a6b9b5a891c5cbb856afa6d3b9c165ec0c8a011292734 │ ├── b05a02e2727c643c61be18ef3d80944cceec0e779ee8377dfea4e4e47c2d9d48 │ ├── b73cd67a4c77db1aaab5159c2eee5dff74a80e26067a27cfb95832eb0efbf060 │ ├── d92b8ab319960fa968031e6f81ab556df172f913181333c528e98c6ad436578a │ ├── e32129a7a793ab12790555c46d5a0fb4accddacf1cab7e9bbabca02d71cbbdb7 │ ├── e7b6d88b27442e32ccc06572bb36c824c03427ec1d5dd721b485612f3b55b1ec │ └── e8813e9a7cebcf576a05697f95cff9b1f2dfaecf3969ccccec84582165d2747d ├── docs ├── README.md ├── compact_ranges.md ├── data_model.md └── images │ ├── compact_ranges.graffle │ ├── compact_ranges.svg │ ├── compact_ranges_merge.svg │ ├── consistency_proof.svg │ ├── data_model.svg │ ├── distributed_tree.svg │ ├── inclusion_proof.svg │ ├── inclusion_proof_range.svg │ └── witness.svg ├── go.mod ├── go.sum ├── hasher.go ├── proof ├── proof.go ├── proof_test.go ├── verify.go └── verify_test.go ├── rfc6962 ├── rfc6962.go └── rfc6962_test.go ├── scripts ├── check_license.sh └── presubmit.sh ├── testdata ├── consistency │ ├── 0 │ │ └── happy-path.json │ ├── 1 │ │ ├── empty-proof.json │ │ ├── happy-path.json │ │ ├── modified-proof@0-bit-@4.json │ │ ├── modified-proof@1-bit-@4.json │ │ ├── modified-proof@2-bit-@4.json │ │ ├── preceding-garbage.json │ │ ├── preceding-proof-@0.json │ │ ├── preceding-root1.json │ │ ├── preceding-root2.json │ │ ├── size1-XOR-@2.json │ │ ├── size1-plus-@1.json │ │ ├── size1-sub-@1.json │ │ ├── size2-div-@2.json │ │ ├── size2-mul-@2.json │ │ ├── swapped-roots.json │ │ ├── trailing-garbage.json │ │ ├── trailing-root1.json │ │ ├── trailing-root2.json │ │ ├── truncated-proof.json │ │ ├── wrong-root1.json │ │ └── wrong-root2.json │ ├── 2 │ │ ├── empty-proof.json │ │ ├── happy-path.json │ │ ├── modified-proof@0-bit-@4.json │ │ ├── modified-proof@1-bit-@4.json │ │ ├── modified-proof@2-bit-@4.json │ │ ├── preceding-garbage.json │ │ ├── preceding-proof-@0.json │ │ ├── preceding-root1.json │ │ ├── preceding-root2.json │ │ ├── size1-XOR-@2.json │ │ ├── size1-plus-@1.json │ │ ├── size1-sub-@1.json │ │ ├── size2-div-@2.json │ │ ├── size2-mul-@2.json │ │ ├── swapped-roots.json │ │ ├── trailing-garbage.json │ │ ├── trailing-root1.json │ │ ├── trailing-root2.json │ │ ├── truncated-proof.json │ │ ├── wrong-root1.json │ │ └── wrong-root2.json │ ├── 3 │ │ ├── empty-proof.json │ │ ├── happy-path.json │ │ ├── modified-proof@0-bit-@4.json │ │ ├── modified-proof@1-bit-@4.json │ │ ├── preceding-garbage.json │ │ ├── preceding-proof-@0.json │ │ ├── preceding-root1.json │ │ ├── preceding-root2.json │ │ ├── size1-XOR-@2.json │ │ ├── size1-plus-@1.json │ │ ├── size1-sub-@1.json │ │ ├── size2-div-@2.json │ │ ├── size2-mul-@2.json │ │ ├── swapped-roots.json │ │ ├── trailing-garbage.json │ │ ├── trailing-root1.json │ │ ├── trailing-root2.json │ │ ├── truncated-proof.json │ │ ├── wrong-root1.json │ │ └── wrong-root2.json │ ├── 4 │ │ ├── empty-proof.json │ │ ├── happy-path.json │ │ ├── modified-proof@0-bit-@4.json │ │ ├── modified-proof@1-bit-@4.json │ │ ├── modified-proof@2-bit-@4.json │ │ ├── preceding-garbage.json │ │ ├── preceding-proof-@0.json │ │ ├── preceding-root1.json │ │ ├── preceding-root2.json │ │ ├── size1-XOR-@2.json │ │ ├── size1-plus-@1.json │ │ ├── size1-sub-@1.json │ │ ├── size2-div-@2.json │ │ ├── size2-mul-@2.json │ │ ├── swapped-roots.json │ │ ├── trailing-garbage.json │ │ ├── trailing-root1.json │ │ ├── trailing-root2.json │ │ ├── truncated-proof.json │ │ ├── wrong-root1.json │ │ └── wrong-root2.json │ └── additional │ │ ├── consistency-check-on-empty-tree-(size1-is-zero)-is-useless.json │ │ ├── roots-do-not-match-and-sizes-are-zero.json │ │ ├── roots-do-not-not-match-and-sizes-are-one.json │ │ ├── size1-is-greater-than-size2-again.json │ │ ├── size1-is-greater-than-size2.json │ │ ├── size1-is-zero-and-does-not-equal-size2.json │ │ ├── sizes-are-equal-(one)-but-roots-are-not.json │ │ ├── sizes-are-equal-(zero)-but-roots-are-not.json │ │ ├── sizes-are-equal-and-proof-is-not-empty-where-both-sizes-are-one.json │ │ ├── sizes-are-equal-and-proof-is-not-empty-where-both-sizes-are-zero.json │ │ ├── sizes-do-not-watch-and-proof-is-empty.json │ │ ├── sizes-match-but-proof-is-not-empty-and-sizes-are-one.json │ │ └── sizes-match-but-proof-is-not-empty-and-sizes-are-zero.json └── inclusion │ ├── 0 │ ├── empty-root.json │ ├── happy-path.json │ ├── leafIdx-XOR-@2.json │ ├── leafIdx-plus-@1.json │ ├── leafIdx-sub-@1.json │ ├── preceding-garbage.json │ ├── preceding-root.json │ ├── random-root.json │ ├── trailing-garbage.json │ ├── trailing-root.json │ ├── treeSize-div-@2.json │ ├── treeSize-mul-@2.json │ └── wrong-leaf.json │ ├── 1 │ ├── empty-root.json │ ├── happy-path.json │ ├── inserted-component.json │ ├── leafIdx-XOR-@2.json │ ├── leafIdx-plus-@1.json │ ├── leafIdx-sub-@1.json │ ├── modified-proof[0]-bit-@3.json │ ├── modified-proof[1]-bit-@3.json │ ├── modified-proof[2]-bit-@3.json │ ├── preceding-garbage.json │ ├── preceding-root.json │ ├── random-root.json │ ├── removed-component.json │ ├── trailing-garbage.json │ ├── trailing-root.json │ ├── treeSize-div-@2.json │ ├── treeSize-mul-@2.json │ └── wrong-leaf.json │ ├── 2 │ ├── empty-root.json │ ├── happy-path.json │ ├── inserted-component.json │ ├── leafIdx-XOR-@2.json │ ├── leafIdx-plus-@1.json │ ├── leafIdx-sub-@1.json │ ├── modified-proof[0]-bit-@3.json │ ├── modified-proof[1]-bit-@3.json │ ├── modified-proof[2]-bit-@3.json │ ├── preceding-garbage.json │ ├── preceding-root.json │ ├── random-root.json │ ├── removed-component.json │ ├── trailing-garbage.json │ ├── trailing-root.json │ ├── treeSize-div-@2.json │ ├── treeSize-mul-@2.json │ └── wrong-leaf.json │ ├── 3 │ ├── empty-root.json │ ├── happy-path.json │ ├── leafIdx-XOR-@2.json │ ├── leafIdx-plus-@1.json │ ├── leafIdx-sub-@1.json │ ├── modified-proof[0]-bit-@3.json │ ├── preceding-garbage.json │ ├── preceding-root.json │ ├── random-root.json │ ├── removed-component.json │ ├── trailing-garbage.json │ ├── trailing-root.json │ ├── treeSize-div-@2.json │ ├── treeSize-mul-@2.json │ └── wrong-leaf.json │ ├── 4 │ ├── empty-root.json │ ├── happy-path.json │ ├── inserted-component.json │ ├── leafIdx-XOR-@2.json │ ├── leafIdx-plus-@1.json │ ├── leafIdx-sub-@1.json │ ├── modified-proof[0]-bit-@3.json │ ├── modified-proof[1]-bit-@3.json │ ├── modified-proof[2]-bit-@3.json │ ├── preceding-garbage.json │ ├── preceding-root.json │ ├── random-root.json │ ├── removed-component.json │ ├── trailing-garbage.json │ ├── trailing-root.json │ ├── treeSize-div-@2.json │ ├── treeSize-mul-@2.json │ └── wrong-leaf.json │ ├── additional │ ├── 0 │ │ ├── empty-root-and-random-leaf.json │ │ ├── empty-root.json │ │ └── random-leaf.json │ ├── 1 │ │ ├── empty-root-and-random-leaf.json │ │ ├── empty-root.json │ │ └── random-leaf.json │ ├── 2 │ │ ├── empty-root-and-random-leaf.json │ │ ├── empty-root.json │ │ └── random-leaf.json │ └── 3 │ │ ├── empty-root-and-random-leaf.json │ │ ├── empty-root.json │ │ └── random-leaf.json │ └── single-entry │ ├── empty-leaf.json │ ├── empty-root-and-leaf.json │ ├── empty-root.json │ └── matching-root-and-leaf.json └── testonly ├── constants.go ├── reference_test.go ├── tree.go ├── tree_fuzz_test.go └── tree_test.go /.clusterfuzzlite/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://google.github.io/clusterfuzzlite/build-integration/#dockerfile 2 | FROM gcr.io/oss-fuzz-base/base-builder-go@sha256:37d560af1d31ae8d339902acaedb8eb6fdf3f04469706f39e3bbea44fa3fdeec 3 | COPY . $SRC/merkle 4 | WORKDIR $SRC/merkle 5 | COPY .clusterfuzzlite/build.sh $SRC/ 6 | -------------------------------------------------------------------------------- /.clusterfuzzlite/build.sh: -------------------------------------------------------------------------------- 1 | # https://google.github.io/oss-fuzz/getting-started/new-project-guide/go-lang/#buildsh 2 | # undocumented dependency 3 | go install github.com/AdamKorcz/go-118-fuzz-build@c5484365413eb6c532d2dbd0d16b553988ce6852 4 | go get github.com/AdamKorcz/go-118-fuzz-build/testing@c5484365413eb6c532d2dbd0d16b553988ce6852 5 | 6 | # workaround https://github.com/AdamKorcz/go-118-fuzz-build/issues/2 7 | mv testonly/constants.go testonly/constants_fuzz.go 8 | mv testonly/reference_test.go testonly/reference_test_fuzz.go 9 | mv testonly/tree_test.go testonly/tree_test_fuzz.go 10 | mv testonly/tree.go testonly/tree_fuzz.go 11 | 12 | # necessary to list each fuzz test explicitly 13 | compile_native_go_fuzzer github.com/transparency-dev/merkle/compact FuzzRangeNodes FuzzRangeNodes 14 | compile_native_go_fuzzer github.com/transparency-dev/merkle/testonly FuzzConsistencyProofAndVerify FuzzConsistencyProofAndVerify 15 | compile_native_go_fuzzer github.com/transparency-dev/merkle/testonly FuzzInclusionProofAndVerify FuzzInclusionProofAndVerify 16 | compile_native_go_fuzzer github.com/transparency-dev/merkle/testonly FuzzHashAtAgainstReferenceImplementation FuzzHashAtAgainstReferenceImplementation 17 | compile_native_go_fuzzer github.com/transparency-dev/merkle/testonly FuzzInclusionProofAgainstReferenceImplementation FuzzInclusionProofAgainstReferenceImplementation 18 | compile_native_go_fuzzer github.com/transparency-dev/merkle/testonly FuzzConsistencyProofAgainstReferenceImplementation FuzzConsistencyProofAgainstReferenceImplementation 19 | -------------------------------------------------------------------------------- /.clusterfuzzlite/project.yaml: -------------------------------------------------------------------------------- 1 | # https://google.github.io/clusterfuzzlite//build-integration/go-lang/ 2 | language: go 3 | fuzzing_engines: 4 | - libfuzzer 5 | sanitizers: 6 | - address 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | groups: 6 | all-go-deps: 7 | patterns: 8 | - "*" 9 | schedule: 10 | interval: weekly 11 | commit-message: 12 | prefix: ":seedling:" 13 | include: "scope" 14 | 15 | - package-ecosystem: github-actions 16 | directory: / 17 | groups: 18 | all-gha-deps: 19 | patterns: 20 | - "*" 21 | schedule: 22 | interval: weekly 23 | commit-message: 24 | prefix: ":seedling:" 25 | include: "scope" 26 | -------------------------------------------------------------------------------- /.github/workflows/cflite_build.yml: -------------------------------------------------------------------------------- 1 | name: ClusterFuzzLite continuous builds 2 | on: 3 | push: 4 | branches: 5 | - main # Use your actual default branch here. 6 | permissions: read-all 7 | jobs: 8 | Build: 9 | runs-on: ubuntu-latest 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | sanitizer: 17 | - address 18 | # Override this with the sanitizers you want. 19 | # - undefined 20 | # - memory 21 | steps: 22 | - name: Build Fuzzers (${{ matrix.sanitizer }}) 23 | id: build 24 | uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 25 | with: 26 | language: go 27 | sanitizer: ${{ matrix.sanitizer }} 28 | upload-build: true 29 | -------------------------------------------------------------------------------- /.github/workflows/cflite_pr.yml: -------------------------------------------------------------------------------- 1 | name: ClusterFuzzLite PR fuzzing 2 | on: 3 | pull_request: 4 | paths: 5 | - '**' 6 | permissions: read-all 7 | jobs: 8 | PR: 9 | runs-on: ubuntu-latest 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | sanitizer: 17 | - address 18 | # Override this with the sanitizers you want. 19 | # - undefined 20 | # - memory 21 | steps: 22 | - name: Build Fuzzers (${{ matrix.sanitizer }}) 23 | id: build 24 | uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 25 | with: 26 | language: go 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | sanitizer: ${{ matrix.sanitizer }} 29 | # Optional but recommended: used to only run fuzzers that are affected 30 | # by the PR. 31 | # See later section on "Git repo for storage". 32 | # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git 33 | # storage-repo-branch: main # Optional. Defaults to "main" 34 | # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". 35 | - name: Run Fuzzers (${{ matrix.sanitizer }}) 36 | id: run 37 | uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 38 | with: 39 | github-token: ${{ secrets.GITHUB_TOKEN }} 40 | fuzz-seconds: 600 41 | mode: 'code-change' 42 | sanitizer: ${{ matrix.sanitizer }} 43 | # Optional but recommended: used to download the corpus produced by 44 | # batch fuzzing. 45 | # See later section on "Git repo for storage". 46 | # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git 47 | # storage-repo-branch: main # Optional. Defaults to "main" 48 | # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". 49 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '37 3 * * 1' 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: [ 'go' ] 39 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 40 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | #- run: | 69 | # make bootstrap 70 | # make release 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 74 | -------------------------------------------------------------------------------- /.github/workflows/go_test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test Go 3 | permissions: 4 | contents: read 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | go-version: [1.22.x, 1.23.x] 10 | os: [ubuntu-latest, macos-latest] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 14 | with: 15 | go-version: ${{ matrix.go-version }} 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - run: go test -v -race -covermode=atomic -coverprofile=coverage.out ./... 18 | - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 19 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | pull_request: 5 | permissions: 6 | contents: read 7 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 8 | # pull-requests: read 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 16 | with: 17 | go-version-file: go.mod 18 | - name: golangci-lint 19 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 20 | with: 21 | version: v2.1.0 22 | args: --timeout=8m 23 | -------------------------------------------------------------------------------- /.github/workflows/govulncheck.yml: -------------------------------------------------------------------------------- 1 | name: govulncheck 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | govulncheck_job: 16 | runs-on: ubuntu-latest 17 | name: Run govulncheck 18 | steps: 19 | - id: govulncheck 20 | uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 21 | with: 22 | go-version-file: go.mod 23 | go-package: ./... 24 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '15 21 * * 3' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecards on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 48 | # TODO(mhutchinson): Instead of this, we should use rulesets and then permissions are not needed: 49 | # https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets 50 | repo_token: ${{ secrets.TD_SCORECARD_READ_TOKEN }} 51 | 52 | # Public repositories: 53 | # - Publish results to OpenSSF REST API for easy access by consumers 54 | # - Allows the repository to include the Scorecard badge. 55 | # - See https://github.com/ossf/scorecard-action#publishing-results. 56 | # For private repositories: 57 | # - `publish_results` will always be set to `false`, regardless 58 | # of the value entered here. 59 | publish_results: true 60 | 61 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 62 | # format to the repository Actions tab. 63 | - name: "Upload artifact" 64 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 65 | with: 66 | name: SARIF file 67 | path: results.sarif 68 | retention-days: 5 69 | 70 | # Upload the results to GitHub's code scanning dashboard. 71 | - name: "Upload to code-scanning" 72 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 73 | with: 74 | sarif_file: results.sarif 75 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # MERKLE changelog 2 | 3 | ## HEAD 4 | 5 | * Breaking change: consistency proofs from `size1 = 0` to `size2 != 0` now always fail 6 | * Previously, this could succeed if the empty proof was provided 7 | * Bump Go version from 1.19 to 1.20 8 | 9 | ## v0.0.2 10 | 11 | * Fuzzing support 12 | * Dependency updates, notably to go1.19 13 | 14 | ## v0.0.1 15 | 16 | Initial release 17 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @transparency-dev/core-team 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute # 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | a just a few small guidelines you need to follow. 5 | 6 | 7 | ## Contributor License Agreement ## 8 | 9 | Contributions to any Google project must be accompanied by a Contributor 10 | License Agreement. This is not a copyright **assignment**, it simply gives 11 | Google permission to use and redistribute your contributions as part of the 12 | project. 13 | 14 | * If you are an individual writing original source code and you're sure you 15 | own the intellectual property, then you'll need to sign an [individual 16 | CLA][]. 17 | 18 | * If you work for a company that wants to allow you to contribute your work, 19 | then you'll need to sign a [corporate CLA][]. 20 | 21 | You generally only need to submit a CLA once, so if you've already submitted 22 | one (even if it was for a different project), you probably don't need to do it 23 | again. 24 | 25 | [individual CLA]: https://developers.google.com/open-source/cla/individual 26 | [corporate CLA]: https://developers.google.com/open-source/cla/corporate 27 | 28 | Once your CLA is submitted (or if you already submitted one for 29 | another Google project), make a commit adding yourself to the 30 | [AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part 31 | of your first [pull request][]. 32 | 33 | [AUTHORS]: AUTHORS 34 | [CONTRIBUTORS]: CONTRIBUTORS 35 | 36 | 37 | ## Submitting a patch ## 38 | 39 | 1. It's generally best to start by opening a new issue describing the bug or 40 | feature you're intending to fix. Even if you think it's relatively minor, 41 | it's helpful to know what people are working on. Mention in the initial 42 | issue that you are planning to work on that bug or feature so that it can 43 | be assigned to you. 44 | 45 | 1. Follow the normal process of [forking][] the project, and setup a new 46 | branch to work in. It's important that each group of changes be done in 47 | separate branches in order to ensure that a pull request only includes the 48 | commits related to that bug or feature. 49 | 50 | 1. Do your best to have [well-formed commit messages][] for each change. 51 | This provides consistency throughout the project, and ensures that commit 52 | messages are able to be formatted properly by various git tools. 53 | 54 | 1. Finally, push the commits to your fork and submit a [pull request][]. 55 | 56 | [forking]: https://help.github.com/articles/fork-a-repo 57 | [well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 58 | [pull request]: https://help.github.com/articles/creating-a-pull-request 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Merkle 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/transparency-dev/merkle.svg)](https://pkg.go.dev/github.com/transparency-dev/merkle) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/transparency-dev/merkle)](https://goreportcard.com/report/github.com/transparency-dev/merkle) 5 | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/transparency-dev/merkle/badge)](https://securityscorecards.dev/viewer/?uri=github.com/transparency-dev/merkle) 6 | [![codecov](https://codecov.io/gh/transparency-dev/merkle/branch/main/graph/badge.svg?token=BBCRAMOBY2)](https://codecov.io/gh/transparency-dev/merkle) 7 | [![Slack Status](https://img.shields.io/badge/Slack-Chat-blue.svg)](https://transparency-dev.slack.com/) 8 | 9 | ## Overview 10 | 11 | This repository contains Go code to help create and manipulate Merkle trees, as 12 | well as constructing and verifying various types of proof. 13 | 14 | This is the data structure which is used by projects such as 15 | [Trillian](https://github.com/google/trillian) to provide 16 | [verifiable logs](https://transparency.dev/verifiable-data-structures/#verifiable-log). 17 | 18 | 19 | ## Support 20 | * Mailing list: https://groups.google.com/forum/#!forum/trillian-transparency 21 | - Slack: https://transparency-dev.slack.com/ ([invitation](https://join.slack.com/t/transparency-dev/shared_invite/zt-27pkqo21d-okUFhur7YZ0rFoJVIOPznQ)) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /compact/node_fuzz_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | 3 | package compact 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | // Test that RangeNodes returns a slice of nodes with contiguous coverage. 10 | // https://github.com/transparency-dev/merkle/blob/main/docs/compact_ranges.md#definition 11 | func FuzzRangeNodes(f *testing.F) { 12 | for begin := 0; begin <= 10; begin++ { 13 | for end := begin; end <= 20; end++ { 14 | f.Add(uint64(end), uint64(end)) 15 | } 16 | } 17 | f.Fuzz(func(t *testing.T, begin, end uint64) { 18 | if begin > end { 19 | return 20 | } 21 | t.Logf("begin=%d, end=%d", begin, end) 22 | nodes := RangeNodes(begin, end, nil) 23 | t.Logf("nodes=%v", nodes) 24 | 25 | // Nodes should be contiguous covering begin to end 26 | previousEnd := begin 27 | for _, node := range nodes { 28 | b, e := node.Coverage() 29 | if b != previousEnd { 30 | t.Errorf("got=%d, want=%d", b, previousEnd) 31 | } 32 | previousEnd = e 33 | } 34 | if previousEnd != end { 35 | t.Errorf("got=%d, want=%d", previousEnd, end) 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /compact/nodes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package compact 16 | 17 | import "math/bits" 18 | 19 | // NodeID identifies a node of a Merkle tree. 20 | // 21 | // The ID consists of a level and index within this level. Levels are numbered 22 | // from 0, which corresponds to the tree leaves. Within each level, nodes are 23 | // numbered with consecutive indices starting from 0. 24 | // 25 | // L4: ┌───────0───────┐ ... 26 | // L3: ┌───0───┐ ┌───1───┐ ┌─── ... 27 | // L2: ┌─0─┐ ┌─1─┐ ┌─2─┐ ┌─3─┐ ┌─4─┐ ... 28 | // L1: ┌0┐ ┌1┐ ┌2┐ ┌3┐ ┌4┐ ┌5┐ ┌6┐ ┌7┐ ┌8┐ ┌9┐ ... 29 | // L0: 0 1 2 3 4 5 6 7 8 9 ... ... ... ... ... ... 30 | // 31 | // When the tree is not perfect, the nodes that would complement it to perfect 32 | // are called ephemeral. Algorithms that operate with ephemeral nodes still map 33 | // them to the same address space. 34 | type NodeID struct { 35 | Level uint 36 | Index uint64 37 | } 38 | 39 | // NewNodeID returns a NodeID with the passed in node coordinates. 40 | func NewNodeID(level uint, index uint64) NodeID { 41 | return NodeID{Level: level, Index: index} 42 | } 43 | 44 | // Parent returns the ID of the parent node. 45 | func (id NodeID) Parent() NodeID { 46 | return NewNodeID(id.Level+1, id.Index>>1) 47 | } 48 | 49 | // Sibling returns the ID of the sibling node. 50 | func (id NodeID) Sibling() NodeID { 51 | return NewNodeID(id.Level, id.Index^1) 52 | } 53 | 54 | // Coverage returns the [begin, end) range of leaves covered by the node. 55 | func (id NodeID) Coverage() (uint64, uint64) { 56 | return id.Index << id.Level, (id.Index + 1) << id.Level 57 | } 58 | 59 | // RangeNodes appends the IDs of the nodes that comprise the [begin, end) 60 | // compact range to the given slice, and returns the new slice. The caller may 61 | // pre-allocate space with the help of the RangeSize function. 62 | func RangeNodes(begin, end uint64, ids []NodeID) []NodeID { 63 | left, right := Decompose(begin, end) 64 | 65 | pos := begin 66 | // Iterate over perfect subtrees along the left border of the range, ordered 67 | // from lower to upper levels. 68 | for bit := uint64(0); left != 0; pos, left = pos+bit, left^bit { 69 | level := uint(bits.TrailingZeros64(left)) 70 | bit = uint64(1) << level 71 | ids = append(ids, NewNodeID(level, pos>>level)) 72 | } 73 | 74 | // Iterate over perfect subtrees along the right border of the range, ordered 75 | // from upper to lower levels. 76 | for bit := uint64(0); right != 0; pos, right = pos+bit, right^bit { 77 | level := uint(bits.Len64(right)) - 1 78 | bit = uint64(1) << level 79 | ids = append(ids, NewNodeID(level, pos>>level)) 80 | } 81 | 82 | return ids 83 | } 84 | 85 | // RangeSize returns the number of nodes in the [begin, end) compact range. 86 | func RangeSize(begin, end uint64) int { 87 | left, right := Decompose(begin, end) 88 | return bits.OnesCount64(left) + bits.OnesCount64(right) 89 | } 90 | -------------------------------------------------------------------------------- /compact/nodes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package compact 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | 21 | "github.com/google/go-cmp/cmp" 22 | ) 23 | 24 | func TestRangeNodesAndSize(t *testing.T) { 25 | n := func(level uint, index uint64) NodeID { 26 | return NewNodeID(level, index) 27 | } 28 | for _, tc := range []struct { 29 | begin uint64 30 | end uint64 31 | want []NodeID 32 | }{ 33 | // Empty ranges. 34 | {end: 0, want: nil}, 35 | {begin: 10, end: 10, want: nil}, 36 | {begin: 1024, end: 1024, want: nil}, 37 | // One entry. 38 | {begin: 10, end: 11, want: []NodeID{n(0, 10)}}, 39 | {begin: 1024, end: 1025, want: []NodeID{n(0, 1024)}}, 40 | {begin: 1025, end: 1026, want: []NodeID{n(0, 1025)}}, 41 | // Two entries. 42 | {begin: 10, end: 12, want: []NodeID{n(1, 5)}}, 43 | {begin: 1024, end: 1026, want: []NodeID{n(1, 512)}}, 44 | {begin: 1025, end: 1027, want: []NodeID{n(0, 1025), n(0, 1026)}}, 45 | // Only right border. 46 | {end: 1, want: []NodeID{n(0, 0)}}, 47 | {end: 2, want: []NodeID{n(1, 0)}}, 48 | {end: 3, want: []NodeID{n(1, 0), n(0, 2)}}, 49 | {end: 4, want: []NodeID{n(2, 0)}}, 50 | {end: 5, want: []NodeID{n(2, 0), n(0, 4)}}, 51 | {end: 15, want: []NodeID{n(3, 0), n(2, 2), n(1, 6), n(0, 14)}}, 52 | {end: 100, want: []NodeID{n(6, 0), n(5, 2), n(2, 24)}}, 53 | {end: 513, want: []NodeID{n(9, 0), n(0, 512)}}, 54 | {end: uint64(1) << 63, want: []NodeID{n(63, 0)}}, 55 | {end: (uint64(1) << 63) + (uint64(1) << 57), want: []NodeID{n(63, 0), n(57, 64)}}, 56 | // Only left border. 57 | {begin: 0, end: 16, want: []NodeID{n(4, 0)}}, 58 | {begin: 1, end: 16, want: []NodeID{n(0, 1), n(1, 1), n(2, 1), n(3, 1)}}, 59 | {begin: 2, end: 16, want: []NodeID{n(1, 1), n(2, 1), n(3, 1)}}, 60 | {begin: 3, end: 16, want: []NodeID{n(0, 3), n(2, 1), n(3, 1)}}, 61 | {begin: 4, end: 16, want: []NodeID{n(2, 1), n(3, 1)}}, 62 | {begin: 6, end: 16, want: []NodeID{n(1, 3), n(3, 1)}}, 63 | {begin: 8, end: 16, want: []NodeID{n(3, 1)}}, 64 | {begin: 11, end: 16, want: []NodeID{n(0, 11), n(2, 3)}}, 65 | // Two-sided. 66 | {begin: 1, end: 31, want: []NodeID{n(0, 1), n(1, 1), n(2, 1), n(3, 1), n(3, 2), n(2, 6), n(1, 14), n(0, 30)}}, 67 | {begin: 1, end: 17, want: []NodeID{n(0, 1), n(1, 1), n(2, 1), n(3, 1), n(0, 16)}}, 68 | } { 69 | t.Run(fmt.Sprintf("range:%d:%d", tc.begin, tc.end), func(t *testing.T) { 70 | got := RangeNodes(tc.begin, tc.end, nil) 71 | if diff := cmp.Diff(got, tc.want); diff != "" { 72 | t.Fatalf("RangeNodes: diff(-want +got):\n%s", diff) 73 | } 74 | if got, want := RangeSize(tc.begin, tc.end), len(tc.want); got != want { 75 | t.Errorf("RangeSize: got %d, want %d", got, want) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestRangeNodesAppend(t *testing.T) { 82 | prefix := []NodeID{NewNodeID(0, 0), NewNodeID(10, 0), NewNodeID(11, 5)} 83 | nodes := RangeNodes(123, 456, prefix) 84 | 85 | if got, min := len(nodes), len(prefix); got < min { 86 | t.Fatalf("RangeNodes returned %d IDs, want >= %d", got, min) 87 | } 88 | got := nodes[:len(prefix)] 89 | if diff := cmp.Diff(got, prefix); diff != "" { 90 | t.Fatalf("RangeNodes: diff(-prefix +got):\n%s", diff) 91 | } 92 | } 93 | 94 | func TestGenRangeNodes(t *testing.T) { 95 | const size = uint64(512) 96 | for begin := uint64(0); begin <= size; begin++ { 97 | for end := begin; end <= size; end++ { 98 | got := RangeNodes(begin, end, nil) 99 | want := refRangeNodes(NewNodeID(63, 0), begin, end) 100 | if diff := cmp.Diff(got, want); diff != "" { 101 | t.Fatalf("RangeNodes(%d, %d): diff(-want +got):\n%s", begin, end, diff) 102 | } 103 | } 104 | } 105 | } 106 | 107 | // refRangeNodes returns node IDs that comprise the [begin, end) compact range. 108 | // This is a reference implementation for cross-checking. 109 | func refRangeNodes(root NodeID, begin, end uint64) []NodeID { 110 | b, e := root.Coverage() 111 | if end <= b || begin >= e { 112 | return nil 113 | } 114 | if b >= begin && e <= end { 115 | return []NodeID{root} 116 | } 117 | return append( 118 | refRangeNodes(NewNodeID(root.Level-1, root.Index*2), begin, end), 119 | refRangeNodes(NewNodeID(root.Level-1, root.Index*2+1), begin, end)...) 120 | } 121 | -------------------------------------------------------------------------------- /compact/range.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package compact provides compact Merkle tree data structures. 16 | package compact 17 | 18 | import ( 19 | "bytes" 20 | "errors" 21 | "fmt" 22 | "math/bits" 23 | ) 24 | 25 | // HashFn computes an internal node's hash using the hashes of its child nodes. 26 | type HashFn func(left, right []byte) []byte 27 | 28 | // VisitFn visits the node with the specified ID and hash. 29 | type VisitFn func(id NodeID, hash []byte) 30 | 31 | // RangeFactory allows creating compact ranges with the specified hash 32 | // function, which must not be nil, and must not be changed. 33 | type RangeFactory struct { 34 | Hash HashFn 35 | } 36 | 37 | // NewRange creates a Range for [begin, end) with the given set of hashes. The 38 | // hashes correspond to the roots of the minimal set of perfect sub-trees 39 | // covering the [begin, end) leaves range, ordered left to right. 40 | func (f *RangeFactory) NewRange(begin, end uint64, hashes [][]byte) (*Range, error) { 41 | if end < begin { 42 | return nil, fmt.Errorf("invalid range: end=%d, want >= %d", end, begin) 43 | } 44 | if got, want := len(hashes), RangeSize(begin, end); got != want { 45 | return nil, fmt.Errorf("invalid hashes: got %d values, want %d", got, want) 46 | } 47 | return &Range{f: f, begin: begin, end: end, hashes: hashes}, nil 48 | } 49 | 50 | // NewEmptyRange returns a new Range for an empty [begin, begin) range. The 51 | // value of begin defines where the range will start growing from when entries 52 | // are appended to it. 53 | func (f *RangeFactory) NewEmptyRange(begin uint64) *Range { 54 | return &Range{f: f, begin: begin, end: begin} 55 | } 56 | 57 | // Range represents a compact Merkle tree range for leaf indices [begin, end). 58 | // 59 | // It contains the minimal set of perfect subtrees whose leaves comprise this 60 | // range. The structure is efficiently mergeable with other compact ranges that 61 | // share one of the endpoints with it. 62 | // 63 | // For more details, see 64 | // https://github.com/transparency-dev/merkle/blob/main/docs/compact_ranges.md. 65 | type Range struct { 66 | f *RangeFactory 67 | begin uint64 68 | end uint64 69 | hashes [][]byte 70 | } 71 | 72 | // Begin returns the first index covered by the range (inclusive). 73 | func (r *Range) Begin() uint64 { 74 | return r.begin 75 | } 76 | 77 | // End returns the last index covered by the range (exclusive). 78 | func (r *Range) End() uint64 { 79 | return r.end 80 | } 81 | 82 | // Hashes returns sub-tree hashes corresponding to the minimal set of perfect 83 | // sub-trees covering the [begin, end) range, ordered left to right. 84 | func (r *Range) Hashes() [][]byte { 85 | return r.hashes 86 | } 87 | 88 | // Append extends the compact range by appending the passed in hash to it. It 89 | // reports all the added nodes through the visitor function (if non-nil). 90 | func (r *Range) Append(hash []byte, visitor VisitFn) error { 91 | if visitor != nil { 92 | visitor(NewNodeID(0, r.end), hash) 93 | } 94 | return r.appendImpl(r.end+1, hash, nil, visitor) 95 | } 96 | 97 | // AppendRange extends the compact range by merging in the other compact range 98 | // from the right. It uses the tree hasher to calculate hashes of newly created 99 | // nodes, and reports them through the visitor function (if non-nil). 100 | func (r *Range) AppendRange(other *Range, visitor VisitFn) error { 101 | if other.f != r.f { 102 | return errors.New("incompatible ranges") 103 | } 104 | if got, want := other.begin, r.end; got != want { 105 | return fmt.Errorf("ranges are disjoint: other.begin=%d, want %d", got, want) 106 | } 107 | if len(other.hashes) == 0 { // The other range is empty, merging is trivial. 108 | return nil 109 | } 110 | return r.appendImpl(other.end, other.hashes[0], other.hashes[1:], visitor) 111 | } 112 | 113 | // GetRootHash returns the root hash of the Merkle tree represented by this 114 | // compact range. Requires the range to start at index 0. If the range is 115 | // empty, returns nil. 116 | // 117 | // If visitor is not nil, it is called with all "ephemeral" nodes (i.e. the 118 | // ones rooting imperfect subtrees) along the right border of the tree. 119 | func (r *Range) GetRootHash(visitor VisitFn) ([]byte, error) { 120 | if r.begin != 0 { 121 | return nil, fmt.Errorf("begin=%d, want 0", r.begin) 122 | } 123 | ln := len(r.hashes) 124 | if ln == 0 { 125 | return nil, nil 126 | } 127 | hash := r.hashes[ln-1] 128 | // All non-perfect subtree hashes along the right border of the tree 129 | // correspond to the parents of all perfect subtree nodes except the lowest 130 | // one (therefore the loop skips it). 131 | for i, size := ln-2, r.end; i >= 0; i-- { 132 | hash = r.f.Hash(r.hashes[i], hash) 133 | if visitor != nil { 134 | size &= size - 1 // Delete the previous node. 135 | level := uint(bits.TrailingZeros64(size)) + 1 // Compute the parent level. 136 | index := size >> level // And its horizontal index. 137 | visitor(NewNodeID(level, index), hash) 138 | } 139 | } 140 | return hash, nil 141 | } 142 | 143 | // Equal compares two Ranges for equality. 144 | func (r *Range) Equal(other *Range) bool { 145 | if r.f != other.f || r.begin != other.begin || r.end != other.end { 146 | return false 147 | } 148 | if len(r.hashes) != len(other.hashes) { 149 | return false 150 | } 151 | for i := range r.hashes { 152 | if !bytes.Equal(r.hashes[i], other.hashes[i]) { 153 | return false 154 | } 155 | } 156 | return true 157 | } 158 | 159 | // appendImpl extends the compact range by merging the [r.end, end) compact 160 | // range into it. The other compact range is decomposed into a seed hash and 161 | // all the other hashes (possibly none). The method uses the tree hasher to 162 | // calculate hashes of newly created nodes, and reports them through the 163 | // visitor function (if non-nil). 164 | func (r *Range) appendImpl(end uint64, seed []byte, hashes [][]byte, visitor VisitFn) error { 165 | // Bits [low, high) of r.end encode the merge path, i.e. the sequence of node 166 | // merges that transforms the two compact ranges into one. 167 | low, high := getMergePath(r.begin, r.end, end) 168 | if high < low { 169 | high = low 170 | } 171 | index := r.end >> low 172 | // Now bits [0, high-low) of index encode the merge path. 173 | 174 | // The number of one bits in index is the number of nodes from the left range 175 | // that will be merged, and zero bits correspond to the nodes in the right 176 | // range. Below we make sure that both ranges have enough hashes, which can 177 | // be false only in case the data is corrupted in some way. 178 | ones := bits.OnesCount64(index & (1<<(high-low) - 1)) 179 | if ln := len(r.hashes); ln < ones { 180 | return fmt.Errorf("corrupted lhs range: got %d hashes, want >= %d", ln, ones) 181 | } 182 | if ln, zeros := len(hashes), int(high-low)-ones; ln < zeros { 183 | return fmt.Errorf("corrupted rhs range: got %d hashes, want >= %d", ln+1, zeros+1) 184 | } 185 | 186 | // Some of the trailing nodes of the left compact range, and some of the 187 | // leading nodes of the right range, are sequentially merged with the seed, 188 | // according to the mask. All new nodes are reported through the visitor. 189 | idx1, idx2 := len(r.hashes), 0 190 | for h := low; h < high; h++ { 191 | if index&1 == 0 { 192 | seed = r.f.Hash(seed, hashes[idx2]) 193 | idx2++ 194 | } else { 195 | idx1-- 196 | seed = r.f.Hash(r.hashes[idx1], seed) 197 | } 198 | index >>= 1 199 | if visitor != nil { 200 | visitor(NewNodeID(h+1, index), seed) 201 | } 202 | } 203 | 204 | // All nodes from both ranges that have not been merged are bundled together 205 | // with the "merged" seed node. 206 | r.hashes = append(append(r.hashes[:idx1], seed), hashes[idx2:]...) 207 | r.end = end 208 | return nil 209 | } 210 | 211 | // getMergePath returns the merging path between the compact range [begin, mid) 212 | // and [mid, end). The path is represented as a range of bits within mid, with 213 | // bit indices [low, high). A bit value of 1 on level i of mid means that the 214 | // node on this level merges with the corresponding node in the left compact 215 | // range, whereas 0 represents merging with the right compact range. If the 216 | // path is empty then high <= low. 217 | // 218 | // The output is not specified if begin <= mid <= end doesn't hold, but the 219 | // function never panics. 220 | func getMergePath(begin, mid, end uint64) (uint, uint) { 221 | low := bits.TrailingZeros64(mid) 222 | high := 64 223 | if begin != 0 { 224 | high = bits.Len64(mid ^ (begin - 1)) 225 | } 226 | if high2 := bits.Len64((mid - 1) ^ end); high2 < high { 227 | high = high2 228 | } 229 | return uint(low), uint(high - 1) 230 | } 231 | 232 | // Decompose splits the [begin, end) range into a minimal number of sub-ranges, 233 | // each of which is of the form [m * 2^k, (m+1) * 2^k), i.e. of length 2^k, for 234 | // some integers m, k >= 0. 235 | // 236 | // The sequence of sizes is returned encoded as bitmasks left and right, where: 237 | // - a 1 bit in a bitmask denotes a sub-range of the corresponding size 2^k 238 | // - left mask bits in LSB-to-MSB order encode the left part of the sequence 239 | // - right mask bits in MSB-to-LSB order encode the right part 240 | // 241 | // The corresponding values of m are not returned (they can be calculated from 242 | // begin and the sub-range sizes). 243 | // 244 | // For example, (begin, end) values of (0b110, 0b11101) would indicate a 245 | // sequence of tree sizes: 2,8; 8,4,1. 246 | // 247 | // The output is not specified if begin > end, but the function never panics. 248 | func Decompose(begin, end uint64) (uint64, uint64) { 249 | // Special case, as the code below works only if begin != 0, or end < 2^63. 250 | if begin == 0 { 251 | return 0, end 252 | } 253 | xbegin := begin - 1 254 | // Find where paths to leaves #begin-1 and #end diverge, and mask the upper 255 | // bits away, as only the nodes strictly below this point are in the range. 256 | d := bits.Len64(xbegin^end) - 1 257 | mask := uint64(1)<= size { 50 | return Nodes{}, fmt.Errorf("index %d out of bounds for tree size %d", index, size) 51 | } 52 | return nodes(index, 0, size).skipFirst(), nil 53 | } 54 | 55 | // Consistency returns the information on how to fetch and construct a 56 | // consistency proof between the two given tree sizes of a log Merkle tree. It 57 | // requires 0 <= size1 <= size2. 58 | func Consistency(size1, size2 uint64) (Nodes, error) { 59 | if size1 > size2 { 60 | return Nodes{}, fmt.Errorf("tree size %d > %d", size1, size2) 61 | } 62 | if size1 == size2 || size1 == 0 { 63 | return Nodes{IDs: []compact.NodeID{}}, nil 64 | } 65 | 66 | // Find the root of the biggest perfect subtree that ends at size1. 67 | level := uint(bits.TrailingZeros64(size1)) 68 | index := (size1 - 1) >> level 69 | // The consistency proof consists of this node (except if size1 is a power of 70 | // two, in which case adding this node would be redundant because the client 71 | // is assumed to know it from a checkpoint), and nodes of the inclusion proof 72 | // into this node in the tree of size2. 73 | p := nodes(index, level, size2) 74 | 75 | // Handle the case when size1 is a power of 2. 76 | if index == 0 { 77 | return p.skipFirst(), nil 78 | } 79 | return p, nil 80 | } 81 | 82 | // nodes returns the node IDs necessary to prove that the (level, index) node 83 | // is included in the Merkle tree of the given size. 84 | func nodes(index uint64, level uint, size uint64) Nodes { 85 | // Compute the `fork` node, where the path from root to (level, index) node 86 | // diverges from the path to (0, size). 87 | // 88 | // The sibling of this node is the ephemeral node which represents a subtree 89 | // that is not complete in the tree of the given size. To compute the hash 90 | // of the ephemeral node, we need all the non-ephemeral nodes that cover the 91 | // same range of leaves. 92 | // 93 | // The `inner` variable is how many layers up from (level, index) the `fork` 94 | // and the ephemeral nodes are. 95 | inner := bits.Len64(index^(size>>level)) - 1 96 | fork := compact.NewNodeID(level+uint(inner), index>>inner) 97 | 98 | begin, end := fork.Coverage() 99 | left := compact.RangeSize(0, begin) 100 | right := compact.RangeSize(end, size) 101 | 102 | node := compact.NewNodeID(level, index) 103 | // Pre-allocate the exact number of nodes for the proof, in order: 104 | // - The seed node for which we are building the proof. 105 | // - The `inner` nodes at each level up to the fork node. 106 | // - The `right` nodes, comprising the ephemeral node. 107 | // - The `left` nodes, completing the coverage of the whole [0, size) range. 108 | nodes := append(make([]compact.NodeID, 0, 1+inner+right+left), node) 109 | 110 | // The first portion of the proof consists of the siblings for nodes of the 111 | // path going up to the level at which the ephemeral node appears. 112 | for ; node.Level < fork.Level; node = node.Parent() { 113 | nodes = append(nodes, node.Sibling()) 114 | } 115 | // This portion of the proof covers the range [begin, end) under it. The 116 | // ranges to the left and to the right from it remain to be covered. 117 | 118 | // Add all the nodes (potentially none) that cover the right range, and 119 | // represent the ephemeral node. Reverse them so that the Rehash method can 120 | // process hashes in the convenient order, from lower to upper levels. 121 | len1 := len(nodes) 122 | nodes = compact.RangeNodes(end, size, nodes) 123 | reverse(nodes[len(nodes)-right:]) 124 | len2 := len(nodes) 125 | // Add the nodes that cover the left range, ordered increasingly by level. 126 | nodes = compact.RangeNodes(0, begin, nodes) 127 | reverse(nodes[len(nodes)-left:]) 128 | 129 | // nodes[len1:len2] contains the nodes representing the ephemeral node. If 130 | // it's empty, make it zero. Note that it can also contain a single node. 131 | // Depending on the preference of the layer above, it may or may not be 132 | // considered ephemeral. 133 | if len1 >= len2 { 134 | len1, len2 = 0, 0 135 | } 136 | 137 | return Nodes{IDs: nodes, begin: len1, end: len2, ephem: fork.Sibling()} 138 | } 139 | 140 | // Ephem returns the ephemeral node, and indices begin and end, such that 141 | // IDs[begin:end] slice contains the child nodes of the ephemeral node. 142 | // 143 | // The list is empty iff there are no ephemeral nodes in the proof. Some 144 | // examples of when this can happen: a proof in a perfect tree; an inclusion 145 | // proof for a leaf in a perfect subtree at the right edge of the tree. 146 | func (n Nodes) Ephem() (compact.NodeID, int, int) { 147 | return n.ephem, n.begin, n.end 148 | } 149 | 150 | // Rehash computes the proof based on the slice of node hashes corresponding to 151 | // their IDs in the n.IDs field. The slices must be of the same length. The hc 152 | // parameter computes a node's hash based on hashes of its children. 153 | // 154 | // Warning: The passed-in slice of hashes can be modified in-place. 155 | func (n Nodes) Rehash(h [][]byte, hc func(left, right []byte) []byte) ([][]byte, error) { 156 | if got, want := len(h), len(n.IDs); got != want { 157 | return nil, fmt.Errorf("got %d hashes but expected %d", got, want) 158 | } 159 | cursor := 0 160 | // Scan the list of node hashes, and store the rehashed list in-place. 161 | // Invariant: cursor <= i, and h[:cursor] contains all the hashes of the 162 | // rehashed list after scanning h up to index i-1. 163 | for i, ln := 0, len(h); i < ln; i, cursor = i+1, cursor+1 { 164 | hash := h[i] 165 | if i >= n.begin && i < n.end { 166 | // Scan the block of node hashes that need rehashing. 167 | for i++; i < n.end; i++ { 168 | hash = hc(h[i], hash) 169 | } 170 | i-- 171 | } 172 | h[cursor] = hash 173 | } 174 | return h[:cursor], nil 175 | } 176 | 177 | func (n Nodes) skipFirst() Nodes { 178 | n.IDs = n.IDs[1:] 179 | // Fixup the indices into the IDs slice. 180 | if n.begin < n.end { 181 | n.begin-- 182 | n.end-- 183 | } 184 | return n 185 | } 186 | 187 | func reverse(ids []compact.NodeID) { 188 | for i, j := 0, len(ids)-1; i < j; i, j = i+1, j-1 { 189 | ids[i], ids[j] = ids[j], ids[i] 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /proof/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proof 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "math/bits" 22 | 23 | "github.com/transparency-dev/merkle" 24 | ) 25 | 26 | // RootMismatchError occurs when an inclusion proof fails. 27 | type RootMismatchError struct { 28 | ExpectedRoot []byte 29 | CalculatedRoot []byte 30 | } 31 | 32 | func (e RootMismatchError) Error() string { 33 | return fmt.Sprintf("calculated root:\n%v\n does not match expected root:\n%v", e.CalculatedRoot, e.ExpectedRoot) 34 | } 35 | 36 | func verifyMatch(calculated, expected []byte) error { 37 | if !bytes.Equal(calculated, expected) { 38 | return RootMismatchError{ExpectedRoot: expected, CalculatedRoot: calculated} 39 | } 40 | return nil 41 | } 42 | 43 | // VerifyInclusion verifies the correctness of the inclusion proof for the leaf 44 | // with the specified hash and index, relatively to the tree of the given size 45 | // and root hash. Requires 0 <= index < size. 46 | func VerifyInclusion(hasher merkle.LogHasher, index, size uint64, leafHash []byte, proof [][]byte, root []byte) error { 47 | calcRoot, err := RootFromInclusionProof(hasher, index, size, leafHash, proof) 48 | if err != nil { 49 | return err 50 | } 51 | return verifyMatch(calcRoot, root) 52 | } 53 | 54 | // RootFromInclusionProof calculates the expected root hash for a tree of the 55 | // given size, provided a leaf index and hash with the corresponding inclusion 56 | // proof. Requires 0 <= index < size. 57 | func RootFromInclusionProof(hasher merkle.LogHasher, index, size uint64, leafHash []byte, proof [][]byte) ([]byte, error) { 58 | if index >= size { 59 | return nil, fmt.Errorf("index is beyond size: %d >= %d", index, size) 60 | } 61 | if got, want := len(leafHash), hasher.Size(); got != want { 62 | return nil, fmt.Errorf("leafHash has unexpected size %d, want %d", got, want) 63 | } 64 | 65 | inner, border := decompInclProof(index, size) 66 | if got, want := len(proof), inner+border; got != want { 67 | return nil, fmt.Errorf("wrong proof size %d, want %d", got, want) 68 | } 69 | 70 | res := chainInner(hasher, leafHash, proof[:inner], index) 71 | res = chainBorderRight(hasher, res, proof[inner:]) 72 | return res, nil 73 | } 74 | 75 | // VerifyConsistency checks that the passed-in consistency proof is valid 76 | // between the passed in tree sizes, with respect to the corresponding root 77 | // hashes. Requires 0 < size1 <= size2. 78 | func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]byte, root1, root2 []byte) error { 79 | hash2, err := RootFromConsistencyProof(hasher, size1, size2, proof, root1) 80 | if err != nil { 81 | return err 82 | } 83 | return verifyMatch(hash2, root2) 84 | } 85 | 86 | // RootFromConsistencyProof calculates the expected root hash for a tree of the 87 | // given size2, provided a tree of size1 with root1, and a consistency proof. 88 | // Requires 0 < size1 <= size2. 89 | // Note that consistency proofs from a size1==0 cannot be computed. 90 | func RootFromConsistencyProof(hasher merkle.LogHasher, size1, size2 uint64, proof [][]byte, root1 []byte) ([]byte, error) { 91 | switch { 92 | case size2 < size1: 93 | return nil, fmt.Errorf("size2 (%d) < size1 (%d)", size1, size2) 94 | case size1 == size2: 95 | if len(proof) > 0 { 96 | return nil, errors.New("size1=size2, but proof is not empty") 97 | } 98 | return root1, nil 99 | case size1 == 0: 100 | return nil, errors.New("consistency proof from empty tree is meaningless") 101 | case len(proof) == 0: 102 | return nil, errors.New("empty proof") 103 | } 104 | 105 | inner, border := decompInclProof(size1-1, size2) 106 | shift := bits.TrailingZeros64(size1) 107 | inner -= shift // Note: shift < inner if size1 < size2. 108 | 109 | // The proof includes the root hash for the sub-tree of size 2^shift. 110 | seed, start := proof[0], 1 111 | if size1 == 1<> uint(shift) // Start chaining from level |shift|. 123 | hash1 := chainInnerRight(hasher, seed, proof[:inner], mask) 124 | hash1 = chainBorderRight(hasher, hash1, proof[inner:]) 125 | if err := verifyMatch(hash1, root1); err != nil { 126 | return nil, err 127 | } 128 | 129 | // Verify the second root. 130 | hash2 := chainInner(hasher, seed, proof[:inner], mask) 131 | hash2 = chainBorderRight(hasher, hash2, proof[inner:]) 132 | return hash2, nil 133 | } 134 | 135 | // decompInclProof breaks down inclusion proof for a leaf at the specified 136 | // |index| in a tree of the specified |size| into 2 components. The splitting 137 | // point between them is where paths to leaves |index| and |size-1| diverge. 138 | // Returns lengths of the bottom and upper proof parts correspondingly. The sum 139 | // of the two determines the correct length of the inclusion proof. 140 | func decompInclProof(index, size uint64) (int, int) { 141 | inner := innerProofSize(index, size) 142 | border := bits.OnesCount64(index >> uint(inner)) 143 | return inner, border 144 | } 145 | 146 | func innerProofSize(index, size uint64) int { 147 | return bits.Len64(index ^ (size - 1)) 148 | } 149 | 150 | // chainInner computes a subtree hash for a node on or below the tree's right 151 | // border. Assumes |proof| hashes are ordered from lower levels to upper, and 152 | // |seed| is the initial subtree/leaf hash on the path located at the specified 153 | // |index| on its level. 154 | func chainInner(hasher merkle.LogHasher, seed []byte, proof [][]byte, index uint64) []byte { 155 | for i, h := range proof { 156 | if (index>>uint(i))&1 == 0 { 157 | seed = hasher.HashChildren(seed, h) 158 | } else { 159 | seed = hasher.HashChildren(h, seed) 160 | } 161 | } 162 | return seed 163 | } 164 | 165 | // chainInnerRight computes a subtree hash like chainInner, but only takes 166 | // hashes to the left from the path into consideration, which effectively means 167 | // the result is a hash of the corresponding earlier version of this subtree. 168 | func chainInnerRight(hasher merkle.LogHasher, seed []byte, proof [][]byte, index uint64) []byte { 169 | for i, h := range proof { 170 | if (index>>uint(i))&1 == 1 { 171 | seed = hasher.HashChildren(h, seed) 172 | } 173 | } 174 | return seed 175 | } 176 | 177 | // chainBorderRight chains proof hashes along tree borders. This differs from 178 | // inner chaining because |proof| contains only left-side subtree hashes. 179 | func chainBorderRight(hasher merkle.LogHasher, seed []byte, proof [][]byte) []byte { 180 | for _, h := range proof { 181 | seed = hasher.HashChildren(h, seed) 182 | } 183 | return seed 184 | } 185 | -------------------------------------------------------------------------------- /proof/verify_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proof 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "io/fs" 21 | "os" 22 | "path/filepath" 23 | "strings" 24 | "testing" 25 | 26 | "github.com/transparency-dev/merkle/rfc6962" 27 | ) 28 | 29 | // inclusionProbe is a parameter set for inclusion proof verification. 30 | type inclusionProbe struct { 31 | LeafIdx uint64 `json:"leafIdx"` 32 | TreeSize uint64 `json:"treeSize"` 33 | Root []byte `json:"root"` 34 | LeafHash []byte `json:"leafHash"` 35 | Proof [][]byte `json:"proof"` 36 | 37 | Desc string `json:"desc"` 38 | WantError bool `json:"wantErr"` 39 | } 40 | 41 | // consistencyProbe is a parameter set for consistency proof verification. 42 | type consistencyProbe struct { 43 | Size1 uint64 `json:"size1"` 44 | Size2 uint64 `json:"size2"` 45 | Root1 []byte `json:"root1"` 46 | Root2 []byte `json:"root2"` 47 | Proof [][]byte `json:"proof"` 48 | 49 | Desc string `json:"desc"` 50 | WantError bool `json:"wantErr"` 51 | } 52 | 53 | func TestVerifyInclusionProbes(t *testing.T) { 54 | var probes []inclusionProbe 55 | 56 | if err := filepath.WalkDir("../testdata/inclusion", func(path string, d fs.DirEntry, err error) error { 57 | if err != nil { 58 | return err 59 | } 60 | 61 | if d.IsDir() { 62 | return nil 63 | } 64 | 65 | if filepath.Ext(d.Name()) != ".json" { 66 | return nil 67 | } 68 | 69 | data, err := os.ReadFile(path) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | var probe inclusionProbe 75 | if err := json.Unmarshal(data, &probe); err != nil { 76 | return fmt.Errorf("failed to parse inclusion probe json: %s", err) 77 | } 78 | 79 | probes = append(probes, probe) 80 | 81 | return nil 82 | }); err != nil { 83 | t.Errorf("failed to read inclusion probes: %s", err) 84 | } 85 | 86 | var wrong []string 87 | for _, p := range probes { 88 | err := VerifyInclusion(rfc6962.DefaultHasher, p.LeafIdx, p.TreeSize, p.LeafHash, p.Proof, p.Root) 89 | if p.WantError && err == nil { 90 | wrong = append(wrong, fmt.Sprintf("expected error but didn't get one: %s", p.Desc)) 91 | continue 92 | } 93 | 94 | if !p.WantError && err != nil { 95 | wrong = append(wrong, fmt.Sprintf("unexpected error: %s, %s", p.Desc, err)) 96 | continue 97 | } 98 | } 99 | 100 | if len(wrong) > 0 { 101 | t.Errorf("errors verifying inclusion probes: \n%d out of %d failures \nError messages: \n%s", len(wrong), len(probes), strings.Join(wrong, "\n")) 102 | } 103 | } 104 | 105 | func TestVerifyConsistencyProbes(t *testing.T) { 106 | var probes []consistencyProbe 107 | 108 | if err := filepath.WalkDir("../testdata/consistency", func(path string, d fs.DirEntry, err error) error { 109 | if err != nil { 110 | return err 111 | } 112 | 113 | if d.IsDir() { 114 | return nil 115 | } 116 | 117 | if filepath.Ext(d.Name()) != ".json" { 118 | return nil 119 | } 120 | 121 | data, err := os.ReadFile(path) 122 | if err != nil { 123 | return err 124 | } 125 | 126 | var probe consistencyProbe 127 | if err := json.Unmarshal(data, &probe); err != nil { 128 | return fmt.Errorf("failed to parse consistency probe json: %s", err) 129 | } 130 | 131 | probes = append(probes, probe) 132 | 133 | return nil 134 | }); err != nil { 135 | t.Errorf("failed to read consistency probes: %s", err) 136 | } 137 | 138 | var wrong []string 139 | for _, p := range probes { 140 | err := VerifyConsistency(rfc6962.DefaultHasher, p.Size1, p.Size2, p.Proof, p.Root1, p.Root2) 141 | if p.WantError && err == nil { 142 | wrong = append(wrong, fmt.Sprintf("expected error but didn't get one: %s", p.Desc)) 143 | continue 144 | } 145 | 146 | if !p.WantError && err != nil { 147 | wrong = append(wrong, fmt.Sprintf("unexpected error: %s, %s", p.Desc, err)) 148 | continue 149 | } 150 | } 151 | 152 | if len(wrong) > 0 { 153 | t.Errorf("errors verifying consistency probes: \n%d out of %d failures \nError messages: \n%s", len(wrong), len(probes), strings.Join(wrong, "\n")) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /rfc6962/rfc6962.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package rfc6962 provides hashing functionality according to RFC6962. 16 | package rfc6962 17 | 18 | import ( 19 | "crypto" 20 | _ "crypto/sha256" // SHA256 is the default algorithm. 21 | ) 22 | 23 | // Domain separation prefixes 24 | const ( 25 | RFC6962LeafHashPrefix = 0 26 | RFC6962NodeHashPrefix = 1 27 | ) 28 | 29 | // DefaultHasher is a SHA256 based LogHasher. 30 | var DefaultHasher = New(crypto.SHA256) 31 | 32 | // Hasher implements the RFC6962 tree hashing algorithm. 33 | type Hasher struct { 34 | crypto.Hash 35 | } 36 | 37 | // New creates a new Hashers.LogHasher on the passed in hash function. 38 | func New(h crypto.Hash) *Hasher { 39 | return &Hasher{Hash: h} 40 | } 41 | 42 | // EmptyRoot returns a special case for an empty tree. 43 | func (t *Hasher) EmptyRoot() []byte { 44 | return t.New().Sum(nil) 45 | } 46 | 47 | // HashLeaf returns the Merkle tree leaf hash of the data passed in through leaf. 48 | // The data in leaf is prefixed by the LeafHashPrefix. 49 | func (t *Hasher) HashLeaf(leaf []byte) []byte { 50 | h := t.New() 51 | h.Write([]byte{RFC6962LeafHashPrefix}) 52 | h.Write(leaf) 53 | return h.Sum(nil) 54 | } 55 | 56 | // HashChildren returns the inner Merkle tree node hash of the two child nodes l and r. 57 | // The hashed structure is NodeHashPrefix||l||r. 58 | func (t *Hasher) HashChildren(l, r []byte) []byte { 59 | h := t.New() 60 | b := append(append(append( 61 | make([]byte, 0, 1+len(l)+len(r)), 62 | RFC6962NodeHashPrefix), 63 | l...), 64 | r...) 65 | 66 | h.Write(b) 67 | return h.Sum(nil) 68 | } 69 | -------------------------------------------------------------------------------- /rfc6962/rfc6962_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package rfc6962 16 | 17 | import ( 18 | "bytes" 19 | "encoding/hex" 20 | "testing" 21 | ) 22 | 23 | func TestRFC6962Hasher(t *testing.T) { 24 | hasher := DefaultHasher 25 | 26 | leafHash := hasher.HashLeaf([]byte("L123456")) 27 | emptyLeafHash := hasher.HashLeaf([]byte{}) 28 | 29 | for _, tc := range []struct { 30 | desc string 31 | got []byte 32 | want string 33 | }{ 34 | // echo -n | sha256sum 35 | { 36 | desc: "RFC6962 Empty", 37 | want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 38 | got: hasher.EmptyRoot(), 39 | }, 40 | // Check that the empty hash is not the same as the hash of an empty leaf. 41 | // echo -n 00 | xxd -r -p | sha256sum 42 | { 43 | desc: "RFC6962 Empty Leaf", 44 | want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", 45 | got: emptyLeafHash, 46 | }, 47 | // echo -n 004C313233343536 | xxd -r -p | sha256sum 48 | { 49 | desc: "RFC6962 Leaf", 50 | want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56", 51 | got: leafHash, 52 | }, 53 | // echo -n 014E3132334E343536 | xxd -r -p | sha256sum 54 | { 55 | desc: "RFC6962 Node", 56 | want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb", 57 | got: hasher.HashChildren([]byte("N123"), []byte("N456")), 58 | }, 59 | } { 60 | t.Run(tc.desc, func(t *testing.T) { 61 | wantBytes, err := hex.DecodeString(tc.want) 62 | if err != nil { 63 | t.Fatalf("hex.DecodeString(%x): %v", tc.want, err) 64 | } 65 | if got, want := tc.got, wantBytes; !bytes.Equal(got, want) { 66 | t.Errorf("got %x, want %x", got, want) 67 | } 68 | }) 69 | } 70 | } 71 | 72 | // TODO(pavelkalinnikov): Apply this test to all LogHasher implementations. 73 | func TestRFC6962HasherCollisions(t *testing.T) { 74 | hasher := DefaultHasher 75 | 76 | // Check that different leaves have different hashes. 77 | leaf1, leaf2 := []byte("Hello"), []byte("World") 78 | hash1 := hasher.HashLeaf(leaf1) 79 | hash2 := hasher.HashLeaf(leaf2) 80 | if bytes.Equal(hash1, hash2) { 81 | t.Errorf("Leaf hashes should differ, but both are %x", hash1) 82 | } 83 | 84 | // Compute an intermediate subtree hash. 85 | subHash1 := hasher.HashChildren(hash1, hash2) 86 | // Check that this is not the same as a leaf hash of their concatenation. 87 | preimage := append(hash1, hash2...) 88 | forgedHash := hasher.HashLeaf(preimage) 89 | if bytes.Equal(subHash1, forgedHash) { 90 | t.Errorf("Hasher is not second-preimage resistant") 91 | } 92 | 93 | // Swap the order of nodes and check that the hash is different. 94 | subHash2 := hasher.HashChildren(hash2, hash1) 95 | if bytes.Equal(subHash1, subHash2) { 96 | t.Errorf("Subtree hash does not depend on the order of leaves") 97 | } 98 | } 99 | 100 | func BenchmarkHashChildren(b *testing.B) { 101 | h := DefaultHasher 102 | l := h.HashLeaf([]byte("one")) 103 | r := h.HashLeaf([]byte("or other")) 104 | for i := 0; i < b.N; i++ { 105 | _ = h.HashChildren(l, r) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /scripts/check_license.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Checks that source files (.go and .proto) have the Apache License header. 4 | # Automatically skips generated files. 5 | set -eu 6 | 7 | check_license() { 8 | local path="$1" 9 | 10 | if head -1 "$path" | grep -iq 'generated by'; then 11 | return 0 12 | fi 13 | 14 | # Look for "Apache License" on the file header 15 | if ! head -10 "$path" | grep -q 'Apache License'; then 16 | # Format: $path:$line:$message 17 | echo "$path:10:license header not found" 18 | return 1 19 | fi 20 | } 21 | 22 | main() { 23 | if [[ $# -lt 1 ]]; then 24 | echo "Usage: $0 " 25 | exit 1 26 | fi 27 | 28 | local code=0 29 | while [[ $# -gt 0 ]]; do 30 | local path="$1" 31 | if [[ -d "$path" ]]; then 32 | for f in "$path"/*.{go,proto}; do 33 | if [[ ! -f "$f" ]]; then 34 | continue # Empty glob 35 | fi 36 | check_license "$f" || code=1 37 | done 38 | else 39 | check_license "$path" || code=1 40 | fi 41 | shift 42 | done 43 | exit $code 44 | } 45 | 46 | main "$@" 47 | -------------------------------------------------------------------------------- /scripts/presubmit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Presubmit checks for this repository. 4 | # 5 | # Checks for lint errors, spelling, licensing, correct builds / tests and so on. 6 | # Flags may be specified to allow suppressing of checks or automatic fixes, try 7 | # `scripts/presubmit.sh --help` for details. 8 | # 9 | # Globals: 10 | # GO_TEST_TIMEOUT: timeout for 'go test'. Optional (defaults to 5m). 11 | set -eu 12 | 13 | check_pkg() { 14 | local cmd="$1" 15 | local pkg="$2" 16 | check_cmd "$cmd" "try running 'go get -u $pkg'" 17 | } 18 | 19 | check_cmd() { 20 | local cmd="$1" 21 | local msg="$2" 22 | if ! type -p "${cmd}" > /dev/null; then 23 | echo "${cmd} not found, ${msg}" 24 | return 1 25 | fi 26 | } 27 | 28 | usage() { 29 | echo "$0 [--coverage] [--fix] [--no-mod-tidy] [--no-build] [--no-linters] [--no-generate] [--empty-diff]" 30 | } 31 | 32 | main() { 33 | local coverage=0 34 | local fix=0 35 | local run_mod_tidy=1 36 | local run_build=1 37 | local run_lint=1 38 | local run_generate=1 39 | local empty_diff=0 40 | while [[ $# -gt 0 ]]; do 41 | case "$1" in 42 | --coverage) 43 | coverage=1 44 | ;; 45 | --fix) 46 | fix=1 47 | ;; 48 | --no-mod-tidy) 49 | run_mod_tidy=0 50 | ;; 51 | --help) 52 | usage 53 | exit 0 54 | ;; 55 | --no-build) 56 | run_build=0 57 | ;; 58 | --no-linters) 59 | run_lint=0 60 | ;; 61 | --no-generate) 62 | run_generate=0 63 | ;; 64 | --empty-diff) 65 | empty_diff=1 66 | ;; 67 | *) 68 | usage 69 | exit 1 70 | ;; 71 | esac 72 | shift 1 73 | done 74 | 75 | cd "$(dirname "$0")" # at scripts/ 76 | cd .. # at top level 77 | 78 | go_srcs="$(find . -name '*.go' | \ 79 | grep -v mock_ | \ 80 | grep -v .pb.go | \ 81 | grep -v _string.go | \ 82 | grep -v .shims.go | \ 83 | tr '\n' ' ')" 84 | 85 | if [[ "$fix" -eq 1 ]]; then 86 | check_pkg goimports golang.org/x/tools/cmd/goimports || exit 1 87 | 88 | echo 'running gofmt' 89 | gofmt -s -w ${go_srcs} 90 | echo 'running goimports' 91 | goimports -w ${go_srcs} 92 | if [[ "$run_mod_tidy" -eq 1 ]]; then 93 | echo 'running go mod tidy' 94 | go mod tidy 95 | fi 96 | fi 97 | 98 | if [[ "${run_build}" -eq 1 ]]; then 99 | echo 'running go build' 100 | go build ./... 101 | 102 | export TEST_FLAGS="-timeout=${GO_TEST_TIMEOUT:-5m}" 103 | 104 | if [[ ${coverage} -eq 1 ]]; then 105 | TEST_FLAGS+=" -covermode=atomic -coverprofile=coverage.txt" 106 | fi 107 | 108 | echo "running go test ${TEST_FLAGS} ./..." 109 | go test ${TEST_FLAGS} ./... 110 | fi 111 | 112 | if [[ "${run_lint}" -eq 1 ]]; then 113 | check_cmd golangci-lint \ 114 | 'have you installed github.com/golangci/golangci-lint?' || exit 1 115 | 116 | echo 'running golangci-lint' 117 | golangci-lint run --deadline=8m 118 | echo 'checking license headers' 119 | ./scripts/check_license.sh ${go_srcs} 120 | fi 121 | 122 | if [[ "${run_generate}" -eq 1 ]]; then 123 | echo 'running go generate' 124 | go run ./cmd/proofgen 125 | fi 126 | 127 | if [[ "${empty_diff}" -eq 1 ]]; then 128 | echo 'checking git diff is empty' 129 | git diff --exit-code 130 | fi 131 | } 132 | 133 | main "$@" 134 | -------------------------------------------------------------------------------- /testdata/consistency/0/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 1, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "happy path", 8 | "wantErr": false 9 | } -------------------------------------------------------------------------------- /testdata/consistency/1/empty-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [], 7 | "desc": "empty proof", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/1/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "happy path", 12 | "wantErr": false 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/modified-proof@0-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "hqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "modified proof@0 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/modified-proof@1-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Twg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "modified proof@1 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/modified-proof@2-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "e0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "modified proof@2 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "", 8 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "preceding garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/preceding-proof-@0.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "preceding proof @0", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/preceding-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "preceding root1", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/preceding-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 8 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "preceding root2", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/size1-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 3, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "size1 XOR @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/size1-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "size1 plus @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/size1-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "size1 sub @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/size2-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 4, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "size2 div @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/size2-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 16, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "size2 mul @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/swapped-roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "root2": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "swapped roots", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=", 10 | "" 11 | ], 12 | "desc": "trailing garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/trailing-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=", 10 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=" 11 | ], 12 | "desc": "trailing root1", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/trailing-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=", 10 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=" 11 | ], 12 | "desc": "trailing root2", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/1/truncated-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=" 9 | ], 10 | "desc": "truncated proof", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/1/wrong-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "V3JvbmdSb290", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "wrong root1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/1/wrong-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 8, 4 | "root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "root2": "V3JvbmdSb290", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "wrong root2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/empty-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [], 7 | "desc": "empty proof", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/2/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "happy path", 12 | "wantErr": false 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/modified-proof@0-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "HrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof@0 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/modified-proof@1-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "2oVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof@1 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/modified-proof@2-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "w37kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof@2 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/preceding-proof-@0.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding proof @0", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/preceding-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding root1", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/preceding-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding root2", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/size1-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 4, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size1 XOR @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/size1-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 7, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size1 plus @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/size1-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 5, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size1 sub @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/size2-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 4, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size2 div @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/size2-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 16, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size2 mul @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/swapped-roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "root2": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "swapped roots", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "" 11 | ], 12 | "desc": "trailing garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/trailing-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=" 11 | ], 12 | "desc": "trailing root1", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/trailing-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=" 11 | ], 12 | "desc": "trailing root2", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/2/truncated-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=" 9 | ], 10 | "desc": "truncated proof", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/2/wrong-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "V3JvbmdSb290", 5 | "root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "wrong root1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/2/wrong-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 8, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "V3JvbmdSb290", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "wrong root2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/empty-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [], 7 | "desc": "empty proof", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/3/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "happy path", 11 | "wantErr": false 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/modified-proof@0-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Twg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "modified proof@0 bit @4", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/modified-proof@1-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "rBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "modified proof@1 bit @4", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "preceding garbage", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/preceding-proof-@0.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "preceding proof @0", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/preceding-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "preceding root1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/preceding-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "preceding root2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/size1-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "size1 XOR @2", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/size1-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 3, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "size1 plus @1", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/size1-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "size1 sub @1", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/size2-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 2, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "size2 div @2", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/size2-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 10, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "size2 mul @2", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/swapped-roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "root2": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "swapped roots", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 9 | "" 10 | ], 11 | "desc": "trailing garbage", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/trailing-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 9 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 10 | ], 11 | "desc": "trailing root1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/trailing-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 9 | "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=" 10 | ], 11 | "desc": "trailing root2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/3/truncated-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=" 8 | ], 9 | "desc": "truncated proof", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/consistency/3/wrong-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "V3JvbmdSb290", 5 | "root2": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "wrong root1", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/3/wrong-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 5, 4 | "root1": "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 5 | "root2": "V3JvbmdSb290", 6 | "proof": [ 7 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 9 | ], 10 | "desc": "wrong root2", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/4/empty-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [], 7 | "desc": "empty proof", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/4/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "happy path", 12 | "wantErr": false 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/modified-proof@0-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "HrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof@0 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/modified-proof@1-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "oIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof@1 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/modified-proof@2-bit-@4.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "w37kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof@2 bit @4", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/preceding-proof-@0.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding proof @0", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/preceding-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding root1", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/preceding-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 8 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 9 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding root2", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/size1-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 4, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size1 XOR @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/size1-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 7, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size1 plus @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/size1-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 5, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size1 sub @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/size2-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 3, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size2 div @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/size2-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 14, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "size2 mul @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/swapped-roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 5 | "root2": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "swapped roots", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "" 11 | ], 12 | "desc": "trailing garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/trailing-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=" 11 | ], 12 | "desc": "trailing root1", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/trailing-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=" 11 | ], 12 | "desc": "trailing root2", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/consistency/4/truncated-proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=" 9 | ], 10 | "desc": "truncated proof", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/consistency/4/wrong-root1.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "V3JvbmdSb290", 5 | "root2": "3bib5AOAnjJXUNPSY814kpwpQreUKjS3fhIslZSnTIw=", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "wrong root1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/4/wrong-root2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 6, 3 | "size2": 7, 4 | "root1": "duZ9rbzfHhDht03cYIq9L5jfsW+851J3tSMqEn8gh+8=", 5 | "root2": "V3JvbmdSb290", 6 | "proof": [ 7 | "DrxdNDf74tsVi58Sah0RjjCBgQMdCpSfje3t68VY72o=", 8 | "sIaT7C5yFZcTBkHoIR5+7cy0wmQTlj7ubB4u0W/7Gl8=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "wrong root2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/consistency-check-on-empty-tree-(size1-is-zero)-is-useless.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 1, 4 | "root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "root2": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 6 | "proof": [ 7 | "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=" 8 | ], 9 | "desc": "consistency check on empty tree (size1 is zero) is useless", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/roots-do-not-match-and-sizes-are-zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 0, 4 | "root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "roots do not match and sizes are zero", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/roots-do-not-not-match-and-sizes-are-one.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 1, 4 | "root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "roots do not not match and sizes are one", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/size1-is-greater-than-size2-again.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 2, 3 | "size2": 1, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "size1 is greater than size2 again", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/size1-is-greater-than-size2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 0, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "size1 is greater than size2", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/size1-is-zero-and-does-not-equal-size2.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 1, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "size1 is zero and does not equal size2", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-are-equal-(one)-but-roots-are-not.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 1, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "sizes are equal (one) but roots are not", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-are-equal-(zero)-but-roots-are-not.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 0, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "sizes are equal (zero) but roots are not", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-are-equal-and-proof-is-not-empty-where-both-sizes-are-one.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 1, 4 | "root1": "ZG9uJ3QgY2FyZSAy", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "sizes are equal and proof is not empty where both sizes are one", 8 | "wantErr": false 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-are-equal-and-proof-is-not-empty-where-both-sizes-are-zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 0, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAx", 6 | "proof": [], 7 | "desc": "sizes are equal and proof is not empty where both sizes are zero", 8 | "wantErr": false 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-do-not-watch-and-proof-is-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 2, 4 | "root1": "ZG9uJ3QgY2FyZSAx", 5 | "root2": "ZG9uJ3QgY2FyZSAy", 6 | "proof": [], 7 | "desc": "sizes do not watch and proof is empty", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-match-but-proof-is-not-empty-and-sizes-are-one.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 1, 3 | "size2": 1, 4 | "root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "root2": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 6 | "proof": [ 7 | "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=" 8 | ], 9 | "desc": "sizes match but proof is not empty and sizes are one", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/consistency/additional/sizes-match-but-proof-is-not-empty-and-sizes-are-zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "size1": 0, 3 | "size2": 0, 4 | "root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "root2": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 6 | "proof": [ 7 | "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=" 8 | ], 9 | "desc": "sizes match but proof is not empty and sizes are zero", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "empty root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "happy path", 8 | "wantErr": false 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/leafIdx-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "leafIdx XOR @2", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/leafIdx-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "leafIdx plus @1", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/leafIdx-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 18446744073709551615, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "leafIdx sub @1", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "" 8 | ], 9 | "desc": "preceding garbage", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/preceding-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=" 8 | ], 9 | "desc": "preceding root", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/random-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "random root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "" 8 | ], 9 | "desc": "trailing garbage", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/trailing-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=" 8 | ], 9 | "desc": "trailing root", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/treeSize-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 0, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "treeSize div @2", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/treeSize-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 2, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": null, 7 | "desc": "treeSize mul @2", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/0/wrong-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 5 | "leafHash": "V3JvbmdMZWFm", 6 | "proof": null, 7 | "desc": "wrong leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "empty root", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "happy path", 12 | "wantErr": false 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/inserted-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "inserted component", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/leafIdx-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "leafIdx XOR @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/leafIdx-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "leafIdx plus @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/leafIdx-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 18446744073709551615, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "leafIdx sub @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/modified-proof[0]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "nqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "modified proof[0] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/modified-proof[1]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Vwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "modified proof[1] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/modified-proof[2]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "Y0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "modified proof[2] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "", 8 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "preceding garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/preceding-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 8 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 11 | ], 12 | "desc": "preceding root", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/random-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "random root", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/removed-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=" 9 | ], 10 | "desc": "removed component", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=", 10 | "" 11 | ], 12 | "desc": "trailing garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/trailing-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=", 10 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=" 11 | ], 12 | "desc": "trailing root", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/treeSize-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 4, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "treeSize div @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/treeSize-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 16, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "treeSize mul @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/1/wrong-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "V3JvbmdMZWFm", 6 | "proof": [ 7 | "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ=" 10 | ], 11 | "desc": "wrong leaf", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "empty root", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "happy path", 12 | "wantErr": false 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/inserted-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "inserted component", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/leafIdx-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 7, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "leafIdx XOR @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/leafIdx-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 6, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "leafIdx plus @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/leafIdx-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 4, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "leafIdx sub @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/modified-proof[0]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "tBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof[0] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/modified-proof[1]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "woVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof[1] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/modified-proof[2]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "237kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "modified proof[2] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/preceding-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 8 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 9 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 10 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 11 | ], 12 | "desc": "preceding root", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/random-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "random root", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/removed-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=" 9 | ], 10 | "desc": "removed component", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "" 11 | ], 12 | "desc": "trailing garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/trailing-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=", 10 | "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=" 11 | ], 12 | "desc": "trailing root", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/treeSize-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 4, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "treeSize div @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/treeSize-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 16, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "QnGia+DYqE8L1UyMMC58s6O10fpngKQLzOKHNHfatlg=", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "treeSize mul @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/2/wrong-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 5, 3 | "treeSize": 8, 4 | "root": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=", 5 | "leafHash": "V3JvbmdMZWFm", 6 | "proof": [ 7 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 8 | "yoVOoSjtBQtBs1/8G4e46yveRh6eO1WW7Oa51ZdaCuA=", 9 | "037kGJdt2VdTwcc4Yrk5j6Kiz5tP8P3+izDNlSCWFLc=" 10 | ], 11 | "desc": "wrong leaf", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "empty root", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "happy path", 10 | "wantErr": false 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/leafIdx-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "leafIdx XOR @2", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/leafIdx-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 3, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "leafIdx plus @1", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/leafIdx-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "leafIdx sub @1", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/modified-proof[0]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "8sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "modified proof[0] bit @3", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "", 8 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 9 | ], 10 | "desc": "preceding garbage", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/preceding-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 8 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 9 | ], 10 | "desc": "preceding root", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/random-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "random root", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/removed-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [], 7 | "desc": "removed component", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 8 | "" 9 | ], 10 | "desc": "trailing garbage", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/trailing-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=", 8 | "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=" 9 | ], 10 | "desc": "trailing root", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/treeSize-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 1, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "treeSize div @2", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/treeSize-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 6, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "ApjRIpBtz8EIkstTpzmS/FufST6kybrbJ7eRtBJ6f+c=", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "treeSize mul @2", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/3/wrong-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 3, 4 | "root": "rra8/idLcKFPsGel5VeCZNsPqbUa9eC6FZFY8yngbnc=", 5 | "leafHash": "V3JvbmdMZWFm", 6 | "proof": [ 7 | "+sVCA+fMaWzw38tCySodnbr3CtnmIfS9jZhmLwDjwSU=" 8 | ], 9 | "desc": "wrong leaf", 10 | "wantErr": true 11 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "empty root", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/happy-path.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "happy path", 12 | "wantErr": false 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/inserted-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 11 | ], 12 | "desc": "inserted component", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/leafIdx-XOR-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 3, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "leafIdx XOR @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/leafIdx-plus-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "leafIdx plus @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/leafIdx-sub-@1.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "leafIdx sub @1", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/modified-proof[0]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "ZjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "modified proof[0] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/modified-proof[1]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Vwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "modified proof[1] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/modified-proof[2]-bit-@3.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "tBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "modified proof[2] bit @3", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/preceding-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "", 8 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 11 | ], 12 | "desc": "preceding garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/preceding-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 8 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 9 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 10 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 11 | ], 12 | "desc": "preceding root", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/random-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "random root", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/removed-component.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=" 9 | ], 10 | "desc": "removed component", 11 | "wantErr": true 12 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/trailing-garbage.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 10 | "" 11 | ], 12 | "desc": "trailing garbage", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/trailing-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=", 10 | "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=" 11 | ], 12 | "desc": "trailing root", 13 | "wantErr": true 14 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/treeSize-div-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 2, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "treeSize div @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/treeSize-mul-@2.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 10, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "treeSize mul @2", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/4/wrong-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 5, 4 | "root": "Tju7H3tHjc/nH7YxYxUZo7yhLJrvyhYSv85ME6hiZNQ=", 5 | "leafHash": "V3JvbmdMZWFm", 6 | "proof": [ 7 | "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=", 8 | "Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=", 9 | "vBoGQ7EuTS18d5GPROD095qDi2z57FtcKD4fTYhZnms=" 10 | ], 11 | "desc": "wrong leaf", 12 | "wantErr": true 13 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/0/empty-root-and-random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 0, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "empty root and random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/0/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 0, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "", 6 | "proof": [], 7 | "desc": "empty root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/0/random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 0, 4 | "root": "", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/1/empty-root-and-random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "empty root and random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/1/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "", 6 | "proof": [], 7 | "desc": "empty root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/1/random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/2/empty-root-and-random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 0, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "empty root and random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/2/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 0, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "", 6 | "proof": [], 7 | "desc": "empty root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/2/random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 1, 3 | "treeSize": 0, 4 | "root": "", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/3/empty-root-and-random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 1, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "empty root and random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/3/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 1, 4 | "root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", 5 | "leafHash": "", 6 | "proof": [], 7 | "desc": "empty root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/additional/3/random-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 2, 3 | "treeSize": 1, 4 | "root": "", 5 | "leafHash": "q6yroAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAGHgASNFY=", 6 | "proof": [], 7 | "desc": "random leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/single-entry/empty-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "DTrtAjFI/9KiWfvQzcf7PPl1ZYdg03dbgq9vkKrMLfw=", 5 | "leafHash": "", 6 | "proof": [], 7 | "desc": "empty leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/single-entry/empty-root-and-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "", 5 | "leafHash": "", 6 | "proof": [], 7 | "desc": "empty root and leaf", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/single-entry/empty-root.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "", 5 | "leafHash": "DTrtAjFI/9KiWfvQzcf7PPl1ZYdg03dbgq9vkKrMLfw=", 6 | "proof": [], 7 | "desc": "empty root", 8 | "wantErr": true 9 | } -------------------------------------------------------------------------------- /testdata/inclusion/single-entry/matching-root-and-leaf.json: -------------------------------------------------------------------------------- 1 | { 2 | "leafIdx": 0, 3 | "treeSize": 1, 4 | "root": "DTrtAjFI/9KiWfvQzcf7PPl1ZYdg03dbgq9vkKrMLfw=", 5 | "leafHash": "DTrtAjFI/9KiWfvQzcf7PPl1ZYdg03dbgq9vkKrMLfw=", 6 | "proof": [], 7 | "desc": "matching root and leaf", 8 | "wantErr": false 9 | } -------------------------------------------------------------------------------- /testonly/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package testonly contains code and data for testing Merkle trees, such as a 16 | // reference implementation of in-memory Merkle tree. 17 | package testonly 18 | 19 | import "encoding/hex" 20 | 21 | // LeafInputs returns a slice of leaf inputs for testing Merkle trees. 22 | func LeafInputs() [][]byte { 23 | return [][]byte{ 24 | hd(""), 25 | hd("00"), 26 | hd("10"), 27 | hd("2021"), 28 | hd("3031"), 29 | hd("40414243"), 30 | hd("5051525354555657"), 31 | hd("606162636465666768696a6b6c6d6e6f"), 32 | } 33 | } 34 | 35 | // NodeHashes returns a structured slice of node hashes for all complete 36 | // subtrees of a Merkle tree built from LeafInputs() using the RFC 6962 hashing 37 | // strategy. The first index in the slice is the tree level (zero being the 38 | // leaves level), the second is the horizontal index within a level. 39 | func NodeHashes() [][][]byte { 40 | return [][][]byte{{ 41 | hd("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"), 42 | hd("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"), 43 | hd("0298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7"), 44 | hd("07506a85fd9dd2f120eb694f86011e5bb4662e5c415a62917033d4a9624487e7"), 45 | hd("bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b"), 46 | hd("4271a26be0d8a84f0bd54c8c302e7cb3a3b5d1fa6780a40bcce2873477dab658"), 47 | hd("b08693ec2e721597130641e8211e7eedccb4c26413963eee6c1e2ed16ffb1a5f"), 48 | hd("46f6ffadd3d06a09ff3c5860d2755c8b9819db7df44251788c7d8e3180de8eb1"), 49 | }, { 50 | hd("fac54203e7cc696cf0dfcb42c92a1d9dbaf70ad9e621f4bd8d98662f00e3c125"), 51 | hd("5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e"), 52 | hd("0ebc5d3437fbe2db158b9f126a1d118e308181031d0a949f8dededebc558ef6a"), 53 | hd("ca854ea128ed050b41b35ffc1b87b8eb2bde461e9e3b5596ece6b9d5975a0ae0"), 54 | }, { 55 | hd("d37ee418976dd95753c1c73862b9398fa2a2cf9b4ff0fdfe8b30cd95209614b7"), 56 | hd("6b47aaf29ee3c2af9af889bc1fb9254dabd31177f16232dd6aab035ca39bf6e4"), 57 | }, { 58 | hd("5dc9da79a70659a9ad559cb701ded9a2ab9d823aad2f4960cfe370eff4604328"), 59 | }} 60 | } 61 | 62 | // RootHashes returns a slice of Merkle tree root hashes for all subsequent 63 | // trees built from LeafInputs() using the RFC 6962 hashing strategy. Hashes 64 | // are indexed by tree size starting from an empty tree. 65 | func RootHashes() [][]byte { 66 | return [][]byte{ 67 | EmptyRootHash(), 68 | hd("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"), 69 | hd("fac54203e7cc696cf0dfcb42c92a1d9dbaf70ad9e621f4bd8d98662f00e3c125"), 70 | hd("aeb6bcfe274b70a14fb067a5e5578264db0fa9b51af5e0ba159158f329e06e77"), 71 | hd("d37ee418976dd95753c1c73862b9398fa2a2cf9b4ff0fdfe8b30cd95209614b7"), 72 | hd("4e3bbb1f7b478dcfe71fb631631519a3bca12c9aefca1612bfce4c13a86264d4"), 73 | hd("76e67dadbcdf1e10e1b74ddc608abd2f98dfb16fbce75277b5232a127f2087ef"), 74 | hd("ddb89be403809e325750d3d263cd78929c2942b7942a34b77e122c9594a74c8c"), 75 | hd("5dc9da79a70659a9ad559cb701ded9a2ab9d823aad2f4960cfe370eff4604328"), 76 | } 77 | } 78 | 79 | // CompactTrees returns a slice of compact.Tree internal hashes for all 80 | // subsequent trees built from LeafInputs() using the RFC 6962 hashing 81 | // strategy. 82 | func CompactTrees() [][][]byte { 83 | nh := NodeHashes() 84 | return [][][]byte{ 85 | nil, // Empty tree. 86 | {nh[0][0]}, 87 | {nh[1][0]}, 88 | {nh[1][0], nh[0][2]}, 89 | {nh[2][0]}, 90 | {nh[2][0], nh[0][4]}, 91 | {nh[2][0], nh[1][2]}, 92 | {nh[2][0], nh[1][2], nh[0][6]}, 93 | {nh[3][0]}, 94 | } 95 | } 96 | 97 | // EmptyRootHash returns the root hash for an empty Merkle tree that uses 98 | // SHA256-based strategy from RFC 6962. 99 | func EmptyRootHash() []byte { 100 | return hd("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") 101 | } 102 | 103 | // hd decodes a hex string or panics. 104 | func hd(b string) []byte { 105 | r, err := hex.DecodeString(b) 106 | if err != nil { 107 | panic(err) 108 | } 109 | return r 110 | } 111 | -------------------------------------------------------------------------------- /testonly/reference_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testonly 16 | 17 | import ( 18 | "fmt" 19 | "math/bits" 20 | "testing" 21 | 22 | "github.com/google/go-cmp/cmp" 23 | "github.com/transparency-dev/merkle" 24 | "github.com/transparency-dev/merkle/rfc6962" 25 | ) 26 | 27 | // The reference Merkle tree hashing and proof algorithms in this file directly 28 | // implement the definitions from RFC 6962 [1]. We use this implementation only 29 | // for testing correctness of other more flexible and performant algorithms, 30 | // such as the in-memory Tree type and compact ranges. 31 | // 32 | // [1] https://datatracker.ietf.org/doc/html/rfc6962#section-2 33 | 34 | // refRootHash returns the root hash of a Merkle tree with the given entries. 35 | // This is a reference implementation for cross-checking. 36 | func refRootHash(entries [][]byte, hasher merkle.LogHasher) []byte { 37 | if len(entries) == 0 { 38 | return hasher.EmptyRoot() 39 | } 40 | if len(entries) == 1 { 41 | return hasher.HashLeaf(entries[0]) 42 | } 43 | split := downToPowerOfTwo(uint64(len(entries))) 44 | return hasher.HashChildren( 45 | refRootHash(entries[:split], hasher), 46 | refRootHash(entries[split:], hasher)) 47 | } 48 | 49 | // refInclusionProof returns the inclusion proof for the given leaf index in a 50 | // Merkle tree with the given entries. This is a reference implementation for 51 | // cross-checking. 52 | func refInclusionProof(entries [][]byte, index uint64, hasher merkle.LogHasher) [][]byte { 53 | size := uint64(len(entries)) 54 | if size == 1 || index >= size { 55 | return nil 56 | } 57 | split := downToPowerOfTwo(size) 58 | if index < split { 59 | return append( 60 | refInclusionProof(entries[:split], index, hasher), 61 | refRootHash(entries[split:], hasher)) 62 | } 63 | return append( 64 | refInclusionProof(entries[split:], index-split, hasher), 65 | refRootHash(entries[:split], hasher)) 66 | } 67 | 68 | // refConsistencyProof returns the consistency proof for the two tree sizes, in 69 | // a Merkle tree with the given entries. This is a reference implementation for 70 | // cross-checking. 71 | func refConsistencyProof(entries [][]byte, size2, size1 uint64, hasher merkle.LogHasher, haveRoot1 bool) [][]byte { 72 | if size1 == 0 || size1 > size2 { 73 | return nil 74 | } 75 | // Consistency proof for two equal sizes is empty. 76 | if size1 == size2 { 77 | // Record the hash of this subtree if it's not the root for which the proof 78 | // was originally requested (which happens when size1 is a power of 2). 79 | if !haveRoot1 { 80 | return [][]byte{refRootHash(entries[:size1], hasher)} 81 | } 82 | return nil 83 | } 84 | 85 | // At this point: 0 < size1 < size2. 86 | split := downToPowerOfTwo(size2) 87 | if size1 <= split { 88 | // Root of size1 is in the left subtree of size2. Prove that the left 89 | // subtrees are consistent, and record the hash of the right subtree (only 90 | // present in size2). 91 | return append( 92 | refConsistencyProof(entries[:split], split, size1, hasher, haveRoot1), 93 | refRootHash(entries[split:], hasher)) 94 | } 95 | 96 | // Root of size1 is at the same level as size2 root. Prove that the right 97 | // subtrees are consistent. The right subtree doesn't contain the root of 98 | // size1, so set haveRoot1 = false. Record the hash of the left subtree 99 | // (equal in both trees). 100 | return append( 101 | refConsistencyProof(entries[split:], size2-split, size1-split, hasher, false), 102 | refRootHash(entries[:split], hasher)) 103 | } 104 | 105 | // downToPowerOfTwo returns the largest power of two smaller than x. 106 | func downToPowerOfTwo(x uint64) uint64 { 107 | if x < 2 { 108 | panic("downToPowerOfTwo requires value >= 2") 109 | } 110 | return uint64(1) << (bits.Len64(x-1) - 1) 111 | } 112 | 113 | func TestDownToPowerOfTwo(t *testing.T) { 114 | for _, inOut := range [][2]uint64{ 115 | {2, 1}, {7, 4}, {8, 4}, {63, 32}, {28937, 16384}, 116 | } { 117 | if got, want := downToPowerOfTwo(inOut[0]), inOut[1]; got != want { 118 | t.Errorf("downToPowerOfTwo(%d): got %d, want %d", inOut[0], got, want) 119 | } 120 | } 121 | } 122 | 123 | func TestRefInclusionProof(t *testing.T) { 124 | for _, tc := range []struct { 125 | index uint64 126 | size uint64 127 | want [][]byte 128 | }{ 129 | {index: 0, size: 1, want: nil}, 130 | {index: 0, size: 2, want: [][]byte{ 131 | hd("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"), 132 | }}, 133 | {index: 1, size: 2, want: [][]byte{ 134 | hd("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"), 135 | }}, 136 | {index: 2, size: 3, want: [][]byte{ 137 | hd("fac54203e7cc696cf0dfcb42c92a1d9dbaf70ad9e621f4bd8d98662f00e3c125"), 138 | }}, 139 | {index: 1, size: 5, want: [][]byte{ 140 | hd("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"), 141 | hd("5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e"), 142 | hd("bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b"), 143 | }}, 144 | {index: 0, size: 8, want: [][]byte{ 145 | hd("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"), 146 | hd("5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e"), 147 | hd("6b47aaf29ee3c2af9af889bc1fb9254dabd31177f16232dd6aab035ca39bf6e4"), 148 | }}, 149 | {index: 5, size: 8, want: [][]byte{ 150 | hd("bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b"), 151 | hd("ca854ea128ed050b41b35ffc1b87b8eb2bde461e9e3b5596ece6b9d5975a0ae0"), 152 | hd("d37ee418976dd95753c1c73862b9398fa2a2cf9b4ff0fdfe8b30cd95209614b7"), 153 | }}, 154 | } { 155 | t.Run(fmt.Sprintf("%d:%d", tc.index, tc.size), func(t *testing.T) { 156 | entries := LeafInputs() 157 | got := refInclusionProof(entries[:tc.size], tc.index, rfc6962.DefaultHasher) 158 | if diff := cmp.Diff(got, tc.want); diff != "" { 159 | t.Errorf("refInclusionProof: diff (-got +want)\n%s", diff) 160 | } 161 | }) 162 | } 163 | } 164 | 165 | func TestRefConsistencyProof(t *testing.T) { 166 | for _, tc := range []struct { 167 | size1 uint64 168 | size2 uint64 169 | want [][]byte 170 | }{ 171 | {size1: 1, size2: 1, want: nil}, 172 | {size1: 1, size2: 8, want: [][]byte{ 173 | hd("96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"), 174 | hd("5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e"), 175 | hd("6b47aaf29ee3c2af9af889bc1fb9254dabd31177f16232dd6aab035ca39bf6e4"), 176 | }}, 177 | {size1: 2, size2: 5, want: [][]byte{ 178 | hd("5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e"), 179 | hd("bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b"), 180 | }}, 181 | {size1: 6, size2: 8, want: [][]byte{ 182 | hd("0ebc5d3437fbe2db158b9f126a1d118e308181031d0a949f8dededebc558ef6a"), 183 | hd("ca854ea128ed050b41b35ffc1b87b8eb2bde461e9e3b5596ece6b9d5975a0ae0"), 184 | hd("d37ee418976dd95753c1c73862b9398fa2a2cf9b4ff0fdfe8b30cd95209614b7"), 185 | }}, 186 | } { 187 | t.Run(fmt.Sprintf("%d:%d", tc.size1, tc.size2), func(t *testing.T) { 188 | entries := LeafInputs() 189 | got := refConsistencyProof(entries[:tc.size2], tc.size2, tc.size1, rfc6962.DefaultHasher, true) 190 | if diff := cmp.Diff(got, tc.want); diff != "" { 191 | t.Errorf("refConsistencyProof: diff (-got +want)\n%s", diff) 192 | } 193 | }) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /testonly/tree.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testonly 16 | 17 | import ( 18 | "github.com/transparency-dev/merkle" 19 | "github.com/transparency-dev/merkle/compact" 20 | "github.com/transparency-dev/merkle/proof" 21 | ) 22 | 23 | // Tree implements an append-only Merkle tree. For testing. 24 | type Tree struct { 25 | hasher merkle.LogHasher 26 | size uint64 27 | hashes [][][]byte // Node hashes, indexed by node (level, index). 28 | } 29 | 30 | // New returns a new empty Merkle tree. 31 | func New(hasher merkle.LogHasher) *Tree { 32 | return &Tree{hasher: hasher} 33 | } 34 | 35 | // AppendData adds the leaf hashes of the given entries to the end of the tree. 36 | func (t *Tree) AppendData(entries ...[]byte) { 37 | for _, data := range entries { 38 | t.appendImpl(t.hasher.HashLeaf(data)) 39 | } 40 | } 41 | 42 | // Append adds the given leaf hashes to the end of the tree. 43 | func (t *Tree) Append(hashes ...[]byte) { 44 | for _, hash := range hashes { 45 | t.appendImpl(hash) 46 | } 47 | } 48 | 49 | func (t *Tree) appendImpl(hash []byte) { 50 | level := 0 51 | for ; (t.size>>level)&1 == 1; level++ { 52 | row := append(t.hashes[level], hash) 53 | hash = t.hasher.HashChildren(row[len(row)-2], hash) 54 | t.hashes[level] = row 55 | } 56 | if level > len(t.hashes) { 57 | panic("gap in tree appends") 58 | } else if level == len(t.hashes) { 59 | t.hashes = append(t.hashes, nil) 60 | } 61 | 62 | t.hashes[level] = append(t.hashes[level], hash) 63 | t.size++ 64 | } 65 | 66 | // Size returns the current number of leaves in the tree. 67 | func (t *Tree) Size() uint64 { 68 | return t.size 69 | } 70 | 71 | // LeafHash returns the leaf hash at the given index. 72 | // Requires 0 <= index < Size(), otherwise panics. 73 | func (t *Tree) LeafHash(index uint64) []byte { 74 | return t.hashes[0][index] 75 | } 76 | 77 | // Hash returns the current root hash of the tree. 78 | func (t *Tree) Hash() []byte { 79 | return t.HashAt(t.size) 80 | } 81 | 82 | // HashAt returns the root hash at the given size. 83 | // Requires 0 <= size <= Size(), otherwise panics. 84 | func (t *Tree) HashAt(size uint64) []byte { 85 | if size == 0 { 86 | return t.hasher.EmptyRoot() 87 | } 88 | hashes := t.getNodes(compact.RangeNodes(0, size, nil)) 89 | 90 | hash := hashes[len(hashes)-1] 91 | for i := len(hashes) - 2; i >= 0; i-- { 92 | hash = t.hasher.HashChildren(hashes[i], hash) 93 | } 94 | return hash 95 | } 96 | 97 | // InclusionProof returns the inclusion proof for the given leaf index in the 98 | // tree of the given size. Requires 0 <= index < size <= Size(), otherwise may 99 | // panic. 100 | func (t *Tree) InclusionProof(index, size uint64) ([][]byte, error) { 101 | nodes, err := proof.Inclusion(index, size) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return nodes.Rehash(t.getNodes(nodes.IDs), t.hasher.HashChildren) 106 | } 107 | 108 | // ConsistencyProof returns the consistency proof between the two given tree 109 | // sizes. Requires 0 <= size1 <= size2 <= Size(), otherwise may panic. 110 | func (t *Tree) ConsistencyProof(size1, size2 uint64) ([][]byte, error) { 111 | nodes, err := proof.Consistency(size1, size2) 112 | if err != nil { 113 | return nil, err 114 | } 115 | return nodes.Rehash(t.getNodes(nodes.IDs), t.hasher.HashChildren) 116 | } 117 | 118 | func (t *Tree) getNodes(ids []compact.NodeID) [][]byte { 119 | hashes := make([][]byte, len(ids)) 120 | for i, id := range ids { 121 | hashes[i] = t.hashes[id.Level][id.Index] 122 | } 123 | return hashes 124 | } 125 | -------------------------------------------------------------------------------- /testonly/tree_fuzz_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | 3 | package testonly 4 | 5 | import ( 6 | "bytes" 7 | "math" 8 | "testing" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/google/go-cmp/cmp/cmpopts" 12 | "github.com/transparency-dev/merkle/proof" 13 | ) 14 | 15 | // Compute and verify consistency proofs 16 | func FuzzConsistencyProofAndVerify(f *testing.F) { 17 | for size := 1; size <= 8; size++ { 18 | for end := 1; end <= size; end++ { 19 | for begin := 1; begin <= end; begin++ { 20 | f.Add(uint64(size), uint64(begin), uint64(end)) 21 | } 22 | } 23 | } 24 | f.Fuzz(func(t *testing.T, size, begin, end uint64) { 25 | // necessary to restrict size for compile_native_go_fuzzer 26 | if size >= math.MaxUint16 { 27 | return 28 | } 29 | t.Logf("size=%d, begin=%d, end=%d", size, begin, end) 30 | if begin > end || end > size { 31 | return 32 | } 33 | if begin == 0 && end > 0 { 34 | return 35 | } 36 | tree := newTree(genEntries(size)) 37 | p, err := tree.ConsistencyProof(begin, end) 38 | t.Logf("proof=%v", p) 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | err = proof.VerifyConsistency(tree.hasher, begin, end, p, tree.HashAt(begin), tree.HashAt(end)) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | }) 47 | } 48 | 49 | // Compute and verify inclusion proofs 50 | func FuzzInclusionProofAndVerify(f *testing.F) { 51 | for size := 0; size <= 8; size++ { 52 | for index := 0; index <= size; index++ { 53 | f.Add(uint64(index), uint64(size)) 54 | } 55 | } 56 | f.Fuzz(func(t *testing.T, index, size uint64) { 57 | if size >= math.MaxUint16 { 58 | return 59 | } 60 | t.Logf("index=%d, size=%d", index, size) 61 | if index >= size { 62 | return 63 | } 64 | tree := newTree(genEntries(size)) 65 | p, err := tree.InclusionProof(index, size) 66 | t.Logf("proof=%v", p) 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | err = proof.VerifyInclusion(tree.hasher, index, size, tree.LeafHash(index), p, tree.Hash()) 71 | if err != nil { 72 | t.Error(err) 73 | } 74 | }) 75 | } 76 | 77 | func FuzzHashAtAgainstReferenceImplementation(f *testing.F) { 78 | for size := 0; size <= 8; size++ { 79 | for index := 0; index <= size; index++ { 80 | f.Add(uint64(index), uint64(size)) 81 | } 82 | } 83 | f.Fuzz(func(t *testing.T, index, size uint64) { 84 | if size >= math.MaxUint16 { 85 | return 86 | } 87 | t.Logf("index=%d, size=%d", index, size) 88 | if index >= size { 89 | return 90 | } 91 | entries := genEntries(size) 92 | mt := newTree(entries) 93 | got := mt.HashAt(uint64(size)) 94 | want := refRootHash(entries[:size], mt.hasher) 95 | if !bytes.Equal(got, want) { 96 | t.Errorf("HashAt(%d): %x, want %x", size, got, want) 97 | } 98 | }) 99 | } 100 | 101 | func FuzzInclusionProofAgainstReferenceImplementation(f *testing.F) { 102 | for size := 0; size <= 8; size++ { 103 | for index := 0; index <= size; index++ { 104 | f.Add(uint64(index), uint64(size)) 105 | } 106 | } 107 | f.Fuzz(func(t *testing.T, index, size uint64) { 108 | if size >= math.MaxUint16 { 109 | return 110 | } 111 | t.Logf("index=%d, size=%d", index, size) 112 | if index >= size { 113 | return 114 | } 115 | entries := genEntries(size) 116 | tree := newTree(entries) 117 | got, err := tree.InclusionProof(index, size) 118 | t.Logf("proof=%v", got) 119 | if err != nil { 120 | t.Error(err) 121 | } 122 | want := refInclusionProof(entries, index, tree.hasher) 123 | if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" { 124 | t.Errorf("InclusionProof(%d, %d): diff (-got +want)\n%s", index, size, diff) 125 | } 126 | }) 127 | } 128 | 129 | func FuzzConsistencyProofAgainstReferenceImplementation(f *testing.F) { 130 | for size := 0; size <= 8; size++ { 131 | for end := 0; end <= size; end++ { 132 | for begin := 0; begin <= end; begin++ { 133 | f.Add(uint64(size), uint64(begin), uint64(end)) 134 | } 135 | } 136 | } 137 | f.Fuzz(func(t *testing.T, size, begin, end uint64) { 138 | if size >= math.MaxUint16 { 139 | return 140 | } 141 | t.Logf("size=%d, begin=%d, end=%d", size, begin, end) 142 | if begin > end || end > size { 143 | return 144 | } 145 | entries := genEntries(size) 146 | tree := newTree(entries) 147 | got, err := tree.ConsistencyProof(begin, end) 148 | if err != nil { 149 | t.Errorf("ConsistencyProof: %v", err) 150 | } 151 | want := refConsistencyProof(entries[:end], end, begin, tree.hasher, true) 152 | if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" { 153 | t.Errorf("ConsistencyProof: diff (-got +want)\n%s", diff) 154 | } 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /testonly/tree_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testonly 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "math/rand/v2" 21 | "strconv" 22 | "testing" 23 | 24 | "github.com/google/go-cmp/cmp" 25 | "github.com/google/go-cmp/cmp/cmpopts" 26 | "github.com/transparency-dev/merkle/rfc6962" 27 | ) 28 | 29 | func validateTree(t *testing.T, mt *Tree, size uint64) { 30 | t.Helper() 31 | if got, want := mt.Size(), size; got != want { 32 | t.Errorf("Size: %d, want %d", got, want) 33 | } 34 | roots := RootHashes() 35 | if got, want := mt.Hash(), roots[size]; !bytes.Equal(got, want) { 36 | t.Errorf("Hash(%d): %x, want %x", size, got, want) 37 | } 38 | for s := uint64(0); s <= size; s++ { 39 | if got, want := mt.HashAt(s), roots[s]; !bytes.Equal(got, want) { 40 | t.Errorf("HashAt(%d/%d): %x, want %x", s, size, got, want) 41 | } 42 | } 43 | } 44 | 45 | func TestBuildTreeBuildOneAtATime(t *testing.T) { 46 | mt := newTree(nil) 47 | validateTree(t, mt, 0) 48 | for i, entry := range LeafInputs() { 49 | mt.AppendData(entry) 50 | validateTree(t, mt, uint64(i+1)) 51 | } 52 | } 53 | 54 | func TestBuildTreeBuildTwoChunks(t *testing.T) { 55 | entries := LeafInputs() 56 | mt := newTree(nil) 57 | mt.AppendData(entries[:3]...) 58 | validateTree(t, mt, 3) 59 | mt.AppendData(entries[3:8]...) 60 | validateTree(t, mt, 8) 61 | } 62 | 63 | func TestBuildTreeBuildAllAtOnce(t *testing.T) { 64 | mt := newTree(nil) 65 | mt.AppendData(LeafInputs()...) 66 | validateTree(t, mt, 8) 67 | } 68 | 69 | func TestTreeHashAt(t *testing.T) { 70 | test := func(desc string, entries [][]byte) { 71 | t.Run(desc, func(t *testing.T) { 72 | mt := newTree(entries) 73 | for size := 0; size <= len(entries); size++ { 74 | got := mt.HashAt(uint64(size)) 75 | want := refRootHash(entries[:size], mt.hasher) 76 | if !bytes.Equal(got, want) { 77 | t.Errorf("HashAt(%d): %x, want %x", size, got, want) 78 | } 79 | } 80 | }) 81 | } 82 | 83 | entries := LeafInputs() 84 | for size := 0; size <= len(entries); size++ { 85 | test(fmt.Sprintf("size:%d", size), entries[:size]) 86 | } 87 | test("generated", genEntries(256)) 88 | } 89 | 90 | func TestTreeInclusionProof(t *testing.T) { 91 | test := func(desc string, entries [][]byte) { 92 | t.Run(desc, func(t *testing.T) { 93 | mt := newTree(entries) 94 | for index, size := uint64(0), uint64(len(entries)); index < size; index++ { 95 | got, err := mt.InclusionProof(index, size) 96 | if err != nil { 97 | t.Fatalf("InclusionProof(%d, %d): %v", index, size, err) 98 | } 99 | want := refInclusionProof(entries[:size], index, mt.hasher) 100 | if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" { 101 | t.Fatalf("InclusionProof(%d, %d): diff (-got +want)\n%s", index, size, diff) 102 | } 103 | } 104 | }) 105 | } 106 | 107 | test("generated", genEntries(256)) 108 | entries := LeafInputs() 109 | for size := 0; size < len(entries); size++ { 110 | test(fmt.Sprintf("golden:%d", size), entries[:size]) 111 | } 112 | } 113 | 114 | func TestTreeConsistencyProof(t *testing.T) { 115 | entries := LeafInputs() 116 | mt := newTree(entries) 117 | validateTree(t, mt, 8) 118 | 119 | if _, err := mt.ConsistencyProof(6, 3); err == nil { 120 | t.Error("ConsistencyProof(6, 3) succeeded unexpectedly") 121 | } 122 | 123 | for size1 := uint64(0); size1 <= 8; size1++ { 124 | for size2 := size1; size2 <= 8; size2++ { 125 | t.Run(fmt.Sprintf("%d:%d", size1, size2), func(t *testing.T) { 126 | got, err := mt.ConsistencyProof(size1, size2) 127 | if err != nil { 128 | t.Fatalf("ConsistencyProof: %v", err) 129 | } 130 | want := refConsistencyProof(entries[:size2], size2, size1, mt.hasher, true) 131 | if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" { 132 | t.Errorf("ConsistencyProof: diff (-got +want)\n%s", diff) 133 | } 134 | }) 135 | } 136 | } 137 | } 138 | 139 | // Make random proof queries and check against the reference implementation. 140 | func TestTreeConsistencyProofFuzz(t *testing.T) { 141 | entries := genEntries(256) 142 | 143 | for treeSize := uint64(1); treeSize <= 256; treeSize++ { 144 | mt := newTree(entries[:treeSize]) 145 | for i := 0; i < 8; i++ { 146 | size2 := rand.Uint64N(treeSize + 1) 147 | size1 := rand.Uint64N(size2 + 1) 148 | 149 | got, err := mt.ConsistencyProof(size1, size2) 150 | if err != nil { 151 | t.Fatalf("ConsistencyProof: %v", err) 152 | } 153 | want := refConsistencyProof(entries[:size2], size2, size1, mt.hasher, true) 154 | if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" { 155 | t.Errorf("ConsistencyProof: diff (-got +want)\n%s", diff) 156 | } 157 | } 158 | } 159 | } 160 | 161 | func TestTreeAppend(t *testing.T) { 162 | entries := genEntries(256) 163 | mt1 := newTree(entries) 164 | 165 | mt2 := newTree(nil) 166 | for _, entry := range entries { 167 | mt2.Append(rfc6962.DefaultHasher.HashLeaf(entry)) 168 | } 169 | 170 | if diff := cmp.Diff(mt1, mt2, cmp.AllowUnexported(Tree{})); diff != "" { 171 | t.Errorf("Trees built with AppendData and Append mismatch: diff (-mt1 +mt2)\n%s", diff) 172 | } 173 | } 174 | 175 | func TestTreeAppendAssociativity(t *testing.T) { 176 | entries := genEntries(256) 177 | mt1 := newTree(nil) 178 | mt1.AppendData(entries...) 179 | 180 | mt2 := newTree(nil) 181 | for _, entry := range entries { 182 | mt2.AppendData(entry) 183 | } 184 | 185 | if diff := cmp.Diff(mt1, mt2, cmp.AllowUnexported(Tree{})); diff != "" { 186 | t.Errorf("AppendData is not associative: diff (-mt1 +mt2)\n%s", diff) 187 | } 188 | } 189 | 190 | func newTree(entries [][]byte) *Tree { 191 | tree := New(rfc6962.DefaultHasher) 192 | tree.AppendData(entries...) 193 | return tree 194 | } 195 | 196 | // genEntries a slice of entries of the given size. 197 | func genEntries(size uint64) [][]byte { 198 | entries := make([][]byte, size) 199 | for i := range entries { 200 | entries[i] = []byte(strconv.Itoa(i)) 201 | } 202 | return entries 203 | } 204 | --------------------------------------------------------------------------------