From ab9ff72d2a2fe7bf776441df884ee5cb4782693f Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:56:56 +0100 Subject: [PATCH 01/12] Add ics23 integration --- ibc/store/proofs_ics23.go | 143 ++++++++++++++++++++++ shared/core/types/error.go | 6 + shared/core/types/proto/commitments.proto | 11 ++ 3 files changed, 160 insertions(+) create mode 100644 ibc/store/proofs_ics23.go create mode 100644 shared/core/types/proto/commitments.proto diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go new file mode 100644 index 000000000..498ad4a79 --- /dev/null +++ b/ibc/store/proofs_ics23.go @@ -0,0 +1,143 @@ +package store + +import ( + "bytes" + "crypto/sha256" + + ics23 "github.com/cosmos/ics23/go" + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/smt" +) + +type position int + +const ( + left position = iota // 0 + right // 1 +) + +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: 32, + MinPrefixLength: 1, + MaxPrefixLength: 1, + EmptyChild: make([]byte, 32), + Hash: ics23.HashOp_SHA256, + }, + MaxDepth: 256, + PrehashKeyBeforeComparison: true, + } + innerPrefix = []byte{1} + + // defaultValue is the default placeholder value in a SparseMerkleTree + defaultValue = make([]byte, 32) +) + +// 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 *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool { + // verify the proof + return ics23.VerifyMembership(smtSpec, root.Hash, 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 *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key []byte) bool { + // Verify the proof of the non-membership data doesn't belong to the key + valid := ics23.VerifyMembership(smtSpec, root.Hash, proof, key, proof.GetExist().GetValue()) + // Verify the key was actually empty + if bytes.Equal(proof.GetExist().GetValue(), defaultValue) { + return valid + } + // Verify the key was present with unrelated data + return !valid +} + +// 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.Error()) + } + return &ics23.CommitmentProof{ + Proof: &ics23.CommitmentProof_Exist{ + Exist: 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.Error()) + } + + value := defaultValue + if proof.NonMembershipLeafData != nil { + value = proof.NonMembershipLeafData[33:] + } + + return &ics23.CommitmentProof{ + Proof: &ics23.CommitmentProof_Exist{ + Exist: convertSMPToExistenceProof(&proof, key, value), + }, + }, nil +} + +// convertSMPToExistenceProof converts a SparseMerkleProof to an ICS23 ExistenceProof used for +// both membership and non-membership proof verification +func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) *ics23.ExistenceProof { + path := sha256.Sum256(key) + steps := make([]*ics23.InnerOp, 0, len(proof.SideNodes)) + for i := 0; i < len(proof.SideNodes); i++ { + var prefix, suffix []byte + prefix = append(prefix, innerPrefix...) + if getPathBit(path[:], len(proof.SideNodes)-1-i) == left { + suffix = make([]byte, 0, len(proof.SideNodes[i])) + suffix = append(suffix, proof.SideNodes[i]...) + } else { + prefix = append(prefix, proof.SideNodes[i]...) + } + op := &ics23.InnerOp{ + Hash: ics23.HashOp_SHA256, + Prefix: prefix, + Suffix: suffix, + } + steps = append(steps, op) + } + leaf := &ics23.LeafOp{ + Hash: ics23.HashOp_SHA256, + PrehashKey: ics23.HashOp_SHA256, + PrehashValue: ics23.HashOp_NO_HASH, + Length: ics23.LengthOp_NO_PREFIX, + Prefix: []byte{0}, + } + return &ics23.ExistenceProof{ + Key: key, + Value: value, + Leaf: leaf, + Path: steps, + } +} + +// getPathBit determines whether the hash of a node at a certain depth in the tree is the +// left or the right child of its parent +func getPathBit(data []byte, position int) position { + if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 { + return right + } + return left +} diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 4de0bc6ef..a7593489c 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -180,6 +180,7 @@ const ( CodeHostAlreadyExists Code = 138 CodeIBCInvalidID Code = 139 CodeIBCInvalidPath Code = 140 + CodeCreatingProofError Code = 141 ) const ( @@ -318,6 +319,7 @@ const ( HostAlreadyExistsError = "an ibc host already exists" IBCInvalidIDError = "invalid ibc identifier" IBCInvalidPathError = "invalid ibc path" + CreatingProofError = "an error occurred creating the CommitmentProof" ) func ErrUnknownParam(paramName string) Error { @@ -864,3 +866,7 @@ func ErrIBCInvalidID(identifier, msg string) Error { func ErrIBCInvalidPath(path string) Error { return NewError(CodeIBCInvalidPath, fmt.Sprintf("%s: %s", IBCInvalidPathError, path)) } + +func ErrCreatingProof(err error) Error { + return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", CreatingProofError, err.Error())) +} diff --git a/shared/core/types/proto/commitments.proto b/shared/core/types/proto/commitments.proto new file mode 100644 index 000000000..aee5008b3 --- /dev/null +++ b/shared/core/types/proto/commitments.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package core; + +option go_package = "github.com/pokt-network/pocket/shared/core/types"; + +// CommitmentRoot represents the merkle root hash of a CommitmentState +// This is used in the verification of CommitmentProof objects +message CommitmentRoot { + bytes hash = 1; +} From b47402d33ec2b63daca60aa85c85e21511bbbd41 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:23:36 +0100 Subject: [PATCH 02/12] Add SMT proof conversion to ics23 Existence and Exclusion proofs with verification --- go.mod | 5 + go.sum | 2 + ibc/host/path_test.go | 18 +-- ibc/store/proofs_ics23.go | 110 +++++++------ ibc/store/proofs_ics23_test.go | 178 ++++++++++++++++++++++ shared/core/types/proto/commitments.proto | 11 -- 6 files changed, 256 insertions(+), 68 deletions(-) create mode 100644 ibc/store/proofs_ics23_test.go delete mode 100644 shared/core/types/proto/commitments.proto diff --git a/go.mod b/go.mod index 61a9ad1c1..937561931 100644 --- a/go.mod +++ b/go.mod @@ -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 => ../ics23/go + // 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 @@ -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 @@ -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 diff --git a/go.sum b/go.sum index 7d7377ae4..4e8bb7827 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/ibc/host/path_test.go b/ibc/host/path_test.go index 1c1c8d367..041b2a239 100644 --- a/ibc/host/path_test.go +++ b/ibc/host/path_test.go @@ -65,14 +65,14 @@ func TestPaths_CommitmentPrefix(t *testing.T) { result string }{ { - name: "Successfully applies and removes prefix to produce the same path", + // Successfully applies and removes prefix to produce the same path path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test")), expected: []byte("test/path"), result: "path", }, { - name: "Fails to produce input path when given a different prefix", + // Fails to produce input path when given a different prefix path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test2")), expected: []byte("test/path"), @@ -81,14 +81,12 @@ func TestPaths_CommitmentPrefix(t *testing.T) { } for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - commitment := ApplyPrefix(prefix, tc.path) - require.NotNil(t, commitment) - require.Equal(t, []byte(commitment), tc.expected) + commitment := ApplyPrefix(prefix, tc.path) + require.NotNil(t, commitment) + require.Equal(t, []byte(commitment), tc.expected) - path := RemovePrefix(tc.prefix, commitment) - require.NotNil(t, path) - require.Equal(t, path, tc.result) - }) + path := RemovePrefix(tc.prefix, commitment) + require.NotNil(t, path) + require.Equal(t, path, tc.result) } } diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 498ad4a79..c1ab2f390 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -1,7 +1,6 @@ package store import ( - "bytes" "crypto/sha256" ics23 "github.com/cosmos/ics23/go" @@ -45,23 +44,17 @@ var ( // 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 *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool { +func VerifyMembership(root ics23.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool { // verify the proof - return ics23.VerifyMembership(smtSpec, root.Hash, proof, key, value) + 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 *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key []byte) bool { - // Verify the proof of the non-membership data doesn't belong to the key - valid := ics23.VerifyMembership(smtSpec, root.Hash, proof, key, proof.GetExist().GetValue()) - // Verify the key was actually empty - if bytes.Equal(proof.GetExist().GetValue(), defaultValue) { - return valid - } - // Verify the key was present with unrelated data - return !valid +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 @@ -69,47 +62,82 @@ func VerifyNonMembership(root *coreTypes.CommitmentRoot, proof *ics23.Commitment func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentProof, error) { proof, err := tree.Prove(key) if err != nil { - return nil, coreTypes.ErrCreatingProof(err.Error()) + return nil, coreTypes.ErrCreatingProof(err) } - return &ics23.CommitmentProof{ - Proof: &ics23.CommitmentProof_Exist{ - Exist: convertSMPToExistenceProof(&proof, key, value), - }, - }, nil + 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.Error()) + return nil, coreTypes.ErrCreatingProof(err) } - value := defaultValue - if proof.NonMembershipLeafData != nil { - value = proof.NonMembershipLeafData[33:] - } + 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: convertSMPToExistenceProof(&proof, key, value), + Exist: &ics23.ExistenceProof{ + Key: key, + Value: value, + Leaf: smtSpec.LeafSpec, + Path: steps, + }, }, - }, nil + } } -// convertSMPToExistenceProof converts a SparseMerkleProof to an ICS23 ExistenceProof used for -// both membership and non-membership proof verification -func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) *ics23.ExistenceProof { +// convertSMPToExistenceProof 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 := make([]*ics23.InnerOp, 0, len(proof.SideNodes)) - for i := 0; i < len(proof.SideNodes); i++ { + 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:33] + actualValue = proof.NonMembershipLeafData[33:] + } + 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 getPathBit(path[:], len(proof.SideNodes)-1-i) == left { - suffix = make([]byte, 0, len(proof.SideNodes[i])) - suffix = append(suffix, proof.SideNodes[i]...) + if getPathBit(path[:], len(sideNodes)-1-i) == left { + suffix = make([]byte, 0, len(sideNodes[i])) + suffix = append(suffix, sideNodes[i]...) } else { - prefix = append(prefix, proof.SideNodes[i]...) + prefix = append(prefix, sideNodes[i]...) } op := &ics23.InnerOp{ Hash: ics23.HashOp_SHA256, @@ -118,19 +146,7 @@ func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) } steps = append(steps, op) } - leaf := &ics23.LeafOp{ - Hash: ics23.HashOp_SHA256, - PrehashKey: ics23.HashOp_SHA256, - PrehashValue: ics23.HashOp_NO_HASH, - Length: ics23.LengthOp_NO_PREFIX, - Prefix: []byte{0}, - } - return &ics23.ExistenceProof{ - Key: key, - Value: value, - Leaf: leaf, - Path: steps, - } + return steps } // getPathBit determines whether the hash of a node at a certain depth in the tree is the diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go new file mode 100644 index 000000000..97cd63011 --- /dev/null +++ b/ibc/store/proofs_ics23_test.go @@ -0,0 +1,178 @@ +package store + +import ( + "crypto/sha256" + "testing" + + ics23 "github.com/cosmos/ics23/go" + "github.com/pokt-network/pocket/persistence/kvstore" + "github.com/pokt-network/smt" + "github.com/stretchr/testify/require" +) + +// Proof generation cannot fail but verification can +func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { + nodeStore := kvstore.NewMemKVStore() + tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) + require.NotNil(t, tree) + + // Set a value in the store + err := tree.Update([]byte("foo"), []byte("bar")) + require.NoError(t, err) + err = tree.Update([]byte("bar"), []byte("foo")) + require.NoError(t, err) + err = tree.Update([]byte("testKey"), []byte("testValue")) + require.NoError(t, err) + err = tree.Update([]byte("testKey2"), []byte("testValue2")) + require.NoError(t, err) + + testCases := []struct { + key []byte + value []byte + nonmembership bool + fails bool + expected error + }{ + { + // Successfully generates a membership proof for a key stored + key: []byte("foo"), + value: []byte("bar"), + nonmembership: false, + fails: false, + expected: nil, + }, + { + // Successfully generates a non-membership proof for a key not stored + key: []byte("baz"), + value: []byte("testValue2"), // unrelated leaf data + nonmembership: true, + fails: false, + expected: nil, + }, + { + // Successfully generates a non-membership proof for an unset nil key + key: nil, + value: []byte("foo"), // unrelated leaf data + nonmembership: true, + fails: false, + expected: nil, + }, + } + + for _, tc := range testCases { + var proof *ics23.CommitmentProof + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + if tc.fails { + require.EqualError(t, err, tc.expected.Error()) + require.Nil(t, proof) + return + } + require.NoError(t, err) + require.NotNil(t, proof) + if tc.nonmembership { + require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) + require.NotNil(t, proof.GetExclusion().GetLeaf()) + require.NotNil(t, proof.GetExclusion().GetPath()) + } else { + require.Equal(t, tc.value, proof.GetExist().GetValue()) + require.NotNil(t, proof.GetExist().GetLeaf()) + require.NotNil(t, proof.GetExist().GetPath()) + } + } + + err = nodeStore.Stop() + require.NoError(t, err) +} + +func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { + nodeStore := kvstore.NewMemKVStore() + tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) + require.NotNil(t, tree) + + // Set a value in the store + err := tree.Update([]byte("foo"), []byte("bar")) + require.NoError(t, err) + err = tree.Update([]byte("bar"), []byte("foo")) + require.NoError(t, err) + err = tree.Update([]byte("testKey"), []byte("testValue")) + require.NoError(t, err) + err = tree.Update([]byte("testKey2"), []byte("testValue2")) + require.NoError(t, err) + + root := tree.Root() + require.NotNil(t, root) + + testCases := []struct { + key []byte + value []byte + nonmembership bool + valid bool + }{ + { + // Successfully verifies a membership proof for a key-value stored pair + key: []byte("foo"), + value: []byte("bar"), + nonmembership: false, + valid: true, + }, + { + // Successfully verifies a non-membership proof for a key-value pair not stored + key: []byte("not stored"), + value: nil, + nonmembership: true, + valid: true, + }, + { + // Fails to verify a membership proof for a key-value pair not stored + key: []byte("baz"), + value: []byte("bar"), + nonmembership: false, + valid: false, + }, + { + // Fails to verify a non-membership proof for a key stored in the tree + key: []byte("foo"), + value: nil, + nonmembership: true, + valid: false, + }, + } + + proof := new(ics23.CommitmentProof) + for _, tc := range testCases { + var err error + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + require.NoError(t, err) + require.NotNil(t, proof) + + if tc.nonmembership { + require.NotNil(t, proof.GetExclusion()) + } else { + require.NotNil(t, proof.GetExist()) + } + + var valid bool + if tc.nonmembership { + valid = VerifyNonMembership(root, proof, tc.key) + } else { + valid = VerifyMembership(root, proof, tc.key, tc.value) + } + + if tc.valid { + require.True(t, valid) + } else { + require.False(t, valid) + } + } + + err = nodeStore.Stop() + require.NoError(t, err) +} diff --git a/shared/core/types/proto/commitments.proto b/shared/core/types/proto/commitments.proto deleted file mode 100644 index aee5008b3..000000000 --- a/shared/core/types/proto/commitments.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; - -package core; - -option go_package = "github.com/pokt-network/pocket/shared/core/types"; - -// CommitmentRoot represents the merkle root hash of a CommitmentState -// This is used in the verification of CommitmentProof objects -message CommitmentRoot { - bytes hash = 1; -} From 20fa2fc2307ce4d489bef343003bf8c4e2d881cb Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:42:39 +0100 Subject: [PATCH 03/12] Add ICS23 docs --- ibc/docs/README.md | 9 +++++- ibc/docs/ics23.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 ibc/docs/ics23.md diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 26de85af7..0ea5dd98d 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -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 @@ -106,6 +107,12 @@ 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 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 -[smt]: https://github.com/pokt-network/smt +[ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md diff --git a/ibc/docs/ics23.md b/ibc/docs/ics23.md new file mode 100644 index 000000000..95cc2895e --- /dev/null +++ b/ibc/docs/ics23.md @@ -0,0 +1,78 @@ +# ICS-23 Vector Commitments + +- [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 what 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. +2. The functions defined for proof verification are separate from the tree structures themselves + - This means we do not need to interact with an SMT instance in order to verify proofs. + +However, there were some changes made specifically for Pocket's implementation of ICS-23. + +### Custom SMT `ProofSpec` + +The `ProofSpec` type in `cosmos/ics23` is used to define the steps needed to verify a proof, what hashing functions should be used, any node prefixes, etc. This 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 on a light-client and as such being able to verify a proof without creating a tree is much more memory efficient. + +As the SMT used by Pocket Network only stores hashed values by default, the IBC stores use the `WithValueHasher(nil)` option which stores the raw bytes of the values in the tree. As such the following `ProofSpec` was created: + +```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 change from the `cosmos/ics23` `SmtSpec` object is that the `PrehashValue` field is set to not hash values prior to 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 a `ExistenceProof` or `ExclusionProof` as defined in `cosmos/ics23`. + +### Proof Verification + +When verifying membership of an element the logic is 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 + +For non-membership the logic is as follows: + +1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, then use the placeholder value as the leaf node hash and follow steps 3-4 +2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields to generate the leaf node hash - 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 + +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 From 52e62e4ad68914c4b7833d677731032d2e3b754a Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:18:23 +0100 Subject: [PATCH 04/12] Fix errors --- go.mod | 2 +- go.sum | 2 ++ ibc/store/proofs_ics23.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 937561931..28281e5ed 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ 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 => ../ics23/go +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 diff --git a/go.sum b/go.sum index 4e8bb7827..271c59bc7 100644 --- a/go.sum +++ b/go.sum @@ -358,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= diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index c1ab2f390..1ae2675e5 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -133,7 +133,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { for i := 0; i < len(sideNodes); i++ { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) - if getPathBit(path[:], len(sideNodes)-1-i) == left { + if getPathBit(path, len(sideNodes)-1-i) == left { suffix = make([]byte, 0, len(sideNodes[i])) suffix = append(suffix, sideNodes[i]...) } else { From 06566581aef0ab80b2f4ff87845e75936e914633 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:20:53 +0100 Subject: [PATCH 05/12] Update next error comment --- shared/core/types/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index a7593489c..870b6b733 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 141 +// NextCode: 142 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials From d631bb3c9676f165ef2d99cca8a1fc0d66478e0a Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:50:50 +0100 Subject: [PATCH 06/12] Address comments in docs --- ibc/docs/README.md | 2 +- ibc/docs/ics23.md | 47 ++++++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 0ea5dd98d..44c1ac1d6 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -109,7 +109,7 @@ See: [ICS-24](./ics24.md) for more details on the specifics of the ICS-24 implem ### 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 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. +[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. diff --git a/ibc/docs/ics23.md b/ibc/docs/ics23.md index 95cc2895e..6f6839132 100644 --- a/ibc/docs/ics23.md +++ b/ibc/docs/ics23.md @@ -2,29 +2,37 @@ - [Overview](#overview) - [Implementation](#implementation) - - [Custom SMT `ProofSpec`](#custom-smt-proofspec) - - [Converting `SparseMerkleProof` to `CommitmentProof`](#converting-sparsemerkleproof-to-commitmentproof) - - [Proof Verification](#proof-verification) + - [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 what 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. +[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. -2. The functions defined for proof verification are separate from the tree structures themselves - - This means we do not need to interact with an SMT instance in order to verify proofs. +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 the steps needed to verify a proof, what hashing functions should be used, any node prefixes, etc. This 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 on a light-client and as such being able to verify a proof without creating a tree is much more memory efficient. +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 stores use the `WithValueHasher(nil)` option which stores the raw bytes of the values in the tree. As such the following `ProofSpec` was created: +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{ @@ -33,14 +41,14 @@ smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ PrehashKey: ics23.HashOp_SHA256, PrehashValue: ics23.HashOp_NO_HASH, Length: ics23.LengthOp_NO_PREFIX, - Prefix: []byte{0}, + Prefix: []byte{0}, }, InnerSpec: &ics23.InnerSpec{ ChildOrder: []int32{0, 1}, ChildSize: 32, MinPrefixLength: 1, MaxPrefixLength: 1, - EmptyChild: make([]byte, 32), + EmptyChild: make([]byte, 32), Hash: ics23.HashOp_SHA256, }, MaxDepth: 256, @@ -48,28 +56,31 @@ smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ } ``` -The main change from the `cosmos/ics23` `SmtSpec` object is that the `PrehashValue` field is set to not hash values prior to hashing the key-value pair. +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 a `ExistenceProof` or `ExclusionProof` as defined in `cosmos/ics23`. +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 -When verifying membership of an element the logic is as follows: +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 +3. Compare the root hash with the one provided and expect them to be identical -For non-membership the logic is as follows: +Non-membership proofs are verified as follows: -1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, then use the placeholder value as the leaf node hash and follow steps 3-4 -2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields to generate the leaf node hash - 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 +1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, 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 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 + - As the non-membership proof uses the `ActualValueHash` field to generate the leaf node hash, the non-membership proof is actually proving membership of either a placeholder key or an unrelated key in the tree. + - This means that if the root hash computed and the one provided are equal then the key we were looking for was not in the tree. If they are not equal the proof is invalid and the key is in the tree. 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). From 1beb4bdb83a33cc1b3e7bf9a22617ee36b0f5fcd Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:54:18 +0100 Subject: [PATCH 07/12] Add names back to tests --- ibc/host/path_test.go | 18 ++--- ibc/store/proofs_ics23_test.go | 118 +++++++++++++++++---------------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/ibc/host/path_test.go b/ibc/host/path_test.go index 041b2a239..1c1c8d367 100644 --- a/ibc/host/path_test.go +++ b/ibc/host/path_test.go @@ -65,14 +65,14 @@ func TestPaths_CommitmentPrefix(t *testing.T) { result string }{ { - // Successfully applies and removes prefix to produce the same path + name: "Successfully applies and removes prefix to produce the same path", path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test")), expected: []byte("test/path"), result: "path", }, { - // Fails to produce input path when given a different prefix + name: "Fails to produce input path when given a different prefix", path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test2")), expected: []byte("test/path"), @@ -81,12 +81,14 @@ func TestPaths_CommitmentPrefix(t *testing.T) { } for _, tc := range testCases { - commitment := ApplyPrefix(prefix, tc.path) - require.NotNil(t, commitment) - require.Equal(t, []byte(commitment), tc.expected) + t.Run(tc.name, func(t *testing.T) { + commitment := ApplyPrefix(prefix, tc.path) + require.NotNil(t, commitment) + require.Equal(t, []byte(commitment), tc.expected) - path := RemovePrefix(tc.prefix, commitment) - require.NotNil(t, path) - require.Equal(t, path, tc.result) + path := RemovePrefix(tc.prefix, commitment) + require.NotNil(t, path) + require.Equal(t, path, tc.result) + }) } } diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go index 97cd63011..cff486b9d 100644 --- a/ibc/store/proofs_ics23_test.go +++ b/ibc/store/proofs_ics23_test.go @@ -27,6 +27,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { require.NoError(t, err) testCases := []struct { + name string key []byte value []byte nonmembership bool @@ -34,7 +35,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { expected error }{ { - // Successfully generates a membership proof for a key stored + name: "Successfully generates a membership proof for a key stored", key: []byte("foo"), value: []byte("bar"), nonmembership: false, @@ -42,7 +43,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { expected: nil, }, { - // Successfully generates a non-membership proof for a key not stored + name: "Successfully generates a non-membership proof for a key not stored", key: []byte("baz"), value: []byte("testValue2"), // unrelated leaf data nonmembership: true, @@ -50,7 +51,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { expected: nil, }, { - // Successfully generates a non-membership proof for an unset nil key + name: "Successfully generates a non-membership proof for an unset nil key", key: nil, value: []byte("foo"), // unrelated leaf data nonmembership: true, @@ -60,28 +61,30 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { } for _, tc := range testCases { - var proof *ics23.CommitmentProof - if tc.nonmembership { - proof, err = createNonMembershipProof(tree, tc.key) - } else { - proof, err = createMembershipProof(tree, tc.key, tc.value) - } - if tc.fails { - require.EqualError(t, err, tc.expected.Error()) - require.Nil(t, proof) - return - } - require.NoError(t, err) - require.NotNil(t, proof) - if tc.nonmembership { - require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) - require.NotNil(t, proof.GetExclusion().GetLeaf()) - require.NotNil(t, proof.GetExclusion().GetPath()) - } else { - require.Equal(t, tc.value, proof.GetExist().GetValue()) - require.NotNil(t, proof.GetExist().GetLeaf()) - require.NotNil(t, proof.GetExist().GetPath()) - } + t.Run(tc.name, func(t *testing.T) { + var proof *ics23.CommitmentProof + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + if tc.fails { + require.EqualError(t, err, tc.expected.Error()) + require.Nil(t, proof) + return + } + require.NoError(t, err) + require.NotNil(t, proof) + if tc.nonmembership { + require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) + require.NotNil(t, proof.GetExclusion().GetLeaf()) + require.NotNil(t, proof.GetExclusion().GetPath()) + } else { + require.Equal(t, tc.value, proof.GetExist().GetValue()) + require.NotNil(t, proof.GetExist().GetLeaf()) + require.NotNil(t, proof.GetExist().GetPath()) + } + }) } err = nodeStore.Stop() @@ -107,34 +110,35 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { require.NotNil(t, root) testCases := []struct { + name string key []byte value []byte nonmembership bool valid bool }{ { - // Successfully verifies a membership proof for a key-value stored pair + name: "Successfully verifies a membership proof for a key-value stored pair", key: []byte("foo"), value: []byte("bar"), nonmembership: false, valid: true, }, { - // Successfully verifies a non-membership proof for a key-value pair not stored + name: "Successfully verifies a non-membership proof for a key-value pair not stored", key: []byte("not stored"), value: nil, nonmembership: true, valid: true, }, { - // Fails to verify a membership proof for a key-value pair not stored + name: "Fails to verify a membership proof for a key-value pair not stored", key: []byte("baz"), value: []byte("bar"), nonmembership: false, valid: false, }, { - // Fails to verify a non-membership proof for a key stored in the tree + name: "Fails to verify a non-membership proof for a key stored in the tree", key: []byte("foo"), value: nil, nonmembership: true, @@ -144,33 +148,35 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { proof := new(ics23.CommitmentProof) for _, tc := range testCases { - var err error - if tc.nonmembership { - proof, err = createNonMembershipProof(tree, tc.key) - } else { - proof, err = createMembershipProof(tree, tc.key, tc.value) - } - require.NoError(t, err) - require.NotNil(t, proof) - - if tc.nonmembership { - require.NotNil(t, proof.GetExclusion()) - } else { - require.NotNil(t, proof.GetExist()) - } - - var valid bool - if tc.nonmembership { - valid = VerifyNonMembership(root, proof, tc.key) - } else { - valid = VerifyMembership(root, proof, tc.key, tc.value) - } - - if tc.valid { - require.True(t, valid) - } else { - require.False(t, valid) - } + t.Run(tc.name, func(t *testing.T) { + var err error + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + require.NoError(t, err) + require.NotNil(t, proof) + + if tc.nonmembership { + require.NotNil(t, proof.GetExclusion()) + } else { + require.NotNil(t, proof.GetExist()) + } + + var valid bool + if tc.nonmembership { + valid = VerifyNonMembership(root, proof, tc.key) + } else { + valid = VerifyMembership(root, proof, tc.key, tc.value) + } + + if tc.valid { + require.True(t, valid) + } else { + require.False(t, valid) + } + }) } err = nodeStore.Stop() From 9a7a578edbcf70899d7a88941f58b612178e7e21 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:24:23 +0100 Subject: [PATCH 08/12] Address comments --- ibc/store/proofs_ics23.go | 38 ++++++++--- ibc/store/proofs_ics23_test.go | 114 ++++++++++++++++----------------- 2 files changed, 83 insertions(+), 69 deletions(-) diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 1ae2675e5..1d209f3a6 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -8,11 +8,14 @@ import ( "github.com/pokt-network/smt" ) +// position refers to whether the node is either the left or right child of its parent +// Ref: https://github.com/pokt-network/smt/blob/main/types.go type position int const ( - left position = iota // 0 - right // 1 + left position = iota // 0 + right // 1 + hashSize = 32 ) var ( @@ -27,10 +30,10 @@ var ( }, InnerSpec: &ics23.InnerSpec{ ChildOrder: []int32{0, 1}, - ChildSize: 32, + ChildSize: hashSize, MinPrefixLength: 1, MaxPrefixLength: 1, - EmptyChild: make([]byte, 32), + EmptyChild: make([]byte, hashSize), Hash: ics23.HashOp_SHA256, }, MaxDepth: 256, @@ -39,7 +42,7 @@ var ( innerPrefix = []byte{1} // defaultValue is the default placeholder value in a SparseMerkleTree - defaultValue = make([]byte, 32) + defaultValue = make([]byte, hashSize) ) // VerifyMembership verifies the CommitmentProof provided, checking whether it produces the same @@ -94,7 +97,7 @@ func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) } } -// convertSMPToExistenceProof converts a SparseMerkleProof to an ics23 +// 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) @@ -110,8 +113,8 @@ func convertSMPToExclusionProof(proof *smt.SparseMerkleProof, key []byte) *ics23 actualPath := path[:] actualValue := defaultValue if proof.NonMembershipLeafData != nil { - actualPath = proof.NonMembershipLeafData[1:33] - actualValue = proof.NonMembershipLeafData[33:] + actualPath = proof.NonMembershipLeafData[1 : 1+hashSize] // len(prefix): len(prefix) + hashSize + actualValue = proof.NonMembershipLeafData[1+hashSize:] } return &ics23.CommitmentProof{ Proof: &ics23.CommitmentProof_Exclusion{ @@ -134,9 +137,11 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) if getPathBit(path, len(sideNodes)-1-i) == left { + // 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{ @@ -149,9 +154,22 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { return steps } -// getPathBit determines whether the hash of a node at a certain depth in the tree is the -// left or the right child of its parent +// getPathBit takes the hash of a key (the path) and a position (depth) and returns whether at +// that position in the tree the path goes left or right. This is used to determine the order +// of child nodes and the order in which they are hashed when verifying proofs. +// Ref: https://github.com/pokt-network/smt/blob/main/utils.go func getPathBit(data []byte, position int) position { + // get the byte at the position and then left shift one by the offset of the position + // from the leftmost bit in the byte. Check if the bitwise and is the same + // Path: []byte{ {0 1 0 1 1 0 1 0}, {0 1 1 0 1 1 0 1}, {1 0 0 1 0 0 1 0} } (length = 24 bits / 3 bytes) + // Position: 13 - 13/8=1 + // Path[1] = {0 1 1 0 1 1 0 1} + // uint(13)%8 = 5, 8-1-5=2 + // 00000001 << 2 = 00000100 + // {0 1 1 0 1 1 0 1} + // & {0 0 0 0 0 1 0 0} + // = {0 0 0 0 0 1 0 0} + // > 0 so Path is on the right at position 13 if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 { return right } diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go index cff486b9d..0695ac681 100644 --- a/ibc/store/proofs_ics23_test.go +++ b/ibc/store/proofs_ics23_test.go @@ -27,43 +27,43 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { require.NoError(t, err) testCases := []struct { - name string - key []byte - value []byte - nonmembership bool - fails bool - expected error + name string + key []byte + value []byte + isNonMembership bool + fails bool + expected error }{ { - name: "Successfully generates a membership proof for a key stored", - key: []byte("foo"), - value: []byte("bar"), - nonmembership: false, - fails: false, - expected: nil, + name: "Successfully generates a membership proof for a key stored", + key: []byte("foo"), + value: []byte("bar"), + isNonMembership: false, + fails: false, + expected: nil, }, { - name: "Successfully generates a non-membership proof for a key not stored", - key: []byte("baz"), - value: []byte("testValue2"), // unrelated leaf data - nonmembership: true, - fails: false, - expected: nil, + name: "Successfully generates a non-membership proof for a key not stored", + key: []byte("baz"), + value: []byte("testValue2"), // unrelated leaf data + isNonMembership: true, + fails: false, + expected: nil, }, { - name: "Successfully generates a non-membership proof for an unset nil key", - key: nil, - value: []byte("foo"), // unrelated leaf data - nonmembership: true, - fails: false, - expected: nil, + name: "Successfully generates a non-membership proof for an unset nil key", + key: nil, + value: []byte("foo"), // unrelated leaf data + isNonMembership: true, + fails: false, + expected: nil, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var proof *ics23.CommitmentProof - if tc.nonmembership { + if tc.isNonMembership { proof, err = createNonMembershipProof(tree, tc.key) } else { proof, err = createMembershipProof(tree, tc.key, tc.value) @@ -75,7 +75,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { } require.NoError(t, err) require.NotNil(t, proof) - if tc.nonmembership { + if tc.isNonMembership { require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) require.NotNil(t, proof.GetExclusion().GetLeaf()) require.NotNil(t, proof.GetExclusion().GetPath()) @@ -110,39 +110,39 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { require.NotNil(t, root) testCases := []struct { - name string - key []byte - value []byte - nonmembership bool - valid bool + name string + key []byte + value []byte + isNonMembership bool + valid bool }{ { - name: "Successfully verifies a membership proof for a key-value stored pair", - key: []byte("foo"), - value: []byte("bar"), - nonmembership: false, - valid: true, + name: "Successfully verifies a membership proof for a key-value stored pair", + key: []byte("foo"), + value: []byte("bar"), + isNonMembership: false, + valid: true, }, { - name: "Successfully verifies a non-membership proof for a key-value pair not stored", - key: []byte("not stored"), - value: nil, - nonmembership: true, - valid: true, + name: "Successfully verifies a non-membership proof for a key-value pair not stored", + key: []byte("not stored"), + value: nil, + isNonMembership: true, + valid: true, }, { - name: "Fails to verify a membership proof for a key-value pair not stored", - key: []byte("baz"), - value: []byte("bar"), - nonmembership: false, - valid: false, + name: "Fails to verify a membership proof for a key-value pair not stored", + key: []byte("baz"), + value: []byte("bar"), + isNonMembership: false, + valid: false, }, { - name: "Fails to verify a non-membership proof for a key stored in the tree", - key: []byte("foo"), - value: nil, - nonmembership: true, - valid: false, + name: "Fails to verify a non-membership proof for a key stored in the tree", + key: []byte("foo"), + value: nil, + isNonMembership: true, + valid: false, }, } @@ -150,7 +150,7 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var err error - if tc.nonmembership { + if tc.isNonMembership { proof, err = createNonMembershipProof(tree, tc.key) } else { proof, err = createMembershipProof(tree, tc.key, tc.value) @@ -158,24 +158,20 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { require.NoError(t, err) require.NotNil(t, proof) - if tc.nonmembership { + if tc.isNonMembership { require.NotNil(t, proof.GetExclusion()) } else { require.NotNil(t, proof.GetExist()) } var valid bool - if tc.nonmembership { + if tc.isNonMembership { valid = VerifyNonMembership(root, proof, tc.key) } else { valid = VerifyMembership(root, proof, tc.key, tc.value) } - if tc.valid { - require.True(t, valid) - } else { - require.False(t, valid) - } + require.Equal(t, tc.valid, valid) }) } From 327ed964fe66b2d7f016d4203e4bb6329ba721df Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:11:34 +0100 Subject: [PATCH 09/12] Address comments --- ibc/docs/README.md | 1 + ibc/docs/ics23.md | 35 ++++++++++++++++++++++++++++++---- ibc/store/proofs_ics23.go | 2 ++ ibc/store/proofs_ics23_test.go | 4 ++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 44c1ac1d6..01a04c4ed 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -116,3 +116,4 @@ See: [ICS-23](./ics23.md) for more details on the specifics of the ICS-23 implem [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 diff --git a/ibc/docs/ics23.md b/ibc/docs/ics23.md index 6f6839132..b6c5ff943 100644 --- a/ibc/docs/ics23.md +++ b/ibc/docs/ics23.md @@ -74,13 +74,40 @@ Membership proofs are verified as follows: Non-membership proofs are verified as follows: -1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, 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 to generate the leaf node hash. +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 - - As the non-membership proof uses the `ActualValueHash` field to generate the leaf node hash, the non-membership proof is actually proving membership of either a placeholder key or an unrelated key in the tree. - - This means that if the root hash computed and the one provided are equal then the key we were looking for was not in the tree. If they are not equal the proof is invalid and the key is in the tree. + - 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). diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 1d209f3a6..a7930dd1c 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -9,6 +9,7 @@ import ( ) // 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 type position int @@ -154,6 +155,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { return steps } +// TECHDEBT(smt #14): Use exposed method from SMT library // getPathBit takes the hash of a key (the path) and a position (depth) and returns whether at // that position in the tree the path goes left or right. This is used to determine the order // of child nodes and the order in which they are hashed when verifying proofs. diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go index 0695ac681..3dff554e4 100644 --- a/ibc/store/proofs_ics23_test.go +++ b/ibc/store/proofs_ics23_test.go @@ -16,7 +16,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) require.NotNil(t, tree) - // Set a value in the store + // Prepare a tree with a predetermined set of key-value pairs err := tree.Update([]byte("foo"), []byte("bar")) require.NoError(t, err) err = tree.Update([]byte("bar"), []byte("foo")) @@ -96,7 +96,7 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) require.NotNil(t, tree) - // Set a value in the store + // Prepare a tree with a predetermined set of key-value pairs err := tree.Update([]byte("foo"), []byte("bar")) require.NoError(t, err) err = tree.Update([]byte("bar"), []byte("foo")) From bc5a878ab7f98f3c9004e963dbb2096975269375 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:31:09 +0100 Subject: [PATCH 10/12] add isLeft helper and use smt.GetPathBit() --- ibc/store/proofs_ics23.go | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index a7930dd1c..f07c9d545 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -137,7 +137,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { for i := 0; i < len(sideNodes); i++ { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) - if getPathBit(path, len(sideNodes)-1-i) == left { + 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]...) @@ -155,25 +155,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { return steps } -// TECHDEBT(smt #14): Use exposed method from SMT library -// getPathBit takes the hash of a key (the path) and a position (depth) and returns whether at -// that position in the tree the path goes left or right. This is used to determine the order -// of child nodes and the order in which they are hashed when verifying proofs. -// Ref: https://github.com/pokt-network/smt/blob/main/utils.go -func getPathBit(data []byte, position int) position { - // get the byte at the position and then left shift one by the offset of the position - // from the leftmost bit in the byte. Check if the bitwise and is the same - // Path: []byte{ {0 1 0 1 1 0 1 0}, {0 1 1 0 1 1 0 1}, {1 0 0 1 0 0 1 0} } (length = 24 bits / 3 bytes) - // Position: 13 - 13/8=1 - // Path[1] = {0 1 1 0 1 1 0 1} - // uint(13)%8 = 5, 8-1-5=2 - // 00000001 << 2 = 00000100 - // {0 1 1 0 1 1 0 1} - // & {0 0 0 0 0 1 0 0} - // = {0 0 0 0 0 1 0 0} - // > 0 so Path is on the right at position 13 - if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 { - return right - } - return left +// 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 } From 2f67dcd61c61fb92e1beceabd7f1aeb8febc98c8 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:40:00 +0100 Subject: [PATCH 11/12] go.mod --- go.mod | 2 +- go.sum | 7 +++++-- ibc/store/proofs_ics23.go | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 28281e5ed..8a4e44748 100644 --- a/go.mod +++ b/go.mod @@ -41,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.0 github.com/quasilyte/go-ruleguard/dsl v0.3.21 github.com/regen-network/gocuke v0.6.2 github.com/rs/zerolog v1.27.0 diff --git a/go.sum b/go.sum index 271c59bc7..eb0a2dba4 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-ecvrf v0.0.1 h1:wv45+kZ0mG4G9oSTMjAlbgKqa4tPbNr4WLoCWqz5/bo= github.com/ProtonMail/go-ecvrf v0.0.1/go.mod h1:fhZbiRYn62/JGnBG2NGwCx0oT+gr/+I5R/hwiyAFpAU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -150,6 +152,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= @@ -702,8 +705,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.0 h1:W4NS6L0N5N0sDRq+2MskbWtCTnBiwzsgIJHxlHbwbgk= +github.com/pokt-network/smt v0.6.0/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= diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index f07c9d545..f07367224 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -68,7 +68,7 @@ func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentP if err != nil { return nil, coreTypes.ErrCreatingProof(err) } - return convertSMPToExistenceProof(&proof, key, value), nil + 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 @@ -78,7 +78,7 @@ func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof return nil, coreTypes.ErrCreatingProof(err) } - return convertSMPToExclusionProof(&proof, key), nil + return convertSMPToExclusionProof(proof, key), nil } // convertSMPToExistenceProof converts a SparseMerkleProof to an ics23 From 17e24199b5421e305c8cbaf4a83fa5b7d13bafea Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:46:20 +0100 Subject: [PATCH 12/12] Fix SMT repo --- go.mod | 2 +- go.sum | 7 ++----- ibc/store/proofs_ics23.go | 6 ++---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 8a4e44748..1be7fb2dd 100644 --- a/go.mod +++ b/go.mod @@ -41,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.6.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 diff --git a/go.sum b/go.sum index eb0a2dba4..925ffa331 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,6 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-ecvrf v0.0.1 h1:wv45+kZ0mG4G9oSTMjAlbgKqa4tPbNr4WLoCWqz5/bo= github.com/ProtonMail/go-ecvrf v0.0.1/go.mod h1:fhZbiRYn62/JGnBG2NGwCx0oT+gr/+I5R/hwiyAFpAU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -152,7 +150,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= @@ -705,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.6.0 h1:W4NS6L0N5N0sDRq+2MskbWtCTnBiwzsgIJHxlHbwbgk= -github.com/pokt-network/smt v0.6.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= diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index f07367224..4f0d43fba 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -11,11 +11,9 @@ import ( // 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 -type position int - const ( - left position = iota // 0 - right // 1 + left int = iota // 0 + right // 1 hashSize = 32 )