Skip to content

Commit

Permalink
Merge branch 'feat/integrate-bg-router' into chore/introduce-submodule
Browse files Browse the repository at this point in the history
* feat/integrate-bg-router:
  Update E2E_FEATURE_LIST.md
  [IBC] Implement ICS-23 CommitmentProof verification for the SMT (#845)
  [Utility] trustless relays servicer token validation (#803)
  • Loading branch information
bryanchriswhite committed Jun 30, 2023
2 parents 8a3155b + bf96542 commit 54d8d99
Show file tree
Hide file tree
Showing 28 changed files with 1,222 additions and 82 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ test_persistence: ## Run all go unit tests in the Persistence module
test_persistence_state_hash: ## Run all go unit tests in the Persistence module related to the state hash
go test ${VERBOSE_TEST} -count=1 -tags=test -run TestStateHash ./persistence/...

.PHONY: test_servicer_relay
test_servicer_relay: ## Run all go unit tests related to servicer relays
go test ${VERBOSE_TEST} -count=1 -tags=test ./utility/servicer -run TestRelay

.PHONY: test_p2p
test_p2p: ## Run all p2p related tests
go test ${VERBOSE_TEST} -count=1 -tags=test ./p2p/...
Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module github.com/pokt-network/pocket

go 1.18

// TECHDEBT: remove once upstream PR is merged (see: https://github.com/cosmos/ics23/pull/153)
replace github.com/cosmos/ics23/go => github.com/h5law/ics23/go v0.0.0-20230619152251-56d948cafb83

// TECHDEBT: remove once upstream PR is merged (see: https://github.com/regen-network/gocuke/pull/12)
replace github.com/regen-network/gocuke => github.com/pokt-network/gocuke v0.0.1

Expand All @@ -21,6 +24,7 @@ require (

require (
github.com/benbjohnson/clock v1.3.0
github.com/cosmos/ics23/go v0.10.0
github.com/deepmap/oapi-codegen v1.12.4
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/foxcpp/go-mockdns v1.0.0
Expand All @@ -37,7 +41,7 @@ require (
github.com/manifoldco/promptui v0.9.0
github.com/mitchellh/mapstructure v1.5.0
github.com/multiformats/go-multiaddr v0.8.0
github.com/pokt-network/smt v0.5.0
github.com/pokt-network/smt v0.6.1
github.com/quasilyte/go-ruleguard/dsl v0.3.21
github.com/regen-network/gocuke v0.6.2
github.com/rs/zerolog v1.27.0
Expand Down Expand Up @@ -87,6 +91,7 @@ require (
github.com/cockroachdb/apd/v3 v3.1.0 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cosmos/gogoproto v1.4.3 // indirect
github.com/cucumber/common/messages/go/v19 v19.1.2 // indirect
github.com/cucumber/gherkin/go/v26 v26.0.3 // indirect
github.com/cucumber/messages/go/v21 v21.0.1 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cosmos/gogoproto v1.4.3 h1:RP3yyVREh9snv/lsOvmsAPQt8f44LgL281X0IOIhhcI=
github.com/cosmos/gogoproto v1.4.3/go.mod h1:0hLIG5TR7IvV1fme1HCFKjfzW9X2x0Mo+RooWXCnOWU=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
Expand Down Expand Up @@ -356,6 +358,8 @@ github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQ
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/h5law/ics23/go v0.0.0-20230619152251-56d948cafb83 h1:uG97IfYQttG5iVt/jHK2wnGZgKUxHUjnzAlWY6EDso8=
github.com/h5law/ics23/go v0.0.0-20230619152251-56d948cafb83/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand Down Expand Up @@ -698,8 +702,8 @@ github.com/pokt-network/go-mockdns v0.0.1 h1:1Kb/kIFH6bNtY9F1bFhJyMRMCc7WyiqfGg0
github.com/pokt-network/go-mockdns v0.0.1/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
github.com/pokt-network/gocuke v0.0.1 h1:qJ/Ryf+hi5L6T9lsOZDNbiAclHkLlDio5/eVKQEYhgE=
github.com/pokt-network/gocuke v0.0.1/go.mod h1:BowLKW4++696gTTU33teodtIhjjyaphEbhQT9D5Refw=
github.com/pokt-network/smt v0.5.0 h1:rNTW3FB6i0pNMnafDqsBySgs0zpbjs0spP3p7ltVjAE=
github.com/pokt-network/smt v0.5.0/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ=
github.com/pokt-network/smt v0.6.1 h1:u5yTGNNND6edXv3vMQrAcjku1Ig4osehdu+EMYSXHUU=
github.com/pokt-network/smt v0.6.1/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ=
github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=
github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
Expand Down
8 changes: 8 additions & 0 deletions ibc/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Persistence](#persistence)
- [Components](#components)
- [ICS-24 Host Requirements](#ics-24-host-requirements)
- [ICS-23 Vector Commitments](#ics-23-vector-commitments)

## Definitions

Expand Down Expand Up @@ -106,6 +107,13 @@ The [IBC specification][ibc-spec] details numerous Interchain Standards (ICSs) t

See: [ICS-24](./ics24.md) for more details on the specifics of the ICS-24 implementation for Pocket.

### ICS-23 Vector Commitments

[ICS-23][ics23] defines the `CommitmentProof` type that is used to prove the membership/non-membership of a key-value pair in the IBC stores. As this type is serialisable the relayers can relay these proofs to a counterparty chain, as described above, for their ibc specific light client (of the source chain) to verify the proof and thus react accordingly. In order to implement ICS-23, the `cosmos/ics23` library was used, specifically its `CommitmentProof` type and its methods to verify the proofs in a way that does not require the tree itself.

See: [ICS-23](./ics23.md) for more details on the specifics of the ICS-23 implementation for Pocket.

[ibc-spec]: https://github.com/cosmos/ibc
[ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md
[ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md
[smt]: https://github.com/pokt-network/smt
116 changes: 116 additions & 0 deletions ibc/docs/ics23.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# ICS-23 Vector Commitments <!-- omit in toc -->

- [Overview](#overview)
- [Implementation](#implementation)
- [Custom SMT `ProofSpec`](#custom-smt-proofspec)
- [Converting `SparseMerkleProof` to `CommitmentProof`](#converting-sparsemerkleproof-to-commitmentproof)
- [Proof Verification](#proof-verification)

## Overview

[ICS-23][ics23] defines the types and functions needed to verify membership of a key-value pair in a `CommitmentState`. As the Pocket IBC implementation uses the [SMT][smt] for its provable stores, this is referred to as the `CommitmentState` object. Cosmos has a library `cosmos/ics23` which is already SDK agnostic and defines many of the types necessary for ICS-23. This library was able to be used _mostly_ out of the box, with some minor adjustments detailed below.

## Implementation

The benefit of using `cosmos/ics23` over implementing similar types ourselves is twofold:

1. It is already SDK agnostic, so can be used by Pocket (a non-cosmos chain) without any issues or major changes.
2. The functions defined for proof verification are decoupled from the underlying tree structure, meaning proof verification is tree agnostic.

However, there were some changes made specifically for Pocket's implementation of ICS-23.

See: [`cosmos/ics23` #152](https://github.com/cosmos/ics23/issues/152) and [`cosmos/ics23` #153](https://github.com/cosmos/ics23/pull/153ß) for the details of the changes made to allow for `ExclusionProof` verification.

### Custom SMT `ProofSpec`

The `ProofSpec` type in `cosmos/ics23` is used to define:

1. The steps needed to verify a proof
2. The hash functions used
3. Node prefixes
4. Etc...

The `ProofSpec` is then passed into the verification functions in order to verify a proof instead of having to interact with the tree itself. This is useful as proofs must be verified via an (IBC) light client, and as such being able to verify a proof without reconstructing a tree is much more memory efficient.

As the SMT used by Pocket Network only stores hashed values by default, the IBC store uses the `WithValueHasher(nil)` option which stores the source value (as raw bytes) in the tree. The following `ProofSpec` was created to support this:

```go
smtSpec *ics23.ProofSpec = &ics23.ProofSpec{
LeafSpec: &ics23.LeafOp{
Hash: ics23.HashOp_SHA256,
PrehashKey: ics23.HashOp_SHA256,
PrehashValue: ics23.HashOp_NO_HASH,
Length: ics23.LengthOp_NO_PREFIX,
Prefix: []byte{0},
},
InnerSpec: &ics23.InnerSpec{
ChildOrder: []int32{0, 1},
ChildSize: 32,
MinPrefixLength: 1,
MaxPrefixLength: 1,
EmptyChild: make([]byte, 32),
Hash: ics23.HashOp_SHA256,
},
MaxDepth: 256,
PrehashKeyBeforeComparison: true,
}
```

The main difference from the `cosmos/ics23` `SmtSpec` object is that the `PrehashValue` field is set to not hash values before hashing the key-value pair.

### Converting `SparseMerkleProof` to `CommitmentProof`

In order to convert the proofs generated by the SMT into a serialisable proof used by `cosmos/ics23`, the `SideNodes` field of the `SparseMerkleProof` must be converted into a list of `InnerOp` types which define the order of the hashes. The order of the hashes is important as depending on whether the next hash is the left or right neighbour of the current hash, they will be hashed in a different order, ultimately creating a different root hash. This conversion allows the verification to produce the same root hash as the SMT would have produced when verifying the proof.

As `SparseMerkleProof` objects represent both inclusion and exclusion proofs as defined in the [JMT whitepaper][jmt]. The conversion step will convert the SMT proof into either an `ExistenceProof` or `ExclusionProof` as defined in `cosmos/ics23`.

### Proof Verification

Membership proofs are verified as follows:

1. Use the key-value pair to generate a leaf hash
2. Hash the leaf with the `SideNodes` found in the `path` field of the `ExistenceProof` to generate the root hash
3. Compare the root hash with the one provided and expect them to be identical

Non-membership proofs are verified as follows:

1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value (`[32]byte`, i.e. the key is not set in the tree), then use the placeholder value as the leaf node hash and skip to step 3 below
2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields (provided via `NonMembershipLeafData`) to generate the leaf node hash.
- **IMPORTANT**: DO NOT hash these values before hashing the node as they are populated from the SMT proof's `NonMembershipLeafData` field and thus are already hashed
3. Hash the leaf node hash with the `SideNodes` found in the `Path` field of the `ExclusionProof` to generate the root hash
4. Compare the root hash with the one provided
- if `computedRootHash == providedRootHash`
- `key` not in tree -> `Proof` is valid -> exclusion QED
- if `computedRootHash != providedRootHash`
- `key` is in tree -> `Proof` is invalid -> exclusion QED

```mermaid
flowchart TD
I["Proof,Key"]
NMD{"proof.NonMembershipLeafData == nil ?"}
KP1["actualPath = sha256(key) \n actualValue = placeholder\ncurrentHash = [32]byte"]
KP2["actualPath = ProvidedKeyHash \n actualValue = ProvidedValueHash\ncurrentHash = sha256([]byte{0}+actualPath+actualValueHash)"]
C["nextHash = sha256(currentHash+sideNodeHash)"]
Compare{"ComputedRootHash == ProvidedRootHash ?"}
EV["Exclusion Prove VALID"]
EI["Exclusion Prove INVALID"]
I --> NMD
NMD -- Yes --> KP1
NMD -- No --> KP2
KP1 -- CurrentHash --> C
KP2 -- CurrentHash --> C
C -- while NextSideNode != nil --> C
C -- ComputedRootHash --> Compare
Compare -- Yes --> EV
Compare -- No --> EI
```

The full implementation of this logic can be found [here](../store/proofs_ics23.go) as well as in the `cosmos/ics23` [library](https://github.com/h5law/ics23/blob/56d948cafb83ded78dc4b9de3c8b04582734851a/go/proof.go#L171).

[ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md
[smt]: https://github.com/pokt-network/smt
[jmt]: https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf
159 changes: 159 additions & 0 deletions ibc/store/proofs_ics23.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package store

import (
"crypto/sha256"

ics23 "github.com/cosmos/ics23/go"
coreTypes "github.com/pokt-network/pocket/shared/core/types"
"github.com/pokt-network/smt"
)

// position refers to whether the node is either the left or right child of its parent
// for the binary SMT
// Ref: https://github.com/pokt-network/smt/blob/main/types.go
const (
left int = iota // 0
right // 1
hashSize = 32
)

var (
// Custom SMT spec as the store does not hash values
smtSpec *ics23.ProofSpec = &ics23.ProofSpec{
LeafSpec: &ics23.LeafOp{
Hash: ics23.HashOp_SHA256,
PrehashKey: ics23.HashOp_SHA256,
PrehashValue: ics23.HashOp_NO_HASH,
Length: ics23.LengthOp_NO_PREFIX,
Prefix: []byte{0},
},
InnerSpec: &ics23.InnerSpec{
ChildOrder: []int32{0, 1},
ChildSize: hashSize,
MinPrefixLength: 1,
MaxPrefixLength: 1,
EmptyChild: make([]byte, hashSize),
Hash: ics23.HashOp_SHA256,
},
MaxDepth: 256,
PrehashKeyBeforeComparison: true,
}
innerPrefix = []byte{1}

// defaultValue is the default placeholder value in a SparseMerkleTree
defaultValue = make([]byte, hashSize)
)

// VerifyMembership verifies the CommitmentProof provided, checking whether it produces the same
// root as the one given. If it does, the key-value pair is a member of the tree
func VerifyMembership(root ics23.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool {
// verify the proof
return ics23.VerifyMembership(smtSpec, root, proof, key, value)
}

// VerifyNonMembership verifies the CommitmentProof provided, checking whether it produces the same
// root as the one given. If it does, the key-value pair is not a member of the tree as the proof's
// value is either the default nil value for the SMT or an unrelated value at the path
func VerifyNonMembership(root ics23.CommitmentRoot, proof *ics23.CommitmentProof, key []byte) bool {
// verify the proof
return ics23.VerifyNonMembership(smtSpec, root, proof, key)
}

// createMembershipProof generates a CommitmentProof object verifying the membership of a key-value pair
// in the SMT provided
func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentProof, error) {
proof, err := tree.Prove(key)
if err != nil {
return nil, coreTypes.ErrCreatingProof(err)
}
return convertSMPToExistenceProof(proof, key, value), nil
}

// createNonMembershipProof generates a CommitmentProof object verifying the membership of an unrealted key at the given key in the SMT provided
func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof, error) {
proof, err := tree.Prove(key)
if err != nil {
return nil, coreTypes.ErrCreatingProof(err)
}

return convertSMPToExclusionProof(proof, key), nil
}

// convertSMPToExistenceProof converts a SparseMerkleProof to an ics23
// ExistenceProof to verify membership of an element
func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) *ics23.CommitmentProof {
path := sha256.Sum256(key)
steps := convertSideNodesToSteps(proof.SideNodes, path[:])
return &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exist{
Exist: &ics23.ExistenceProof{
Key: key,
Value: value,
Leaf: smtSpec.LeafSpec,
Path: steps,
},
},
}
}

// convertSMPToExclusionProof converts a SparseMerkleProof to an ics23
// ExclusionProof to verify non-membership of an element
func convertSMPToExclusionProof(proof *smt.SparseMerkleProof, key []byte) *ics23.CommitmentProof {
path := sha256.Sum256(key)
steps := convertSideNodesToSteps(proof.SideNodes, path[:])
leaf := &ics23.LeafOp{
Hash: ics23.HashOp_SHA256,
// Do not re-hash already hashed fields from NonMembershipLeafData
PrehashKey: ics23.HashOp_NO_HASH,
PrehashValue: ics23.HashOp_NO_HASH,
Length: ics23.LengthOp_NO_PREFIX,
Prefix: []byte{0},
}
actualPath := path[:]
actualValue := defaultValue
if proof.NonMembershipLeafData != nil {
actualPath = proof.NonMembershipLeafData[1 : 1+hashSize] // len(prefix): len(prefix) + hashSize
actualValue = proof.NonMembershipLeafData[1+hashSize:]
}
return &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exclusion{
Exclusion: &ics23.ExclusionProof{
Key: key,
ActualPath: actualPath,
ActualValueHash: actualValue,
Leaf: leaf,
Path: steps,
},
},
}
}

// convertSideNodesToSteps converts the SideNodes field in the SparseMerkleProof
// into a list of InnerOps for the ics23 CommitmentProof
func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp {
steps := make([]*ics23.InnerOp, 0, len(sideNodes))
for i := 0; i < len(sideNodes); i++ {
var prefix, suffix []byte
prefix = append(prefix, innerPrefix...)
if isLeft(path, len(sideNodes)-1-i) {
// path is on the left so sidenode must be on the right
suffix = make([]byte, 0, len(sideNodes[i]))
suffix = append(suffix, sideNodes[i]...)
} else {
// path is on the right so sidenode must be on the left
prefix = append(prefix, sideNodes[i]...)
}
op := &ics23.InnerOp{
Hash: ics23.HashOp_SHA256,
Prefix: prefix,
Suffix: suffix,
}
steps = append(steps, op)
}
return steps
}

// isLeft returns true is the i-th bit of path is a left child in the SMT
func isLeft(path []byte, i int) bool {
return smt.GetPathBit(path, i) == left
}
Loading

0 comments on commit 54d8d99

Please sign in to comment.