├── .clang-format ├── .github ├── dependabot.yml ├── mergify.yml └── workflows │ ├── go.yml │ ├── lint-pr.yml │ ├── proto-register.yml │ ├── proto.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── buf.work.yaml ├── docs ├── README.md └── membership │ ├── 01-verify-membership.png │ ├── 02-verify.png │ ├── 03-existence-proof.png │ ├── 04-check-against-spec-1.png │ ├── 05-check-against-spec-2.png │ ├── 06-check-against-spec-3.png │ ├── 07-leafop-apply-1.png │ ├── 08-leafop-apply-2.png │ ├── 09-innerop-apply-1.png │ ├── 10-innerop-apply-2.png │ ├── 11-compare-root-hash.png │ └── tree-verify-membership.png ├── go ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── Makefile ├── fuzz_test.go ├── go.mod ├── go.sum ├── ics23.go ├── ops.go ├── ops_data_test.go ├── ops_test.go ├── proof.go ├── proof_data_test.go ├── proof_test.go ├── proofs.pb.go ├── sonar-project.properties ├── testdata │ └── fuzz │ │ ├── FuzzExistenceProofCalculate │ │ └── eb33978947ce32e1 │ │ ├── FuzzExistenceProofCheckAgainstSpec │ │ ├── 09bebc2fc8d0a79b │ │ ├── 19e35d361fe85847 │ │ ├── 1f84363823f5c624 │ │ └── a9ba9cba7c7724a0 │ │ ├── FuzzVerifyMembership │ │ ├── 8093511184ad3e25 │ │ └── 99dd1125ca292163 │ │ └── FuzzVerifyNonMembership │ │ ├── 5f0bfc6c6efd28aa │ │ └── 8093511184ad3e25 ├── vectors_data_test.go └── vectors_test.go ├── proto ├── buf.gen.gogo.yaml ├── buf.yaml └── cosmos │ └── ics23 │ └── v1 │ └── proofs.proto ├── rust ├── .cargo │ └── config ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── codegen │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── no-std-check │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── README.md │ └── src │ │ └── lib.rs └── src │ ├── api.rs │ ├── compress.rs │ ├── cosmos.ics23.v1.rs │ ├── cosmos.ics23.v1.serde.rs │ ├── helpers.rs │ ├── host_functions.rs │ ├── lib.rs │ ├── ops.rs │ ├── proto_descriptor.bin │ └── verify.rs ├── scripts ├── protocgen_go.sh └── protocgen_rust.sh ├── sonar-project.properties └── testdata ├── TestCheckAgainstSpecData.json ├── TestCheckLeafData.json ├── TestDoHashData.json ├── TestEmptyBranchData.json ├── TestExistenceProofData.json ├── TestInnerOpData.json ├── TestLeafOpData.json ├── iavl ├── exist_left.json ├── exist_middle.json ├── exist_right.json ├── nonexist_left.json ├── nonexist_middle.json └── nonexist_right.json ├── smt ├── exist_left.json ├── exist_middle.json ├── exist_right.json ├── nonexist_left.json ├── nonexist_middle.json └── nonexist_right.json └── tendermint ├── exist_left.json ├── exist_middle.json ├── exist_right.json ├── nonexist_left.json ├── nonexist_middle.json └── nonexist_right.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Proto 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveDeclarations: true 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: true 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 120 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: true 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: None 82 | ObjCBlockIndentWidth: 2 83 | ObjCSpaceAfterProperty: false 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Right 93 | RawStringFormats: 94 | - Delimiters: 95 | - pb 96 | Language: TextProto 97 | BasedOnStyle: google 98 | ReflowComments: true 99 | SortIncludes: true 100 | SortUsingDeclarations: true 101 | SpaceAfterCStyleCast: false 102 | SpaceAfterTemplateKeyword: true 103 | SpaceBeforeAssignmentOperators: true 104 | SpaceBeforeParens: ControlStatements 105 | SpaceInEmptyParentheses: false 106 | SpacesBeforeTrailingComments: 1 107 | SpacesInAngles: false 108 | SpacesInContainerLiterals: false 109 | SpacesInCStyleCastParentheses: false 110 | SpacesInParentheses: false 111 | SpacesInSquareBrackets: false 112 | Standard: Cpp11 113 | TabWidth: 8 114 | UseTab: Never 115 | ... 116 | 117 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | commit-message: 10 | prefix: "chore" 11 | 12 | # Maintain dependencies for Composer 13 | - package-ecosystem: "gomod" 14 | directory: "/go" 15 | schedule: 16 | interval: "weekly" 17 | commit-message: 18 | prefix: "chore" 19 | 20 | # Maintain dependencies for Composer 21 | - package-ecosystem: "cargo" 22 | directory: "/rust" 23 | schedule: 24 | interval: "weekly" 25 | commit-message: 26 | prefix: "chore" 27 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | conditions: 4 | - "#approved-reviews-by>=1" 5 | - base=master 6 | - label=automerge 7 | 8 | pull_request_rules: 9 | - name: automerge to main with label automerge and branch protection passing 10 | conditions: 11 | - "#approved-reviews-by>=1" 12 | - base=main 13 | - label=automerge 14 | actions: 15 | queue: 16 | name: default 17 | method: squash 18 | commit_message_template: | 19 | {{ title }} (#{{ number }}) 20 | {{ body }} 21 | - name: backport patches to release/v0.9.x branch 22 | conditions: 23 | - base=main 24 | - label=backport:v0.9.x 25 | actions: 26 | backport: 27 | branches: 28 | - release/v0.9.x 29 | - name: backport patches to release/v0.10.x branch 30 | conditions: 31 | - base=main 32 | - label=backport:v0.10.x 33 | actions: 34 | backport: 35 | branches: 36 | - release/v0.10.x 37 | - name: backport patches to release/v0.11.x branch 38 | conditions: 39 | - base=main 40 | - label=backport:v0.11.x 41 | actions: 42 | backport: 43 | branches: 44 | - release/v0.11.x -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | pull_request: 4 | paths: 5 | - go/** 6 | - .github/workflows/go.yml 7 | push: 8 | branches: 9 | - master 10 | 11 | permissions: 12 | contents: read 13 | 14 | concurrency: 15 | group: go-ci-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | lint: 20 | name: Lint 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: "1.21" 26 | - uses: actions/checkout@v4 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v6 29 | with: 30 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 31 | version: v1.54.2 32 | working-directory: go 33 | 34 | test: 35 | name: Test 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - uses: actions/setup-go@v5 40 | with: 41 | go-version: "1.21" 42 | - name: test 43 | working-directory: ./go 44 | run: make test 45 | 46 | analysis: 47 | name: Go analysis 48 | needs: [test] 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v4 52 | with: 53 | # Disabling shallow clone is recommended for improving relevancy of reporting 54 | fetch-depth: 0 55 | - name: sonarcloud 56 | if: ${{ env.GIT_DIFF && !github.event.pull_request.draft && env.SONAR_TOKEN != null }} 57 | uses: SonarSource/sonarcloud-github-action@v4.0.0 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 61 | with: 62 | projectBaseDir: go 63 | 64 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | name: Lint 16 | permissions: 17 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs 18 | statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: amannn/action-semantic-pull-request@v5.5.3 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/proto-register.yml: -------------------------------------------------------------------------------- 1 | name: Buf-Push 2 | # Protobuf runs buf (https://buf.build/) push updated proto files to https://buf.build/cosmos/cosmos-sdk 3 | # This workflow is only run when a .proto file has been changed 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - "proto/**" 10 | 11 | jobs: 12 | push: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: bufbuild/buf-setup-action@v1.49.0 17 | - uses: bufbuild/buf-push-action@v1 18 | with: 19 | input: "proto" 20 | buf_token: ${{ secrets.BUF_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/proto.yml: -------------------------------------------------------------------------------- 1 | name: Protobuf 2 | # Protobuf runs buf (https://buf.build/) lint and check-breakage 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: bufbuild/buf-setup-action@v1.49.0 16 | - uses: bufbuild/buf-lint-action@v1 17 | with: 18 | input: "proto" 19 | 20 | break-check: 21 | runs-on: ubuntu-latest 22 | name: Protobuf break check 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: bufbuild/buf-setup-action@v1.49.0 26 | - uses: bufbuild/buf-breaking-action@v1 27 | with: 28 | input: "proto" 29 | against: "https://github.com/${{ github.repository }}.git#branch=${{ github.event.pull_request.base.ref }},ref=HEAD~1,subdir=proto" 30 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | pull_request: 4 | paths: 5 | - rust/** 6 | - .github/workflows/rust.yml 7 | push: 8 | branches: 9 | - master 10 | 11 | permissions: 12 | contents: read 13 | 14 | concurrency: 15 | group: rust-ci-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | fmt: 20 | name: Format 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | - name: Check formatting 28 | working-directory: ./rust 29 | run: cargo fmt -- --check 30 | 31 | clippy: 32 | name: Clippy 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | - uses: taiki-e/install-action@cargo-hack 40 | 41 | - name: Clippy (features powerset) 42 | working-directory: ./rust 43 | run: cargo hack clippy --feature-powerset --no-dev-deps -- -D warnings 44 | 45 | - name: Clippy (features powerset, tests) 46 | working-directory: ./rust 47 | run: cargo hack clippy --tests --feature-powerset -- -D warnings 48 | 49 | test: 50 | name: Test 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: actions-rs/toolchain@v1 55 | with: 56 | toolchain: stable 57 | - name: Check all target in std 58 | working-directory: ./rust 59 | run: cargo check --all 60 | 61 | - name: Check all target in no_std 62 | working-directory: ./rust 63 | run: cargo check --no-default-features --all 64 | 65 | - name: Build all targets in std 66 | working-directory: ./rust 67 | run: cargo build --all --all-targets 68 | 69 | - name: Build all targets in no_std 70 | working-directory: ./rust 71 | run: cargo build --all --no-default-features 72 | 73 | - name: Run all tests with no-std 74 | working-directory: ./rust 75 | run: cargo test --all --no-default-features 76 | 77 | - name: Run all tests with std 78 | working-directory: ./rust 79 | run: cargo test --all 80 | 81 | - name: Check no_std compatibility 82 | run: cd rust/no-std-check/; make setup; make all 83 | 84 | coverage: 85 | name: Code Coverage 86 | runs-on: ubuntu-latest 87 | env: 88 | CARGO_TERM_COLOR: always 89 | steps: 90 | - uses: actions/checkout@v4 91 | - name: Install Rust 92 | uses: actions-rs/toolchain@v1 93 | with: 94 | toolchain: stable 95 | override: true 96 | - name: Install cargo-llvm-cov 97 | uses: taiki-e/install-action@cargo-llvm-cov 98 | - name: Install cargo-nextest 99 | uses: taiki-e/install-action@nextest 100 | - name: Generate code coverage 101 | working-directory: ./rust 102 | run: cargo llvm-cov nextest --all-features --lcov --output-path lcov.info 103 | - name: Upload coverage to Codecov 104 | uses: codecov/codecov-action@v5 105 | with: 106 | token: ${{ secrets.CODECOV_TOKEN }} 107 | files: rust/lcov.info 108 | flags: rust 109 | fail_ci_if_error: true 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | For Rust, please check [here](./rust/CHANGELOG.md). 4 | 5 | For Go, please check [here](./go/CHANGELOG.md). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Confio UO 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER := $(shell which docker) 2 | HTTPS_GIT := https://github.com/cosmos/cosmos-sdk.git 3 | 4 | ##### GO commands ##### 5 | 6 | include go/Makefile 7 | 8 | 9 | ##### Protobuf ##### 10 | protoVer=0.11.2 11 | protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) 12 | protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) 13 | 14 | ##### Rust ##### 15 | rustVer=1.70-slim 16 | rustImageName=rust:$(rustVer) 17 | rustImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(rustImageName) 18 | 19 | proto-all: proto-format proto-lint proto-gen-go proto-gen-rust 20 | 21 | proto-gen-go: 22 | @echo "Generating Protobuf definitons for Go" 23 | @$(protoImage) sh ./scripts/protocgen_go.sh 24 | 25 | proto-gen-rust: 26 | @echo "Generating Protobuf definitons for Rust" 27 | @$(rustImage) sh ./scripts/protocgen_rust.sh 28 | 29 | proto-format: 30 | @$(protoImage) find ./ -name "*.proto" -exec clang-format -i {} \; 31 | 32 | proto-lint: 33 | @$(protoImage) buf lint --error-format=json 34 | 35 | proto-check-breaking: 36 | @$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=master 37 | 38 | .PHONY: proto-all proto-gen-go proto-gen-rust proto-format proto-lint proto-check-breaking 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Cosmos ecosystem][cosmos-shield]][cosmos-link] 2 | 3 | # ICS 23 [![Apache 2.0 Licensed][license-badge]][license-link] 4 | 5 | | Language | Test Suite | Code Coverage | 6 | | ------------------ | ------------------------------------------------- | ---------------------------------------------- | 7 | | [Go](./go) | [![Go Test][go-test-badge]][go-test-link] | [![Go Cov][go-cov-badge]][go-cov-link] | 8 | | [Rust](./rust) | [![Rust Test][rust-test-badge]][rust-test-link] | [![Rust Cov][rust-cov-badge]][rust-cov-link] | 9 | 10 | [cosmos-link]: https://cosmos.network 11 | [go-test-link]: https://github.com/cosmos/ics23/actions/workflows/go.yml 12 | [go-test-badge]: https://github.com/cosmos/ics23/actions/workflows/go.yml/badge.svg?branch=master 13 | [go-cov-link]: https://sonarcloud.io/project/configuration?id=ics23-go 14 | [go-cov-badge]: https://sonarcloud.io/api/project_badges/measure?project=ics23-go&metric=coverage 15 | [rust-test-link]: https://github.com/cosmos/ics23/actions/workflows/rust.yml 16 | [rust-test-badge]: https://github.com/cosmos/ics23/actions/workflows/rust.yml/badge.svg?branch=master 17 | [rust-cov-link]: https://codecov.io/gh/cosmos/ics23/tree/master/rust 18 | [rust-cov-badge]: https://codecov.io/github/cosmos/ics23/branch/master/graph/badge.svg?token=xlGriS907o&flag=rust 19 | [license-link]: https://github.com/cosmos/ics23/blob/master/LICENSE 20 | [license-badge]: https://img.shields.io/badge/license-Apache2.0-blue.svg 21 | 22 | ## Proofs 23 | 24 | This is an attempt to define a generic, cross-language, binary representation 25 | of merkle proofs, which can be generated by many underlying merkle tree storage 26 | implementations and validated by many client libraries over various languages. 27 | 28 | The end goal is to provide a standard representation not only for light-client 29 | proofs of blockchains, but more specifically for proofs that accompany IBC 30 | (inter-blockchain communication) packets as defined in the cosmos specification. 31 | 32 | ## Feature set 33 | 34 | The features and naming follow the [ICS23: Vector Commitments](https://github.com/cosmos/ibc/tree/master/spec/core/ics-023-vector-commitments) Specification 35 | 36 | * Proof of Existence (key-value pair linked to root hash) 37 | * Hold Existence Proof to db-specific spec (avoid reinterpretation of bytes to different key-value pair) 38 | * Proof of Non-Existence (key proven not to be inside tree with given root hash) 39 | 40 | ### Future features 41 | 42 | * Batch Proof or Range Proof (prove many keys at once, more efficiently than separate proofs) 43 | 44 | ## Organization 45 | 46 | The top level package will provide language-agnostic documentation and protobuf specifications. 47 | Every language should have a sub-folder, containing both protobuf generated code, 48 | as well as client-side code for validating such proofs. The server-side code should 49 | live alongside the various merkle tree representations (eg. not in this repository). 50 | 51 | ### Supported client languages 52 | 53 | * [Go](./go) 54 | * [Rust](./rust) 55 | 56 | The repository used to have a TypeScript implementation, but due to lack of maintenance and usage, it was removed in [#353](https://github.com/cosmos/ics23/pull/353). 57 | 58 | Anyone interested in adding support for Solidity could pick up where [#58](https://github.com/cosmos/ics23/pull/58) left off. 59 | 60 | ### Compatibility table 61 | 62 | | ICS 023 Spec | Go | Rust | 63 | |---------------|-------------------|---------------------| 64 | | 2019-08-25 | 0.9.x | 0.9.x | 65 | | 2019-08-25 | 0.10.x | 0.10.x, 0.11.x | 66 | 67 | ## Supported merkle stores 68 | 69 | * [cosmos/iavl](https://github.com/cosmos/iavl) 70 | * [cometbft/crypto/merkle](https://github.com/cometbft/cometbft/tree/main/crypto/merkle) 71 | * [penumbra-zone/jmt](https://github.com/penumbra-zone/jmt) 72 | 73 | Supported merkle stores must be lexicographically ordered to maintain soundness. 74 | 75 | ### Unsupported 76 | 77 | * [turbofish-org/merk](https://github.com/turbofish-org/merk) 78 | * [go-ethereum Merkle Patricia Trie](https://github.com/ethereum/go-ethereum/blob/master/trie/trie.go) 79 | 80 | Ethan Frey [spent quite some time](https://github.com/confio/proofs-ethereum) to wrestle out a well-defined serialization and a validation logic that didn't involve importing too much code from go-ethereum (copying parts and stripping it down to the minimum). At the end, he realized the key is only present from parsing the entire path and is quite a painstaking process, even with go code that imports rlp and has the custom patricia key encodings. After a long time reflecting, he cannot see any way to implement this that doesn't either: (A) allow the provider to forge a different key that cannot be detected by the verifier or (B) include a huge amount of custom code in the client side. 81 | 82 | If anyone has a solution to this, please open an issue in this repository. 83 | 84 | ## Known limitations 85 | 86 | This format is designed to support any merklized data store that encodes key-value pairs into 87 | a node, and computes a root hash by repeatedly concatenating hashes and re-hashing them. 88 | 89 | Notably, this requires the *key* to be present in the leaf node in order to enforce the structure properly 90 | and prove the *key* provided matches the proof without extensive db-dependent code. Since some 91 | tries (such as Ethereum's Patricia Trie) do not store the key in the leaf, but require precise analysis of 92 | every step along the path in order to reconstruct the path, these are not supported. Doing so would 93 | require a much more complex format and most likely custom code for each such database. The design goal 94 | was to be able to add new data sources with only configuration (spec object), rather than custom code. 95 | 96 | [cosmos-shield]: https://img.shields.io/static/v1?label=&labelColor=1B1E36&color=1B1E36&message=cosmos%20ecosystem&style=for-the-badge&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjMuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAyNTAwIDI1MDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDI1MDAgMjUwMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM2RjczOTA7fQoJLnN0MXtmaWxsOiNCN0I5Qzg7fQo8L3N0eWxlPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNMTI1Mi42LDE1OS41Yy0xMzQuOSwwLTI0NC4zLDQ4OS40LTI0NC4zLDEwOTMuMXMxMDkuNCwxMDkzLjEsMjQ0LjMsMTA5My4xczI0NC4zLTQ4OS40LDI0NC4zLTEwOTMuMQoJUzEzODcuNSwxNTkuNSwxMjUyLjYsMTU5LjV6IE0xMjY5LjQsMjI4NGMtMTUuNCwyMC42LTMwLjksNS4xLTMwLjksNS4xYy02Mi4xLTcyLTkzLjItMjA1LjgtOTMuMi0yMDUuOAoJYy0xMDguNy0zNDkuOC04Mi44LTExMDAuOC04Mi44LTExMDAuOGM1MS4xLTU5Ni4yLDE0NC03MzcuMSwxNzUuNi03NjguNGM2LjctNi42LDE3LjEtNy40LDI0LjctMmM0NS45LDMyLjUsODQuNCwxNjguNSw4NC40LDE2OC41CgljMTEzLjYsNDIxLjgsMTAzLjMsODE3LjksMTAzLjMsODE3LjljMTAuMywzNDQuNy01Ni45LDczMC41LTU2LjksNzMwLjVDMTM0MS45LDIyMjIuMiwxMjY5LjQsMjI4NCwxMjY5LjQsMjI4NHoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIyMDAuNyw3MDguNmMtNjcuMi0xMTcuMS01NDYuMSwzMS42LTEwNzAsMzMycy04OTMuNSw2MzguOS04MjYuMyw3NTUuOXM1NDYuMS0zMS42LDEwNzAtMzMyCglTMjI2Ny44LDgyNS42LDIyMDAuNyw3MDguNkwyMjAwLjcsNzA4LjZ6IE0zNjYuNCwxNzgwLjRjLTI1LjctMy4yLTE5LjktMjQuNC0xOS45LTI0LjRjMzEuNi04OS43LDEzMi0xODMuMiwxMzItMTgzLjIKCWMyNDkuNC0yNjguNCw5MTMuOC02MTkuNyw5MTMuOC02MTkuN2M1NDIuNS0yNTIuNCw3MTEuMS0yNDEuOCw3NTMuOC0yMzBjOS4xLDIuNSwxNSwxMS4yLDE0LDIwLjZjLTUuMSw1Ni0xMDQuMiwxNTctMTA0LjIsMTU3CgljLTMwOS4xLDMwOC42LTY1Ny44LDQ5Ni44LTY1Ny44LDQ5Ni44Yy0yOTMuOCwxODAuNS02NjEuOSwzMTQuMS02NjEuOSwzMTQuMUM0NTYsMTgxMi42LDM2Ni40LDE3ODAuNCwzNjYuNCwxNzgwLjRMMzY2LjQsMTc4MC40CglMMzY2LjQsMTc4MC40eiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNMjE5OC40LDE4MDAuNGM2Ny43LTExNi44LTMwMC45LTQ1Ni44LTgyMy03NTkuNVMzNzQuNCw1ODcuOCwzMDYuOCw3MDQuN3MzMDAuOSw0NTYuOCw4MjMuMyw3NTkuNQoJUzIxMzAuNywxOTE3LjQsMjE5OC40LDE4MDAuNHogTTM1MS42LDc0OS44Yy0xMC0yMy43LDExLjEtMjkuNCwxMS4xLTI5LjRjOTMuNS0xNy42LDIyNC43LDIyLjYsMjI0LjcsMjIuNgoJYzM1Ny4yLDgxLjMsOTk0LDQ4MC4yLDk5NCw0ODAuMmM0OTAuMywzNDMuMSw1NjUuNSw0OTQuMiw1NzYuOCw1MzcuMWMyLjQsOS4xLTIuMiwxOC42LTEwLjcsMjIuNGMtNTEuMSwyMy40LTE4OC4xLTExLjUtMTg4LjEtMTEuNQoJYy00MjIuMS0xMTMuMi03NTkuNi0zMjAuNS03NTkuNi0zMjAuNWMtMzAzLjMtMTYzLjYtNjAzLjItNDE1LjMtNjAzLjItNDE1LjNjLTIyNy45LTE5MS45LTI0NS0yODUuNC0yNDUtMjg1LjRMMzUxLjYsNzQ5Ljh6Ii8+CjxjaXJjbGUgY2xhc3M9InN0MSIgY3g9IjEyNTAiIGN5PSIxMjUwIiByPSIxMjguNiIvPgo8ZWxsaXBzZSBjbGFzcz0ic3QxIiBjeD0iMTc3Ny4zIiBjeT0iNzU2LjIiIHJ4PSI3NC42IiByeT0iNzcuMiIvPgo8ZWxsaXBzZSBjbGFzcz0ic3QxIiBjeD0iNTUzIiBjeT0iMTAxOC41IiByeD0iNzQuNiIgcnk9Ijc3LjIiLz4KPGVsbGlwc2UgY2xhc3M9InN0MSIgY3g9IjEwOTguMiIgY3k9IjE5NjUiIHJ4PSI3NC42IiByeT0iNzcuMiIvPgo8L3N2Zz4K 97 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Coordinated Vulnerability Disclosure Policy 2 | 3 | The Cosmos ecosystem believes that strong security is a blend of highly 4 | technical security researchers who care about security and the forward 5 | progression of the ecosystem and the attentiveness and openness of Cosmos core 6 | contributors to help continually secure our operations. 7 | 8 | > **IMPORTANT**: *DO NOT* open public issues on this repository for security 9 | > vulnerabilities. 10 | 11 | ## Scope 12 | 13 | | Scope | 14 | |-----------------------| 15 | | last release (tagged) | 16 | | main branch | 17 | 18 | The latest **release tag** of this repository is supported for security updates 19 | as well as the **main** branch. Security vulnerabilities should be reported if 20 | the vulnerability can be reproduced on either one of those. 21 | 22 | ## Reporting a Vulnerability 23 | 24 | | Reporting methods | 25 | |---------------------------------------------------------------| 26 | | [GitHub Private Vulnerability Reporting][gh-private-advisory] | 27 | | [HackerOne bug bounty program][h1] | 28 | 29 | All security vulnerabilities can be reported under GitHub's [Private 30 | vulnerability reporting][gh-private-advisory] system. This will open a private 31 | issue for the developers. Try to fill in as much of the questions as possible. 32 | If you are not familiar with the CVSS system for assessing vulnerabilities, just 33 | use the Low/High/Critical severity ratings. A partially filled in report for a 34 | critical vulnerability is still better than no report at all. 35 | 36 | Vulnerabilities associated with the **Go, Rust or Protobuf code** of the 37 | repository may be eligible for a [bug bounty][h1]. Please see the bug bounty 38 | page for more details on submissions and rewards. If you think the vulnerability 39 | is eligible for a payout, **report on HackerOne first**. 40 | 41 | Vulnerabilities in services and their source codes (JavaScript, web page, Google 42 | Workspace) are not in scope for the bug bounty program, but they are welcome to 43 | be reported in GitHub. 44 | 45 | ### Guidelines 46 | 47 | We require that all researchers: 48 | 49 | * Abide by this policy to disclose vulnerabilities, and avoid posting 50 | vulnerability information in public places, including GitHub, Discord, 51 | Telegram, and Twitter. 52 | * Make every effort to avoid privacy violations, degradation of user experience, 53 | disruption to production systems (including but not limited to the Cosmos 54 | Hub), and destruction of data. 55 | * Keep any information about vulnerabilities that you’ve discovered confidential 56 | between yourself and the Cosmos engineering team until the issue has been 57 | resolved and disclosed. 58 | * Avoid posting personally identifiable information, privately or publicly. 59 | 60 | If you follow these guidelines when reporting an issue to us, we commit to: 61 | 62 | * Not pursue or support any legal action related to your research on this 63 | vulnerability 64 | * Work with you to understand, resolve and ultimately disclose the issue in a 65 | timely fashion 66 | 67 | ### More information 68 | 69 | * See [TIMELINE.md] for an example timeline of a disclosure. 70 | * See [DISCLOSURE.md] to see more into the inner workings of the disclosure 71 | process. 72 | * See [EXAMPLES.md] for some of the examples that we are interested in for the 73 | bug bounty program. 74 | 75 | [gh-private-advisory]: /../../security/advisories/new 76 | [h1]: https://hackerone.com/cosmos 77 | [TIMELINE.md]: https://github.com/cosmos/security/blob/main/TIMELINE.md 78 | [DISCLOSURE.md]: https://github.com/cosmos/security/blob/main/DISCLOSURE.md 79 | [EXAMPLES.md]: https://github.com/cosmos/security/blob/main/EXAMPLES.md 80 | -------------------------------------------------------------------------------- /buf.work.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | 3 | directories: 4 | - proto 5 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ICS 23 2 | 3 | As already stated in the README.md, ICS 23 attempts to be a generic library to represent and verify merkle proofs for (ideally) many different merkle tree storage implementations. The library is heavily used in [ibc-go](https://github.com/cosmos/ibc-go) for proof verification. The following documentation uses the Golang implementation as a reference. 4 | 5 | The two most important top level types in the library are `ProofSpec` and `CommitmmentProof`. We will explain them in the coming sections. 6 | 7 | ## Proof specs 8 | 9 | The [`ProofSpec`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L145-L170) message defines what the expected parameters are for a given proof type. Different types of merkle trees will have different proof specs. For example, for [IAVL](https://github.com/cosmos/iavl) trees, [this](https://github.com/cosmos/ics23/blob/go/v0.10.0/go/proof.go#L9-L26) is the corresponding proof spec. The proof spec defines any contraints (e.g. minimum and maximum allowed depth of trees), what data should be added (e.g. prefix or suffix data), what operations are executed on [leaf](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L96-L120) and [inner](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L172-L194) nodes of the tree when calculating the root hash, etc. 10 | 11 | ```proto 12 | message ProofSpec { 13 | LeafOp leaf_spec = 1; 14 | InnerSpec inner_spec = 2; 15 | int32 max_depth = 3; 16 | int32 min_depth = 4; 17 | bool prehash_key_before_comparison = 5; 18 | } 19 | ``` 20 | 21 | where: 22 | 23 | - `max_depth` is the maximum number of inner nodes in the tree. 24 | - `min-depth` is the minimum number of inner nodes in the tree. 25 | - `prehash_key_before_comparison` is a flag that indicates whether to prehash the key using the `prehash_key` hash operation specified in the `leaf_spec`. This is used to compare lexical ordering of keys for non-existence proofs. 26 | 27 | The `leaf_spec` is a [`LeafOp`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L96-L120). The `LeafOp` type specifies the internal transformation from the original key/value pair of the leaf node into a hash. It specifies any prefix that should prepended and the hash operations to transform the key and value, among other things: 28 | 29 | ```proto 30 | message LeafOp { 31 | HashOp hash = 1; 32 | HashOp prehash_key = 2; 33 | HashOp prehash_value = 3; 34 | LengthOp length = 4; 35 | bytes prefix = 5; 36 | } 37 | ``` 38 | 39 | where: 40 | 41 | - `hash` is [hash operation](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L7-L16) that is applied to the result of `(prefix || length(prehash_key(key)) || length(prehash_value(value))`, where the `||` operator is concatenation of binary data and `key` and `value` are the key and value of the leaf node. 42 | - `prehash_key` is the hash operation that is applied to the key of the leaf. 43 | - `prehash_value` is the hash operation that is applied to the value of the leaf. 44 | - `length` is the [length encoding operation](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L18-L43) that determines the length of the input and returns it prepended to the data. 45 | - `prefix` is a fixed set of bytes that may optionally be prepended to differentiate a leaf node from an inner node. 46 | 47 | And `inner_spec` is an [`InnerSpec`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L172-L194). The `InnerSpec` specifies all store-specific structure information to determine if two proofs from a 48 | given store are neighbors. 49 | 50 | ```proto 51 | message InnerSpec { 52 | repeated int32 child_order = 1; 53 | int32 child_size = 2; 54 | int32 min_prefix_length = 3; 55 | int32 max_prefix_length = 4; 56 | bytes empty_child = 5; 57 | HashOp hash = 6; 58 | } 59 | ``` 60 | 61 | where: 62 | 63 | - `child_order` is the ordering of the children node and counts from 0. For example, for an IAVL tree is [0, 1] (left, then right); for a merk tree is [0, 2, 1] (left, right, here). 64 | - `child_size` is the size of the child node when calculating the root hash. For example, the child size for an [IAVL tree](https://github.com/cosmos/ics23/blob/4fd3a5af9a290e80bca166fd40f0ef316e167245/go/proof.go#L22) is 33, as the child is: `length op (1 byte) | child_hash (32 bytes)` 65 | - `min_prefix_length` is the minimum allowed prefix length. For example, for an IAVL tree the `min_prefix_length` is [4](https://github.com/cosmos/ics23/blob/4fd3a5af9a290e80bca166fd40f0ef316e167245/go/proof.go#L20). This derives from the logic that when the child comes from the left, the prefix is: `IAVL height | IAVL size | IAVL version | length byte` 66 | - `max_prefix_length` is the maximum allowed prefix length. It must be strictly less than the `min_prefix_length` + `child_size`. 67 | - `empty_child` is the prehash image that is used when one child is `nil`. 68 | - `hash` is the hashing algorithm that must be used in each element of an `InnerOp` list in an `ExistenceProof`. 69 | 70 | ## Proof types 71 | 72 | The [`CommitmentProof`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L84-L94) is either an `ExistenceProof` or a `NonExistenceProof`, or a batch of such messages: 73 | 74 | ```proto 75 | message CommitmentProof { 76 | oneof proof { 77 | ExistenceProof exist = 1; 78 | NonExistenceProof nonexist = 2; 79 | BatchProof batch = 3; 80 | CompressedBatchProof compressed = 4; 81 | } 82 | } 83 | ``` 84 | 85 | At the moment the library has support for [`ExistenceProof`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L45-L71) and [`NonExistenceProof`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L73-L82). 86 | 87 | The `ExistenceProof` 88 | 89 | ```proto 90 | message ExistenceProof { 91 | bytes key = 1; 92 | bytes value = 2; 93 | LeafOp leaf = 3; 94 | repeated InnerOp path = 4; 95 | } 96 | ``` 97 | 98 | where: 99 | 100 | - `key` is the key of the leaf node. 101 | - `value` is the value of the leaf node. 102 | - `leaf` specifies the operations to perform on the leaf node to internally transform its data into a hash. 103 | - `path` is a list of items of type `InnerOp` that specify the operations to iteratively perform on inner nodes of a tree to calculate a root hash. `InnerOp` represents a merkle-proof step that is not a leaf. It represents concatenating two children and hashing them to provide the next result: 104 | 105 | ```proto 106 | message InnerOp { 107 | HashOp hash = 1; 108 | bytes prefix = 2; 109 | bytes suffix = 3; 110 | } 111 | ``` 112 | 113 | where: 114 | 115 | - `hash` is the hash operation that is applied to the result of `(prefix || child || suffix)`, where the `||` operator is concatenation of binary data and where `child` the result of hashing all the tree below this step. 116 | - `prefix` is is a fixed set of bytes that may optionally be prepended to differentiate leaf nodes from inner nodes. 117 | - `suffix` is a fixed set of bytes that may optionally be appended to differentiate leaf nodes from inner nodes. 118 | 119 | We will explain more details of the different supported proof types in the following sections, where we see an example of how each proof type is used. 120 | 121 | ## Membership verification 122 | 123 | For membership verification of a key/value pair in a merkle tree we need an [`ExistenceProof`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L45-L71). An `ExistenceProof` contains the key and value to prove existence of, and a set of steps to calculate a hash that can be compared with a merkle tree root hash. The `ExistenceProof` struct contains the method `Verify`: 124 | 125 | ```go 126 | func (p *ExistenceProof) Verify( 127 | spec *ProofSpec, 128 | root CommitmentRoot, 129 | key []byte, 130 | value []byte 131 | ) error 132 | ``` 133 | 134 | where `spec` is the corresponding `ProofSpec` for the concrete merkle tree, `root` is a byte slice that represents the merkle tree root hash (with which the calculated hash from the proof will be compared), and `key` and `value` are byte slices for the leaf node pair to be verified. We will see this method in action in the following sample program: 135 | 136 | ```go 137 | package main 138 | 139 | import ( 140 | "fmt" 141 | 142 | log "cosmossdk.io/log" 143 | iavl "github.com/cosmos/iavl" 144 | dbm "github.com/cosmos/iavl/db" 145 | ics23 "github.com/cosmos/ics23/go" 146 | ) 147 | 148 | func main() { 149 | key := []byte("c") 150 | value := []byte{3} 151 | 152 | tree := iavl.NewMutableTree(dbm.NewMemDB(), 0, false, log.NewNopLogger()) 153 | 154 | _, err := tree.Set(key, value) 155 | if err != nil { 156 | fmt.Println(err) 157 | } 158 | 159 | tree.Set([]byte("f"), []byte{6}) 160 | tree.Set([]byte("d"), []byte{4}) 161 | tree.Set([]byte("c"), []byte{3}) 162 | tree.Set([]byte("b"), []byte{2}) 163 | tree.Set([]byte("a"), []byte{1}) 164 | 165 | // retrieve root hash of merkle tree 166 | rootHash, version, err := tree.SaveVersion() 167 | if err != nil { 168 | fmt.Println(err) 169 | os.Exit(1) 170 | } 171 | 172 | fmt.Printf("saved version %v with root hash %x\n", version, rootHash) 173 | 174 | // retrieve membership proof for key "c" 175 | proof, err := tree.GetMembershipProof(key) 176 | if err != nil { 177 | fmt.Println(err) 178 | os.Exit(1) 179 | } 180 | 181 | // check membership of key/value "c"/3 182 | ok := ics23.VerifyMembership(ics23.IavlSpec, rootHash, proof, key, value) 183 | fmt.Println(ok) 184 | } 185 | ``` 186 | 187 | This simple program creates an IAVL tree, adds a few key/value pairs, and verifies an existence proof. The following diagram represents the IAVL tree created with the sample code above and the steps that the ics23 library performs [during membership verification](https://github.com/cosmos/ics23/blob/go/v0.10.0/go/proof.go#L110) (the hexadecimal values in the picture are the actual values generated during the verification for this particular tree and proof): 188 | 189 | ![tree verify memberhip](./membership/tree-verify-membership.png) 190 | 191 | If we run the program with the debugger and step through it, the entry point for ics23 is the [`VerifyMembership`](https://github.com/cosmos/ics23/blob/go/v0.10.0/go/ics23.go#L36) function: 192 | 193 | ![alt text](./membership/01-verify-membership.png) 194 | 195 | Inside `VerifyMembership` the `proof`, which is of type [`CommitmentProof`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L84-L94) is converted to an `ExistenceProof` and then the function `Verify` is called: 196 | 197 | ![alt text](./membership/02-verify.png) 198 | 199 | The existence proof contains a `LeafOp` and a `Path`, which is a list of items of type `InnerOp` (there are as many items as inner nodes on the tree - i.e. excluding the leaf and the root nodes): 200 | 201 | ![alt text](./membership/03-existence-proof.png) 202 | 203 | Inside `Verify` the existence proof is checked against the corresponging proof spec (i.e. an existence proof for an IAVL tree will be checked against the IAVL proof spec). [This means that the parameters of the leaf operation and the parameters of each inner operation in the path of the existence proof will be checked](https://github.com/cosmos/ics23/blob/go/v0.10.0/go/proof.go#L181-L206) against the specs for leaf and inner nodes: 204 | 205 | ![alt text](./membership/04-check-against-spec-1.png) 206 | ![alt text](./membership/05-check-against-spec-2.png) 207 | ![alt text](./membership/06-check-against-spec-3.png) 208 | 209 | If the check against the proof spec passes, then the leaf operation is applied: 210 | 211 | ![alt text](./membership/07-leafop-apply-1.png) 212 | ![alt text](./membership/08-leafop-apply-2.png) 213 | 214 | And the result of that operation is fed into the firs inner operation. The result of each inner operation will be fed into the next one until we iterate over the complete list of inner operations: 215 | 216 | ![alt text](./membership/09-innerop-apply-1.png) 217 | ![alt text](./membership/10-innerop-apply-2.png) 218 | 219 | The result from the last inner operation can be compared against the input root hash and if they match then the verification succeeds: 220 | 221 | ![alt text](./membership/11-compare-root-hash.png) 222 | 223 | ## Non-membership verification 224 | 225 | TODO: https://github.com/cosmos/ics23/issues/385 226 | -------------------------------------------------------------------------------- /docs/membership/01-verify-membership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/01-verify-membership.png -------------------------------------------------------------------------------- /docs/membership/02-verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/02-verify.png -------------------------------------------------------------------------------- /docs/membership/03-existence-proof.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/03-existence-proof.png -------------------------------------------------------------------------------- /docs/membership/04-check-against-spec-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/04-check-against-spec-1.png -------------------------------------------------------------------------------- /docs/membership/05-check-against-spec-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/05-check-against-spec-2.png -------------------------------------------------------------------------------- /docs/membership/06-check-against-spec-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/06-check-against-spec-3.png -------------------------------------------------------------------------------- /docs/membership/07-leafop-apply-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/07-leafop-apply-1.png -------------------------------------------------------------------------------- /docs/membership/08-leafop-apply-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/08-leafop-apply-2.png -------------------------------------------------------------------------------- /docs/membership/09-innerop-apply-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/09-innerop-apply-1.png -------------------------------------------------------------------------------- /docs/membership/10-innerop-apply-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/10-innerop-apply-2.png -------------------------------------------------------------------------------- /docs/membership/11-compare-root-hash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/11-compare-root-hash.png -------------------------------------------------------------------------------- /docs/membership/tree-verify-membership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/docs/membership/tree-verify-membership.png -------------------------------------------------------------------------------- /go/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /go/.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | # # timeout for analysis, e.g. 30s, 5m, default is 1m 4 | timeout: 5m 5 | 6 | linters: 7 | disable-all: true 8 | enable: 9 | - exportloopref 10 | - errcheck 11 | - gci 12 | - goconst 13 | - gocritic 14 | - gofumpt 15 | - gosec 16 | - gosimple 17 | - govet 18 | - ineffassign 19 | - misspell 20 | - nakedret 21 | - staticcheck 22 | - thelper 23 | - typecheck 24 | - stylecheck 25 | - revive 26 | - typecheck 27 | - tenv 28 | - unconvert 29 | # Prefer unparam over revive's unused param. It is more thorough in its checking. 30 | - unparam 31 | - unused 32 | - misspell 33 | 34 | issues: 35 | exclude-rules: 36 | - text: 'differs only by capitalization to method' 37 | linters: 38 | - revive 39 | - text: 'Use of weak random number generator' 40 | linters: 41 | - gosec 42 | 43 | max-issues-per-linter: 10000 44 | max-same-issues: 10000 45 | 46 | linters-settings: 47 | gci: 48 | sections: 49 | - standard # Standard section: captures all standard packages. 50 | - default # Default section: contains all imports that could not be matched to another section type. 51 | - blank # blank imports 52 | - dot # dot imports 53 | - prefix(cosmossdk.io) 54 | - prefix(github.com/cosmos/cosmos-sdk) 55 | - prefix(github.com/cometbft/cometbft) 56 | - prefix(github.com/cosmos/ics23) 57 | custom-order: true 58 | revive: 59 | enable-all-rules: true 60 | # Do NOT whine about the following, full explanation found in: 61 | # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#description-of-available-rules 62 | rules: 63 | - name: use-any 64 | disabled: true 65 | - name: if-return 66 | disabled: true 67 | - name: max-public-structs 68 | disabled: true 69 | - name: cognitive-complexity 70 | disabled: true 71 | - name: argument-limit 72 | disabled: true 73 | - name: cyclomatic 74 | disabled: true 75 | - name: file-header 76 | disabled: true 77 | - name: function-length 78 | disabled: true 79 | - name: function-result-limit 80 | disabled: true 81 | - name: line-length-limit 82 | disabled: true 83 | - name: flag-parameter 84 | disabled: true 85 | - name: add-constant 86 | disabled: true 87 | - name: empty-lines 88 | disabled: true 89 | - name: banned-characters 90 | disabled: true 91 | - name: deep-exit 92 | disabled: true 93 | - name: confusing-results 94 | disabled: true 95 | - name: unused-parameter 96 | disabled: true 97 | - name: modifies-value-receiver 98 | disabled: true 99 | - name: early-return 100 | disabled: true 101 | - name: nested-structs 102 | disabled: true 103 | - name: confusing-naming 104 | disabled: true 105 | - name: defer 106 | disabled: true 107 | # Disabled in favour of unparam. 108 | - name: unused-parameter 109 | disabled: true 110 | - name: unhandled-error 111 | disabled: false 112 | arguments: 113 | - 'fmt.Printf' 114 | - 'fmt.Print' 115 | - 'fmt.Println' 116 | - 'myFunction' 117 | -------------------------------------------------------------------------------- /go/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # Unreleased 4 | 5 | - deps: bump golang to v1.22 ([#363](https://github.com/cosmos/ics23/pull/363)). 6 | - fix: guarantee that `spec.InnerSpec.MaxPrefixLength` < `spec.InnerSpec.MinPrefixLength` + `spec.InnerSpec.ChildSize` ([#369](https://github.com/cosmos/ics23/pull/369)) 7 | - fix: return error instead of panic in `getPosition` which results in error returns from `IsLeftMost`, `IsRightMost`, `IsLeftNeighbor`, `leftBranchesAreEmpty`, `rightBranchesAreEmpty`, and `getPadding` 8 | - refactor: support for `BatchProof` and `CompressedBatchProof` is being dropped. 9 | * The API's `BatchVerifyMembership`, `BatchVerifyNonMembership`, and `CombineProofs` have been removed. ([#390](https://github.com/cosmos/ics23/pull/390)) 10 | * The API's `IsCompressed`, `Compress`, and `Decompress` have been removed. ([#392](https://github.com/cosmos/ics23/pull/392)) 11 | 12 | # v0.11.0 13 | 14 | - fix(go): interpret max_depth in proof specs as 128 if left to 0 ([#352](https://github.com/cosmos/ics23/pull/352)). 15 | - deps: bump cosmos/gogoproto to v1.7.0. 16 | - deps: bump x/crypto v0.26.0. 17 | - feat: add support for Blake2b/2s/3 hash functions ([#212](https://github.com/cosmos/ics23/pull/212)). 18 | - fix(go): bullet-proof against nil dereferences, add more fuzzers ([\#244](https://github.com/cosmos/ics23/pull/244)). 19 | 20 | # v0.10.0 21 | 22 | This release introduces one single boolean new parameter to the top-level `ProofSpec`: `prehash_compare_key`. 23 | When set to `true`, this flag causes keys to be consistently compared lexicographically according to their hashes 24 | within nonexistence proof verification, using the same hash function as specified by the already-extant `prehash_key` field. 25 | 26 | This is a backwards-compatible change, as it requires opt-in via setting the `prehash_compare_key` flag to `true` in the `ProofSpec`. 27 | All existing `ProofSpec`s will continue to behave identically. 28 | 29 | Please note that the version of the TypeScript library has been bump from 0.6.8 to 0.10.0 to align it with 30 | the Go and Rust implementations. 31 | 32 | ## Full changes 33 | 34 | - feat: Add `prehash_compare_key` to allow proving nonexistence in sparse trees ([#136](https://github.com/cosmos/ics23/pull/136)) 35 | - chore: retract previous versions ([#131](https://github.com/cosmos/ics23/pull/131)) 36 | - fix: protobuf formatting using clang-format ([#129](https://github.com/cosmos/ics23/pull/129)) 37 | - fix: use /go suffix in go package option ([#127](https://github.com/cosmos/ics23/pull/127)) 38 | - add buf support to repo ([#126](https://github.com/cosmos/ics23/pull/126)) 39 | - chore: Add Cosmos, license and coverage badges to the README ([#122](https://github.com/cosmos/ics23/pull/122)) 40 | - ci: Enable code coverage for TypeScript version ([#123](https://github.com/cosmos/ics23/pull/123)) 41 | - ci: Add tags to codecov reports ([#121](https://github.com/cosmos/ics23/pull/121)) (4 months ago) 42 | - ci: Refactor GitHub workflows and add code coverage job ([#120](https://github.com/cosmos/ics23/pull/120)) 43 | 44 | # v0.9.1 45 | 46 | This release is a backport into the `release/v0.9.x` line of the feature that added the `prehash_compare_key` boolean parameter to the top-level `ProofSpec`. 47 | When set to `true`, this flag causes keys to be consistently compared lexicographically according to their hashes 48 | within nonexistence proof verification, using the same hash function as specified by the already-extant `prehash_key` field. 49 | 50 | This is a backwards-compatible change, as it requires opt-in via setting the `prehash_compare_key` flag to `true` in the `ProofSpec`. 51 | All existing `ProofSpec`s will continue to behave identically. 52 | 53 | ## Full changes 54 | 55 | - feat(go): Add `prehash_compare_key` to allow proving nonexistence in sparse trees ([#136](https://github.com/cosmos/ics23/pull/136)) 56 | 57 | # v0.9.0 58 | 59 | Release of ics23/go including changes made in the fork of ics23/go housed in the [Cosmos SDK](http://github.com/cosmos/cosmos-sdk). 60 | 61 | # v0.7.0 62 | 63 | This handles non-existence tests for empty branches properly. This 64 | is needed for properly handling proofs on Tries, like the SMT being 65 | integrated with the Cosmos SDK. 66 | 67 | This is used in ibc-go v3 68 | 69 | # 0.6.x 70 | 71 | This handles proofs for normal merkle trees, where every branch is full. 72 | This works for tendermint merkle hashes and iavl hashes, and should work 73 | for merk (nomic's db) proofs. 74 | 75 | This was used in the original ibc release (cosmos sdk v0.40) and up until 76 | ibc-go v2. 77 | -------------------------------------------------------------------------------- /go/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test . 3 | 4 | .PHONY: test 5 | ##### Linting ##### 6 | 7 | golangci_lint_cmd=golangci-lint 8 | golangci_version=v1.50.0 9 | 10 | lint: 11 | @echo "--> Running linter" 12 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 13 | @$(golangci_lint_cmd) run --timeout=10m 14 | 15 | lint-fix: 16 | @echo "--> Running linter" 17 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 18 | @$(golangci_lint_cmd) run --fix --out-format=tab --issues-exit-code=0 19 | 20 | .PHONY: lint lint-fix 21 | 22 | format: 23 | @go install mvdan.cc/gofumpt@latest 24 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 25 | find . -name '*.go' -type f -not -name "*.pb.go" | xargs gofumpt -w -l 26 | $(golangci_lint_cmd) run --fix 27 | 28 | .PHONY: format 29 | 30 | -------------------------------------------------------------------------------- /go/fuzz_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | func FuzzExistenceProofCalculate(f *testing.F) { 12 | if testing.Short() { 13 | f.Skip("in -short mode") 14 | } 15 | 16 | seedJSON, err := os.ReadFile(filepath.Join("..", "testdata", "TestExistenceProofData.json")) 17 | if err != nil { 18 | f.Fatal(err) 19 | } 20 | 21 | // 2. Isolate the individual JSON per case which eases with fuzz case mutation. 22 | existenceWholeSeedJSON := make(map[string]json.RawMessage) 23 | if err := json.Unmarshal(seedJSON, &existenceWholeSeedJSON); err != nil { 24 | f.Fatal(err) 25 | } 26 | 27 | // 3. Add the seeds: 28 | for _, epJSON := range existenceWholeSeedJSON { 29 | f.Add([]byte(epJSON)) 30 | } 31 | 32 | // 4. Now run the fuzzer. 33 | f.Fuzz(func(t *testing.T, fJSON []byte) { 34 | ep := new(ExistenceProof) 35 | if err := json.Unmarshal(fJSON, ep); err != nil { 36 | return 37 | } 38 | 39 | // Now let's try this seemingly well formed ExistenceProof. 40 | _, _ = ep.Calculate() 41 | }) 42 | } 43 | 44 | func FuzzExistenceProofCheckAgainstSpec(f *testing.F) { 45 | if testing.Short() { 46 | f.Skip("in -short mode") 47 | } 48 | 49 | seedDataMap := CheckAgainstSpecTestData(f) 50 | for _, seed := range seedDataMap { 51 | if seed.Err != "" { 52 | // Erroneous data, skip it. 53 | continue 54 | } 55 | if blob, err := json.Marshal(seed); err == nil { 56 | f.Add(blob) 57 | } 58 | } 59 | 60 | // 2. Now run the fuzzer. 61 | f.Fuzz(func(t *testing.T, fJSON []byte) { 62 | pvs := new(CheckAgainstSpecTestStruct) 63 | if err := json.Unmarshal(fJSON, pvs); err != nil { 64 | return 65 | } 66 | if pvs.Proof == nil { 67 | // No need checking from a nil ExistenceProof. 68 | return 69 | } 70 | 71 | ep, spec := pvs.Proof, pvs.Spec 72 | _ = ep.CheckAgainstSpec(spec) 73 | }) 74 | } 75 | 76 | var batchVectorDataSeeds []*BatchVectorData 77 | 78 | func init() { 79 | svtdL := VectorsTestData() 80 | bsL := make([]*BatchVectorData, 0, len(svtdL)) 81 | for _, tc := range svtdL { 82 | proof, ref := LoadFile(new(testing.T), tc.Dir, tc.Filename) 83 | // Test Calculate method and skip if it produces invalid values. 84 | if _, err := proof.Calculate(); err != nil { 85 | continue 86 | } 87 | 88 | bsL = append(bsL, &BatchVectorData{ 89 | Spec: tc.Spec, 90 | Ref: ref, 91 | Proof: proof, 92 | }) 93 | } 94 | batchVectorDataSeeds = bsL 95 | } 96 | 97 | func FuzzVerifyNonMembership(f *testing.F) { 98 | if testing.Short() { 99 | f.Skip("in -short mode") 100 | } 101 | 102 | // 1. Add some seeds. 103 | for _, batchVec := range batchVectorDataSeeds { 104 | blob, err := json.Marshal(batchVec) 105 | if err != nil { 106 | f.Fatal(err) 107 | } 108 | f.Add(blob) 109 | } 110 | 111 | // 2. Now run the fuzzer. 112 | f.Fuzz(func(t *testing.T, inputJSON []byte) { 113 | var bv BatchVectorData 114 | if err := json.Unmarshal(inputJSON, &bv); err != nil { 115 | return 116 | } 117 | if bv.Ref == nil || bv.Proof == nil || bv.Ref.RootHash == nil { 118 | return 119 | } 120 | // Otherwise now run VerifyNonMembership. 121 | _ = VerifyNonMembership(bv.Spec, bv.Ref.RootHash, bv.Proof, bv.Ref.Key) 122 | }) 123 | } 124 | 125 | // verifyJSON is necessary so we already have sources of Proof, Ref and Spec 126 | // that'll be mutated by the fuzzer to get much closer to values for greater coverage. 127 | type verifyJSON struct { 128 | Proof *CommitmentProof 129 | Ref *RefData 130 | Spec *ProofSpec 131 | } 132 | 133 | func FuzzVerifyMembership(f *testing.F) { 134 | seeds := VectorsTestData() 135 | 136 | // VerifyMembership requires: 137 | // Spec, ExistenceProof, CommitmentRootref. 138 | for _, seed := range seeds { 139 | proof, ref := LoadFile(f, seed.Dir, seed.Filename) 140 | root, err := proof.Calculate() 141 | if err != nil { 142 | continue 143 | } 144 | if !bytes.Equal(ref.RootHash, root) { 145 | continue 146 | } 147 | 148 | if ref.Value == nil { 149 | continue 150 | } 151 | 152 | // Now serialize this value as a seed. 153 | // The use of already calculated values is necessary 154 | // for the fuzzers to have a basis of much better coverage 155 | // generating values. 156 | blob, err := json.Marshal(&verifyJSON{Proof: proof, Ref: ref, Spec: seed.Spec}) 157 | if err == nil { 158 | f.Add(blob) 159 | } 160 | } 161 | 162 | // 2. Now let's run the fuzzer. 163 | f.Fuzz(func(t *testing.T, input []byte) { 164 | var con verifyJSON 165 | if err := json.Unmarshal(input, &con); err != nil { 166 | return 167 | } 168 | spec, ref, proof := con.Spec, con.Ref, con.Proof 169 | if ref == nil { 170 | return 171 | } 172 | _ = VerifyMembership(spec, ref.RootHash, proof, ref.Key, ref.Value) 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cosmos/ics23/go 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/cosmos/gogoproto v1.7.0 7 | golang.org/x/crypto v0.31.0 8 | ) 9 | 10 | require ( 11 | github.com/google/go-cmp v0.6.0 // indirect 12 | golang.org/x/sys v0.28.0 // indirect 13 | google.golang.org/protobuf v1.33.0 // indirect 14 | ) 15 | 16 | // subject to the dragonberry vulnerability 17 | retract [v0.0.0, v0.7.0] 18 | -------------------------------------------------------------------------------- /go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= 2 | github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= 3 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 4 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 5 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 6 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 8 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 9 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 10 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 11 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 12 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 13 | -------------------------------------------------------------------------------- /go/ics23.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | This implements the client side functions as specified in 4 | https://github.com/cosmos/ibc/tree/main/spec/core/ics-023-vector-commitments 5 | 6 | In particular: 7 | 8 | // Assumes ExistenceProof 9 | type verifyMembership = (root: CommitmentRoot, proof: CommitmentProof, key: Key, value: Value) => boolean 10 | 11 | // Assumes NonExistenceProof 12 | type verifyNonMembership = (root: CommitmentRoot, proof: CommitmentProof, key: Key) => boolean 13 | 14 | // Assumes BatchProof - required ExistenceProofs may be a subset of all items proven 15 | type batchVerifyMembership = (root: CommitmentRoot, proof: CommitmentProof, items: Map) => boolean 16 | 17 | // Assumes BatchProof - required NonExistenceProofs may be a subset of all items proven 18 | type batchVerifyNonMembership = (root: CommitmentRoot, proof: CommitmentProof, keys: Set) => boolean 19 | 20 | We make an adjustment to accept a Spec to ensure the provided proof is in the format of the expected merkle store. 21 | This can avoid an range of attacks on fake preimages, as we need to be careful on how to map key, value -> leaf 22 | and determine neighbors 23 | */ 24 | package ics23 25 | 26 | // CommitmentRoot is a byte slice that represents the merkle root of a tree that can be used to validate proofs 27 | type CommitmentRoot []byte 28 | 29 | // VerifyMembership returns true iff 30 | // proof is an ExistenceProof for the given key and value AND 31 | // calculating the root for the ExistenceProof matches the provided CommitmentRoot. 32 | func VerifyMembership(spec *ProofSpec, root CommitmentRoot, proof *CommitmentProof, key []byte, value []byte) bool { 33 | if proof == nil { 34 | return false 35 | } 36 | 37 | ep := proof.GetExist() 38 | if ep == nil { 39 | return false 40 | } 41 | 42 | return ep.Verify(spec, root, key, value) == nil 43 | } 44 | 45 | // VerifyNonMembership returns true iff 46 | // proof is (contains) a NonExistenceProof 47 | // both left and right sub-proofs are valid existence proofs (see above) or nil 48 | // left and right proofs are neighbors (or left/right most if one is nil) 49 | // provided key is between the keys of the two proofs 50 | func VerifyNonMembership(spec *ProofSpec, root CommitmentRoot, proof *CommitmentProof, key []byte) bool { 51 | if proof == nil { 52 | return false 53 | } 54 | 55 | np := proof.GetNonexist() 56 | if np == nil { 57 | return false 58 | } 59 | 60 | return np.Verify(spec, root, key) == nil 61 | } 62 | -------------------------------------------------------------------------------- /go/ops.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "hash" 10 | 11 | // adds sha256 capability to crypto.SHA256 12 | _ "crypto/sha256" 13 | // adds sha512 capability to crypto.SHA512 14 | _ "crypto/sha512" 15 | // adds blake2b capability to crypto.BLAKE2b_512 16 | _ "golang.org/x/crypto/blake2b" 17 | // adds blake2s capability to crypto.BLAKE2s_256 18 | _ "golang.org/x/crypto/blake2s" 19 | // adds ripemd160 capability to crypto.RIPEMD160 20 | _ "golang.org/x/crypto/ripemd160" //nolint:staticcheck 21 | ) 22 | 23 | // validateIavlOps validates the prefix to ensure it begins with 24 | // the height, size, and version of the IAVL tree. Each varint must be a bounded value. 25 | // In addition, the remaining bytes are validated to ensure they correspond to the correct 26 | // length. The layerNum is the inverse of the tree depth, i.e. depth=0 means leaf, depth>=1 means inner node 27 | func validateIavlOps(op opType, layerNum int) error { 28 | r := bytes.NewReader(op.GetPrefix()) 29 | 30 | height, err := binary.ReadVarint(r) 31 | if err != nil { 32 | return fmt.Errorf("failed to read IAVL height varint: %w", err) 33 | } 34 | if int(height) < 0 || int(height) < layerNum { 35 | return fmt.Errorf("IAVL height (%d) must be non-negative and greater than or equal to the layer number (%d)", height, layerNum) 36 | } 37 | 38 | size, err := binary.ReadVarint(r) 39 | if err != nil { 40 | return fmt.Errorf("failed to read IAVL size varint: %w", err) 41 | } 42 | 43 | if int(size) < 0 { 44 | return fmt.Errorf("IAVL size must be non-negative") 45 | } 46 | 47 | version, err := binary.ReadVarint(r) 48 | if err != nil { 49 | return fmt.Errorf("failed to read IAVL version varint: %w", err) 50 | } 51 | 52 | if int(version) < 0 { 53 | return fmt.Errorf("IAVL version must be non-negative") 54 | } 55 | 56 | // grab the length of the remainder of the prefix 57 | remLen := r.Len() 58 | if layerNum == 0 { 59 | // leaf node 60 | if remLen != 0 { 61 | return fmt.Errorf("expected remaining prefix length to be 0, got: %d", remLen) 62 | } 63 | if height != 0 { 64 | return fmt.Errorf("expected leaf node height to be 0, got: %d", remLen) 65 | } 66 | if size != 1 { 67 | return fmt.Errorf("expected leaf node size to be 1, got: %d", remLen) 68 | } 69 | } else { 70 | // inner node 71 | // 72 | // when the child comes from the left, the suffix if filled in 73 | // prefix: height | size | version | length byte (1 remainder) 74 | // 75 | // when the child comes from the right, the suffix is empty 76 | // prefix: height | size | version | length byte | 32 byte hash | next length byte (34 remainder) 77 | if remLen != 1 && remLen != 34 { 78 | return fmt.Errorf("remainder of prefix must be of length 1 or 34, got: %d", remLen) 79 | } 80 | if op.GetHash() != HashOp_SHA256 { 81 | return fmt.Errorf("IAVL hash op must be %v", HashOp_SHA256) 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | // validateTendermintOps validates the prefix to ensure it begins with []byte{1}. 88 | func validateTendermintOps(op *InnerOp) error { 89 | if len(op.Prefix) == 0 { 90 | return fmt.Errorf("inner op prefix must not be empty") 91 | } 92 | 93 | innerPrefix := []byte{1} 94 | if op.Suffix != nil { 95 | if !bytes.Equal(op.Prefix, innerPrefix) { 96 | return fmt.Errorf("expected inner op prefix: %v, got: %v", innerPrefix, op.Prefix) 97 | } 98 | } 99 | 100 | if !bytes.HasPrefix(op.Prefix, innerPrefix) { 101 | return fmt.Errorf("expected inner op prefix to begin with: %v, got: %v", innerPrefix, op.Prefix[:1]) 102 | } 103 | 104 | return nil 105 | } 106 | 107 | // Apply will calculate the leaf hash given the key and value being proven 108 | func (op *LeafOp) Apply(key []byte, value []byte) ([]byte, error) { 109 | if len(key) == 0 { 110 | return nil, errors.New("leaf op needs key") 111 | } 112 | if len(value) == 0 { 113 | return nil, errors.New("leaf op needs value") 114 | } 115 | pkey, err := prepareLeafData(op.PrehashKey, op.Length, key) 116 | if err != nil { 117 | return nil, fmt.Errorf("prehash key, %w", err) 118 | } 119 | pvalue, err := prepareLeafData(op.PrehashValue, op.Length, value) 120 | if err != nil { 121 | return nil, fmt.Errorf("prehash value, %w", err) 122 | } 123 | 124 | data := op.Prefix 125 | data = append(data, pkey...) 126 | data = append(data, pvalue...) 127 | 128 | return doHash(op.Hash, data) 129 | } 130 | 131 | // Apply will calculate the hash of the next step, given the hash of the previous step 132 | func (op *InnerOp) Apply(child []byte) ([]byte, error) { 133 | if len(child) == 0 { 134 | return nil, errors.New("inner op needs child value") 135 | } 136 | preimage := op.Prefix 137 | preimage = append(preimage, child...) 138 | preimage = append(preimage, op.Suffix...) 139 | return doHash(op.Hash, preimage) 140 | } 141 | 142 | // CheckAgainstSpec will verify the LeafOp is in the format defined in spec 143 | func (op *LeafOp) CheckAgainstSpec(spec *ProofSpec) error { 144 | if spec == nil { 145 | return errors.New("op and spec must be non-nil") 146 | } 147 | lspec := spec.LeafSpec 148 | if lspec == nil { 149 | return errors.New("spec.LeafSpec must be non-nil") 150 | } 151 | 152 | if spec.SpecEquals(IavlSpec) { 153 | err := validateIavlOps(op, 0) 154 | if err != nil { 155 | return err 156 | } 157 | } 158 | 159 | if op.Hash != lspec.Hash { 160 | return fmt.Errorf("unexpected HashOp: %d", op.Hash) 161 | } 162 | if op.PrehashKey != lspec.PrehashKey { 163 | return fmt.Errorf("unexpected PrehashKey: %d", op.PrehashKey) 164 | } 165 | if op.PrehashValue != lspec.PrehashValue { 166 | return fmt.Errorf("unexpected PrehashValue: %d", op.PrehashValue) 167 | } 168 | if op.Length != lspec.Length { 169 | return fmt.Errorf("unexpected LengthOp: %d", op.Length) 170 | } 171 | if !bytes.HasPrefix(op.Prefix, lspec.Prefix) { 172 | return fmt.Errorf("leaf Prefix doesn't start with %X", lspec.Prefix) 173 | } 174 | return nil 175 | } 176 | 177 | // CheckAgainstSpec will verify the InnerOp is in the format defined in spec 178 | func (op *InnerOp) CheckAgainstSpec(spec *ProofSpec, b int) error { 179 | if spec == nil { 180 | return errors.New("op and spec must be both non-nil") 181 | } 182 | if spec.InnerSpec == nil { 183 | return errors.New("spec.InnerSpec must be non-nil") 184 | } 185 | if spec.LeafSpec == nil { 186 | return errors.New("spec.LeafSpec must be non-nil") 187 | } 188 | 189 | if op.Hash != spec.InnerSpec.Hash { 190 | return fmt.Errorf("unexpected HashOp: %d", op.Hash) 191 | } 192 | 193 | if spec.SpecEquals(IavlSpec) { 194 | err := validateIavlOps(op, b) 195 | if err != nil { 196 | return err 197 | } 198 | } 199 | 200 | if spec.SpecEquals(TendermintSpec) { 201 | err := validateTendermintOps(op) 202 | if err != nil { 203 | return err 204 | } 205 | } 206 | 207 | leafPrefix := spec.LeafSpec.Prefix 208 | if bytes.HasPrefix(op.Prefix, leafPrefix) { 209 | return fmt.Errorf("inner Prefix starts with %X", leafPrefix) 210 | } 211 | if len(op.Prefix) < int(spec.InnerSpec.MinPrefixLength) { 212 | return fmt.Errorf("innerOp prefix too short (%d)", len(op.Prefix)) 213 | } 214 | maxLeftChildBytes := (len(spec.InnerSpec.ChildOrder) - 1) * int(spec.InnerSpec.ChildSize) 215 | if len(op.Prefix) > int(spec.InnerSpec.MaxPrefixLength)+maxLeftChildBytes { 216 | return fmt.Errorf("innerOp prefix too long (%d)", len(op.Prefix)) 217 | } 218 | 219 | if spec.InnerSpec.ChildSize <= 0 { 220 | return errors.New("spec.InnerSpec.ChildSize must be >= 1") 221 | } 222 | 223 | if spec.InnerSpec.MaxPrefixLength >= spec.InnerSpec.MinPrefixLength+spec.InnerSpec.ChildSize { 224 | return errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize") 225 | } 226 | 227 | // ensures soundness, with suffix having to be of correct length 228 | if len(op.Suffix)%int(spec.InnerSpec.ChildSize) != 0 { 229 | return fmt.Errorf("InnerOp suffix malformed") 230 | } 231 | 232 | return nil 233 | } 234 | 235 | // doHash will preform the specified hash on the preimage. 236 | // if hashOp == NONE, it will return an error (use doHashOrNoop if you want different behavior) 237 | func doHash(hashOp HashOp, preimage []byte) ([]byte, error) { 238 | switch hashOp { 239 | case HashOp_SHA256: 240 | return hashBz(crypto.SHA256, preimage) 241 | case HashOp_SHA512: 242 | return hashBz(crypto.SHA512, preimage) 243 | case HashOp_RIPEMD160: 244 | return hashBz(crypto.RIPEMD160, preimage) 245 | case HashOp_BITCOIN: 246 | // ripemd160(sha256(x)) 247 | tmp, err := hashBz(crypto.SHA256, preimage) 248 | if err != nil { 249 | return nil, err 250 | } 251 | return hashBz(crypto.RIPEMD160, tmp) 252 | case HashOp_SHA512_256: 253 | return hashBz(crypto.SHA512_256, preimage) 254 | case HashOp_BLAKE2B_512: 255 | return hashBz(crypto.BLAKE2b_512, preimage) 256 | case HashOp_BLAKE2S_256: 257 | return hashBz(crypto.BLAKE2s_256, preimage) 258 | } 259 | return nil, fmt.Errorf("unsupported hashop: %d", hashOp) 260 | } 261 | 262 | type hasher interface { 263 | New() hash.Hash 264 | } 265 | 266 | func hashBz(h hasher, preimage []byte) ([]byte, error) { 267 | hh := h.New() 268 | _, err := hh.Write(preimage) 269 | if err != nil { 270 | return nil, err 271 | } 272 | return hh.Sum(nil), nil 273 | } 274 | 275 | func prepareLeafData(hashOp HashOp, lengthOp LengthOp, data []byte) ([]byte, error) { 276 | // TODO: lengthop before or after hash ??? 277 | hdata, err := doHashOrNoop(hashOp, data) 278 | if err != nil { 279 | return nil, err 280 | } 281 | 282 | return doLengthOp(lengthOp, hdata) 283 | } 284 | 285 | type opType interface { 286 | GetPrefix() []byte 287 | GetHash() HashOp 288 | Reset() 289 | String() string 290 | } 291 | 292 | // doLengthOp will calculate the proper prefix and return it prepended 293 | // 294 | // doLengthOp(op, data) -> length(data) || data 295 | func doLengthOp(lengthOp LengthOp, data []byte) ([]byte, error) { 296 | switch lengthOp { 297 | case LengthOp_NO_PREFIX: 298 | return data, nil 299 | case LengthOp_VAR_PROTO: 300 | res := append(encodeVarintProto(len(data)), data...) 301 | return res, nil 302 | case LengthOp_REQUIRE_32_BYTES: 303 | if len(data) != 32 { 304 | return nil, fmt.Errorf("data was %d bytes, not 32", len(data)) 305 | } 306 | return data, nil 307 | case LengthOp_REQUIRE_64_BYTES: 308 | if len(data) != 64 { 309 | return nil, fmt.Errorf("data was %d bytes, not 64", len(data)) 310 | } 311 | return data, nil 312 | case LengthOp_FIXED32_BIG: 313 | res := make([]byte, 4, 4+len(data)) 314 | binary.BigEndian.PutUint32(res[:4], uint32(len(data))) 315 | res = append(res, data...) 316 | return res, nil 317 | case LengthOp_FIXED32_LITTLE: 318 | res := make([]byte, 4, 4+len(data)) 319 | binary.LittleEndian.PutUint32(res[:4], uint32(len(data))) 320 | res = append(res, data...) 321 | return res, nil 322 | case LengthOp_FIXED64_BIG: 323 | res := make([]byte, 8, 8+len(data)) 324 | binary.BigEndian.PutUint64(res[:8], uint64(len(data))) 325 | res = append(res, data...) 326 | return res, nil 327 | case LengthOp_FIXED64_LITTLE: 328 | res := make([]byte, 8, 8+len(data)) 329 | binary.LittleEndian.PutUint64(res[:8], uint64(len(data))) 330 | res = append(res, data...) 331 | return res, nil 332 | // TODO 333 | // case LengthOp_VAR_RLP: 334 | } 335 | return nil, fmt.Errorf("unsupported lengthop: %d", lengthOp) 336 | } 337 | 338 | // doHashOrNoop will return the preimage untouched if hashOp == NONE, 339 | // otherwise, perform doHash 340 | func doHashOrNoop(hashOp HashOp, preimage []byte) ([]byte, error) { 341 | if hashOp == HashOp_NO_HASH { 342 | return preimage, nil 343 | } 344 | return doHash(hashOp, preimage) 345 | } 346 | -------------------------------------------------------------------------------- /go/ops_data_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | type LeafOpTestStruct struct { 12 | Op *LeafOp 13 | Key []byte 14 | Value []byte 15 | IsErr bool 16 | Expected []byte 17 | } 18 | 19 | func LeafOpTestData(t *testing.T) map[string]LeafOpTestStruct { 20 | t.Helper() 21 | fname := filepath.Join("..", "testdata", "TestLeafOpData.json") 22 | ffile, err := os.Open(fname) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | var cases map[string]LeafOpTestStruct 27 | jsonDecoder := json.NewDecoder(ffile) 28 | err = jsonDecoder.Decode(&cases) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | return cases 33 | } 34 | 35 | type InnerOpTestStruct struct { 36 | Op *InnerOp 37 | Child []byte 38 | IsErr bool 39 | Expected []byte 40 | } 41 | 42 | func InnerOpTestData(t *testing.T) map[string]InnerOpTestStruct { 43 | t.Helper() 44 | fname := filepath.Join("..", "testdata", "TestInnerOpData.json") 45 | ffile, err := os.Open(fname) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | var cases map[string]InnerOpTestStruct 50 | jsonDecoder := json.NewDecoder(ffile) 51 | err = jsonDecoder.Decode(&cases) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | return cases 56 | } 57 | 58 | type DoHashTestStruct struct { 59 | HashOp HashOp 60 | Preimage string 61 | ExpectedHash string 62 | } 63 | 64 | func DoHashTestData(t *testing.T) map[string]DoHashTestStruct { 65 | t.Helper() 66 | fname := filepath.Join("..", "testdata", "TestDoHashData.json") 67 | ffile, err := os.Open(fname) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | var cases map[string]DoHashTestStruct 72 | jsonDecoder := json.NewDecoder(ffile) 73 | err = jsonDecoder.Decode(&cases) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | return cases 78 | } 79 | 80 | func toHex(data []byte) string { 81 | return hex.EncodeToString(data) 82 | } 83 | -------------------------------------------------------------------------------- /go/ops_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func TestValidateIavlOps(t *testing.T) { 13 | var ( 14 | op opType 15 | layerNum int 16 | ) 17 | cases := []struct { 18 | name string 19 | malleate func() 20 | expError error 21 | }{ 22 | { 23 | "success", 24 | func() {}, 25 | nil, 26 | }, 27 | { 28 | "failure: reading varint", 29 | func() { 30 | op.(*InnerOp).Prefix = []byte{} 31 | }, 32 | errors.New("failed to read IAVL height varint: EOF"), 33 | }, 34 | { 35 | "failure: invalid height value", 36 | func() { 37 | op.(*InnerOp).Prefix = []byte{1} 38 | }, 39 | errors.New("IAVL height (-1) must be non-negative and greater than or equal to the layer number (1)"), 40 | }, 41 | { 42 | "failure: invalid size varint", 43 | func() { 44 | var varintBuf [binary.MaxVarintLen64]byte 45 | prefix := convertVarIntToBytes(int64(5), varintBuf) 46 | op.(*InnerOp).Prefix = prefix 47 | }, 48 | errors.New("failed to read IAVL size varint: EOF"), 49 | }, 50 | { 51 | "failure: invalid size value", 52 | func() { 53 | var varintBuf [binary.MaxVarintLen64]byte 54 | prefix := convertVarIntToBytes(int64(5), varintBuf) 55 | prefix = append(prefix, convertVarIntToBytes(int64(-1), varintBuf)...) // size 56 | op.(*InnerOp).Prefix = prefix 57 | }, 58 | errors.New("IAVL size must be non-negative"), 59 | }, 60 | { 61 | "failure: invalid version varint", 62 | func() { 63 | var varintBuf [binary.MaxVarintLen64]byte 64 | prefix := convertVarIntToBytes(int64(5), varintBuf) 65 | prefix = append(prefix, convertVarIntToBytes(int64(10), varintBuf)...) 66 | op.(*InnerOp).Prefix = prefix 67 | }, 68 | errors.New("failed to read IAVL version varint: EOF"), 69 | }, 70 | { 71 | "failure: invalid version value", 72 | func() { 73 | var varintBuf [binary.MaxVarintLen64]byte 74 | prefix := convertVarIntToBytes(int64(5), varintBuf) 75 | prefix = append(prefix, convertVarIntToBytes(int64(10), varintBuf)...) 76 | prefix = append(prefix, convertVarIntToBytes(int64(-1), varintBuf)...) // version 77 | op.(*InnerOp).Prefix = prefix 78 | }, 79 | errors.New("IAVL version must be non-negative"), 80 | }, 81 | { 82 | "failure: invalid remaining length with layer number is 0", 83 | func() { 84 | layerNum = 0 85 | }, 86 | fmt.Errorf("expected remaining prefix length to be 0, got: 1"), 87 | }, 88 | { 89 | "failure: invalid remaining length with non-zero layer number", 90 | func() { 91 | layerNum = 1 92 | op.(*InnerOp).Prefix = append(op.(*InnerOp).Prefix, []byte{1}...) 93 | }, 94 | fmt.Errorf("remainder of prefix must be of length 1 or 34, got: 2"), 95 | }, 96 | { 97 | "failure: invalid hash", 98 | func() { 99 | op.(*InnerOp).Hash = HashOp_NO_HASH 100 | }, 101 | fmt.Errorf("IAVL hash op must be %v", HashOp_SHA256), 102 | }, 103 | } 104 | for _, tc := range cases { 105 | tc := tc 106 | 107 | t.Run(tc.name, func(t *testing.T) { 108 | op = &InnerOp{ 109 | Hash: HashOp_SHA256, 110 | Prefix: generateInnerOpPrefix(), 111 | Suffix: []byte(""), 112 | } 113 | layerNum = 1 114 | 115 | tc.malleate() 116 | 117 | err := validateIavlOps(op, layerNum) 118 | if tc.expError == nil && err != nil { 119 | t.Fatal(err) 120 | } 121 | 122 | if tc.expError != nil && err.Error() != tc.expError.Error() { 123 | t.Fatalf("expected: %v, got: %v", tc.expError, err) 124 | } 125 | }) 126 | 127 | } 128 | } 129 | 130 | func TestValidateTendermintOps(t *testing.T) { 131 | var op *InnerOp 132 | cases := []struct { 133 | name string 134 | malleate func() 135 | expError error 136 | }{ 137 | { 138 | "success: valid prefix when suffix populated", 139 | func() {}, 140 | nil, 141 | }, 142 | { 143 | "success: valid prefix when suffix empty", 144 | func() { 145 | op.Prefix = []byte{1, 2} 146 | op.Suffix = nil 147 | }, 148 | nil, 149 | }, 150 | { 151 | "failure: empty prefix and suffix", 152 | func() { 153 | op.Prefix = nil 154 | op.Suffix = nil 155 | }, 156 | errors.New("inner op prefix must not be empty"), 157 | }, 158 | { 159 | "failure: invalid prefix when suffix populated", 160 | func() { 161 | op.Prefix = []byte{0} 162 | op.Suffix = []byte{1} 163 | }, 164 | fmt.Errorf("expected inner op prefix: %v, got: %v", []byte{1}, []byte{0}), 165 | }, 166 | { 167 | "failure: invalid prefix when suffix empty", 168 | func() { 169 | op.Prefix = []byte{2, 1} 170 | op.Suffix = nil 171 | }, 172 | fmt.Errorf("expected inner op prefix to begin with: %v, got: %v", []byte{1}, []byte{2}), 173 | }, 174 | } 175 | 176 | for _, tc := range cases { 177 | tc := tc 178 | 179 | t.Run(tc.name, func(t *testing.T) { 180 | op = &InnerOp{ 181 | Hash: HashOp_SHA256, 182 | Prefix: []byte{1}, 183 | Suffix: []byte{1}, 184 | } 185 | 186 | tc.malleate() 187 | 188 | err := validateTendermintOps(op) 189 | if tc.expError == nil && err != nil { 190 | t.Fatal(err) 191 | } 192 | 193 | if tc.expError != nil && err.Error() != tc.expError.Error() { 194 | t.Fatalf("expected: %v, got: %v", tc.expError, err) 195 | } 196 | }) 197 | 198 | } 199 | } 200 | 201 | func TestLeafOp(t *testing.T) { 202 | cases := LeafOpTestData(t) 203 | 204 | for name, tc := range cases { 205 | t.Run(name, func(t *testing.T) { 206 | res, err := tc.Op.Apply(tc.Key, tc.Value) 207 | // short-circuit with error case 208 | if tc.IsErr && err == nil { 209 | t.Fatal("expected error, but got none") 210 | } 211 | if !tc.IsErr && err != nil { 212 | t.Fatal(err) 213 | } 214 | if !bytes.Equal(res, tc.Expected) { 215 | t.Errorf("bad result: %s vs %s", toHex(res), toHex(tc.Expected)) 216 | } 217 | }) 218 | } 219 | } 220 | 221 | func TestInnerOp(t *testing.T) { 222 | cases := InnerOpTestData(t) 223 | 224 | for name, tc := range cases { 225 | t.Run(name, func(t *testing.T) { 226 | res, err := tc.Op.Apply(tc.Child) 227 | if tc.IsErr && err == nil { 228 | t.Fatal("Expected error, but got none") 229 | } 230 | if !tc.IsErr && err != nil { 231 | t.Fatal(err) 232 | } 233 | if !bytes.Equal(res, tc.Expected) { 234 | t.Errorf("Bad result: %s vs %s", toHex(res), toHex(tc.Expected)) 235 | } 236 | }) 237 | } 238 | } 239 | 240 | func TestDoHash(t *testing.T) { 241 | cases := DoHashTestData(t) 242 | 243 | for name, tc := range cases { 244 | t.Run(name, func(t *testing.T) { 245 | res, err := doHash(tc.HashOp, []byte(tc.Preimage)) 246 | if err != nil { 247 | t.Fatal(err) 248 | } 249 | hexRes := hex.EncodeToString(res) 250 | if hexRes != tc.ExpectedHash { 251 | t.Fatalf("Expected %s got %s", tc.ExpectedHash, hexRes) 252 | } 253 | }) 254 | } 255 | } 256 | 257 | func TestInnerOpCheckAgainstSpec(t *testing.T) { 258 | var ( 259 | spec *ProofSpec 260 | innerOp *InnerOp 261 | ) 262 | 263 | cases := []struct { 264 | name string 265 | malleate func() 266 | expError error 267 | }{ 268 | { 269 | "success", 270 | func() {}, 271 | nil, 272 | }, 273 | { 274 | "failure: empty spec", 275 | func() { 276 | spec = nil 277 | }, 278 | errors.New("op and spec must be both non-nil"), 279 | }, 280 | { 281 | "failure: empty inner spec", 282 | func() { 283 | spec.InnerSpec = nil 284 | }, 285 | errors.New("spec.InnerSpec must be non-nil"), 286 | }, 287 | { 288 | "failure: empty leaf spec", 289 | func() { 290 | spec.LeafSpec = nil 291 | }, 292 | errors.New("spec.LeafSpec must be non-nil"), 293 | }, 294 | { 295 | "failure: incorrect hash function inner op", 296 | func() { 297 | innerOp.Hash = HashOp_BITCOIN 298 | }, 299 | fmt.Errorf("unexpected HashOp: %d", HashOp_BITCOIN), 300 | }, 301 | { 302 | "failure: spec fails validation", 303 | func() { 304 | innerOp.Prefix = []byte{0x01} 305 | }, 306 | fmt.Errorf("IAVL height (-1) must be non-negative and greater than or equal to the layer number (1)"), 307 | }, 308 | { 309 | "failure: inner prefix starts with leaf prefix", 310 | func() { 311 | // change spec to be non-iavl spec to skip strict iavl validation 312 | spec.LeafSpec.PrehashKey = HashOp_BITCOIN 313 | innerOp.Prefix = append(spec.LeafSpec.Prefix, innerOp.Prefix...) 314 | }, 315 | fmt.Errorf("inner Prefix starts with %X", []byte{0}), 316 | }, 317 | { 318 | "failure: inner prefix too short", 319 | func() { 320 | // change spec to be non-iavl spec to skip strict iavl validation 321 | spec.LeafSpec.PrehashKey = HashOp_BITCOIN 322 | innerOp.Prefix = []byte{0x01} 323 | }, 324 | errors.New("innerOp prefix too short (1)"), 325 | }, 326 | { 327 | "failure: inner prefix too long", 328 | func() { 329 | // change spec to be non-iavl spec to skip strict iavl validation 330 | spec.LeafSpec.PrehashKey = HashOp_BITCOIN 331 | innerOp.Prefix = []byte("AgQIIGe3bHuC1g6+5/Qd0RoCU0waFu+nDCFzEDViMN/VrQwgIA==") 332 | }, 333 | errors.New("innerOp prefix too long (52)"), 334 | }, 335 | { 336 | "failure: child size must be greater than zero", 337 | func() { 338 | spec.InnerSpec.ChildSize = 0 339 | }, 340 | errors.New("spec.InnerSpec.ChildSize must be >= 1"), 341 | }, 342 | { 343 | "failure: MaxPrefixLength >= MinPrefixLength + ChildSize", 344 | func() { 345 | spec.InnerSpec.MaxPrefixLength = spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize 346 | }, 347 | errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize"), 348 | }, 349 | { 350 | "failure: inner op suffix malformed", 351 | func() { 352 | innerOp.Suffix = []byte{0x01} 353 | }, 354 | fmt.Errorf("InnerOp suffix malformed"), 355 | }, 356 | } 357 | 358 | for _, tc := range cases { 359 | tc := tc 360 | 361 | t.Run(tc.name, func(t *testing.T) { 362 | spec = &ProofSpec{ // IavlSpec 363 | LeafSpec: &LeafOp{ 364 | Prefix: []byte{0}, 365 | PrehashKey: HashOp_NO_HASH, 366 | Hash: HashOp_SHA256, 367 | PrehashValue: HashOp_SHA256, 368 | Length: LengthOp_VAR_PROTO, 369 | }, 370 | InnerSpec: &InnerSpec{ 371 | ChildOrder: []int32{0, 1}, 372 | MinPrefixLength: 4, 373 | MaxPrefixLength: 12, 374 | ChildSize: 33, // (with length byte) 375 | EmptyChild: nil, 376 | Hash: HashOp_SHA256, 377 | }, 378 | } 379 | 380 | innerOp = &InnerOp{ 381 | Hash: HashOp_SHA256, 382 | Prefix: generateInnerOpPrefix(), 383 | Suffix: []byte(""), 384 | } 385 | tc.malleate() 386 | 387 | err := innerOp.CheckAgainstSpec(spec, 1) // use a layer number of 1 388 | if tc.expError == nil && err != nil { 389 | t.Fatal(err) 390 | } 391 | 392 | if tc.expError != nil && err.Error() != tc.expError.Error() { 393 | t.Fatalf("expected: %v, got: %v", tc.expError, err) 394 | } 395 | }) 396 | 397 | } 398 | } 399 | 400 | func TestForgeNonExistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { 401 | spec := &ProofSpec{ // TendermintSpec 402 | LeafSpec: &LeafOp{ 403 | Prefix: []byte{0}, 404 | PrehashKey: HashOp_NO_HASH, 405 | Hash: HashOp_SHA256, 406 | PrehashValue: HashOp_SHA256, 407 | Length: LengthOp_VAR_PROTO, 408 | }, 409 | InnerSpec: &InnerSpec{ 410 | ChildOrder: []int32{0, 1}, 411 | MinPrefixLength: 1, 412 | MaxPrefixLength: 1, 413 | ChildSize: 32, // (no length byte) 414 | Hash: HashOp_SHA256, 415 | }, 416 | } 417 | 418 | spec.InnerSpec.MaxPrefixLength = 33 419 | leafOp := spec.LeafSpec 420 | aLeaf, _ := leafOp.Apply([]byte("a"), []byte("a")) 421 | bLeaf, _ := leafOp.Apply([]byte("b"), []byte("b")) 422 | b2Leaf, _ := leafOp.Apply([]byte("b2"), []byte("b2")) 423 | 424 | cLeaf, _ := leafOp.Apply([]byte("c"), []byte("c")) 425 | aExist := ExistenceProof{ 426 | Key: []byte("a"), 427 | Value: []byte("a"), 428 | Leaf: leafOp, 429 | Path: []*InnerOp{ 430 | { 431 | Hash: spec.InnerSpec.Hash, 432 | Prefix: []byte{1}, 433 | Suffix: append(bLeaf, b2Leaf...), 434 | }, 435 | { 436 | Hash: spec.InnerSpec.Hash, 437 | Prefix: []byte{1}, 438 | Suffix: cLeaf, 439 | }, 440 | }, 441 | } 442 | bExist := ExistenceProof{ 443 | Key: []byte("b"), 444 | Value: []byte("b"), 445 | Leaf: leafOp, 446 | Path: []*InnerOp{ 447 | { 448 | Hash: spec.InnerSpec.Hash, 449 | Prefix: append([]byte{1}, aLeaf...), 450 | Suffix: b2Leaf, 451 | }, 452 | { 453 | Hash: spec.InnerSpec.Hash, 454 | Prefix: []byte{1}, 455 | Suffix: cLeaf, 456 | }, 457 | }, 458 | } 459 | b2Exist := ExistenceProof{ 460 | Key: []byte("b2"), 461 | Value: []byte("b2"), 462 | Leaf: leafOp, 463 | Path: []*InnerOp{ 464 | { 465 | Hash: spec.InnerSpec.Hash, 466 | Prefix: append(append([]byte{1}, aLeaf...), bLeaf...), 467 | Suffix: []byte{}, 468 | }, 469 | { 470 | Hash: spec.InnerSpec.Hash, 471 | Prefix: []byte{1}, 472 | Suffix: cLeaf, 473 | }, 474 | }, 475 | } 476 | yHash, _ := aExist.Path[0].Apply(aLeaf) 477 | cExist := ExistenceProof{ 478 | Key: []byte("c"), 479 | Value: []byte("c"), 480 | Leaf: leafOp, 481 | Path: []*InnerOp{ 482 | { 483 | Hash: spec.InnerSpec.Hash, 484 | Prefix: append([]byte{1}, yHash...), 485 | Suffix: []byte{}, 486 | }, 487 | }, 488 | } 489 | aNotExist := NonExistenceProof{ 490 | Key: []byte("a"), 491 | Left: nil, 492 | Right: &bExist, 493 | } 494 | root, err := aExist.Calculate() 495 | if err != nil { 496 | t.Fatal("failed to calculate existence proof of leaf a") 497 | } 498 | 499 | expError := fmt.Errorf("inner, %w", errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize")) 500 | err = aExist.Verify(spec, root, []byte("a"), []byte("a")) 501 | if err.Error() != expError.Error() { 502 | t.Fatal("attempting to prove existence of leaf a returned incorrect error") 503 | } 504 | 505 | err = bExist.Verify(spec, root, []byte("b"), []byte("b")) 506 | if err.Error() != expError.Error() { 507 | t.Fatal("attempting to prove existence of leaf b returned incorrect error") 508 | } 509 | 510 | err = b2Exist.Verify(spec, root, []byte("b2"), []byte("b2")) 511 | if err.Error() != expError.Error() { 512 | t.Fatal("attempting to prove existence of third leaf returned incorrect error") 513 | } 514 | 515 | err = cExist.Verify(spec, root, []byte("c"), []byte("c")) 516 | if err.Error() != expError.Error() { 517 | t.Fatal("attempting to prove existence of leaf c returned incorrect error") 518 | } 519 | 520 | err = aNotExist.Verify(spec, root, []byte("a")) 521 | expError = fmt.Errorf("right proof, %w", expError) 522 | if err.Error() != expError.Error() { 523 | t.Fatal("attempting to prove non-existence of leaf a returned incorrect error") 524 | } 525 | } 526 | 527 | // generatePrefix generates a valid iavl prefix for an inner op. 528 | func generateInnerOpPrefix() []byte { 529 | var ( 530 | varintBuf [binary.MaxVarintLen64]byte 531 | lengthByte byte = 0x20 532 | ) 533 | height, size, version := 5, 10, 20 534 | prefix := convertVarIntToBytes(int64(height), varintBuf) 535 | prefix = append(prefix, convertVarIntToBytes(int64(size), varintBuf)...) 536 | prefix = append(prefix, convertVarIntToBytes(int64(version), varintBuf)...) 537 | prefix = append(prefix, lengthByte) 538 | return prefix 539 | } 540 | 541 | func convertVarIntToBytes(orig int64, buf [binary.MaxVarintLen64]byte) []byte { 542 | n := binary.PutVarint(buf[:], orig) 543 | return buf[:n] 544 | } 545 | -------------------------------------------------------------------------------- /go/proof_data_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | type ExistenceProofTestStruct struct { 11 | Proof *ExistenceProof 12 | IsErr bool 13 | Expected []byte 14 | } 15 | 16 | func ExistenceProofTestData(tb testing.TB) map[string]ExistenceProofTestStruct { 17 | tb.Helper() 18 | fname := filepath.Join("..", "testdata", "TestExistenceProofData.json") 19 | ffile, err := os.Open(fname) 20 | if err != nil { 21 | tb.Fatal(err) 22 | } 23 | var cases map[string]ExistenceProofTestStruct 24 | jsonDecoder := json.NewDecoder(ffile) 25 | err = jsonDecoder.Decode(&cases) 26 | if err != nil { 27 | tb.Fatal(err) 28 | } 29 | return cases 30 | } 31 | 32 | type CheckLeafTestStruct struct { 33 | Leaf *LeafOp 34 | Spec *LeafOp 35 | IsErr bool 36 | } 37 | 38 | func CheckLeafTestData(tb testing.TB) map[string]CheckLeafTestStruct { 39 | tb.Helper() 40 | fname := filepath.Join("..", "testdata", "TestCheckLeafData.json") 41 | ffile, err := os.Open(fname) 42 | if err != nil { 43 | tb.Fatal(err) 44 | } 45 | var cases map[string]CheckLeafTestStruct 46 | jsonDecoder := json.NewDecoder(ffile) 47 | err = jsonDecoder.Decode(&cases) 48 | if err != nil { 49 | tb.Fatal(err) 50 | } 51 | return cases 52 | } 53 | 54 | type CheckAgainstSpecTestStruct struct { 55 | Proof *ExistenceProof 56 | Spec *ProofSpec 57 | Err string 58 | } 59 | 60 | func CheckAgainstSpecTestData(tb testing.TB) map[string]CheckAgainstSpecTestStruct { 61 | tb.Helper() 62 | fname := filepath.Join("..", "testdata", "TestCheckAgainstSpecData.json") 63 | ffile, err := os.Open(fname) 64 | if err != nil { 65 | tb.Fatal(err) 66 | } 67 | var cases map[string]CheckAgainstSpecTestStruct 68 | jsonDecoder := json.NewDecoder(ffile) 69 | err = jsonDecoder.Decode(&cases) 70 | if err != nil { 71 | tb.Fatal(err) 72 | } 73 | 74 | return cases 75 | } 76 | 77 | type EmptyBranchTestStruct struct { 78 | Op *InnerOp 79 | Spec *ProofSpec 80 | IsLeft bool 81 | IsRight bool 82 | } 83 | 84 | func EmptyBranchTestData(tb testing.TB) []EmptyBranchTestStruct { 85 | tb.Helper() 86 | fname := filepath.Join("..", "testdata", "TestEmptyBranchData.json") 87 | ffile, err := os.Open(fname) 88 | if err != nil { 89 | tb.Fatal(err) 90 | } 91 | var cases []EmptyBranchTestStruct 92 | jsonDecoder := json.NewDecoder(ffile) 93 | err = jsonDecoder.Decode(&cases) 94 | if err != nil { 95 | tb.Fatal(err) 96 | } 97 | return cases 98 | } 99 | -------------------------------------------------------------------------------- /go/proof_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestExistenceProof(t *testing.T) { 10 | cases := ExistenceProofTestData(t) 11 | 12 | for name, tc := range cases { 13 | t.Run(name, func(t *testing.T) { 14 | res, err := tc.Proof.Calculate() 15 | // short-circuit with error case 16 | if tc.IsErr { 17 | if err == nil { 18 | t.Fatal("Expected error, but got none") 19 | } 20 | } else { 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | } 25 | if !bytes.Equal(res, tc.Expected) { 26 | t.Errorf("Bad result: %s vs %s", toHex(res), toHex(tc.Expected)) 27 | } 28 | }) 29 | } 30 | } 31 | 32 | func TestCheckLeaf(t *testing.T) { 33 | cases := CheckLeafTestData(t) 34 | 35 | for name, tc := range cases { 36 | t.Run(name, func(t *testing.T) { 37 | err := tc.Leaf.CheckAgainstSpec(&ProofSpec{LeafSpec: tc.Spec}) 38 | if tc.IsErr && err == nil { 39 | t.Fatal("Expected error, but got nil") 40 | } else if !tc.IsErr && err != nil { 41 | t.Fatalf("Unexpected error: %v", err) 42 | } 43 | }) 44 | } 45 | } 46 | 47 | func TestCheckAgainstSpec(t *testing.T) { 48 | cases := CheckAgainstSpecTestData(t) 49 | 50 | for name, tc := range cases { 51 | t.Run(name, func(t *testing.T) { 52 | err := tc.Proof.CheckAgainstSpec(tc.Spec) 53 | if tc.Err == "" && err != nil { 54 | t.Fatalf("Unexpected error: %v", err) 55 | } else if tc.Err != "" && tc.Err != err.Error() { 56 | t.Fatalf("Expected error: %s, got: %s", tc.Err, err.Error()) 57 | } 58 | }) 59 | } 60 | } 61 | 62 | func TestEmptyBranch(t *testing.T) { 63 | cases := EmptyBranchTestData(t) 64 | 65 | for _, tc := range cases { 66 | t.Run("case", func(t *testing.T) { 67 | if err := tc.Op.CheckAgainstSpec(tc.Spec, 1); err != nil { 68 | t.Errorf("Invalid InnerOp: %v", err) 69 | } 70 | leftBranchesAreEmpty, err := leftBranchesAreEmpty(tc.Spec.InnerSpec, tc.Op) 71 | if err != nil { 72 | t.Errorf("Error: %v", err) 73 | } 74 | if leftBranchesAreEmpty != tc.IsLeft { 75 | t.Errorf("Expected leftBranchesAreEmpty to be %t but it wasn't", tc.IsLeft) 76 | } 77 | rightBranchesAreEmpty, err := rightBranchesAreEmpty(tc.Spec.InnerSpec, tc.Op) 78 | if err != nil { 79 | t.Errorf("Error: %v", err) 80 | } 81 | if rightBranchesAreEmpty != tc.IsRight { 82 | t.Errorf("Expected rightBranchesAreEmpty to be %t but it wasn't", tc.IsRight) 83 | } 84 | }) 85 | } 86 | } 87 | 88 | func TestGetPosition(t *testing.T) { 89 | cases := []struct { 90 | name string 91 | order []int32 92 | branch int32 93 | expPos int 94 | expError error 95 | }{ 96 | { 97 | name: "success: first branch", 98 | order: IavlSpec.InnerSpec.ChildOrder, 99 | branch: 0, 100 | expPos: 0, 101 | expError: nil, 102 | }, 103 | { 104 | name: "success: second branch", 105 | order: IavlSpec.InnerSpec.ChildOrder, 106 | branch: 1, 107 | expPos: 1, 108 | expError: nil, 109 | }, 110 | { 111 | name: "failure: negative branch value", 112 | order: IavlSpec.InnerSpec.ChildOrder, 113 | branch: -1, 114 | expPos: -1, 115 | expError: fmt.Errorf("invalid branch: %d", int32(-1)), 116 | }, 117 | { 118 | name: "failure: branch is greater than length of child order", 119 | order: IavlSpec.InnerSpec.ChildOrder, 120 | branch: 3, 121 | expPos: -1, 122 | expError: fmt.Errorf("invalid branch: %d", int32(3)), 123 | }, 124 | { 125 | name: "failure: branch not found in child order", 126 | order: []int32{0, 2}, 127 | branch: 1, 128 | expPos: -1, 129 | expError: fmt.Errorf("branch %d not found in order %v", 1, []int32{0, 2}), 130 | }, 131 | } 132 | 133 | for _, tc := range cases { 134 | tc := tc 135 | 136 | t.Run(tc.name, func(t *testing.T) { 137 | pos, err := getPosition(tc.order, tc.branch) 138 | if tc.expError == nil && err != nil { 139 | t.Fatal(err) 140 | } 141 | 142 | if tc.expError != nil && (err.Error() != tc.expError.Error() || pos != -1) { 143 | t.Fatalf("expected: %v, got: %v", tc.expError, err) 144 | } 145 | 146 | if tc.expPos != pos { 147 | t.Fatalf("expected position: %d, got: %d", tc.expPos, pos) 148 | } 149 | }) 150 | 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /go/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=ics23-go 2 | sonar.organization=cosmos 3 | 4 | sonar.projectName=ics23 - Go 5 | sonar.project.monorepo.enabled=true 6 | 7 | sonar.sources=. 8 | sonar.exclusions=**/*_test.go,**/testdata/**, 9 | sonar.tests=. 10 | sonar.test.inclusions=**/*_test.go 11 | sonar.go.coverage.reportPaths=coverage.out 12 | 13 | sonar.sourceEncoding=UTF-8 14 | sonar.scm.provider=git -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzExistenceProofCalculate/eb33978947ce32e1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"\"\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzExistenceProofCheckAgainstSpec/09bebc2fc8d0a79b: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"Proof\":{\"leAf\":{\"prehAsh_vAlue\":1,\"length\":1,\"prefiX\":\"AA00\"},\"pAth\":[{\"hAsh\":1}]},\"SpeC\":{\"leAf_speC\":{\"prehAsh_vAlue\":1,\"length\":1,\"prefiX\":\"AA00\"},\"inner_speC\":{\"hAsh\":1}}}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzExistenceProofCheckAgainstSpec/19e35d361fe85847: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"Proof\":{\"leAf\":{},\"pAth\":[{}]},\"SpeC\":{\"leAf_speC\":{}}}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzExistenceProofCheckAgainstSpec/1f84363823f5c624: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"Proof\":{\"leAf\":{}},\"SpeC\":{}}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzExistenceProofCheckAgainstSpec/a9ba9cba7c7724a0: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"Proof\":{\"leAf\":{}}}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzVerifyMembership/8093511184ad3e25: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzVerifyMembership/99dd1125ca292163: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"Ref\":{}}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzVerifyNonMembership/5f0bfc6c6efd28aa: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{\"Ref\":{\"RootHAsh\":\"\"}}") 3 | -------------------------------------------------------------------------------- /go/testdata/fuzz/FuzzVerifyNonMembership/8093511184ad3e25: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("{}") 3 | -------------------------------------------------------------------------------- /go/vectors_data_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | // TestVector is what is stored in the file 12 | type TestVector struct { 13 | RootHash string `json:"root"` 14 | Proof string `json:"proof"` 15 | Key string `json:"key"` 16 | Value string `json:"value"` 17 | } 18 | 19 | // RefData is parsed version of everything except the CommitmentProof itself 20 | type RefData struct { 21 | RootHash []byte 22 | Key []byte 23 | Value []byte 24 | } 25 | 26 | type TestVectorsStruct struct { 27 | Dir string 28 | Filename string 29 | Spec *ProofSpec 30 | } 31 | 32 | func VectorsTestData() []TestVectorsStruct { 33 | iavl := filepath.Join("..", "testdata", "iavl") 34 | tendermint := filepath.Join("..", "testdata", "tendermint") 35 | smt := filepath.Join("..", "testdata", "smt") 36 | cases := []TestVectorsStruct{ 37 | {Dir: iavl, Filename: "exist_left.json", Spec: IavlSpec}, 38 | {Dir: iavl, Filename: "exist_right.json", Spec: IavlSpec}, 39 | {Dir: iavl, Filename: "exist_middle.json", Spec: IavlSpec}, 40 | {Dir: iavl, Filename: "nonexist_left.json", Spec: IavlSpec}, 41 | {Dir: iavl, Filename: "nonexist_right.json", Spec: IavlSpec}, 42 | {Dir: iavl, Filename: "nonexist_middle.json", Spec: IavlSpec}, 43 | {Dir: tendermint, Filename: "exist_left.json", Spec: TendermintSpec}, 44 | {Dir: tendermint, Filename: "exist_right.json", Spec: TendermintSpec}, 45 | {Dir: tendermint, Filename: "exist_middle.json", Spec: TendermintSpec}, 46 | {Dir: tendermint, Filename: "nonexist_left.json", Spec: TendermintSpec}, 47 | {Dir: tendermint, Filename: "nonexist_right.json", Spec: TendermintSpec}, 48 | {Dir: tendermint, Filename: "nonexist_middle.json", Spec: TendermintSpec}, 49 | {Dir: smt, Filename: "exist_left.json", Spec: SmtSpec}, 50 | {Dir: smt, Filename: "exist_right.json", Spec: SmtSpec}, 51 | {Dir: smt, Filename: "exist_middle.json", Spec: SmtSpec}, 52 | {Dir: smt, Filename: "nonexist_left.json", Spec: SmtSpec}, 53 | {Dir: smt, Filename: "nonexist_right.json", Spec: SmtSpec}, 54 | {Dir: smt, Filename: "nonexist_middle.json", Spec: SmtSpec}, 55 | } 56 | return cases 57 | } 58 | 59 | // BatchVector is what is stored in the file 60 | type BatchVector struct { 61 | RootHash string `json:"root"` 62 | Proof string `json:"proof"` 63 | Items []struct { 64 | Key string `json:"key"` 65 | Value string `json:"value"` 66 | } 67 | } 68 | 69 | type BatchVectorData struct { 70 | Spec *ProofSpec 71 | Proof *CommitmentProof 72 | Ref *RefData 73 | Invalid bool // default is valid 74 | } 75 | 76 | func LoadFile(tb testing.TB, dir string, filename string) (*CommitmentProof, *RefData) { 77 | tb.Helper() 78 | // load the file into a json struct 79 | name := filepath.Join(dir, filename) 80 | bz, err := os.ReadFile(name) 81 | if err != nil { 82 | tb.Fatalf("Read file: %+v", err) 83 | } 84 | var data TestVector 85 | err = json.Unmarshal(bz, &data) 86 | if err != nil { 87 | tb.Fatalf("Unmarshal json: %+v", err) 88 | } 89 | // parse the protobuf object 90 | var proof CommitmentProof 91 | err = proof.Unmarshal(mustHex(tb, data.Proof)) 92 | if err != nil { 93 | tb.Fatalf("Unmarshal protobuf: %+v", err) 94 | } 95 | var ref RefData 96 | ref.RootHash = CommitmentRoot(mustHex(tb, data.RootHash)) 97 | ref.Key = mustHex(tb, data.Key) 98 | if data.Value != "" { 99 | ref.Value = mustHex(tb, data.Value) 100 | } 101 | return &proof, &ref 102 | } 103 | 104 | func mustHex(tb testing.TB, data string) []byte { 105 | tb.Helper() 106 | if data == "" { 107 | return nil 108 | } 109 | res, err := hex.DecodeString(data) 110 | if err != nil { 111 | tb.Fatalf("decoding hex: %v", err) 112 | } 113 | return res 114 | } 115 | -------------------------------------------------------------------------------- /go/vectors_test.go: -------------------------------------------------------------------------------- 1 | package ics23 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestVectors(t *testing.T) { 10 | cases := VectorsTestData() 11 | for _, tc := range cases { 12 | tc := tc 13 | name := fmt.Sprintf("%s/%s", tc.Dir, tc.Filename) 14 | t.Run(name, func(t *testing.T) { 15 | proof, ref := LoadFile(t, tc.Dir, tc.Filename) 16 | 17 | // Test Calculate method 18 | calculatedRoot, err := proof.Calculate() 19 | if err != nil { 20 | t.Fatal("proof.Calculate() returned error") 21 | } 22 | if !bytes.Equal(ref.RootHash, calculatedRoot) { 23 | t.Fatalf("calculated root: %X did not match expected root: %X", calculatedRoot, ref.RootHash) 24 | } 25 | // Test Verify method 26 | if ref.Value == nil { 27 | // non-existence 28 | valid := VerifyNonMembership(tc.Spec, ref.RootHash, proof, ref.Key) 29 | if !valid { 30 | t.Fatalf("Invalid proof: %v", err) 31 | } 32 | } else { 33 | valid := VerifyMembership(tc.Spec, ref.RootHash, proof, ref.Key, ref.Value) 34 | if !valid { 35 | t.Fatalf("Invalid proof: %v", err) 36 | } 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: .. 5 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | name: buf.build/cosmos/ics23 3 | lint: 4 | use: 5 | - DEFAULT 6 | except: 7 | - ENUM_ZERO_VALUE_SUFFIX 8 | - ENUM_VALUE_PREFIX 9 | breaking: 10 | use: 11 | - FILE 12 | -------------------------------------------------------------------------------- /proto/cosmos/ics23/v1/proofs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cosmos.ics23.v1; 4 | 5 | option go_package = "github.com/cosmos/ics23/go;ics23"; 6 | 7 | enum HashOp { 8 | // NO_HASH is the default if no data passed. Note this is an illegal argument some places. 9 | NO_HASH = 0; 10 | SHA256 = 1; 11 | SHA512 = 2; 12 | KECCAK256 = 3; 13 | RIPEMD160 = 4; 14 | BITCOIN = 5; // ripemd160(sha256(x)) 15 | SHA512_256 = 6; 16 | BLAKE2B_512 = 7; 17 | BLAKE2S_256 = 8; 18 | BLAKE3 = 9; 19 | } 20 | 21 | /** 22 | LengthOp defines how to process the key and value of the LeafOp 23 | to include length information. After encoding the length with the given 24 | algorithm, the length will be prepended to the key and value bytes. 25 | (Each one with it's own encoded length) 26 | */ 27 | enum LengthOp { 28 | // NO_PREFIX don't include any length info 29 | NO_PREFIX = 0; 30 | // VAR_PROTO uses protobuf (and go-amino) varint encoding of the length 31 | VAR_PROTO = 1; 32 | // VAR_RLP uses rlp int encoding of the length 33 | VAR_RLP = 2; 34 | // FIXED32_BIG uses big-endian encoding of the length as a 32 bit integer 35 | FIXED32_BIG = 3; 36 | // FIXED32_LITTLE uses little-endian encoding of the length as a 32 bit integer 37 | FIXED32_LITTLE = 4; 38 | // FIXED64_BIG uses big-endian encoding of the length as a 64 bit integer 39 | FIXED64_BIG = 5; 40 | // FIXED64_LITTLE uses little-endian encoding of the length as a 64 bit integer 41 | FIXED64_LITTLE = 6; 42 | // REQUIRE_32_BYTES is like NONE, but will fail if the input is not exactly 32 bytes (sha256 output) 43 | REQUIRE_32_BYTES = 7; 44 | // REQUIRE_64_BYTES is like NONE, but will fail if the input is not exactly 64 bytes (sha512 output) 45 | REQUIRE_64_BYTES = 8; 46 | } 47 | 48 | /** 49 | ExistenceProof takes a key and a value and a set of steps to perform on it. 50 | The result of peforming all these steps will provide a "root hash", which can 51 | be compared to the value in a header. 52 | 53 | Since it is computationally infeasible to produce a hash collission for any of the used 54 | cryptographic hash functions, if someone can provide a series of operations to transform 55 | a given key and value into a root hash that matches some trusted root, these key and values 56 | must be in the referenced merkle tree. 57 | 58 | The only possible issue is maliablity in LeafOp, such as providing extra prefix data, 59 | which should be controlled by a spec. Eg. with lengthOp as NONE, 60 | prefix = FOO, key = BAR, value = CHOICE 61 | and 62 | prefix = F, key = OOBAR, value = CHOICE 63 | would produce the same value. 64 | 65 | With LengthOp this is tricker but not impossible. Which is why the "leafPrefixEqual" field 66 | in the ProofSpec is valuable to prevent this mutability. And why all trees should 67 | length-prefix the data before hashing it. 68 | */ 69 | message ExistenceProof { 70 | bytes key = 1; 71 | bytes value = 2; 72 | LeafOp leaf = 3; 73 | repeated InnerOp path = 4; 74 | } 75 | 76 | /* 77 | NonExistenceProof takes a proof of two neighbors, one left of the desired key, 78 | one right of the desired key. If both proofs are valid AND they are neighbors, 79 | then there is no valid proof for the given key. 80 | */ 81 | message NonExistenceProof { 82 | bytes key = 1; // TODO: remove this as unnecessary??? we prove a range 83 | ExistenceProof left = 2; 84 | ExistenceProof right = 3; 85 | } 86 | 87 | /* 88 | CommitmentProof is either an ExistenceProof or a NonExistenceProof, or a Batch of such messages 89 | */ 90 | message CommitmentProof { 91 | oneof proof { 92 | ExistenceProof exist = 1; 93 | NonExistenceProof nonexist = 2; 94 | BatchProof batch = 3; 95 | CompressedBatchProof compressed = 4; 96 | } 97 | } 98 | 99 | /** 100 | LeafOp represents the raw key-value data we wish to prove, and 101 | must be flexible to represent the internal transformation from 102 | the original key-value pairs into the basis hash, for many existing 103 | merkle trees. 104 | 105 | key and value are passed in. So that the signature of this operation is: 106 | leafOp(key, value) -> output 107 | 108 | To process this, first prehash the keys and values if needed (ANY means no hash in this case): 109 | hkey = prehashKey(key) 110 | hvalue = prehashValue(value) 111 | 112 | Then combine the bytes, and hash it 113 | output = hash(prefix || length(hkey) || hkey || length(hvalue) || hvalue) 114 | */ 115 | message LeafOp { 116 | HashOp hash = 1; 117 | HashOp prehash_key = 2; 118 | HashOp prehash_value = 3; 119 | LengthOp length = 4; 120 | // prefix is a fixed bytes that may optionally be included at the beginning to differentiate 121 | // a leaf node from an inner node. 122 | bytes prefix = 5; 123 | } 124 | 125 | /** 126 | InnerOp represents a merkle-proof step that is not a leaf. 127 | It represents concatenating two children and hashing them to provide the next result. 128 | 129 | The result of the previous step is passed in, so the signature of this op is: 130 | innerOp(child) -> output 131 | 132 | The result of applying InnerOp should be: 133 | output = op.hash(op.prefix || child || op.suffix) 134 | 135 | where the || operator is concatenation of binary data, 136 | and child is the result of hashing all the tree below this step. 137 | 138 | Any special data, like prepending child with the length, or prepending the entire operation with 139 | some value to differentiate from leaf nodes, should be included in prefix and suffix. 140 | If either of prefix or suffix is empty, we just treat it as an empty string 141 | */ 142 | message InnerOp { 143 | HashOp hash = 1; 144 | bytes prefix = 2; 145 | bytes suffix = 3; 146 | } 147 | 148 | /** 149 | ProofSpec defines what the expected parameters are for a given proof type. 150 | This can be stored in the client and used to validate any incoming proofs. 151 | 152 | verify(ProofSpec, Proof) -> Proof | Error 153 | 154 | As demonstrated in tests, if we don't fix the algorithm used to calculate the 155 | LeafHash for a given tree, there are many possible key-value pairs that can 156 | generate a given hash (by interpretting the preimage differently). 157 | We need this for proper security, requires client knows a priori what 158 | tree format server uses. But not in code, rather a configuration object. 159 | */ 160 | message ProofSpec { 161 | // any field in the ExistenceProof must be the same as in this spec. 162 | // except Prefix, which is just the first bytes of prefix (spec can be longer) 163 | LeafOp leaf_spec = 1; 164 | InnerSpec inner_spec = 2; 165 | // max_depth (if > 0) is the maximum number of InnerOps allowed (mainly for fixed-depth tries) 166 | // the max_depth is interpreted as 128 if set to 0 167 | int32 max_depth = 3; 168 | // min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries) 169 | int32 min_depth = 4; 170 | // prehash_key_before_comparison is a flag that indicates whether to use the 171 | // prehash_key specified by LeafOp to compare lexical ordering of keys for 172 | // non-existence proofs. 173 | bool prehash_key_before_comparison = 5; 174 | } 175 | 176 | /* 177 | InnerSpec contains all store-specific structure info to determine if two proofs from a 178 | given store are neighbors. 179 | 180 | This enables: 181 | 182 | isLeftMost(spec: InnerSpec, op: InnerOp) 183 | isRightMost(spec: InnerSpec, op: InnerOp) 184 | isLeftNeighbor(spec: InnerSpec, left: InnerOp, right: InnerOp) 185 | */ 186 | message InnerSpec { 187 | // Child order is the ordering of the children node, must count from 0 188 | // iavl tree is [0, 1] (left then right) 189 | // merk is [0, 2, 1] (left, right, here) 190 | repeated int32 child_order = 1; 191 | int32 child_size = 2; 192 | int32 min_prefix_length = 3; 193 | // the max prefix length must be less than the minimum prefix length + child size 194 | int32 max_prefix_length = 4; 195 | // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) 196 | bytes empty_child = 5; 197 | // hash is the algorithm that must be used for each InnerOp 198 | HashOp hash = 6; 199 | } 200 | 201 | /* 202 | BatchProof is a group of multiple proof types than can be compressed 203 | */ 204 | message BatchProof { 205 | repeated BatchEntry entries = 1; 206 | } 207 | 208 | // Use BatchEntry not CommitmentProof, to avoid recursion 209 | message BatchEntry { 210 | oneof proof { 211 | ExistenceProof exist = 1; 212 | NonExistenceProof nonexist = 2; 213 | } 214 | } 215 | 216 | /****** all items here are compressed forms *******/ 217 | 218 | message CompressedBatchProof { 219 | repeated CompressedBatchEntry entries = 1; 220 | repeated InnerOp lookup_inners = 2; 221 | } 222 | 223 | // Use BatchEntry not CommitmentProof, to avoid recursion 224 | message CompressedBatchEntry { 225 | oneof proof { 226 | CompressedExistenceProof exist = 1; 227 | CompressedNonExistenceProof nonexist = 2; 228 | } 229 | } 230 | 231 | message CompressedExistenceProof { 232 | bytes key = 1; 233 | bytes value = 2; 234 | LeafOp leaf = 3; 235 | // these are indexes into the lookup_inners table in CompressedBatchProof 236 | repeated int32 path = 4; 237 | } 238 | 239 | message CompressedNonExistenceProof { 240 | bytes key = 1; // TODO: remove this as unnecessary??? we prove a range 241 | CompressedExistenceProof left = 2; 242 | CompressedExistenceProof right = 3; 243 | } 244 | -------------------------------------------------------------------------------- /rust/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | protoc = "run --package codegen --bin codegen .." -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /rust/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # Unreleased 4 | 5 | - fix: guarantee that `spec.InnerSpec.MaxPrefixLength` < `spec.InnerSpec.MinPrefixLength` + `spec.InnerSpec.ChildSize` ([#369](https://github.com/cosmos/ics23/pull/369)) 6 | 7 | # v0.12.0 8 | 9 | - chore(rust): Update `prost` to v0.13 ([#335](https://github.com/cosmos/ics23/pull/335), [#336](https://github.com/cosmos/ics23/pull/336)) 10 | 11 | # v0.11.3 12 | 13 | - fix(rust): Enforce that `spec.InnerSpec.ChildSize` is >= 1 ([#339](https://github.com/cosmos/ics23/pull/339)) 14 | 15 | # v0.11.2 16 | 17 | This release was yanked, please use v0.11.3 instead. 18 | 19 | # v0.11.1 20 | 21 | - chore(rust): Update `informalsystems-pbjson` to v0.7.0 ([#274](https://github.com/cosmos/ics23/pull/274)) 22 | 23 | # v0.11.0 24 | 25 | - chore(rust): update `prost` to v0.12 ([#202](https://github.com/cosmos/ics23/pull/202)) 26 | 27 | # v0.10.2 28 | 29 | This release re-enables `no_std` support for ProtoJSON `Serialize` and `Deserialize` instances, 30 | by swapping out `pbjson` for the `no_std`-compatible fork `informalsystems-pbjson`. 31 | 32 | ## Full changes 33 | 34 | - feat(rust): enable no_std support for pbjson ([#158](https://github.com/cosmos/ics23/pull/146)) 35 | 36 | # v0.10.1 37 | 38 | The only change in this release of the `ics23` crate is the addition of a `serde` feature 39 | which adds ProtoJSON-compatible `Serialize` and `Deserialize` instances on all Protobuf definitions via 40 | the [`pbjson-build`](https://docs.rs/pbjson-build/latest/pbjson_build/) crate. 41 | 42 | ## Full changes 43 | 44 | - feat(rust): Add ProtoJSON-compatible `Serialize` and `Deserialize` instances on all Protobuf definitions via `pbjson` ([#146](https://github.com/cosmos/ics23/pull/146)) 45 | 46 | # v0.10.0 47 | 48 | This release introduces one single boolean new parameter to the top-level `ProofSpec`: `prehash_compare_key`. 49 | When set to `true`, this flag causes keys to be consistently compared lexicographically according to their hashes 50 | within nonexistence proof verification, using the same hash function as specified by the already-extant `prehash_key` field. 51 | 52 | This is a backwards-compatible change, as it requires opt-in via setting the `prehash_compare_key` flag to `true` in the `ProofSpec`. 53 | All existing `ProofSpec`s will continue to behave identically. 54 | 55 | ## Full changes 56 | 57 | - feat: Add `prehash_compare_key` to allow proving nonexistence in sparse trees ([#136](https://github.com/cosmos/ics23/pull/136)) 58 | - fix: protobuf formatting using clang-format ([#129](https://github.com/cosmos/ics23/pull/129)) 59 | - add buf support to repo ([#126](https://github.com/cosmos/ics23/pull/126)) 60 | - chore: Add Cosmos, license and coverage badges to the README ([#122](https://github.com/cosmos/ics23/pull/122)) 61 | - ci: Add tags to codecov reports ([#121](https://github.com/cosmos/ics23/pull/121)) (4 months ago) 62 | - ci: Refactor GitHub workflows and add code coverage job ([#120](https://github.com/cosmos/ics23/pull/120)) 63 | 64 | # v0.9.0 65 | 66 | Release of the `ics23` create, including changes that reflect changes made in the Go implementation in the fork of `ics23` housed in the [Cosmos SDK](http://github.com/cosmos/cosmos-sdk). 67 | 68 | # v0.8.1 69 | 70 | - Fix no\_std compatibility and add check for this on CI ([#104](https://github.com/confio/ics23/pull/104)) 71 | 72 | # v0.8.0 73 | 74 | The following functions have been made generic over a new trait `HostFunctionProvider`: 75 | 76 | - [x] `calculate_existence_root` 77 | - [x] `verify_batch_membership` 78 | - [x] `verify_batch_non_membership` 79 | - [x] `verify_membership` 80 | - [x] `verify_non_membership` 81 | 82 | For `wasm32-unknown-unknown` environments this trait allows you to delegate hashing functions to a native implementation through host functions. 83 | 84 | With the `host-functions` feature (enabled by default), you can simply use `ics23::HostFunctionManager` as this provides a default implementation of this trait. 85 | 86 | # v0.7.0 87 | 88 | This handles non-existence tests for empty branches properly. This 89 | is needed for properly handling proofs on Tries, like the SMT being 90 | integrated with the Cosmos SDK. 91 | 92 | This is used in ibc-go v3 93 | 94 | # 0.6.x 95 | 96 | This handles proofs for normal merkle trees, where every branch is full. 97 | This works for tendermint merkle hashes and iavl hashes, and should work 98 | for merk (nomic's db) proofs. 99 | 100 | This was used in the original ibc release (cosmos sdk v0.40) and up until 101 | ibc-go v2. 102 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ethan Frey "] 3 | description = "Merkle proof verification library - implements Cosmos ICS23 Spec" 4 | edition = "2021" 5 | exclude = ["codegen", "no-std-check"] 6 | license = "Apache-2.0" 7 | name = "ics23" 8 | repository = "https://github.com/cosmos/ics23/tree/master/rust" 9 | rust-version = "1.60" 10 | version = "0.12.0" 11 | 12 | [workspace] 13 | members = ["codegen", "no-std-check"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | [dependencies] 17 | anyhow = {version = "1.0.40", default-features = false} 18 | blake2 = {version = "0.10.6", optional = true, default-features = false} 19 | blake3 = {version = "1.5.0", optional = true, default-features = false} 20 | bytes = {version = "1.0.1", default-features = false} 21 | hex = {version = "0.4.3", default-features = false, features = ["alloc"]} 22 | prost = {version = "0.13", default-features = false, features = ["prost-derive"]} 23 | ripemd = {version = "0.1.1", optional = true, default-features = false} 24 | sha2 = {version = "0.10.2", optional = true, default-features = false} 25 | sha3 = {version = "0.10.2", optional = true, default-features = false} 26 | serde = {version = "1.0", optional = true, default-features = false} 27 | informalsystems-pbjson = { version = "0.7.0", optional = true, default-features = false } 28 | 29 | [dev-dependencies] 30 | blake2 = {version = "0.10.6"} 31 | blake3 = {version = "1.5.0"} 32 | ripemd = {version = "0.1.1"} 33 | serde = {version = "1.0", features = ["derive"]} 34 | serde_json = {version = "1.0.64"} 35 | sha2 = {version = "0.10.2"} 36 | sha3 = {version = "0.10.2"} 37 | 38 | [features] 39 | default = ["std", "host-functions"] 40 | 41 | std = ["prost/std", "bytes/std", "hex/std", "anyhow/std", "informalsystems-pbjson/std", "serde/std"] 42 | 43 | host-functions = ["sha2", "sha3", "ripemd", "blake2", "blake3"] 44 | 45 | serde = ["dep:serde", "informalsystems-pbjson"] 46 | -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | # Rust Proof Validation 2 | 3 | ## Codegen 4 | 5 | To avoid direct dependencies on `protoc` in the build system, I have separated 6 | `codegen` into a sub-crate. This will generate the rust `proofs.rs` file from 7 | the `proofs.proto` file. The rest of the main build/test cycle is now independent 8 | of the `protoc` binary. 9 | 10 | To rebuild protobuf, simply: `cargo protoc` (on a dev machine with `protoc` in path). 11 | Unless you modify the protobuf file, you can ignore this step. 12 | 13 | ## Formatting 14 | 15 | `cargo fmt` 16 | 17 | ## Testing 18 | 19 | `cargo test` 20 | 21 | ## Linting 22 | 23 | `cargo clippy -- --test -W clippy::pedantic` 24 | 25 | ## Code Coverage 26 | 27 | `cargo llvm-cov` 28 | 29 | ## MSRV 30 | 31 | The minimum supported Rust version (MSRV) is 1.60. 32 | 33 | -------------------------------------------------------------------------------- /rust/codegen/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "c2-chacha" 5 | version = "0.2.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "cfg-if" 14 | version = "0.1.9" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "codegen" 19 | version = "0.1.0" 20 | dependencies = [ 21 | "protobuf 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "protoc-rust 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "getrandom" 27 | version = "0.1.8" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 32 | ] 33 | 34 | [[package]] 35 | name = "lazy_static" 36 | version = "1.3.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "libc" 41 | version = "0.2.60" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "log" 46 | version = "0.4.8" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | dependencies = [ 49 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 50 | ] 51 | 52 | [[package]] 53 | name = "ppv-lite86" 54 | version = "0.2.5" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | 57 | [[package]] 58 | name = "protobuf" 59 | version = "2.8.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | 62 | [[package]] 63 | name = "protobuf-codegen" 64 | version = "2.8.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "protobuf 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "protoc" 72 | version = "2.8.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "protoc-rust" 80 | version = "2.8.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "protobuf 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "protobuf-codegen 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "protoc 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | ] 88 | 89 | [[package]] 90 | name = "rand" 91 | version = "0.7.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "getrandom 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 99 | ] 100 | 101 | [[package]] 102 | name = "rand_chacha" 103 | version = "0.2.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "rand_core" 112 | version = "0.5.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "getrandom 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "rand_hc" 120 | version = "0.2.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | dependencies = [ 123 | "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 124 | ] 125 | 126 | [[package]] 127 | name = "redox_syscall" 128 | version = "0.1.56" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | 131 | [[package]] 132 | name = "remove_dir_all" 133 | version = "0.5.2" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | dependencies = [ 136 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 137 | ] 138 | 139 | [[package]] 140 | name = "tempfile" 141 | version = "3.1.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | dependencies = [ 144 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 150 | ] 151 | 152 | [[package]] 153 | name = "winapi" 154 | version = "0.3.7" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | dependencies = [ 157 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | ] 160 | 161 | [[package]] 162 | name = "winapi-i686-pc-windows-gnu" 163 | version = "0.4.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "winapi-x86_64-pc-windows-gnu" 168 | version = "0.4.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | 171 | [metadata] 172 | "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" 173 | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 174 | "checksum getrandom 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "34f33de6f0ae7c9cb5e574502a562e2b512799e32abb801cd1e79ad952b62b49" 175 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 176 | "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" 177 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 178 | "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" 179 | "checksum protobuf 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8aefcec9f142b524d98fc81d07827743be89dd6586a1ba6ab21fa66a500b3fa5" 180 | "checksum protobuf-codegen 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31539be8028d6b9e8e1b3b7c74e2fa3555302e27b2cc20dbaee6ffba648f75e2" 181 | "checksum protoc 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d60e39b07eb4039379829c55c11eba3fd8bd72b265b9ece8cc623b106908c08d" 182 | "checksum protoc-rust 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d48a8543845706a2cf6336b9540d4a27efba0f666189690c1a8f50ae182ee1b" 183 | "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" 184 | "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 185 | "checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" 186 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 187 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 188 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 189 | "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 190 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 191 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 192 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 193 | -------------------------------------------------------------------------------- /rust/codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.1.0" 4 | authors = ["Ethan Frey "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | bytes = "1.0.1" 11 | prost = "0.13" 12 | prost-build = "0.13" 13 | informalsystems-pbjson-build = "0.7.0" 14 | -------------------------------------------------------------------------------- /rust/codegen/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::Result; 3 | use std::path::PathBuf; 4 | use std::vec::Vec; 5 | 6 | fn main() -> Result<()> { 7 | let args: Vec<_> = env::args().collect(); 8 | let root = if args.len() > 1 { 9 | &args[1] 10 | } else { 11 | env!("CARGO_MANIFEST_DIR") 12 | }; 13 | 14 | println!("Root: {root}"); 15 | 16 | let root = PathBuf::from(root).join("..").join("..").canonicalize()?; 17 | let input = root.join("proto/cosmos/ics23/v1/proofs.proto"); 18 | let out_dir = root.join("rust/src"); 19 | let descriptor_path = out_dir.join("proto_descriptor.bin"); 20 | 21 | println!("Input: {}", input.display()); 22 | println!("Output: {}", out_dir.display()); 23 | println!("Descriptor: {}", descriptor_path.display()); 24 | 25 | prost_build::Config::new() 26 | .out_dir(&out_dir) 27 | .format(true) 28 | // Needed for pbjson_build to generate the serde implementations 29 | .file_descriptor_set_path(&descriptor_path) 30 | .compile_well_known_types() 31 | // As recommended in pbjson_types docs 32 | .extern_path(".google.protobuf", "::pbjson_types") 33 | .compile_protos(&[input], &[root])?; 34 | 35 | // Finally, build pbjson Serialize, Deserialize impls: 36 | let descriptor_set = std::fs::read(descriptor_path)?; 37 | 38 | pbjson_build::Builder::new() 39 | .register_descriptors(&descriptor_set)? 40 | .out_dir(&out_dir) 41 | .build(&[".cosmos.ics23.v1"])?; 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /rust/no-std-check/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /rust/no-std-check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no-std-check" 3 | version = "0.1.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | ics23 = { path = "../", default-features = false } 9 | 10 | [features] 11 | panic-handler = [] 12 | -------------------------------------------------------------------------------- /rust/no-std-check/Makefile: -------------------------------------------------------------------------------- 1 | NIGHTLY_VERSION=nightly 2 | 3 | .DEFAULT_GOAL := help 4 | 5 | .PHONY: all setup check-panic-conflict check-cargo-build-std check-wasm help 6 | 7 | all: ## Run all checks 8 | $(MAKE) check-panic-conflict 9 | $(MAKE) check-cargo-build-std 10 | $(MAKE) check-wasm 11 | 12 | setup: ## Setup the required nightly toolchain and the wasm32 target 13 | rustup install $(NIGHTLY_VERSION) 14 | rustup target add wasm32-unknown-unknown --toolchain $(NIGHTLY_VERSION) 15 | rustup component add rust-src --toolchain $(NIGHTLY_VERSION) 16 | 17 | check-panic-conflict: ## Check for `no_std` compliance by installing a panic handler, and any other crate importing `std` will cause a conflict. Runs on default target. 18 | cargo build \ 19 | --no-default-features \ 20 | --features panic-handler 21 | 22 | check-cargo-build-std: ## Check for `no_std` compliance using Cargo nightly's `build-std` feature. Runs on the target `x86_64-unknown-linux-gnu`. 23 | rustup run $(NIGHTLY_VERSION) -- \ 24 | cargo build -Z build-std=core,alloc \ 25 | --no-default-features \ 26 | --target x86_64-unknown-linux-gnu 27 | 28 | check-wasm: ## Check for WebAssembly and `no_std` compliance by building on the target `wasm32-unknown-unknown` and installing a panic handler. 29 | rustup run $(NIGHTLY_VERSION) -- \ 30 | cargo build \ 31 | --features panic-handler \ 32 | --target wasm32-unknown-unknown 33 | 34 | help: ## Show this help message 35 | @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 36 | 37 | -------------------------------------------------------------------------------- /rust/no-std-check/README.md: -------------------------------------------------------------------------------- 1 | # `no_std` Compliance Check 2 | 3 | This crate checks the `no_std` compliance of the `ics23` crate. 4 | 5 | Based on the corresponding check in the [`ibc-rs`](https://github.com/informalsystems/ibc-rs) project. 6 | 7 | ## Make Recipes 8 | 9 | - `check-panic-conflict` - Check for `no_std` compliance by installing a panic handler, and any other crate importing `std` will cause a conflict. Runs on default target. 10 | 11 | - `check-cargo-build-std` - Check for `no_std` compliance using Cargo nightly's `build-std` feature. Runs on the target `x86_64-unknown-linux-gnu`. 12 | 13 | - `check-wasm` - Check for WebAssembly and `no_std` compliance by building on the target `wasm32-unknown-unknown` and installing a panic handler. 14 | 15 | ## Checking Single Unsupported Dependency 16 | 17 | By default, the check scripts try to build all unsupported dependencies and will fail. To test if a particular crate still fails the no_std check, edit the `use-unsupported` list in [Cargo.toml](./Cargo.toml) to uncomment all crates except the crate that we are interested to check. For example, to check for only the `getrandom` crate: 18 | 19 | ```toml 20 | use-unsupported = [ 21 | # "tonic", 22 | # "socket2", 23 | "getrandom", 24 | # "serde", 25 | # ..., 26 | ] 27 | ``` 28 | 29 | ## Adding New Dependencies 30 | 31 | For a crate named `my-package-1.2.3`, first try and add the crate in [Cargo.toml](./Cargo.toml) of this project as: 32 | 33 | ```toml 34 | my-package = { version = "1.2.3" } 35 | ``` 36 | 37 | Then comment out the `use-unsupported` list in the `[features]` section of Cargo.toml and replace it with an empty list temporarily for testing: 38 | 39 | ```toml 40 | [features] 41 | ... 42 | use-unsupported = [] 43 | # use-unsupported = [ 44 | # "tonic", 45 | # "socket2", 46 | # "getrandom", 47 | # ... 48 | # ] 49 | ``` 50 | 51 | Then import the package in [src/lib.rs](./src/lib.rs): 52 | 53 | ```rust 54 | use my_package 55 | ``` 56 | 57 | Note that you must import the package in `lib.rs`, otherwise Cargo will skip linking the crate and fail to check for the panic handler conflicts. 58 | 59 | Then run all of the check scripts and see if any of them fails. If the check script fails, try and disable the default features and run the checks again: 60 | 61 | ```rust 62 | my-package = { version = "1.2.3", default-features = false } 63 | ``` 64 | 65 | You may also need other tweaks such as enable custom features to make it run on Wasm. 66 | At this point if the checks pass, we have verified the no_std compliance of `my-package`. Restore the original `use-unsupported` list and commit the code. 67 | 68 | Otherwise if it fails, we have found a dependency that does not support `no_std`. Update Cargo.toml to make the crate optional: 69 | 70 | ```rust 71 | my-package = { version = "1.2.3", optional = true, default-features = false } 72 | ``` 73 | 74 | Now we have to modify [lib.rs](./src/lib.rs) again and only import the crate if it is enabled: 75 | 76 | ```rust 77 | #[cfg(feature = "my-package")] 78 | use my_package; 79 | ``` 80 | 81 | Retore the original `use-unsupported` list, and add `my-package` to the end of the list: 82 | 83 | ```toml 84 | use-unsupported = [ 85 | "tonic", 86 | "socket2", 87 | "getrandom", 88 | ..., 89 | "my-package", 90 | ] 91 | ``` 92 | 93 | Commit the changes so that we will track if newer version of the crate would support no_std in the future. 94 | 95 | ## Conflict Detection Methods 96 | 97 | There are two methods that we use to detect `std` conflict: 98 | 99 | ### Panic Handler Conflict 100 | 101 | This follows the outline of the guide by 102 | [Danilo Bargen](https://blog.dbrgn.ch/2019/12/24/testing-for-no-std-compatibility/) 103 | to register a panic handler in the `no-std-check` crate. 104 | Any crate imported `no-std-check` that uses `std` will cause a compile error that 105 | looks like follows: 106 | 107 | ``` 108 | $ cargo build 109 | Updating crates.io index 110 | Compiling no-std-check v0.1.0 (/data/development/informal/ibc-rs/no-std-check) 111 | error[E0152]: found duplicate lang item `panic_impl` 112 | --> src/lib.rs:31:1 113 | | 114 | 31 | fn panic(_info: &PanicInfo) -> ! { 115 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 116 | | 117 | = note: the lang item is first defined in crate `std` (which `prost` depends on) 118 | = note: first definition in `std` loaded from /home/ubuntu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b6b48477bfa8c673.rlib 119 | = note: second definition in the local crate (`no_std_check`) 120 | 121 | error: aborting due to previous error 122 | 123 | For more information about this error, try `rustc --explain E0152`. 124 | error: could not compile `no-std-check` 125 | ``` 126 | 127 | - Pros: 128 | - Can be tested using Rust stable. 129 | - Cons: 130 | - Crates must be listed on both `Cargo.toml` and `lib.rs`. 131 | - Crates that are listed in `Cargo.toml` but not imported inside `lib.rs` are not checked. 132 | 133 | ### Overrride std crates using Cargo Nightly 134 | 135 | This uses the unstable `build-std` feature provided by 136 | [Cargo Nightly](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std). 137 | With this we can explicitly pass the std crates we want to support, `core` and `alloc`, 138 | via command line, and exclude the `std` crate. 139 | 140 | If any of the dependency uses `std`, they will fail to compile at all, albeit with 141 | confusing error messages: 142 | 143 | ``` 144 | $ rustup run nightly -- cargo build -j1 -Z build-std=core,alloc --target x86_64-unknown-linux-gnu 145 | ... 146 | Compiling bytes v1.0.1 147 | error[E0773]: attempted to define built-in macro more than once 148 | --> /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:1201:5 149 | | 150 | 1201 | / macro_rules! cfg { 151 | 1202 | | ($($cfg:tt)*) => { 152 | 1203 | | /* compiler built-in */ 153 | 1204 | | }; 154 | 1205 | | } 155 | | |_____^ 156 | | 157 | note: previously defined here 158 | --> /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:1201:5 159 | | 160 | 1201 | / macro_rules! cfg { 161 | 1202 | | ($($cfg:tt)*) => { 162 | 1203 | | /* compiler built-in */ 163 | 1204 | | }; 164 | 1205 | | } 165 | | |_____^ 166 | 167 | error: duplicate lang item in crate `core` (which `std` depends on): `bool`. 168 | | 169 | = note: the lang item is first defined in crate `core` (which `bytes` depends on) 170 | = note: first definition in `core` loaded from /data/development/informal/ibc-rs/no-std-check/target/x86_64-unknown-linux-gnu/debug/deps/libcore-c00d94870d25cd7e.rmeta 171 | = note: second definition in `core` loaded from /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9924c22ae1efcf66.rlib 172 | 173 | error: duplicate lang item in crate `core` (which `std` depends on): `char`. 174 | | 175 | = note: the lang item is first defined in crate `core` (which `bytes` depends on) 176 | = note: first definition in `core` loaded from /data/development/informal/ibc-rs/no-std-check/target/x86_64-unknown-linux-gnu/debug/deps/libcore-c00d94870d25cd7e.rmeta 177 | = note: second definition in `core` loaded from /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9924c22ae1efcf66.rlib 178 | ... 179 | ``` 180 | 181 | The above error are shown when building the `bytes` crate. This is caused by `bytes` using imports from `std`, 182 | which causes `std` to be included and produce conflicts with the `core` crate that is explicitly built by Cargo. 183 | This produces very long error messages, so we may want to use tools like `less` to scroll through the errors. 184 | 185 | Pros: 186 | - Directly identify use of `std` in dependencies. 187 | - Error is raised on the first dependency that imports `std`. 188 | 189 | Cons: 190 | - Nightly-only feature that is subject to change. 191 | - Confusing and long error messages. 192 | -------------------------------------------------------------------------------- /rust/no-std-check/src/lib.rs: -------------------------------------------------------------------------------- 1 | // ensure_no_std/src/main.rs 2 | #![no_std] 3 | #![allow(unused_imports)] 4 | 5 | extern crate alloc; 6 | 7 | // Import the crates that we want to check if they are fully no-std compliance 8 | 9 | use ics23; 10 | 11 | use core::panic::PanicInfo; 12 | 13 | /* 14 | 15 | This function definition checks for the compliance of no-std in 16 | dependencies by causing a compile error if this crate is 17 | linked with `std`. When that happens, you should see error messages 18 | such as follows: 19 | 20 | ``` 21 | error[E0152]: found duplicate lang item `panic_impl` 22 | --> no-std-check/src/lib.rs 23 | | 24 | 12 | fn panic(_info: &PanicInfo) -> ! { 25 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | | 27 | = note: the lang item is first defined in crate `std` (which `offending-crate` depends on) 28 | ``` 29 | 30 | */ 31 | #[cfg(feature = "panic-handler")] 32 | #[panic_handler] 33 | #[no_mangle] 34 | fn panic(_info: &PanicInfo) -> ! { 35 | loop {} 36 | } 37 | -------------------------------------------------------------------------------- /rust/src/compress.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::ToOwned; 2 | use alloc::collections::btree_map::BTreeMap as HashMap; 3 | use alloc::vec::Vec; 4 | use prost::Message; 5 | 6 | use crate::helpers::Result; 7 | use crate::ics23; 8 | 9 | pub fn is_compressed(proof: &ics23::CommitmentProof) -> bool { 10 | matches!( 11 | &proof.proof, 12 | Some(ics23::commitment_proof::Proof::Compressed(_)) 13 | ) 14 | } 15 | 16 | pub fn compress(proof: &ics23::CommitmentProof) -> Result { 17 | if let Some(ics23::commitment_proof::Proof::Batch(batch)) = &proof.proof { 18 | compress_batch(batch) 19 | } else { 20 | Ok(proof.to_owned()) 21 | } 22 | } 23 | 24 | pub fn decompress(proof: &ics23::CommitmentProof) -> Result { 25 | if let Some(ics23::commitment_proof::Proof::Compressed(compressed)) = &proof.proof { 26 | decompress_batch(compressed) 27 | } else { 28 | Ok(proof.to_owned()) 29 | } 30 | } 31 | 32 | pub fn compress_batch(proof: &ics23::BatchProof) -> Result { 33 | let mut lookup = Vec::new(); 34 | let mut registry = HashMap::new(); 35 | 36 | let centries = proof 37 | .entries 38 | .iter() 39 | .map(|entry| match &entry.proof { 40 | Some(ics23::batch_entry::Proof::Exist(ex)) => { 41 | let exist = compress_exist(ex, &mut lookup, &mut registry)?; 42 | Ok(ics23::CompressedBatchEntry { 43 | proof: Some(ics23::compressed_batch_entry::Proof::Exist(exist)), 44 | }) 45 | } 46 | Some(ics23::batch_entry::Proof::Nonexist(non)) => { 47 | let left = non 48 | .left 49 | .as_ref() 50 | .map(|l| compress_exist(l, &mut lookup, &mut registry)) 51 | .transpose()?; 52 | let right = non 53 | .right 54 | .as_ref() 55 | .map(|r| compress_exist(r, &mut lookup, &mut registry)) 56 | .transpose()?; 57 | let nonexist = ics23::CompressedNonExistenceProof { 58 | key: non.key.clone(), 59 | left, 60 | right, 61 | }; 62 | Ok(ics23::CompressedBatchEntry { 63 | proof: Some(ics23::compressed_batch_entry::Proof::Nonexist(nonexist)), 64 | }) 65 | } 66 | None => Ok(ics23::CompressedBatchEntry { proof: None }), 67 | }) 68 | .collect::>()?; 69 | 70 | Ok(ics23::CommitmentProof { 71 | proof: Some(ics23::commitment_proof::Proof::Compressed( 72 | ics23::CompressedBatchProof { 73 | entries: centries, 74 | lookup_inners: lookup, 75 | }, 76 | )), 77 | }) 78 | } 79 | 80 | pub fn compress_exist( 81 | exist: &ics23::ExistenceProof, 82 | lookup: &mut Vec, 83 | registry: &mut HashMap, i32>, 84 | ) -> Result { 85 | let path = exist 86 | .path 87 | .iter() 88 | .map(|x| { 89 | let buf = x.encode_to_vec(); 90 | let idx = *registry.entry(buf).or_insert_with(|| { 91 | let idx = lookup.len() as i32; 92 | lookup.push(x.to_owned()); 93 | idx 94 | }); 95 | Ok(idx) 96 | }) 97 | .collect::>()?; 98 | 99 | Ok(ics23::CompressedExistenceProof { 100 | key: exist.key.clone(), 101 | value: exist.value.clone(), 102 | leaf: exist.leaf.clone(), 103 | path, 104 | }) 105 | } 106 | 107 | pub fn decompress_batch(proof: &ics23::CompressedBatchProof) -> Result { 108 | let lookup = &proof.lookup_inners; 109 | let entries = proof 110 | .entries 111 | .iter() 112 | .map(|cent| match ¢.proof { 113 | Some(ics23::compressed_batch_entry::Proof::Exist(ex)) => { 114 | let exist = decompress_exist(ex, lookup); 115 | Ok(ics23::BatchEntry { 116 | proof: Some(ics23::batch_entry::Proof::Exist(exist)), 117 | }) 118 | } 119 | Some(ics23::compressed_batch_entry::Proof::Nonexist(non)) => { 120 | let left = non.left.as_ref().map(|l| decompress_exist(l, lookup)); 121 | let right = non.right.as_ref().map(|r| decompress_exist(r, lookup)); 122 | let nonexist = ics23::NonExistenceProof { 123 | key: non.key.clone(), 124 | left, 125 | right, 126 | }; 127 | Ok(ics23::BatchEntry { 128 | proof: Some(ics23::batch_entry::Proof::Nonexist(nonexist)), 129 | }) 130 | } 131 | None => Ok(ics23::BatchEntry { proof: None }), 132 | }) 133 | .collect::>()?; 134 | 135 | Ok(ics23::CommitmentProof { 136 | proof: Some(ics23::commitment_proof::Proof::Batch(ics23::BatchProof { 137 | entries, 138 | })), 139 | }) 140 | } 141 | 142 | fn decompress_exist( 143 | exist: &ics23::CompressedExistenceProof, 144 | lookup: &[ics23::InnerOp], 145 | ) -> ics23::ExistenceProof { 146 | let path = exist 147 | .path 148 | .iter() 149 | .map(|&x| lookup.get(x as usize).cloned()) 150 | .collect::>>() 151 | .unwrap_or_default(); 152 | ics23::ExistenceProof { 153 | key: exist.key.clone(), 154 | value: exist.value.clone(), 155 | leaf: exist.leaf.clone(), 156 | path, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /rust/src/helpers.rs: -------------------------------------------------------------------------------- 1 | pub type Result = anyhow::Result; 2 | pub type Hash = alloc::vec::Vec; 3 | -------------------------------------------------------------------------------- /rust/src/host_functions.rs: -------------------------------------------------------------------------------- 1 | /// If this is to be executed in a blockchain context, then we need to delegate these hashing 2 | /// functions to a native implementation through host function calls. 3 | /// This trait provides that interface. 4 | pub trait HostFunctionsProvider { 5 | /// The SHA-256 hash algorithm 6 | fn sha2_256(message: &[u8]) -> [u8; 32]; 7 | 8 | /// The SHA-512 hash algorithm 9 | fn sha2_512(message: &[u8]) -> [u8; 64]; 10 | 11 | /// The SHA-512 hash algorithm with its output truncated to 256 bits. 12 | fn sha2_512_truncated(message: &[u8]) -> [u8; 32]; 13 | 14 | /// The Keccak-256 hash function. 15 | fn keccak_256(message: &[u8]) -> [u8; 32]; 16 | 17 | /// The Ripemd160 hash function. 18 | fn ripemd160(message: &[u8]) -> [u8; 20]; 19 | 20 | /// BLAKE2b-512 hash function. 21 | fn blake2b_512(message: &[u8]) -> [u8; 64]; 22 | 23 | /// BLAKE2s-256 hash function. 24 | fn blake2s_256(message: &[u8]) -> [u8; 32]; 25 | 26 | /// BLAKE3 hash function. 27 | fn blake3(message: &[u8]) -> [u8; 32]; 28 | } 29 | 30 | #[cfg(any(feature = "host-functions", test))] 31 | pub mod host_functions_impl { 32 | use crate::host_functions::HostFunctionsProvider; 33 | use blake2::{Blake2b512, Blake2s256}; 34 | use ripemd::Ripemd160; 35 | use sha2::{Digest, Sha256, Sha512, Sha512_256}; 36 | use sha3::Keccak256; 37 | 38 | pub struct HostFunctionsManager; 39 | impl HostFunctionsProvider for HostFunctionsManager { 40 | fn sha2_256(message: &[u8]) -> [u8; 32] { 41 | let digest = Sha256::digest(message); 42 | let mut buf = [0u8; 32]; 43 | buf.copy_from_slice(&digest); 44 | buf 45 | } 46 | 47 | fn sha2_512(message: &[u8]) -> [u8; 64] { 48 | let digest = Sha512::digest(message); 49 | let mut buf = [0u8; 64]; 50 | buf.copy_from_slice(&digest); 51 | buf 52 | } 53 | 54 | fn sha2_512_truncated(message: &[u8]) -> [u8; 32] { 55 | let digest = Sha512_256::digest(message); 56 | let mut buf = [0u8; 32]; 57 | buf.copy_from_slice(&digest); 58 | buf 59 | } 60 | 61 | fn keccak_256(message: &[u8]) -> [u8; 32] { 62 | let digest = Keccak256::digest(message); 63 | let mut buf = [0u8; 32]; 64 | buf.copy_from_slice(&digest); 65 | buf 66 | } 67 | 68 | fn ripemd160(message: &[u8]) -> [u8; 20] { 69 | let digest = Ripemd160::digest(message); 70 | let mut buf = [0u8; 20]; 71 | buf.copy_from_slice(&digest); 72 | buf 73 | } 74 | 75 | fn blake2b_512(message: &[u8]) -> [u8; 64] { 76 | let digest = Blake2b512::digest(message); 77 | let mut buf = [0u8; 64]; 78 | buf.copy_from_slice(&digest); 79 | buf 80 | } 81 | 82 | fn blake2s_256(message: &[u8]) -> [u8; 32] { 83 | let digest = Blake2s256::digest(message); 84 | let mut buf = [0u8; 32]; 85 | buf.copy_from_slice(&digest); 86 | buf 87 | } 88 | 89 | fn blake3(message: &[u8]) -> [u8; 32] { 90 | blake3::hash(message).into() 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![allow(clippy::large_enum_variant)] 3 | 4 | extern crate alloc; 5 | extern crate core; 6 | 7 | mod api; 8 | mod compress; 9 | mod helpers; 10 | mod host_functions; 11 | mod ops; 12 | mod verify; 13 | 14 | mod ics23 { 15 | include!("cosmos.ics23.v1.rs"); 16 | 17 | #[cfg(feature = "serde")] 18 | include!("cosmos.ics23.v1.serde.rs"); 19 | } 20 | 21 | pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("proto_descriptor.bin"); 22 | 23 | pub use crate::ics23::*; 24 | pub use api::{ 25 | iavl_spec, smt_spec, tendermint_spec, verify_batch_membership, verify_batch_non_membership, 26 | verify_membership, verify_non_membership, 27 | }; 28 | pub use compress::{compress, decompress, is_compressed}; 29 | pub use helpers::{Hash, Result}; 30 | pub use host_functions::HostFunctionsProvider; 31 | pub use verify::calculate_existence_root; 32 | 33 | #[cfg(feature = "host-functions")] 34 | pub use host_functions::host_functions_impl::HostFunctionsManager; 35 | -------------------------------------------------------------------------------- /rust/src/ops.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, ensure}; 2 | use core::convert::TryInto; 3 | 4 | use crate::helpers::{Hash, Result}; 5 | use crate::host_functions::HostFunctionsProvider; 6 | use crate::ics23::{HashOp, InnerOp, LeafOp, LengthOp}; 7 | 8 | pub fn apply_inner(inner: &InnerOp, child: &[u8]) -> Result { 9 | ensure!(!child.is_empty(), "Missing child hash"); 10 | let mut image = inner.prefix.clone(); 11 | image.extend(child); 12 | image.extend(&inner.suffix); 13 | Ok(do_hash::(inner.hash(), &image)) 14 | } 15 | 16 | // apply_leaf will take a key, value pair and a LeafOp and return a LeafHash 17 | pub fn apply_leaf( 18 | leaf: &LeafOp, 19 | key: &[u8], 20 | value: &[u8], 21 | ) -> Result { 22 | let mut hash = leaf.prefix.clone(); 23 | let prekey = prepare_leaf_data::(leaf.prehash_key(), leaf.length(), key)?; 24 | hash.extend(prekey); 25 | let preval = prepare_leaf_data::(leaf.prehash_value(), leaf.length(), value)?; 26 | hash.extend(preval); 27 | Ok(do_hash::(leaf.hash(), &hash)) 28 | } 29 | 30 | fn prepare_leaf_data( 31 | prehash: HashOp, 32 | length: LengthOp, 33 | data: &[u8], 34 | ) -> Result { 35 | ensure!(!data.is_empty(), "Input to prepare_leaf_data missing"); 36 | let h = do_hash::(prehash, data); 37 | do_length(length, &h) 38 | } 39 | 40 | pub(crate) fn do_hash(hash: HashOp, data: &[u8]) -> Hash { 41 | match hash { 42 | HashOp::NoHash => Hash::from(data), 43 | HashOp::Sha256 => Hash::from(H::sha2_256(data)), 44 | HashOp::Sha512 => Hash::from(H::sha2_512(data)), 45 | HashOp::Keccak256 => Hash::from(H::keccak_256(data)), 46 | HashOp::Ripemd160 => Hash::from(H::ripemd160(data)), 47 | HashOp::Bitcoin => Hash::from(H::ripemd160(&H::sha2_256(data)[..])), 48 | HashOp::Sha512256 => Hash::from(H::sha2_512_truncated(data)), 49 | HashOp::Blake2b512 => Hash::from(H::blake2b_512(data)), 50 | HashOp::Blake2s256 => Hash::from(H::blake2s_256(data)), 51 | HashOp::Blake3 => Hash::from(H::blake3(data)), 52 | } 53 | } 54 | 55 | pub(crate) fn do_length(length: LengthOp, data: &[u8]) -> Result { 56 | match length { 57 | LengthOp::NoPrefix => {} 58 | LengthOp::Require32Bytes => ensure!(data.len() == 32, "Invalid length"), 59 | LengthOp::Require64Bytes => ensure!(data.len() == 64, "Invalid length"), 60 | LengthOp::VarProto => { 61 | let mut len = proto_len(data.len())?; 62 | len.extend(data); 63 | return Ok(len); 64 | } 65 | LengthOp::Fixed32Big => { 66 | let mut len = (data.len() as u32).to_be_bytes().to_vec(); 67 | len.extend(data); 68 | return Ok(len); 69 | } 70 | LengthOp::Fixed32Little => { 71 | let mut len = (data.len() as u32).to_le_bytes().to_vec(); 72 | len.extend(data); 73 | return Ok(len); 74 | } 75 | LengthOp::Fixed64Big => { 76 | let mut len = (data.len() as u64).to_be_bytes().to_vec(); 77 | len.extend(data); 78 | return Ok(len); 79 | } 80 | LengthOp::Fixed64Little => { 81 | let mut len = (data.len() as u64).to_le_bytes().to_vec(); 82 | len.extend(data); 83 | return Ok(len); 84 | } 85 | _ => bail!("Unsupported LengthOp {:?}", length), 86 | } 87 | // if we don't error above or return custom string, just return item untouched (common case) 88 | Ok(data.to_vec()) 89 | } 90 | 91 | pub(crate) fn proto_len(length: usize) -> Result { 92 | let size: u64 = length.try_into().map_err(anyhow::Error::msg)?; 93 | let mut len = Hash::new(); 94 | prost::encoding::encode_varint(size, &mut len); 95 | Ok(len) 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | use crate::host_functions::host_functions_impl::HostFunctionsManager; 103 | 104 | use alloc::vec; 105 | use alloc::vec::Vec; 106 | 107 | fn decode(input: &str) -> Vec { 108 | hex::decode(input).unwrap() 109 | } 110 | 111 | #[test] 112 | fn hashing_food() { 113 | let hash = do_hash::(HashOp::NoHash, b"food"); 114 | assert!(hash == hex::decode("666f6f64").unwrap(), "no hash fails"); 115 | 116 | let hash = do_hash::(HashOp::Sha256, b"food"); 117 | assert!( 118 | hash == decode("c1f026582fe6e8cb620d0c85a72fe421ddded756662a8ec00ed4c297ad10676b"), 119 | "sha256 hash fails" 120 | ); 121 | 122 | let hash = do_hash::(HashOp::Sha512, b"food"); 123 | assert!(hash == decode("c235548cfe84fc87678ff04c9134e060cdcd7512d09ed726192151a995541ed8db9fda5204e72e7ac268214c322c17787c70530513c59faede52b7dd9ce64331"), "sha512 hash fails"); 124 | 125 | let hash = do_hash::(HashOp::Ripemd160, b"food"); 126 | assert!( 127 | hash == decode("b1ab9988c7c7c5ec4b2b291adfeeee10e77cdd46"), 128 | "ripemd160 hash fails" 129 | ); 130 | 131 | let hash = do_hash::(HashOp::Bitcoin, b"food"); 132 | assert!( 133 | hash == decode("0bcb587dfb4fc10b36d57f2bba1878f139b75d24"), 134 | "bitcoin hash fails" 135 | ); 136 | 137 | let hash = do_hash::(HashOp::Sha512256, b"food"); 138 | assert!( 139 | hash == decode("5b3a452a6acbf1fc1e553a40c501585d5bd3cca176d562e0a0e19a3c43804e88"), 140 | "sha512/256 hash fails" 141 | ); 142 | 143 | let hash = do_hash::(HashOp::Blake2b512, b"food"); 144 | assert!( 145 | hash == decode("b1f115361afc179415d93d4f58dc2fc7d8fa434192d7cb9b65fca592f6aa904103d1f12b28655c2355478e10908ab002c418dc52a4367d8e645309cd25e3a504"), 146 | "blake2b hash fails" 147 | ); 148 | 149 | let hash = do_hash::(HashOp::Blake2s256, b"food"); 150 | assert!( 151 | hash == decode("5a1ec796f11f3dfc7e8ca5de13828edf2e910eb7dd41caaac356a4acbefb1758"), 152 | "blake2s hash fails" 153 | ); 154 | 155 | let hash = do_hash::(HashOp::Blake3, b"food"); 156 | assert!( 157 | hash == decode("f775a8ccf8cb78cd1c63ade4e9802de4ead836b36cea35242accf31d2c6a3697"), 158 | "blake3 hash fails" 159 | ); 160 | 161 | let hash = do_hash::(HashOp::Keccak256, b"food"); 162 | assert!( 163 | hash == decode("a471c7c90860799b1facb54795f0a93d821fb727241025770865602471b765a8"), 164 | "Keccak256 hash fails" 165 | ); 166 | } 167 | 168 | #[test] 169 | fn length_prefix() { 170 | let prefixed = do_length(LengthOp::NoPrefix, b"food").unwrap(); 171 | assert!(prefixed == decode("666f6f64"), "no prefix modifies data"); 172 | 173 | let prefixed = do_length(LengthOp::VarProto, b"food").unwrap(); 174 | assert!( 175 | prefixed == decode("04666f6f64"), 176 | "proto prefix returned {}", 177 | hex::encode(&prefixed), 178 | ); 179 | 180 | let prefixed = do_length(LengthOp::Fixed32Big, b"food").unwrap(); 181 | assert!( 182 | prefixed == decode("00000004666f6f64"), 183 | "proto prefix returned {}", 184 | hex::encode(&prefixed), 185 | ); 186 | 187 | let prefixed = do_length(LengthOp::Fixed32Little, b"food").unwrap(); 188 | assert!( 189 | prefixed == decode("04000000666f6f64"), 190 | "proto prefix returned {}", 191 | hex::encode(&prefixed), 192 | ); 193 | 194 | let prefixed = do_length(LengthOp::Fixed64Big, b"food").unwrap(); 195 | assert!( 196 | prefixed == decode("0000000000000004666f6f64"), 197 | "proto prefix returned {}", 198 | hex::encode(&prefixed), 199 | ); 200 | 201 | let prefixed = do_length(LengthOp::Fixed64Little, b"food").unwrap(); 202 | assert!( 203 | prefixed == decode("0400000000000000666f6f64"), 204 | "proto prefix returned {}", 205 | hex::encode(&prefixed), 206 | ); 207 | } 208 | 209 | #[test] 210 | fn apply_leaf_hash() { 211 | let leaf = LeafOp { 212 | hash: HashOp::Sha256.into(), 213 | prehash_key: 0, 214 | prehash_value: 0, 215 | length: 0, 216 | prefix: vec![], 217 | }; 218 | let key = b"foo"; 219 | let val = b"bar"; 220 | let hash = decode("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"); 221 | assert!( 222 | hash == apply_leaf::(&leaf, key, val).unwrap(), 223 | "unexpected leaf hash" 224 | ); 225 | } 226 | 227 | #[test] 228 | fn apply_leaf_hash_512() { 229 | let leaf = LeafOp { 230 | hash: HashOp::Sha512.into(), 231 | prehash_key: 0, 232 | prehash_value: 0, 233 | length: 0, 234 | prefix: vec![], 235 | }; 236 | let key = b"f"; 237 | let val = b"oobaz"; 238 | let hash = decode("4f79f191298ec7461d60136c60f77c2ae8ddd85dbf6168bb925092d51bfb39b559219b39ae5385ba04946c87f64741385bef90578ea6fe6dac85dbf7ad3f79e1"); 239 | assert!( 240 | hash == apply_leaf::(&leaf, key, val).unwrap(), 241 | "unexpected leaf hash" 242 | ); 243 | } 244 | 245 | #[test] 246 | fn apply_leaf_hash_length() { 247 | let leaf = LeafOp { 248 | hash: HashOp::Sha256.into(), 249 | prehash_key: 0, 250 | prehash_value: 0, 251 | length: LengthOp::VarProto.into(), 252 | prefix: vec![], 253 | }; 254 | let key = b"food"; 255 | let val = b"some longer text"; 256 | let hash = decode("b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265"); 257 | assert!( 258 | hash == apply_leaf::(&leaf, key, val).unwrap(), 259 | "unexpected leaf hash" 260 | ); 261 | } 262 | 263 | #[test] 264 | fn apply_leaf_prehash_length() { 265 | let leaf = LeafOp { 266 | hash: HashOp::Sha256.into(), 267 | prehash_key: 0, 268 | prehash_value: HashOp::Sha256.into(), 269 | length: LengthOp::VarProto.into(), 270 | prefix: vec![], 271 | }; 272 | let key = b"food"; 273 | let val = b"yet another long string"; 274 | let hash = decode("87e0483e8fb624aef2e2f7b13f4166cda485baa8e39f437c83d74c94bedb148f"); 275 | assert!( 276 | hash == apply_leaf::(&leaf, key, val).unwrap(), 277 | "unexpected leaf hash" 278 | ); 279 | } 280 | 281 | #[test] 282 | fn apply_inner_prefix_suffix() { 283 | let inner = InnerOp { 284 | hash: HashOp::Sha256.into(), 285 | prefix: decode("0123456789"), 286 | suffix: decode("deadbeef"), 287 | }; 288 | let child = decode("00cafe00"); 289 | 290 | // echo -n 012345678900cafe00deadbeef | xxd -r -p | sha256sum 291 | let expected = decode("0339f76086684506a6d42a60da4b5a719febd4d96d8b8d85ae92849e3a849a5e"); 292 | assert!( 293 | expected == apply_inner::(&inner, &child).unwrap(), 294 | "unexpected inner hash" 295 | ); 296 | } 297 | 298 | #[test] 299 | fn apply_inner_prefix_only() { 300 | let inner = InnerOp { 301 | hash: HashOp::Sha256.into(), 302 | prefix: decode("00204080a0c0e0"), 303 | suffix: vec![], 304 | }; 305 | let child = decode("ffccbb997755331100"); 306 | 307 | // echo -n 00204080a0c0e0ffccbb997755331100 | xxd -r -p | sha256sum 308 | let expected = decode("45bece1678cf2e9f4f2ae033e546fc35a2081b2415edcb13121a0e908dca1927"); 309 | assert!( 310 | expected == apply_inner::(&inner, &child).unwrap(), 311 | "unexpected inner hash" 312 | ); 313 | } 314 | 315 | #[test] 316 | fn apply_inner_suffix_only() { 317 | let inner = InnerOp { 318 | hash: HashOp::Sha256.into(), 319 | prefix: vec![], 320 | suffix: b" just kidding!".to_vec(), 321 | }; 322 | let child = b"this is a sha256 hash, really....".to_vec(); 323 | 324 | // echo -n 'this is a sha256 hash, really.... just kidding!' | sha256sum 325 | let expected = decode("79ef671d27e42a53fba2201c1bbc529a099af578ee8a38df140795db0ae2184b"); 326 | assert!( 327 | expected == apply_inner::(&inner, &child).unwrap(), 328 | "unexpected inner hash" 329 | ); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /rust/src/proto_descriptor.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/ics23/a324422529b8c00ead00b4dcee825867c494cddd/rust/src/proto_descriptor.bin -------------------------------------------------------------------------------- /scripts/protocgen_go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo "Generating gogo proto code" 6 | cd proto 7 | proto_dirs=$(find ./cosmos -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 8 | for dir in $proto_dirs; do 9 | for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do 10 | buf generate --template buf.gen.gogo.yaml $file 11 | done 12 | done 13 | 14 | cd .. 15 | 16 | cp -r github.com/cosmos/ics23/go/* ./go 17 | rm -rf github.com 18 | -------------------------------------------------------------------------------- /scripts/protocgen_rust.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | apt-get update 6 | apt-get install -y protobuf-compiler 7 | 8 | cd rust/codegen 9 | cargo run 10 | 11 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar-projects.propertiessonar.projectKey=cosmos_ics23 2 | sonar.organization=cosmos 3 | 4 | sonar.projectName=ics23 5 | sonar.project.monorepo.enabled=true 6 | 7 | sonar.sources=. 8 | sonar.exclusions=**/*_test.go,**/testdata/**,scripts/**,rust/**, 9 | sonar.tests=. 10 | sonar.test.inclusions=**/*_test.go,**/testdata/** 11 | sonar.go.coverage.reportPaths=go/coverage.txt 12 | 13 | sonar.python.version=3 14 | sonar.sourceEncoding=UTF-8 15 | sonar.scm.provider=git 16 | 17 | # Exclude C/C++/Objective-C files from analysis 18 | sonar.c.file.suffixes=- 19 | sonar.cpp.file.suffixes=- 20 | sonar.objc.file.suffixes=- -------------------------------------------------------------------------------- /testdata/TestCheckLeafData.json: -------------------------------------------------------------------------------- 1 | { 2 | "empty spec allows prefix": { 3 | "Leaf": { 4 | "prefix": "qrs=" 5 | }, 6 | "Spec": {}, 7 | "IsErr": false 8 | }, 9 | "empty spec doesn't allow hashop": { 10 | "Leaf": { 11 | "hash": 1 12 | }, 13 | "Spec": {}, 14 | "IsErr": true 15 | }, 16 | "empty spec, empty leaf": { 17 | "Leaf": {}, 18 | "Spec": {}, 19 | "IsErr": false 20 | }, 21 | "leaf and spec differ on hash": { 22 | "Leaf": { 23 | "hash": 1, 24 | "prehash_value": 1, 25 | "length": 1, 26 | "prefix": "AA==" 27 | }, 28 | "Spec": { 29 | "hash": 2, 30 | "prehash_value": 1, 31 | "length": 1, 32 | "prefix": "AA==" 33 | }, 34 | "IsErr": true 35 | }, 36 | "leaf and spec differ on length": { 37 | "Leaf": { 38 | "hash": 2, 39 | "prehash_value": 1, 40 | "prefix": "AA==" 41 | }, 42 | "Spec": { 43 | "hash": 2, 44 | "prehash_value": 1, 45 | "length": 1, 46 | "prefix": "AA==" 47 | }, 48 | "IsErr": true 49 | }, 50 | "leaf and spec differ on prehash key": { 51 | "Leaf": { 52 | "hash": 2, 53 | "prehash_key": 1, 54 | "prehash_value": 1, 55 | "length": 1, 56 | "prefix": "AA==" 57 | }, 58 | "Spec": { 59 | "hash": 2, 60 | "prehash_value": 1, 61 | "length": 1, 62 | "prefix": "AA==" 63 | }, 64 | "IsErr": true 65 | }, 66 | "leaf and spec differ on prehash value": { 67 | "Leaf": { 68 | "hash": 2, 69 | "length": 1, 70 | "prefix": "AA==" 71 | }, 72 | "Spec": { 73 | "hash": 2, 74 | "prehash_value": 1, 75 | "length": 1, 76 | "prefix": "AA==" 77 | }, 78 | "IsErr": true 79 | }, 80 | "leaf and spec match, all fields full": { 81 | "Leaf": { 82 | "hash": 2, 83 | "prehash_value": 1, 84 | "length": 1, 85 | "prefix": "AA==" 86 | }, 87 | "Spec": { 88 | "hash": 2, 89 | "prehash_value": 1, 90 | "length": 1, 91 | "prefix": "AA==" 92 | }, 93 | "IsErr": false 94 | }, 95 | "leaf with empty prefix (but spec has one)": { 96 | "Leaf": {}, 97 | "Spec": { 98 | "prefix": "uw==" 99 | }, 100 | "IsErr": true 101 | }, 102 | "spec with different prefixes": { 103 | "Leaf": { 104 | "prefix": "qrs=" 105 | }, 106 | "Spec": { 107 | "prefix": "uw==" 108 | }, 109 | "IsErr": true 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /testdata/TestDoHashData.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha256": { 3 | "HashOp": 1, 4 | "Preimage": "food", 5 | "ExpectedHash": "c1f026582fe6e8cb620d0c85a72fe421ddded756662a8ec00ed4c297ad10676b" 6 | }, 7 | "sha512": { 8 | "HashOp": 2, 9 | "PreImage": "food", 10 | "ExpectedHash": "c235548cfe84fc87678ff04c9134e060cdcd7512d09ed726192151a995541ed8db9fda5204e72e7ac268214c322c17787c70530513c59faede52b7dd9ce64331" 11 | }, 12 | "ripemd160": { 13 | "HashOp": 4, 14 | "Preimage": "food", 15 | "ExpectedHash": "b1ab9988c7c7c5ec4b2b291adfeeee10e77cdd46" 16 | }, 17 | "bitcoin": { 18 | "HashOp": 5, 19 | "Preimage": "food", 20 | "ExpectedHash": "0bcb587dfb4fc10b36d57f2bba1878f139b75d24" 21 | }, 22 | "sha512_256": { 23 | "HashOp": 6, 24 | "Preimage": "food", 25 | "ExpectedHash": "5b3a452a6acbf1fc1e553a40c501585d5bd3cca176d562e0a0e19a3c43804e88" 26 | }, 27 | "blake2b": { 28 | "HashOp": 7, 29 | "Preimage": "food", 30 | "ExpectedHash": "b1f115361afc179415d93d4f58dc2fc7d8fa434192d7cb9b65fca592f6aa904103d1f12b28655c2355478e10908ab002c418dc52a4367d8e645309cd25e3a504" 31 | }, 32 | "blake2s": { 33 | "HashOp": 8, 34 | "Preimage": "food", 35 | "ExpectedHash": "5a1ec796f11f3dfc7e8ca5de13828edf2e910eb7dd41caaac356a4acbefb1758" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /testdata/TestEmptyBranchData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Op": { 4 | "hash": 1, 5 | "prefix": "ATMyX2VtcHR5X2NoaWxkX3BsYWNlaG9sZGVyX2J5dGVz" 6 | }, 7 | "Spec": { 8 | "leaf_spec": { 9 | "hash": 1, 10 | "prehash_value": 1, 11 | "prefix": "AA==" 12 | }, 13 | "inner_spec": { 14 | "child_order": [ 15 | 0, 16 | 1 17 | ], 18 | "child_size": 32, 19 | "min_prefix_length": 1, 20 | "max_prefix_length": 1, 21 | "empty_child": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=", 22 | "hash": 1 23 | } 24 | }, 25 | "IsLeft": true, 26 | "IsRight": false 27 | }, 28 | { 29 | "Op": { 30 | "hash": 1, 31 | "prefix": "AQ==", 32 | "suffix": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=" 33 | }, 34 | "Spec": { 35 | "leaf_spec": { 36 | "hash": 1, 37 | "prehash_value": 1, 38 | "prefix": "AA==" 39 | }, 40 | "inner_spec": { 41 | "child_order": [ 42 | 0, 43 | 1 44 | ], 45 | "child_size": 32, 46 | "min_prefix_length": 1, 47 | "max_prefix_length": 1, 48 | "empty_child": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=", 49 | "hash": 1 50 | } 51 | }, 52 | "IsLeft": false, 53 | "IsRight": true 54 | }, 55 | { 56 | "Op": { 57 | "hash": 1, 58 | "prefix": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 59 | }, 60 | "Spec": { 61 | "leaf_spec": { 62 | "hash": 1, 63 | "prehash_value": 1, 64 | "prefix": "AA==" 65 | }, 66 | "inner_spec": { 67 | "child_order": [ 68 | 0, 69 | 1 70 | ], 71 | "child_size": 32, 72 | "min_prefix_length": 1, 73 | "max_prefix_length": 1, 74 | "empty_child": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=", 75 | "hash": 1 76 | } 77 | }, 78 | "IsLeft": false, 79 | "IsRight": false 80 | }, 81 | { 82 | "Op": { 83 | "hash": 1, 84 | "prefix": "AQ==", 85 | "suffix": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" 86 | }, 87 | "Spec": { 88 | "leaf_spec": { 89 | "hash": 1, 90 | "prehash_value": 1, 91 | "prefix": "AA==" 92 | }, 93 | "inner_spec": { 94 | "child_order": [ 95 | 0, 96 | 1 97 | ], 98 | "child_size": 32, 99 | "min_prefix_length": 1, 100 | "max_prefix_length": 1, 101 | "empty_child": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=", 102 | "hash": 1 103 | } 104 | }, 105 | "IsLeft": false, 106 | "IsRight": false 107 | }, 108 | { 109 | "Op": { 110 | "hash": 1, 111 | "prefix": "ATMyX2VtcHR5X2NoaWxkX3BsYWNlaG9sZGVyX2J4eHh4" 112 | }, 113 | "Spec": { 114 | "leaf_spec": { 115 | "hash": 1, 116 | "prehash_value": 1, 117 | "prefix": "AA==" 118 | }, 119 | "inner_spec": { 120 | "child_order": [ 121 | 0, 122 | 1 123 | ], 124 | "child_size": 32, 125 | "min_prefix_length": 1, 126 | "max_prefix_length": 1, 127 | "empty_child": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=", 128 | "hash": 1 129 | } 130 | }, 131 | "IsLeft": false, 132 | "IsRight": false 133 | }, 134 | { 135 | "Op": { 136 | "hash": 1, 137 | "prefix": "AQ==", 138 | "suffix": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnh4eHg=" 139 | }, 140 | "Spec": { 141 | "leaf_spec": { 142 | "hash": 1, 143 | "prehash_value": 1, 144 | "prefix": "AA==" 145 | }, 146 | "inner_spec": { 147 | "child_order": [ 148 | 0, 149 | 1 150 | ], 151 | "child_size": 32, 152 | "min_prefix_length": 1, 153 | "max_prefix_length": 1, 154 | "empty_child": "MzJfZW1wdHlfY2hpbGRfcGxhY2Vob2xkZXJfYnl0ZXM=", 155 | "hash": 1 156 | } 157 | }, 158 | "IsLeft": false, 159 | "IsRight": false 160 | }, 161 | { 162 | "Op": { 163 | "hash": 1, 164 | "prefix": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 165 | }, 166 | "Spec": { 167 | "leaf_spec": { 168 | "hash": 1, 169 | "prehash_value": 1, 170 | "length": 1, 171 | "prefix": "AA==" 172 | }, 173 | "inner_spec": { 174 | "child_order": [ 175 | 0, 176 | 1 177 | ], 178 | "child_size": 32, 179 | "min_prefix_length": 1, 180 | "max_prefix_length": 1, 181 | "hash": 1 182 | } 183 | }, 184 | "IsLeft": false, 185 | "IsRight": false 186 | }, 187 | { 188 | "Op": { 189 | "hash": 1, 190 | "prefix": "AQ==", 191 | "suffix": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" 192 | }, 193 | "Spec": { 194 | "leaf_spec": { 195 | "hash": 1, 196 | "prehash_value": 1, 197 | "length": 1, 198 | "prefix": "AA==" 199 | }, 200 | "inner_spec": { 201 | "child_order": [ 202 | 0, 203 | 1 204 | ], 205 | "child_size": 32, 206 | "min_prefix_length": 1, 207 | "max_prefix_length": 1, 208 | "hash": 1 209 | } 210 | }, 211 | "IsLeft": false, 212 | "IsRight": false 213 | } 214 | ] -------------------------------------------------------------------------------- /testdata/TestExistenceProofData.json: -------------------------------------------------------------------------------- 1 | { 2 | "cannot execute inner first": { 3 | "Proof": { 4 | "key": "Zm9vZA==", 5 | "value": "c29tZSBsb25nZXIgdGV4dA==", 6 | "path": [ 7 | { 8 | "hash": 1, 9 | "prefix": "3q2+7wDK/gA=" 10 | } 11 | ] 12 | }, 13 | "IsErr": true, 14 | "Expected": null 15 | }, 16 | "demonstrate maliability of leaf if we change leaf algorithm": { 17 | "Proof": { 18 | "key": "BGZvb2Q=", 19 | "value": "EHNvbWUgbG9uZ2VyIHRleHQ=", 20 | "leaf": { 21 | "hash": 1 22 | } 23 | }, 24 | "IsErr": false, 25 | "Expected": "to9dKY6RWuF1PdMz2h+c9gVBGl8uElFr5nWPNl5tsmU=" 26 | }, 27 | "demonstrate maliability of leaf if we change leaf prefix": { 28 | "Proof": { 29 | "key": "b2QQ", 30 | "value": "c29tZSBsb25nZXIgdGV4dA==", 31 | "leaf": { 32 | "hash": 1, 33 | "prefix": "BGZv" 34 | } 35 | }, 36 | "IsErr": false, 37 | "Expected": "to9dKY6RWuF1PdMz2h+c9gVBGl8uElFr5nWPNl5tsmU=" 38 | }, 39 | "executes leaf then inner op": { 40 | "Proof": { 41 | "key": "Zm9vZA==", 42 | "value": "c29tZSBsb25nZXIgdGV4dA==", 43 | "leaf": { 44 | "hash": 1, 45 | "length": 1 46 | }, 47 | "path": [ 48 | { 49 | "hash": 1, 50 | "prefix": "3q2+7wDK/gA=" 51 | } 52 | ] 53 | }, 54 | "IsErr": false, 55 | "Expected": "g26iNqaQKmZcKgBMkgNk8kytUt7SCx5PIsMXm/4lsqk=" 56 | }, 57 | "executes one leaf step": { 58 | "Proof": { 59 | "key": "Zm9vZA==", 60 | "value": "c29tZSBsb25nZXIgdGV4dA==", 61 | "leaf": { 62 | "hash": 1, 63 | "length": 1 64 | } 65 | }, 66 | "IsErr": false, 67 | "Expected": "to9dKY6RWuF1PdMz2h+c9gVBGl8uElFr5nWPNl5tsmU=" 68 | }, 69 | "must have at least one step": { 70 | "Proof": { 71 | "key": "Zm9v", 72 | "value": "YmFy" 73 | }, 74 | "IsErr": true, 75 | "Expected": null 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /testdata/TestInnerOpData.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash child with only prefix": { 3 | "Op": { 4 | "hash": 1, 5 | "prefix": "ACBAgKDA4A==" 6 | }, 7 | "Child": "/8y7mXdVMxEA", 8 | "IsErr": false, 9 | "Expected": "Rb7OFnjPLp9PKuAz5Ub8NaIIGyQV7csTEhoOkI3KGSc=" 10 | }, 11 | "hash child with only suffix": { 12 | "Op": { 13 | "hash": 1, 14 | "suffix": "IGp1c3Qga2lkZGluZyE=" 15 | }, 16 | "Child": "dGhpcyBpcyBhIHNoYTI1NiBoYXNoLCByZWFsbHkuLi4u", 17 | "IsErr": false, 18 | "Expected": "ee9nHSfkKlP7oiAcG7xSmgma9XjuijjfFAeV2wriGEs=" 19 | }, 20 | "hash child with prefix and suffix": { 21 | "Op": { 22 | "hash": 1, 23 | "prefix": "ASNFZ4k=", 24 | "suffix": "3q2+7w==" 25 | }, 26 | "Child": "AMr+AA==", 27 | "IsErr": false, 28 | "Expected": "Azn3YIZoRQam1Cpg2ktacZ/r1Nlti42FrpKEnjqEml4=" 29 | }, 30 | "requires child": { 31 | "Op": { 32 | "hash": 1, 33 | "prefix": "ASNFZ4k=", 34 | "suffix": "3q2+7w==" 35 | }, 36 | "Child": null, 37 | "IsErr": true, 38 | "Expected": null 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /testdata/TestLeafOpData.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash foobar": { 3 | "Op": { 4 | "hash": 1 5 | }, 6 | "Key": "Zm9v", 7 | "Value": "YmFy", 8 | "IsErr": false, 9 | "Expected": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI=" 10 | }, 11 | "hash foobar (different break)": { 12 | "Op": { 13 | "hash": 1 14 | }, 15 | "Key": "Zg==", 16 | "Value": "b29iYXI=", 17 | "IsErr": false, 18 | "Expected": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI=" 19 | }, 20 | "hash foobaz, sha-512": { 21 | "Op": { 22 | "hash": 2 23 | }, 24 | "Key": "Zm9v", 25 | "Value": "YmF6", 26 | "IsErr": false, 27 | "Expected": "T3nxkSmOx0YdYBNsYPd8Kujd2F2/YWi7klCS1Rv7ObVZIZs5rlOFugSUbIf2R0E4W++QV46m/m2shdv3rT954Q==" 28 | }, 29 | "hash with length prefix": { 30 | "Op": { 31 | "hash": 1, 32 | "length": 1 33 | }, 34 | "Key": "Zm9vZA==", 35 | "Value": "c29tZSBsb25nZXIgdGV4dA==", 36 | "IsErr": false, 37 | "Expected": "to9dKY6RWuF1PdMz2h+c9gVBGl8uElFr5nWPNl5tsmU=" 38 | }, 39 | "hash with length prefix (fixed 32-bit big-endian encoding)": { 40 | "Op": { 41 | "hash": 1, 42 | "length": 3 43 | }, 44 | "Key": "Zm9vZA==", 45 | "Value": "c29tZSBsb25nZXIgdGV4dA==", 46 | "IsErr": false, 47 | "Expected": "3aKSo5jYgP68i5P2GN+958ViipD1gKhhSkTH7WbujqA=" 48 | }, 49 | "hash with length prefix (fixed 32-bit little-endian encoding)": { 50 | "Op": { 51 | "hash": 1, 52 | "length": 4 53 | }, 54 | "Key": "Zm9vZA==", 55 | "Value": "c29tZSBsb25nZXIgdGV4dA==", 56 | "IsErr": false, 57 | "Expected": "yFNlJDe+AlAcZ0dEvyorRdkqCp8pxLEEQBD7Pi1DqUk=" 58 | }, 59 | "hash with length prefix (fixed 64-bit big-endian encoding)": { 60 | "Op": { 61 | "hash": 1, 62 | "length": 5 63 | }, 64 | "Key": "Zm9vZA==", 65 | "Value": "c29tZSBsb25nZXIgdGV4dA==", 66 | "IsErr": false, 67 | "Expected": "MdmqaTGpBztx8P0weVWka+tw4YpYyS63U11hPfWTvAo=" 68 | }, 69 | "hash with length prefix (fixed 64-bit little-endian encoding)": { 70 | "Op": { 71 | "hash": 1, 72 | "length": 6 73 | }, 74 | "Key": "Zm9vZA==", 75 | "Value": "c29tZSBsb25nZXIgdGV4dA==", 76 | "IsErr": false, 77 | "Expected": "Ta2v+wNch1+5Oz5ASqGvSotk2Gp4a8BPFEs6r/qYT/4=" 78 | }, 79 | "hash with prehash and length prefix": { 80 | "Op": { 81 | "hash": 1, 82 | "prehash_value": 1, 83 | "length": 1 84 | }, 85 | "Key": "Zm9vZA==", 86 | "Value": "eWV0IGFub3RoZXIgbG9uZyBzdHJpbmc=", 87 | "IsErr": false, 88 | "Expected": "h+BIPo+2JK7y4vexP0FmzaSFuqjjn0N8g9dMlL7bFI8=" 89 | }, 90 | "requires key": { 91 | "Op": { 92 | "hash": 1 93 | }, 94 | "Key": "Zm9v", 95 | "Value": null, 96 | "IsErr": true, 97 | "Expected": null 98 | }, 99 | "requires value": { 100 | "Op": { 101 | "hash": 1 102 | }, 103 | "Key": null, 104 | "Value": "YmFy", 105 | "IsErr": true, 106 | "Expected": null 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /testdata/iavl/exist_left.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "3033763434454574647248423556417579715966", 3 | "proof": "0a89040a143033763434454574647248423556417579715966121e76616c75655f666f725f30337634344545746472484235564175797159661a0b0801180120012a03000202222b08011204040602201a2120afca3de8c7aefe1041f185a34e977a976b37d6ce4cce80e5e4545b93413eca02222b08011204060c02201a21205e1712938d9dcef396a76bbd7ea844bcc7e72a64d416485ba14e8c679402dfc3222b08011204081802201a2120c6a6430436f6e95ab0c90d7c3d32c7e62884a1e28e22da87f9e8c863782b7195222b080112040a2c02201a21201204acd0c729844aa19ffa80cfdfcb931f1ea54167babe1872a2fdcdf520962a222b080112040c4402201a2120c21911260b253d74c89d95ec7534b7499b98a8b7523857f7f31e6af723245b89222b080112040e6e02201a212045a06e0b8f7391f60a5f716eeef9eb01d8c588bbdbfb4e6a67718e92ab3ed12d222c0801120512960202201a21209b77ab64f5b7c290b608533b70612d18a0d55ede4ccb2f9418b56aa17069e966222c0801120514e60302201a2120246dce928b807f040230219a808c049d7a172108db6b1c83c445c66373ea4cd9222c0801120516800802201a2120ec794fb7e49d3d3554680ad0df7d1cc3797201fe33fc8288581f7ad32bb995c0222c0801120518b60f02201a21203b05428644520c7bae4be197ecb6de54a03a18a1ebd34836eea31fe593a6b6e0", 4 | "root": "77e43ef93047a91fe457f5498bd7afc60b9dddd661d8f1225e5f40a91bda4623", 5 | "value": "76616c75655f666f725f3033763434454574647248423556417579715966" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/iavl/exist_middle.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "367a757a4f7845416530357967617a57634b6b70", 3 | "proof": "0ad7030a14367a757a4f7845416530357967617a57634b6b70121e76616c75655f666f725f367a757a4f7845416530357967617a57634b6b701a0b0801180120012a03000202222b08011204040602201a2120e573ba32e4d48752f750145e8f49272d81e5f4d81f418c6c8e01e2bd2bb03e92222b08011204060e02201a21202f7453b9df6afecefcecd464d557701685ceeddcf82e2064f5d6a751fe6f448c222b08011204081a02201a21200a6025a49f3aff53a9fd7bd60e0f241897bdff7ff5b0386de6e582fa60a2ff642229080112250a3402209bae0613f7ecad99b5b448714f1b66929f06ca05e92dad7a7fc9c6243475135020222a080112260e80010220e508b7f979ad0f1601b3442603f928b3aba91287a4c988045a88fa1bf26df17120222a0801122610cc0102201abeb41c3a4ec85c07a77af6017dcb2171a1d5c278b300cc361d7b51c593b7cf20222c0801120512fa0202201a212055337b4192edc05ba1ca4c51b110aa56feb46b5830ba19ee682c9c88a48cdfee222c08011205149c0602201a212092fdf4836b2561fb018c29660262b102761f25920ed43ae46bc4153a55fd161b222c0801120516d80c02201a212004ddd20b8bd3c1461293922bb3f9404eecaa2d1869d651f1a304a4757bd5f646", 4 | "root": "ce93fb31420cca24940fd7e8742ca1061b51c5d3c5438b68bf0526bc93e45274", 5 | "value": "76616c75655f666f725f367a757a4f7845416530357967617a57634b6b70" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/iavl/exist_right.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "7a77734b56614b6b485847436c795073686b3969", 3 | "proof": "0acd040a147a77734b56614b6b485847436c795073686b3969121e76616c75655f666f725f7a77734b56614b6b485847436c795073686b39691a0b0801180120012a0300020222290801122502040220a80964839e7d4f4ea955249109f96d6b18909a484d5908ef1795d52f1a71b9f92022290801122504080220f316e66f51c941ed0d6ec7ef9f3f230353aeac1502e833ee49644a3766062a852022290801122506100220b9657b80ff141a29840b6d66726b81168401ca5c5e47f5506aadd3ba239d97a220222908011225081a0220102c2c767a05dbb21fe67a529175934f951f0a8446e72bff5bc8b00d195c6a43202229080112250a3002207368a0f84b7d52725296277d1e372557884dcf9c479894dad91e79668acfc65d202229080112250c5202208e823d5949da2f70d695f115d44a26b0a5f4fe9b15261216026442b2dd68943f20222a080112260e9e0102203eb03a9dcd754a78ebd23f239699c3d7afef6ecd8ae73ae15967fdbb7242b48320222a0801122610f2010220d64602759351774dab3624c47b5833dd85cc5b59a92c199ffd99e0d638ccb21520222a0801122612d603022054299ee0368a22e4f6cb1005b188f8595e4cdec5382f34e316510d685df9fc7b20222a0801122614da0502209b54b6b19b6756a88140298a7b60c0449e013c7762ca26c6c7fcda1c7acf8e3b20222a0801122616c20a0220affb7724162f85be6e8735e8498d8467de4beaf0972162b4dc1ca1ecc89e0cc020222a0801122618da130220d1dcc3ed25046b94c128ff3c7d31f9c112b679411dbb8cc4acbfc872f873d00620", 4 | "root": "3c58f3ce248859b07e2984a4fc95f28ee9ca31729f36d5d248ce806babd27c39", 5 | "value": "76616c75655f666f725f7a77734b56614b6b485847436c795073686b3969" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/iavl/nonexist_left.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "00000001", 3 | "proof": "1292040a04000000011a89040a143037716d4953765447687855656b624d69764e76121e76616c75655f666f725f3037716d4953765447687855656b624d69764e761a0b0801180120012a03000202222b08011204020402201a212070816e5f4a9adfba29e8b997562fc558234eec332473833ffb79334d8aea18d9222b08011204040802201a21201e2cc6227a41e0ecee802602fbcc52affb1e43aafdebed7fb3e7a7d15550a600222b08011204060c02201a2120dcc5eb2e6d821b74b61352dda635efb261ad714a85065fff80df2306c8d71f7d222b08011204081202201a2120133c8766e74d14fb335b5012041d861586905e0b2bd7951313d34dc0907fd65e222b080112040c3802201a21209f1ca33c91f5d5c6b99dbfd742576f5f1b83fb01d29976b03126c0a895cac245222b080112040e7202201a21209c70775d39253c1acfb26a850ef71330675773a2995cb5c6577ecb5fa127fedb222c08011205128e0202201a21204683caa58226dae3cd2645a027ea776437ec684a25dccce06458b52f93206ab4222c0801120514e80402201a212065858d65c8d6bcb67196333f91863e93f4363fe214638c576afa8995a288b5c7222c0801120516800902201a2120ee7c4502ea7f283583e3e9c4cae4ee75c35e30b56f10aa235cec940c90b6a545222c0801120518da0c02201a2120452eb300e7ca64587be02ba982b9b1d9e936804e805b1c1b61f97b349e79d478", 4 | "root": "455153ed2bcdd96de87a7105119f4025ca720555f364af7d5e48aae048cf054e", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/iavl/nonexist_middle.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "6a4741645a757077494e714a3534507a4764ffff", 3 | "proof": "12eb070a146a4741645a757077494e714a3534507a4764ffff12d0030a146a4741645a757077494e714a3534507a47644872121e76616c75655f666f725f6a4741645a757077494e714a3534507a476448721a0b0801180120012a030002022229080112250406022091215ed62d8407853f476e122569bbf96ae3ab0f06b446d7198277d2a832cbb720222908011225060a02202aaddc8b32ce91d382c332b50071be9cc7f91d0728d5e9cd573ce974caca7d3a20222b08011204081a02201a2120500dad435aa1a3d3636b5dddbe4f3a22116d9ce33f3b3282d0b7d21a003d423c2229080112250a2c0220fef90250d29f80a9a95af8af3c1f4658f3de65188b87047c32d4979500621b1920222b080112040e7202201a21206b589b1f3500370ed450b13f6db71d3273f510a4651191e2bb98124ebec85e24222a0801122610c401022004ba8413104cc5a48201fb14f400c2d9e6899abd2702018335f4d9f454724e0220222a0801122612da02022003f3510245d88dd86e2957aa3fc96fe61ff1719b802facf5ed340966be42f33920222c0801120514ca0402201a2120c44e20433b25fd83ec6757083ecf6ebd4796268ed0732056ab6a3d160e97483f222a0801122616e60a02205e14fc09624f31aaece0f062b5e16feb32cb4bf913ae6062852f279057090896201aff030a146a4f3559356a324544354d6e446c4871446b7775121e76616c75655f666f725f6a4f3559356a324544354d6e446c4871446b77751a0b0801180120012a03000202222b08011204020402201a21209c89f42397ba12d9a97026547dbf686696fcd90cca3843835de6ea7ab38ef2c1222b08011204040802201a2120d5f0ef78af12da2be87f3590c9425e810e3f23d0451ef91b3114028f9c25e07e222b08011204061002201a2120abb1ef0ab102e07aa96aa8f8a4050636cfe5e253e0e0e77dfe9c0675d6b55ce5222908011225081a022022511d952d965f2739b5b9dcfa6d964d0b77e13b8ac489d13372b73f87ef8ce7202229080112250a2c0220fef90250d29f80a9a95af8af3c1f4658f3de65188b87047c32d4979500621b1920222b080112040e7202201a21206b589b1f3500370ed450b13f6db71d3273f510a4651191e2bb98124ebec85e24222a0801122610c401022004ba8413104cc5a48201fb14f400c2d9e6899abd2702018335f4d9f454724e0220222a0801122612da02022003f3510245d88dd86e2957aa3fc96fe61ff1719b802facf5ed340966be42f33920222c0801120514ca0402201a2120c44e20433b25fd83ec6757083ecf6ebd4796268ed0732056ab6a3d160e97483f222a0801122616e60a02205e14fc09624f31aaece0f062b5e16feb32cb4bf913ae6062852f27905709089620", 4 | "root": "b707740dc2f75381c4c8e97a743f5a9848ff38a471018fef2851d59aae059dfa", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/iavl/nonexist_right.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "ffffffff", 3 | "proof": "12d6040a04ffffffff12cd040a147a7a7149637259446f70766e7a71504a79755a6f121e76616c75655f666f725f7a7a7149637259446f70766e7a71504a79755a6f1a0b0801180120012a030002022229080112250204022081435c94d234d22341c3d1e9da371bcce7aff2ee1aa92023fe14f0822051b895202229080112250406022012872bc098a6ab4554579cab54703137b9bbb964cc83b059f3fbca6dc8debab820222908011225060c02209cea7e3eb06f35a6595d4f61e8ca235ed0c514c47c8a4dd0b3a4246f8bb6b3532022290801122508160220030a6304850f9a70d96f7f721ffc35f773a77b2a5a022366b4a0aa16b57772c0202229080112250a3002208dbdcf1558ab10c2a94c976cd3cf1146b4118b8dff3ea5a369d59b484504315b202229080112250c5402201dd8bff9bab4fc6e7bfca6d609148d17be7441549ef8ea0393b4ea03b987a46420222a080112260ea6010220bee209c60e2c628598372fd228cf940ca640d74e345f36ab46839b7b46b6668420222a080112261086020220e401a0196934b3bee3cd7b63d15e1b52a51efdc4255cfb4fc82cb819180c3c9520222a0801122612de03022064f43952d0d4b6b6b0d41cf44afa12e521dcfbb5beb3a228f803762779c5e71520222a080112261496070220400ea7940c9c639f83478cdb68c8a01743a34447c3dccaf8ee02cfc5a37025c020222a0801122618900f0220b78678d923540c0be0d2603450d719f59f5e400464dd4b140e557eb755d8557220222a080112261afe170220334749245dda3385a8368b09db0cb878cb077a97519983a98342ea30e893850120", 4 | "root": "18c8722ce7e9f7a487110ff501ffcb745be5ecb3c9615fe53cfca8b29bdbe549", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/smt/exist_left.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "4445687a30613576355036616536514c376d6445", 3 | "proof": "0adb030a144445687a30613576355036616536514c376d6445121e76616c75655f666f725f4445687a30613576355036616536514c376d64451a090801100118012a0100222708011201011a20bc79ee59a60c4f08cc0216a5f64d39f871cd730b6983079429e91d9bcd57d470222708011201011a206732b1eaa2cf07fbc94599aecc2df057d8efb0ccd775d86b6ea176bfaad367ad222708011201011a203112ffea611a3ef030f5ed8dafb8bcaf8e33bec16fff9027b1a0c718136de2d3222708011201011a20480983a2541b5527e1b5607e32f0597b12204d4973b5d9b498d1ff2a2350352b222708011201011a2097058f1bf50e2b47ad129fdaf2dfd53f88f81467dad54dabfcf0fbe8330c4e4c222708011201011a2008f6f1e45958b4780e1e0f7791315d44355eba4e519e2fb25c3dc48627a59a45222708011201011a20bb4f607cb62f20e673fdfad3e40a8b596db50dc7c34e65aba74eec37f791e886222708011201011a20fe97c3211b6a7eb3d859e754b373e89897a54f74244f0a84defc1df2a203dfd8222708011201011a202cab93bf9b91d25010972968d69b47ca9dfd5b4cea0c71310e0adced33acb19d222708011201011a2069ca519f1f2528c971a930cdd00b65c41762e8510ce3d4a998d4ad8ccf598984", 4 | "root": "432d73b49019d8df5c7e38fd93288d0240a62a3c010e7217a4a74f5885c391d5", 5 | "value": "76616c75655f666f725f4445687a30613576355036616536514c376d6445" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/smt/exist_middle.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "464a544d75787a575873713470436c46506e6b48", 3 | "proof": "0af6030a14464a544d75787a575873713470436c46506e6b48121e76616c75655f666f725f464a544d75787a575873713470436c46506e6b481a090801100118012a010022250801122101c607dff72d77496a8088f58c886a59e2c2e115c18688c1ea21007fa8a9c884da2225080112210152491dba094231188b893d16b0b08ad25246d5147a7a62149186518712618054222708011201011a20000000000000000000000000000000000000000000000000000000000000000022250801122101e3a85febd896e193b7ba37bc638c46d69530de820647fccb40da8583473c8feb222508011221014baf41d85dc399f406edbe4374e6b767663e4680fee217bfbca889618b7a83dc222708011201011a204ed121ef20f0e1d65a8a5e7d9ed86844de0bfd82621f3325f60eb872e9e738ea222708011201011a20a7cd5e87f54353e0b8d36901ba175daf7c00ff05c00183f522e94850a39ac4ce222708011201011a205a927fe700b5feacbf30a7cd160533e326c513eedc8e1d000cd8a71abd8480f522250801122101d9423c60941997d2078abd897d53f09cb4a844c684bc6a38140c04d6869809de22250801122101b1de51ac7abc0e0e5bc6955c4223228d16aba8cdb6e59bfd111d0901595bc32b22250801122101edbbd22b093f48404d082448bd335028477d700b449450b6800708095f78b9d6", 4 | "root": "d32dd06714f3cee110cdb37ad7bc4760d4e291a555160f54429f9f15ddb04c4d", 5 | "value": "76616c75655f666f725f464a544d75787a575873713470436c46506e6b48" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/smt/exist_right.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "796c52704356713162706b4575786b526b68644f", 3 | "proof": "0ac7030a14796c52704356713162706b4575786b526b68644f121e76616c75655f666f725f796c52704356713162706b4575786b526b68644f1a090801100118012a0100222508011221013ae2d90a118dd6f8a42f4006817e777f22540e921df1d97ed334a8b9de92c6cd2225080112210156a62a015a84af9a33ca716f52ed414382bed27a70b00391f15a5471f94443d32225080112210144c48bf2a802195fba5164a7a6c171a9af7a114c7880136108a5544d82fbacba22250801122101c0bda8822a881b6a6a1dc5016bb062a9dd434ee3fe1c9bfb5bc0247bd6aeb5b42225080112210138cb0f6ef907a179a530fba848bae9547bcc37b9207aead6f7ef44064900386c2225080112210108f120d84fa1d8b2e7cc080673b382cb29169dd6e954b090f9cb7a25195d59a422250801122101ad476a123e0e8fbbbd4a225e29b193af4ae64600ccba4cc37922fc5d09159963222508011221013ad52a705d9b923dd07047aea6ad1bc1327e7557210c77cf06bcab7833ae313622250801122101c3a40ac7ab7c6cd3f267981dcd75158053dcad700af423ac0541ac0d315b4e7b222508011221017176acbe6927c4797f9e0456b7073802ad015fa8a915712e87b22830b72a1f94", 4 | "root": "6177836ac520808df67bc402ae76382282f4f07a526a2ac25c409cf0380cad97", 5 | "value": "76616c75655f666f725f796c52704356713162706b4575786b526b68644f" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/smt/nonexist_left.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "636c536e4d46714a4e6162776a7a5134356e6d4d", 3 | "proof": "1280040a20002c041fe1df37d806cc5b493bed02222d83b25ffebf9cb529b4524cc547fbeb1adb030a144445687a30613576355036616536514c376d6445121e76616c75655f666f725f4445687a30613576355036616536514c376d64451a090801100118012a0100222708011201011a205aa5f240071f196e8d21e38b3fa6e942d5c529cd5fa0eec85607ba8cd8e37985222708011201011a206732b1eaa2cf07fbc94599aecc2df057d8efb0ccd775d86b6ea176bfaad367ad222708011201011a20074e19b2ce9887b188cabd61f0af10d699c206d4eee9c5e914bcfea5960439f8222708011201011a208c8012ca455d57135bea305ad1080cc0a9d8dec078a7813736ff871b40708a72222708011201011a200c70649c4ea15de4a2f2087a340fa93adb880f851d5465481349803315201ecd222708011201011a20ca5d1f4ee19172af78ff61c113336292c11c89884ead5b541f54cc53449e2edf222708011201011a2091a26f99a2852c72dd4a15c6b0444421a6d033f7f570f10c8c564bc3dce2fbc3222708011201011a206bdf78f09ef3fc2789b9a48e5e741e722dbf44050ec052de79135fbabf10ccd6222708011201011a20ecbc7b302eae49d180b8d1f1fb36e7c5d19f84ce3c5d9a84e4913ef73a9cbab2222708011201011a2010d3a9e28ea143c3b9910b1d40e6087e97520bc2a3dbe66a806bf214039fdb1b", 4 | "root": "50be82f8013965f595392462f6c68b4c1d3d3401210a1084f6f73e3e2caf0648", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/smt/nonexist_middle.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "364e58393178705248554d796e514c465332ffff", 3 | "proof": "12bd080a20ba46e91ed56ac7bea290f31a5b5492090243f111a536c49f589e978f2d3ee8df12a8030a14676e4e34356d52326831367467786a646f38544e121e76616c75655f666f725f676e4e34356d52326831367467786a646f38544e1a090801100118012a0100222708011201011a20d9948e386372dc85899e1890e988d8e47677c0bc211c65b55b5f70134dc15b0f222708011201011a201c2caf7504cfca30f253fce9c82051c3dd167058b0e22d840f4d90d0a0b2a9f7222508011221012db282d912c7d3f1dfd2750db451ad6e8176abddef45eb41fe1830c8789c1700222708011201011a200bd4fd01f2784fc637cc886ec0db6c4465cd4b4c8eb7266aaf84e0127740af2c222508011221018bf18f4ddf61ff31cb450737f6cf733d53d40f99bc3da526071755c37cc08ee1222508011221017bc720cf72271f5ac20af7962453209d5278965672e2cfebe5135bf85187681222250801122101f8500cdcf5ae664bdefce1221c42a69cf057b071e34a0ab946526525d6f3bc4d222708011201011a2027e93aecb0ea7d88fc13ce568017187ee5f07361f4d90e03866fb7f3f37a396622250801122101346d7d14c3a5521852f37261f20e91af45c5c0f772c13917b67d6eb6be9171ea1aed040a147252507a47715765774a6d6a38706b4345327464121e76616c75655f666f725f7252507a47715765774a6d6a38706b43453274641a090801100118012a0100222708011201011a2003414f6402648178d0e76d39250d2e4cee54ec1f16e451cb602756a0a3044507222508011221010000000000000000000000000000000000000000000000000000000000000000222508011221010000000000000000000000000000000000000000000000000000000000000000222708011201011a20000000000000000000000000000000000000000000000000000000000000000022250801122101000000000000000000000000000000000000000000000000000000000000000022250801122101f18e35df2f213a21db25d856f3532afb9b1f7f47073f6f009507ddacd6d7457c222708011201011a201c2caf7504cfca30f253fce9c82051c3dd167058b0e22d840f4d90d0a0b2a9f7222508011221012db282d912c7d3f1dfd2750db451ad6e8176abddef45eb41fe1830c8789c1700222708011201011a200bd4fd01f2784fc637cc886ec0db6c4465cd4b4c8eb7266aaf84e0127740af2c222508011221018bf18f4ddf61ff31cb450737f6cf733d53d40f99bc3da526071755c37cc08ee1222508011221017bc720cf72271f5ac20af7962453209d5278965672e2cfebe5135bf85187681222250801122101f8500cdcf5ae664bdefce1221c42a69cf057b071e34a0ab946526525d6f3bc4d222708011201011a2027e93aecb0ea7d88fc13ce568017187ee5f07361f4d90e03866fb7f3f37a396622250801122101346d7d14c3a5521852f37261f20e91af45c5c0f772c13917b67d6eb6be9171ea", 4 | "root": "771301eae8764c6147399a9944dd4454e865db8bc33e60f22bebea1c9bf7021e", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/smt/nonexist_right.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "68316d656864356a774f646f4f4264613530684e", 3 | "proof": "12ba040a20ffffedc8e4ee7f13c9892bf6277cdd60e4e8fad77abb37ca4f3b888a63b60df41295040a146b63395874466e5a4645784d416f4b6f37566c5a121e76616c75655f666f725f6b63395874466e5a4645784d416f4b6f37566c5a1a090801100118012a010022250801122101b76263b0c4e8438e81f21cdbff1e21b894a3c0e4ea08a0deab7c1de3292cd16a222508011221010000000000000000000000000000000000000000000000000000000000000000222508011221013ae2d90a118dd6f8a42f4006817e777f22540e921df1d97ed334a8b9de92c6cd2225080112210156a62a015a84af9a33ca716f52ed414382bed27a70b00391f15a5471f94443d32225080112210144c48bf2a802195fba5164a7a6c171a9af7a114c7880136108a5544d82fbacba22250801122101354656582d1a82da7965901b2035596704419e5d238def48fdc03950624ee5182225080112210123ea47d69d737db4713327399685b2b15fc605d8b9fc63dad042cc7e2ca86b88222508011221010f0393085c3dd349cc0f45cb559fae81a1c4f24231afcb2b5ae51fd89262149322250801122101c5c632712f2341f6a31c3db7420c52c8d34df31704ce55856377cd9f23226469222508011221019209817b26ad0a91845bcbbd42453be5a6ba6f1c47e462014d6de896feede6522225080112210146446f4723153a265d03a7a20f4b906738d1af822434e77189134f45f2b2a80f22250801122101680acaa83722e275ea78c80914940c67efd48e5da6b331c821aaa2eb0ae184ed", 4 | "root": "7bfe3d7630eb1f47be11d1724abad7a1b5de70c7131e7ed8ed60d9656ea49886", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/tendermint/exist_left.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "303142424373615a55715146735259436c6a5767", 3 | "proof": "0adb030a14303142424373615a55715146735259436c6a5767121e76616c75655f666f725f303142424373615a55715146735259436c6a57671a090801180120012a0100222708011201011a20cb3131cd98b069efcc0e8c7e68da47370adbff32266d7fcd1b0580fdf3961266222708011201011a2021d1205c1f8537205e8fb4b176f960b459d9131669968d59c456442f7673b68b222708011201011a20b82a0e7f4434b3cedb87ea83eb5a70c7dc664c77b2fe21c6245f315e58fdf745222708011201011a20bf0657a0e6fbd8f2043eb2cf751561adcf50547d16201224133eeb8d38145229222708011201011a206d47c03df91a4a0252055d116439d34b5b73f3a24d5cb3cf0d4b08caa540cac4222708011201011a20d5d2926993fa15c7410ac4ee1f1d81afddfb0ab5f6f4706b05f407bc01638149222708011201011a20540719b26a7301ad012ac45ebe716679e5595e5570d78be9b6da8d8591afb374222708011201011a20fccaaa9950730e80b9ccf75ad2cfeab26ae750b8bd6ac1ff1c7a7502f3c64be2222708011201011a20ecb61a6d70accb79c2325fb0b51677ed1561c91af5e10578c8294002fbb3c21e222708011201011a201b3bc1bd8d08af9f6199de84e95d646570cbd9b306a632a5acf617cbd7d1ab0a", 4 | "root": "c569a38a5775bbda2051c34ae00894186f837c39d11dca55495b9aed14f17ddf", 5 | "value": "76616c75655f666f725f303142424373615a55715146735259436c6a5767" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/tendermint/exist_middle.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "513334656d766f39447145585735325257523835", 3 | "proof": "0ad1030a14513334656d766f39447145585735325257523835121e76616c75655f666f725f513334656d766f394471455857353252575238351a090801180120012a010022250801122101e231d775380f2d663651e213cc726660e2ce0a2f2e9ee12cbb7df32294104a8c222708011201011a2014af194c63500236e52cc290ab24244fab39a520ece7e20fa93f4c9ff80c6626222508011221017966d2ead34418db2eaa04c0dffb9316805e8a0d421d1270c8954c35ee3221382225080112210172339e20a49bb16795a99bd905b47f99c45e5e5a9e6b7fb223dc8fe6751e1bda222708011201011a2053dd1ecc25ff906a0ef4db37ee068f3d8ad6d1d49913eefb847a675a681c5ffa222708011201011a20de90f9951a19497be7e389e02aa79e26faf77080e740e8743249a17a537f287d22250801122101ad4e53e981afc5a71e34ab0c4ffbccf1b468414d9d0939bd08edbd2461bc944a222708011201011a209b4cf89c3995b9dd66d58ab088846b2c6b59c52c6d10ec1d759ca9e9aa5eef5c222508011221013928a078bd66ab3949f5b1846b6d354dbdc1968a416607c7d91555ca26716667222708011201011a20d2d82cf8915b9ae6f92c7eae343e37d312ace05e654ce47acdf57d0a5490b873", 4 | "root": "494b16e3a64a85df143b2881bdd3ec94c3f8e18b343e8ff9c2d61afd05d040c8", 5 | "value": "76616c75655f666f725f513334656d766f39447145585735325257523835" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/tendermint/exist_right.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "7a785a4e6b534c64634d655657526c7658456644", 3 | "proof": "0aab020a147a785a4e6b534c64634d655657526c7658456644121e76616c75655f666f725f7a785a4e6b534c64634d655657526c76584566441a090801180120012a0100222508011221012634b831468dbafb1fc61a979c348ff8462da9a7d550191a6afc916ade16cc9922250801122101ab814d419bfc94ee9920d0ce993ce5da011e43613daf4b6f302855760083d7dd222508011221015a1568c73eaeaba567a6b2b2944b0e9a0228c931884cb5942f58ed835b8a7ac522250801122101a171412db5ee84835ef247768914e835ff80b7711e4aa8060871c2667ec3ea2922250801122101f9c2491884de24fb61ba8f358a56b306a8989bd35f1f8a4c8dabce22f703cc14222508011221012f12a6aa6270eff8a1628052938ff5e36cfcc5bf2eaedc0941ee46398ebc7c38", 4 | "root": "f54227f1a7d90aa2bf7931066196fd3072b7fe6b1fbd49d1e26e85a90d9541bb", 5 | "value": "76616c75655f666f725f7a785a4e6b534c64634d655657526c7658456644" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/tendermint/nonexist_left.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "01010101", 3 | "proof": "12e4030a04010101011adb030a143032615465366472483456706f4f583245507137121e76616c75655f666f725f3032615465366472483456706f4f5832455071371a090801180120012a0100222708011201011a20b843481496dc10561056b63ec8f726f3357395b610355b25082f5768b2073e91222708011201011a20d5281fdd872060e89173d4de1100fa6c96f778467df66abb10cf3b1f5821f182222708011201011a20eb981020433d929c6275ad772accf2e6aa916db97e31d2f26d0b6b07b444bbef222708011201011a204a40e813132aff60b64ba9d109548ab39459ad48a203ab8d3455dd842a7ab1da222708011201011a208f354a84ce1476e0b9cca92e65301a6435b1f242c2f53f943b764a4f326a71c7222708011201011a20ac6451617a6406005035dddad36657fde5312cc4d67d69ca1464611847c10cfb222708011201011a2023c1d1dd62002a0e2efcc679196589a4337234dcd209cb449cc3ac10773b60e0222708011201011a203b11c267328ba761ddc630dd5ef7642aeda05f180539fe93c0ca57729705bc46222708011201011a205ff2e1933be704539463c264b157ff2b8d9960813bd36c69c5208d57e3b1e07e222708011201011a20c4a79e6c0cbf60fb8e5bf940db4c444b7e442951b69c840db38cf28c8aa008be", 4 | "root": "4e2e78d2da505b7d0b00fda55a4b048eed9a23a7f7fc3d801f20ce4851b442aa", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/tendermint/nonexist_middle.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "544f31483668784a4b667136547a56767649ffff", 3 | "proof": "12c0070a14544f31483668784a4b667136547a56767649ffff12cf030a14544f31483668784a4b667136547a567676497747121e76616c75655f666f725f544f31483668784a4b667136547a5676764977471a090801180120012a01002225080112210143e19cb5e5dab017734caa78a2e2bccbb4797b7dc5a91abeab630c66fa6b162522250801122101b575404a1bb42b0fef8ae7f217af88aec769f7d66b5bc4b2913e74d651365473222508011221017c22dc50e866f9a1dce517ea01621161cecd70f4bdcd024b5a392746a1c8dc2622250801122101578105344f2c98c323ba0b8ca31e75aaa2b865cc389681e300b14d1c20713796222708011201011a20895c070c14546ecef7f5cb3a4bda1fd436a0ff99190f90bd037cbeaf52b2ffc1222708011201011a20f7571fca06ac4387c3eae5469c152427b797abb55fa98727eacbd5c1c91b5fb4222508011221015056e6472f8e5c5c9b8881c5f0e49601e9eca31f3e1766aa69c2dc9c6d9112be222708011201011a206c74439556c5edb5aa693af410d3718dbb613d37799f2f4e8ff304a8bfe3351b22250801122101253014334c7b8cd78436979554f7890f3dc1c971925ea31b48fc729cd179c701222708011201011a20b81c19ad4b5d8d15f716b91519bf7ad3d6e2289f9061fd2592a8431ea97806fe1ad5030a14544f433344683150664f76657538585166635778121e76616c75655f666f725f544f433344683150664f766575385851666357781a090801180120012a0100222708011201011a20415d4cfaed0bfc98ac32acc219a8517bfa1983a15cc742e8b2f860167484bd46222708011201011a2098d853d9cc0ee1d2162527f660f2b90ab55b13e5534f1b7753ec481d7901d3ec222708011201011a20b5113e6000c5411b7cfa6fd09b6752a43de0fcd3951ed3b154d162deb53224a2222708011201011a208ce18cd72cc83511cb8ff706433f2fa4208c85b9f4c8d0ed71a614f24b89ae6c22250801122101c611244fe6b5fda4257615902eb24c14efcd9708c7c875d1ac5e867767aa1eab222708011201011a20f7571fca06ac4387c3eae5469c152427b797abb55fa98727eacbd5c1c91b5fb4222508011221015056e6472f8e5c5c9b8881c5f0e49601e9eca31f3e1766aa69c2dc9c6d9112be222708011201011a206c74439556c5edb5aa693af410d3718dbb613d37799f2f4e8ff304a8bfe3351b22250801122101253014334c7b8cd78436979554f7890f3dc1c971925ea31b48fc729cd179c701222708011201011a20b81c19ad4b5d8d15f716b91519bf7ad3d6e2289f9061fd2592a8431ea97806fe", 4 | "root": "4bf28d948566078c5ebfa86db7471c1541eab834f539037075b9f9e3b1c72cfc", 5 | "value": "" 6 | } 7 | -------------------------------------------------------------------------------- /testdata/tendermint/nonexist_right.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "ffffffff", 3 | "proof": "12a9030a04ffffffff12a0030a147a774e4d4a456f7932674253586277666e63504a121e76616c75655f666f725f7a774e4d4a456f7932674253586277666e63504a1a090801180120012a01002225080112210178a215355c17371583418df95773476b347a853f6eae317677721e0c24e78ad2222508011221015e2cf893e7cd70251eb4debd855c8c9a92f6e0a1fd931cf41e0575846ab174e822250801122101414bae883f8133f0201a2791dafeaef3daa24a6631b3f9402de3a4dc658fd035222508011221012e2829beee266a814af4db08046f4575b011e5ec9d2d93c1510c3cc7d8219edc22250801122101f8286597078491ae0ef61264c218c6e167e4e03f1de47945d9ba75bb41deb81a22250801122101dea6a53098d11ce2138cbcae26b392959f05d7e1e24b9547584571012280f289222508011221010a8e535094d18b2120c38454b445d9accf3f1b255690e6f3d48164ae73b4c775222508011221012cbb518f52ec1f8e26dd36587f29a6890a11c0dd3f94e7a28546e695f296d3a722250801122101839d9ddd9dadf41c0ecfc3f7e20f57833b8fb5bcb703bef4f97910bbe5b579b9", 4 | "root": "83952b0b17e64c862628bcc1277e7f8847589af794ed5a855339281d395ec04f", 5 | "value": "" 6 | } 7 | --------------------------------------------------------------------------------