Skip to content

Commit

Permalink
feat: ADR-040: ICS-23 proofs for SMT store (#10015)
Browse files Browse the repository at this point in the history
## Description

Implements [ICS-23](https://github.com/cosmos/ibc/tree/master/spec/core/ics-023-vector-commitments) conformant proofs for the SMT-based KV store and defines the proof spec as part of [ADR-040](https://github.com/cosmos/cosmos-sdk/blob/eb7d939f86c6cd7b4218492364cdda3f649f06b5/docs/architecture/adr-040-storage-and-smt-state-commitments.md). 

Closes: vulcanize#8

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - n/a
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [x] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
roysc authored Feb 22, 2022
1 parent 56af6a7 commit e66b8ef
Show file tree
Hide file tree
Showing 22 changed files with 552 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (gov) [\#11036](https://github.com/cosmos/cosmos-sdk/pull/11036) Add in-place migrations for 0.43->0.46. Add a `migrate v0.46` CLI command for v0.43->0.46 JSON genesis migration.
* [\#11006](https://github.com/cosmos/cosmos-sdk/pull/11006) Add `debug pubkey-raw` command to allow inspecting of pubkeys in legacy bech32 format
* (x/authz) [\#10714](https://github.com/cosmos/cosmos-sdk/pull/10714) Add support for pruning expired authorizations
* [\#10015](https://github.com/cosmos/cosmos-sdk/pull/10015) ADR-040: ICS-23 proofs for SMT store

### API Breaking Changes

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/btcsuite/btcd v0.22.0-beta
github.com/cockroachdb/apd/v2 v2.0.2
github.com/coinbase/rosetta-sdk-go v0.7.2
github.com/confio/ics23/go v0.6.6
github.com/confio/ics23/go v0.7.0-rc
github.com/cosmos/btcutil v1.0.4
github.com/cosmos/cosmos-proto v1.0.0-alpha7
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha4
Expand All @@ -18,6 +18,7 @@ require (
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/iavl v0.17.3
github.com/cosmos/ledger-cosmos-go v0.11.1
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/gogo/gateway v1.1.0
github.com/gogo/protobuf v1.3.3
github.com/golang/mock v1.6.0
Expand Down Expand Up @@ -72,7 +73,6 @@ require (
github.com/cosmos/ledger-go v0.9.2 // indirect
github.com/danieljoos/wincred v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,9 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coinbase/rosetta-sdk-go v0.7.2 h1:uCNrASIyt7rV9bA3gzPG3JDlxVP5v/zLgi01GWngncM=
github.com/coinbase/rosetta-sdk-go v0.7.2/go.mod h1:wk9dvjZFSZiWSNkFuj3dMleTA1adLFotg5y71PhqKB4=
github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8=
github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=
github.com/confio/ics23/go v0.7.0-rc h1:cH2I3xkPE6oD4tP5pmZDAfYq8V7VeXCr98X1MpARTaI=
github.com/confio/ics23/go v0.7.0-rc/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=
github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
Expand Down
8 changes: 4 additions & 4 deletions store/internal/proofs/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
)

/*
CreateMembershipProof will produce a CommitmentProof that the given key (and queries value) exists in the iavl tree.
CreateMembershipProof will produce a CommitmentProof that the given key (and queries value) exists in the map.
If the key doesn't exist in the tree, this will return an error.
*/
func CreateMembershipProof(data map[string][]byte, key []byte) (*ics23.CommitmentProof, error) {
Expand All @@ -36,7 +36,7 @@ func CreateMembershipProof(data map[string][]byte, key []byte) (*ics23.Commitmen
}

/*
CreateNonMembershipProof will produce a CommitmentProof that the given key doesn't exist in the iavl tree.
CreateNonMembershipProof will produce a CommitmentProof that the given key doesn't exist in the map.
If the key exists in the tree, this will return an error.
*/
func CreateNonMembershipProof(data map[string][]byte, key []byte) (*ics23.CommitmentProof, error) {
Expand Down Expand Up @@ -94,8 +94,8 @@ func createExistenceProof(data map[string][]byte, key []byte) (*ics23.ExistenceP
return nil, fmt.Errorf("cannot make existence proof if key is not in map")
}

_, ics23, _ := sdkmaps.ProofsFromMap(data)
proof := ics23[string(key)]
_, proofs, _ := sdkmaps.ProofsFromMap(data)
proof := proofs[string(key)]
if proof == nil {
return nil, fmt.Errorf("returned no proof for key")
}
Expand Down
8 changes: 0 additions & 8 deletions store/rootmulti/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/tendermint/tendermint/crypto/merkle"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/store/v2/smt"
)

// RequireProof returns whether proof is required for the subpath.
Expand All @@ -26,10 +25,3 @@ func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt.RegisterOpDecoder(storetypes.ProofOpSimpleMerkleCommitment, storetypes.CommitmentOpDecoder)
return
}

// SMTProofRuntime returns a ProofRuntime for sparse merkle trees.
func SMTProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(smt.ProofType, smt.ProofDecoder)
return prt
}
26 changes: 3 additions & 23 deletions store/types/commit_info.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package types

import (
fmt "fmt"

ics23 "github.com/confio/ics23/go"
tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto"

sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps"
sdkproofs "github.com/cosmos/cosmos-sdk/store/internal/proofs"
)

// GetHash returns the GetHash from the CommitID.
Expand Down Expand Up @@ -42,27 +38,11 @@ func (ci CommitInfo) Hash() []byte {
}

func (ci CommitInfo) ProofOp(storeName string) tmcrypto.ProofOp {
cmap := ci.toMap()
_, proofs, _ := sdkmaps.ProofsFromMap(cmap)

proof := proofs[storeName]
if proof == nil {
panic(fmt.Sprintf("ProofOp for %s but not registered store name", storeName))
}

// convert merkle.SimpleProof to CommitmentProof
existProof, err := sdkproofs.ConvertExistenceProof(proof, []byte(storeName), cmap[storeName])
ret, err := ProofOpFromMap(ci.toMap(), storeName)
if err != nil {
panic(fmt.Errorf("could not convert simple proof to existence proof: %w", err))
panic(err)
}

commitmentProof := &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exist{
Exist: existProof,
},
}

return NewSimpleMerkleCommitmentOp([]byte(storeName), commitmentProof).ProofOp()
return ret
}

func (ci CommitInfo) CommitID() CommitID {
Expand Down
45 changes: 44 additions & 1 deletion store/types/proof.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package types

import (
"fmt"

ics23 "github.com/confio/ics23/go"
"github.com/tendermint/tendermint/crypto/merkle"
tmmerkle "github.com/tendermint/tendermint/proto/tendermint/crypto"

sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps"
sdkproofs "github.com/cosmos/cosmos-sdk/store/internal/proofs"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

const (
ProofOpIAVLCommitment = "ics23:iavl"
ProofOpSimpleMerkleCommitment = "ics23:simple"
ProofOpSMTCommitment = "ics23:smt"
)

// CommitmentOp implements merkle.ProofOperator by wrapping an ics23 CommitmentProof
Expand Down Expand Up @@ -46,6 +51,15 @@ func NewSimpleMerkleCommitmentOp(key []byte, proof *ics23.CommitmentProof) Commi
}
}

func NewSmtCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp {
return CommitmentOp{
Type: ProofOpSMTCommitment,
Spec: ics23.SmtSpec,
Key: key,
Proof: proof,
}
}

// CommitmentOpDecoder takes a merkle.ProofOp and attempts to decode it into a CommitmentOp ProofOperator
// The proofOp.Data is just a marshalled CommitmentProof. The Key of the CommitmentOp is extracted
// from the unmarshalled proof.
Expand All @@ -56,8 +70,10 @@ func CommitmentOpDecoder(pop tmmerkle.ProofOp) (merkle.ProofOperator, error) {
spec = ics23.IavlSpec
case ProofOpSimpleMerkleCommitment:
spec = ics23.TendermintSpec
case ProofOpSMTCommitment:
spec = ics23.SmtSpec
default:
return nil, sdkerrors.Wrapf(ErrInvalidProof, "unexpected ProofOp.Type; got %s, want supported ics23 subtypes 'ProofOpIAVLCommitment' or 'ProofOpSimpleMerkleCommitment'", pop.Type)
return nil, sdkerrors.Wrapf(ErrInvalidProof, "unexpected ProofOp.Type; got %s, want supported ics23 subtypes 'ProofOpSimpleMerkleCommitment', 'ProofOpIAVLCommitment', or 'ProofOpSMTCommitment'", pop.Type)
}

proof := &ics23.CommitmentProof{}
Expand Down Expand Up @@ -129,3 +145,30 @@ func (op CommitmentOp) ProofOp() tmmerkle.ProofOp {
Data: bz,
}
}

// ProofOpFromMap generates a single proof from a map and converts it to a ProofOp.
func ProofOpFromMap(cmap map[string][]byte, storeName string) (ret tmmerkle.ProofOp, err error) {
_, proofs, _ := sdkmaps.ProofsFromMap(cmap)

proof := proofs[storeName]
if proof == nil {
err = fmt.Errorf("ProofOp for %s but not registered store name", storeName)
return
}

// convert merkle.SimpleProof to CommitmentProof
existProof, err := sdkproofs.ConvertExistenceProof(proof, []byte(storeName), cmap[storeName])
if err != nil {
err = fmt.Errorf("could not convert simple proof to existence proof: %w", err)
return
}

commitmentProof := &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exist{
Exist: existProof,
},
}

ret = NewSimpleMerkleCommitmentOp([]byte(storeName), commitmentProof).ProofOp()
return
}
2 changes: 1 addition & 1 deletion store/v2/multi/cache_store.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package root
package multi

import (
"github.com/cosmos/cosmos-sdk/store/cachekv"
Expand Down
2 changes: 1 addition & 1 deletion store/v2/multi/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
// of a key's (non)existence within the substore SMT, and a proof of the substore's existence within the
// MultiStore (using the Merkle map proof spec (TendermintSpec)).

package root
package multi
52 changes: 52 additions & 0 deletions store/v2/multi/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package multi

import (
"crypto/sha256"

"github.com/tendermint/tendermint/crypto/merkle"
tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto"

types "github.com/cosmos/cosmos-sdk/store/v2"
"github.com/cosmos/cosmos-sdk/store/v2/smt"
)

// DefaultProofRuntime returns a ProofRuntime supporting SMT and simple merkle proofs.
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(types.ProofOpSMTCommitment, types.CommitmentOpDecoder)
prt.RegisterOpDecoder(types.ProofOpSimpleMerkleCommitment, types.CommitmentOpDecoder)
return prt
}

// Prove commitment of key within an smt store and return ProofOps
func proveKey(s *smt.Store, key []byte) (*tmcrypto.ProofOps, error) {
var ret tmcrypto.ProofOps
keyProof, err := s.GetProofICS23(key)
if err != nil {
return nil, err
}
hkey := sha256.Sum256(key)
ret.Ops = append(ret.Ops, types.NewSmtCommitmentOp(hkey[:], keyProof).ProofOp())
return &ret, nil
}

// GetProof returns ProofOps containing: a proof for the given key within this substore;
// and a proof of the substore's existence within the MultiStore.
func (s *viewSubstore) GetProof(key []byte) (*tmcrypto.ProofOps, error) {
ret, err := proveKey(s.stateCommitmentStore, key)
if err != nil {
return nil, err
}

// Prove commitment of substore within root store
storeHashes, err := s.root.getMerkleRoots()
if err != nil {
return nil, err
}
storeProof, err := types.ProofOpFromMap(storeHashes, s.name)
if err != nil {
return nil, err
}
ret.Ops = append(ret.Ops, storeProof)
return ret, nil
}
Loading

0 comments on commit e66b8ef

Please sign in to comment.