Skip to content

Commit

Permalink
Revert "fix: remove RangeProofs (cosmos#586)"
Browse files Browse the repository at this point in the history
  • Loading branch information
ulbqb committed Jun 12, 2023
1 parent 3e88a07 commit ea54cd6
Show file tree
Hide file tree
Showing 17 changed files with 3,154 additions and 182 deletions.
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

- [#599](https://github.com/cosmos/iavl/pull/599) Populate ImmutableTree creation in copy function with missing field
- [#589](https://github.com/cosmos/iavl/pull/589) Wrap `tree.addUnsavedRemoval()` with missing `if !tree.skipFastStorageUpgrade` statement
- [#586](https://github.com/cosmos/iavl/pull/586) Remove the `RangeProof` and refactor the ics23_proof to use the internal methods.

## 0.19.3 (October 8, 2022)

Expand Down
46 changes: 27 additions & 19 deletions basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,17 +513,13 @@ func TestProof(t *testing.T) {

// Now for each item, construct a proof and verify
tree.Iterate(func(key []byte, value []byte) bool {
proof, err := tree.GetMembershipProof(key)
value2, proof, err := tree.GetWithProof(key)
assert.NoError(t, err)
assert.Equal(t, value, proof.GetExist().Value)
res, err := tree.VerifyMembership(proof, key)
assert.NoError(t, err)
value2, err := tree.ImmutableTree.Get(key)
assert.NoError(t, err)
if value2 != nil {
assert.True(t, res)
} else {
assert.False(t, res)
assert.Equal(t, value, value2)
if assert.NotNil(t, proof) {
hash, err := tree.WorkingHash()
require.NoError(t, err)
verifyProof(t, proof, hash)
}
return false
})
Expand All @@ -538,8 +534,11 @@ func TestTreeProof(t *testing.T) {
assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", hex.EncodeToString(hash))

// should get false for proof with nil root
_, err = tree.GetProof([]byte("foo"))
require.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 @@ -552,18 +551,27 @@ func TestTreeProof(t *testing.T) {
tree.SaveVersion()

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

// valid proof for real keys
root, err := tree.WorkingHash()
assert.NoError(t, err)
for _, key := range keys {
proof, err := tree.GetMembershipProof(key)
value, proof, err := tree.GetWithProof(key)
if assert.NoError(t, err) {
require.Nil(t, err, "Failed to read proof from bytes: %v", err)
assert.Equal(t, key, proof.GetExist().Value)
res, err := tree.VerifyMembership(proof, key)
require.NoError(t, err)
require.True(t, res)
assert.Equal(t, key, value)
err := proof.Verify(root)
assert.NoError(t, err, "#### %v", proof.String())
err = proof.VerifyItem(key, key)
assert.NoError(t, err, "#### %v", proof.String())
}
}
}
9 changes: 4 additions & 5 deletions mutable_tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,17 @@ func TestDelete(t *testing.T) {

require.NoError(t, tree.DeleteVersion(version))

proof, err := tree.GetVersionedProof([]byte("k1"), version)
require.EqualError(t, err, ErrVersionDoesNotExist.Error())
require.Nil(t, proof)
k1Value, _, _ := tree.GetVersionedWithProof([]byte("k1"), version)
require.Nil(t, k1Value)

key := tree.ndb.rootKey(version)
err = tree.ndb.db.Set(key, hash)
require.NoError(t, err)
tree.versions[version] = true

proof, err = tree.GetVersionedProof([]byte("k1"), version)
k1Value, _, err = tree.GetVersionedWithProof([]byte("k1"), version)
require.Nil(t, err)
require.Equal(t, 0, bytes.Compare([]byte("Fred"), proof.GetExist().Value))
require.Equal(t, 0, bytes.Compare([]byte("Fred"), k1Value))
}

func TestGetRemove(t *testing.T) {
Expand Down
51 changes: 51 additions & 0 deletions proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"bytes"
"crypto/sha256"
"fmt"
"math"
"sync"

"github.com/pkg/errors"

hexbytes "github.com/cosmos/iavl/internal/bytes"
"github.com/cosmos/iavl/internal/encoding"
iavlproto "github.com/cosmos/iavl/proto"
)

var bufPool = &sync.Pool{
Expand Down Expand Up @@ -107,6 +109,34 @@ func (pin ProofInnerNode) Hash(childHash []byte) ([]byte, error) {
return hasher.Sum(nil), nil
}

// toProto converts the inner node proof to Protobuf, for use in ProofOps.
func (pin ProofInnerNode) toProto() *iavlproto.ProofInnerNode {
return &iavlproto.ProofInnerNode{
Height: int32(pin.Height),
Size_: pin.Size,
Version: pin.Version,
Left: pin.Left,
Right: pin.Right,
}
}

// proofInnerNodeFromProto converts a Protobuf ProofInnerNode to a ProofInnerNode.
func proofInnerNodeFromProto(pbInner *iavlproto.ProofInnerNode) (ProofInnerNode, error) {
if pbInner == nil {
return ProofInnerNode{}, errors.New("inner node cannot be nil")
}
if pbInner.Height > math.MaxInt8 || pbInner.Height < math.MinInt8 {
return ProofInnerNode{}, fmt.Errorf("height must fit inside an int8, got %v", pbInner.Height)
}
return ProofInnerNode{
Height: int8(pbInner.Height),
Size: pbInner.Size_,
Version: pbInner.Version,
Left: pbInner.Left,
Right: pbInner.Right,
}, nil
}

//----------------------------------------

type ProofLeafNode struct {
Expand Down Expand Up @@ -163,6 +193,27 @@ func (pln ProofLeafNode) Hash() ([]byte, error) {
return hasher.Sum(nil), nil
}

// toProto converts the leaf node proof to Protobuf, for use in ProofOps.
func (pln ProofLeafNode) toProto() *iavlproto.ProofLeafNode {
return &iavlproto.ProofLeafNode{
Key: pln.Key,
ValueHash: pln.ValueHash,
Version: pln.Version,
}
}

// proofLeafNodeFromProto converts a Protobuf ProofLeadNode to a ProofLeafNode.
func proofLeafNodeFromProto(pbLeaf *iavlproto.ProofLeafNode) (ProofLeafNode, error) {
if pbLeaf == nil {
return ProofLeafNode{}, errors.New("leaf node cannot be nil")
}
return ProofLeafNode{
Key: pbLeaf.Key,
ValueHash: pbLeaf.ValueHash,
Version: pbLeaf.Version,
}, nil
}

//----------------------------------------

// If the key does not exist, returns the path to the next leaf left of key (w/
Expand Down
106 changes: 106 additions & 0 deletions proof_forgery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package iavl_test

import (
"encoding/hex"
"math/rand"
"strings"
"testing"

"github.com/cosmos/iavl"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/tmhash"
db "github.com/tendermint/tm-db"
)

func TestProofFogery(t *testing.T) {
source := rand.NewSource(0)
r := rand.New(source)
cacheSize := 0
tree, err := iavl.NewMutableTreeWithOpts(db.NewMemDB(), cacheSize, nil, false)
require.NoError(t, err)

// two keys only
keys := []byte{0x11, 0x32}
values := make([][]byte, len(keys))
// make random values and insert into tree
for i, ikey := range keys {
key := []byte{ikey}
v := r.Intn(255)
values[i] = []byte{byte(v)}
tree.Set(key, values[i])
}

// get root
root, err := tree.WorkingHash()
require.NoError(t, err)
// use the rightmost kv pair in the tree so the inner nodes will populate left
k := []byte{keys[1]}
v := values[1]

val, proof, err := tree.GetWithProof(k)
require.NoError(t, err)

err = proof.Verify(root)
require.NoError(t, err)
err = proof.VerifyItem(k, val)
require.NoError(t, err)

// ------------------- FORGE PROOF -------------------

forgedPayloadBytes := mustDecode("0xabcd")
forgedValueHash := tmhash.Sum(forgedPayloadBytes)
// make a forgery of the proof by adding:
// - a new leaf node to the right
// - an empty inner node
// - a right entry in the path
_, proof2, _ := tree.GetWithProof(k)
forgedNode := proof2.Leaves[0]
forgedNode.Key = []byte{0xFF}
forgedNode.ValueHash = forgedValueHash
proof2.Leaves = append(proof2.Leaves, forgedNode)
proof2.InnerNodes = append(proof2.InnerNodes, iavl.PathToLeaf{})
// figure out what hash we need via https://twitter.com/samczsun/status/1578181160345034752
proof2.LeftPath[0].Right = mustDecode("82C36CED85E914DAE8FDF6DD11FD5833121AA425711EB126C470CE28FF6623D5")

rootHashValid := proof.ComputeRootHash()
verifyErr := proof.Verify(rootHashValid)
require.NoError(t, verifyErr, "should verify")
// forgery gives empty root hash (previously it returned the same one!)
rootHashForged := proof2.ComputeRootHash()
require.Empty(t, rootHashForged, "roothash must be empty if both left and right are set")
verifyErr = proof2.Verify(rootHashForged)
require.Error(t, verifyErr, "should not verify")

// verify proof two fails with valid proof
err = proof2.Verify(rootHashValid)
require.Error(t, err, "should not verify different root hash")

{
// legit node verifies against legit proof (expected)
verifyErr = proof.VerifyItem(k, v)
require.NoError(t, verifyErr, "valid proof should verify")
// forged node fails to verify against legit proof (expected)
verifyErr = proof.VerifyItem(forgedNode.Key, forgedPayloadBytes)
require.Error(t, verifyErr, "forged proof should fail to verify")
}
{
// legit node fails to verify against forged proof (expected)
verifyErr = proof2.VerifyItem(k, v)
require.Error(t, verifyErr, "valid proof should verify, but has a forged sister node")

// forged node fails to verify against forged proof (previously this succeeded!)
verifyErr = proof2.VerifyItem(forgedNode.Key, forgedPayloadBytes)
require.Error(t, verifyErr, "forged proof should fail verify")
}
}

func mustDecode(str string) []byte {
if strings.HasPrefix(str, "0x") {
str = str[2:]
}
b, err := hex.DecodeString(str)
if err != nil {
panic(err)
}
return b
}
Loading

0 comments on commit ea54cd6

Please sign in to comment.