Skip to content

Commit

Permalink
VerifyItem takes no index; Return keys/values in range; Fix
Browse files Browse the repository at this point in the history
0.9.1 -- compute hash from rangeproof

Require Verify() before Verify*()

Review fixes from cosmos#75

First commit for generalized merkle proofs

Fix imports

Bump version to 0.9.3

Return nil on empty tree - bump version to 0.10.0

Add OpDecoders

fix test

finalize rebase

fix test
  • Loading branch information
jaekwon authored and mossid committed Aug 30, 2018
1 parent 5ee244d commit d6bb142
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ IMPROVEMENTS

- Change tendermint dep to ^v0.22.0 (#91)

## 0.10.0 (July 11, 2018)

BREAKING CHANGES

- getRangeProof and Get\[Versioned\]\[Range\]WithProof return nil proof/error if tree is empty.

## 0.9.2 (July 3, 2018)

IMPROVEMENTS
Expand Down
1 change: 1 addition & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[[constraint]]
version = "=0.10.1"
name = "github.com/tendermint/go-amino"
version = "0.10.1"

[[constraint]]
version = "^0.22.0"
Expand Down
16 changes: 13 additions & 3 deletions basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,14 @@ func TestProof(t *testing.T) {
func TestTreeProof(t *testing.T) {
db := db.NewMemDB()
tree := NewMutableTree(db, 100)
assert.Equal(t, tree.Hash(), []byte(nil))

// should get false for proof with nil root
_, _, err := tree.GetWithProof([]byte("foo"))
assert.Error(t, err)
value, proof, err := tree.GetWithProof([]byte("foo"))
assert.Nil(t, value)
assert.Nil(t, proof)
assert.Error(t, proof.Verify([]byte(nil)))
assert.NoError(t, err)

// insert lots of info and store the bytes
keys := make([][]byte, 200)
Expand All @@ -436,9 +440,15 @@ func TestTreeProof(t *testing.T) {
keys[i] = []byte(key)
}

tree.SaveVersion()

// query random key fails
_, _, err = tree.GetWithProof([]byte("foo"))
value, proof, err = tree.GetWithProof([]byte("foo"))
assert.Nil(t, value)
assert.NotNil(t, proof)
assert.NoError(t, err)
assert.NoError(t, proof.Verify(tree.Hash()))
assert.NoError(t, proof.VerifyAbsence([]byte("foo")))

// valid proof for real keys
root := tree.WorkingHash()
Expand Down
3 changes: 0 additions & 3 deletions proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ var (

// ErrInvalidRoot is returned when the root passed in does not match the proof's.
ErrInvalidRoot = fmt.Errorf("invalid root")

// ErrNilRoot is returned when the root of the tree is nil.
ErrNilRoot = fmt.Errorf("tree root is nil")
)

//----------------------------------------
Expand Down
87 changes: 87 additions & 0 deletions proof_iavl_absence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package iavl

import (
"fmt"

"github.com/tendermint/tendermint/crypto/merkle"
cmn "github.com/tendermint/tmlibs/common"
)

const ProofOpIAVLAbsence = "iavl:a"

// IAVLAbsenceOp takes a key as its only argument
//
// If the produced root hash matches the expected hash, the proof
// is good.
type IAVLAbsenceOp struct {
// Encoded in ProofOp.Key.
key []byte

// To encode in ProofOp.Data.
// Proof is nil for an empty tree.
// The hash of an empty tree is nil.
Proof *RangeProof `json:"proof"`
}

var _ merkle.ProofOperator = IAVLAbsenceOp{}

func NewIAVLAbsenceOp(key []byte, proof *RangeProof) IAVLAbsenceOp {
return IAVLAbsenceOp{
key: key,
Proof: proof,
}
}

func IAVLAbsenceOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
if pop.Type != ProofOpIAVLAbsence {
return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVLAbsence)
}
var op IAVLAbsenceOp // a bit strange as we'll discard this, but it works.
err := cdc.UnmarshalBinary(pop.Data, &op)
if err != nil {
return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into IAVLAbsenceOp")
}
return NewIAVLAbsenceOp(pop.Key, op.Proof), nil
}

func (op IAVLAbsenceOp) ProofOp() merkle.ProofOp {
bz := cdc.MustMarshalBinary(op)
return merkle.ProofOp{
Type: ProofOpIAVLAbsence,
Key: op.key,
Data: bz,
}
}

func (op IAVLAbsenceOp) String() string {
return fmt.Sprintf("IAVLAbsenceOp{%v}", op.GetKey())
}

func (op IAVLAbsenceOp) Run(args [][]byte) ([][]byte, error) {
if len(args) != 0 {
return nil, cmn.NewError("expected 0 args, got %v", len(args))
}
// If the tree is nil, the proof is nil, and all keys are absent.
if op.Proof == nil {
return [][]byte{[]byte(nil)}, nil
}
// Compute the root hash and assume it is valid.
// The caller checks the ultimate root later.
root := op.Proof.ComputeRootHash()
err := op.Proof.Verify(root)
if err != nil {
return nil, cmn.ErrorWrap(err, "computing root hash")
}
// XXX What is the encoding for keys?
// We should decode the key depending on whether it's a string or hex,
// maybe based on quotes and 0x prefix?
err = op.Proof.VerifyAbsence([]byte(op.key))
if err != nil {
return nil, cmn.ErrorWrap(err, "verifying absence")
}
return [][]byte{root}, nil
}

func (op IAVLAbsenceOp) GetKey() []byte {
return op.key
}
86 changes: 86 additions & 0 deletions proof_iavl_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package iavl

import (
"fmt"

"github.com/tendermint/tendermint/crypto/merkle"
cmn "github.com/tendermint/tmlibs/common"
)

const ProofOpIAVLValue = "iavl:v"

// IAVLValueOp takes a key and a single value as argument and
// produces the root hash.
//
// If the produced root hash matches the expected hash, the proof
// is good.
type IAVLValueOp struct {
// Encoded in ProofOp.Key.
key []byte

// To encode in ProofOp.Data.
// Proof is nil for an empty tree.
// The hash of an empty tree is nil.
Proof *RangeProof `json:"proof"`
}

var _ merkle.ProofOperator = IAVLValueOp{}

func NewIAVLValueOp(key []byte, proof *RangeProof) IAVLValueOp {
return IAVLValueOp{
key: key,
Proof: proof,
}
}

func IAVLValueOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
if pop.Type != ProofOpIAVLValue {
return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVLValue)
}
var op IAVLValueOp // a bit strange as we'll discard this, but it works.
err := cdc.UnmarshalBinary(pop.Data, &op)
if err != nil {
return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into IAVLValueOp")
}
return NewIAVLValueOp(pop.Key, op.Proof), nil
}

func (op IAVLValueOp) ProofOp() merkle.ProofOp {
bz := cdc.MustMarshalBinary(op)
return merkle.ProofOp{
Type: ProofOpIAVLValue,
Key: op.key,
Data: bz,
}
}

func (op IAVLValueOp) String() string {
return fmt.Sprintf("IAVLValueOp{%v}", op.GetKey())
}

func (op IAVLValueOp) Run(args [][]byte) ([][]byte, error) {
if len(args) != 1 {
return nil, cmn.NewError("Value size is not 1")
}
value := args[0]

// Compute the root hash and assume it is valid.
// The caller checks the ultimate root later.
root := op.Proof.ComputeRootHash()
err := op.Proof.Verify(root)
if err != nil {
return nil, cmn.ErrorWrap(err, "computing root hash")
}
// XXX What is the encoding for keys?
// We should decode the key depending on whether it's a string or hex,
// maybe based on quotes and 0x prefix?
err = op.Proof.VerifyItem([]byte(op.key), value)
if err != nil {
return nil, cmn.ErrorWrap(err, "verifying value")
}
return [][]byte{root}, nil
}

func (op IAVLValueOp) GetKey() []byte {
return op.key
}
Loading

0 comments on commit d6bb142

Please sign in to comment.