ibc: proof fixes (#7869)

* change proof proto def

* progress fixing tests

* complete proof restructure

* remove use of KeyPath

* improve error msgs

* docs and lint

* Apply suggestions from code review

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>

* address code review

* fix string tests

* add ConvertProofs tests

* Apply suggestions from code review

Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>
This commit is contained in:
Aditya 2020-11-10 14:39:09 +00:00 committed by GitHub
parent 15b285dd0d
commit fdcb028636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 980 additions and 1277 deletions

View File

@ -4,7 +4,7 @@ package ibc.core.commitment.v1;
option go_package = "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types";
import "gogoproto/gogo.proto";
import "tendermint/crypto/proof.proto";
import "confio/proofs.proto";
// MerkleRoot defines a merkle root hash.
// In the Cosmos SDK, the AppHash of a block header becomes the root.
@ -23,42 +23,18 @@ message MerklePrefix {
// MerklePath is the path used to verify commitment proofs, which can be an
// arbitrary structured object (defined by a commitment type).
// MerklePath is represented from root-to-leaf
message MerklePath {
option (gogoproto.goproto_stringer) = false;
KeyPath key_path = 1 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"key_path\""];
repeated string key_path = 1 [(gogoproto.moretags) = "yaml:\"key_path\""];
}
// MerkleProof is a wrapper type that contains a merkle proof.
// MerkleProof is a wrapper type over a chain of CommitmentProofs.
// It demonstrates membership or non-membership for an element or set of
// elements, verifiable in conjunction with a known commitment root. Proofs
// should be succinct.
// MerkleProofs are ordered from leaf-to-root
message MerkleProof {
tendermint.crypto.ProofOps proof = 1;
}
// KeyPath defines a slice of keys
message KeyPath {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.goproto_getters) = false;
repeated Key keys = 1;
}
// Key defines a proof Key
message Key {
option (gogoproto.goproto_getters) = false;
bytes name = 1;
KeyEncoding enc = 2;
}
// KeyEncoding defines the encoding format of a key's bytes.
enum KeyEncoding {
option (gogoproto.goproto_enum_prefix) = false;
// URL encoding
KEY_ENCODING_URL_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "URL"];
// Hex encoding
KEY_ENCODING_HEX = 1 [(gogoproto.enumvalue_customname) = "HEX"];
}
repeated ics23.CommitmentProof proofs = 1;
}

View File

@ -62,9 +62,10 @@ func (suite *MsgTestSuite) SetupTest() {
Prove: true,
})
merkleProof := commitmenttypes.MerkleProof{Proof: res.ProofOps}
merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps)
suite.Require().NoError(err)
proof, err := app.AppCodec().MarshalBinaryBare(&merkleProof)
suite.NoError(err)
suite.Require().NoError(err)
suite.proof = proof

View File

@ -30,6 +30,9 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))
// commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1)
suite.coordinator.CommitNBlocks(suite.chainA, 2)
suite.coordinator.CommitNBlocks(suite.chainB, 2)
}
// TestSetChannel create clients and connections on both chains. It tests for the non-existence

View File

@ -93,9 +93,10 @@ func (suite *TypesTestSuite) SetupTest() {
Prove: true,
})
merkleProof := commitmenttypes.MerkleProof{Proof: res.ProofOps}
merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps)
suite.Require().NoError(err)
proof, err := app.AppCodec().MarshalBinaryBare(&merkleProof)
suite.NoError(err)
suite.Require().NoError(err)
suite.proof = proof
}

View File

@ -5,9 +5,9 @@ package types
import (
fmt "fmt"
_go "github.com/confio/ics23/go"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/gogo/protobuf/proto"
crypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
io "io"
math "math"
math_bits "math/bits"
@ -24,34 +24,6 @@ var _ = math.Inf
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// KeyEncoding defines the encoding format of a key's bytes.
type KeyEncoding int32
const (
// URL encoding
URL KeyEncoding = 0
// Hex encoding
HEX KeyEncoding = 1
)
var KeyEncoding_name = map[int32]string{
0: "KEY_ENCODING_URL_UNSPECIFIED",
1: "KEY_ENCODING_HEX",
}
var KeyEncoding_value = map[string]int32{
"KEY_ENCODING_URL_UNSPECIFIED": 0,
"KEY_ENCODING_HEX": 1,
}
func (x KeyEncoding) String() string {
return proto.EnumName(KeyEncoding_name, int32(x))
}
func (KeyEncoding) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_7921d88972a41469, []int{0}
}
// MerkleRoot defines a merkle root hash.
// In the Cosmos SDK, the AppHash of a block header becomes the root.
type MerkleRoot struct {
@ -141,7 +113,7 @@ func (m *MerklePrefix) GetKeyPrefix() []byte {
// MerklePath is the path used to verify commitment proofs, which can be an
// arbitrary structured object (defined by a commitment type).
type MerklePath struct {
KeyPath KeyPath `protobuf:"bytes,1,opt,name=key_path,json=keyPath,proto3" json:"key_path" yaml:"key_path"`
KeyPath []string `protobuf:"bytes,1,rep,name=key_path,json=keyPath,proto3" json:"key_path,omitempty" yaml:"key_path"`
}
func (m *MerklePath) Reset() { *m = MerklePath{} }
@ -176,19 +148,19 @@ func (m *MerklePath) XXX_DiscardUnknown() {
var xxx_messageInfo_MerklePath proto.InternalMessageInfo
func (m *MerklePath) GetKeyPath() KeyPath {
func (m *MerklePath) GetKeyPath() []string {
if m != nil {
return m.KeyPath
}
return KeyPath{}
return nil
}
// MerkleProof is a wrapper type that contains a merkle proof.
// MerkleProof is a wrapper type over a chain of CommitmentProofs.
// It demonstrates membership or non-membership for an element or set of
// elements, verifiable in conjunction with a known commitment root. Proofs
// should be succinct.
type MerkleProof struct {
Proof *crypto.ProofOps `protobuf:"bytes,1,opt,name=proof,proto3" json:"proof,omitempty"`
Proofs []*_go.CommitmentProof `protobuf:"bytes,1,rep,name=proofs,proto3" json:"proofs,omitempty"`
}
func (m *MerkleProof) Reset() { *m = MerkleProof{} }
@ -224,97 +196,18 @@ func (m *MerkleProof) XXX_DiscardUnknown() {
var xxx_messageInfo_MerkleProof proto.InternalMessageInfo
func (m *MerkleProof) GetProof() *crypto.ProofOps {
func (m *MerkleProof) GetProofs() []*_go.CommitmentProof {
if m != nil {
return m.Proof
return m.Proofs
}
return nil
}
// KeyPath defines a slice of keys
type KeyPath struct {
Keys []*Key `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
}
func (m *KeyPath) Reset() { *m = KeyPath{} }
func (*KeyPath) ProtoMessage() {}
func (*KeyPath) Descriptor() ([]byte, []int) {
return fileDescriptor_7921d88972a41469, []int{4}
}
func (m *KeyPath) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *KeyPath) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_KeyPath.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *KeyPath) XXX_Merge(src proto.Message) {
xxx_messageInfo_KeyPath.Merge(m, src)
}
func (m *KeyPath) XXX_Size() int {
return m.Size()
}
func (m *KeyPath) XXX_DiscardUnknown() {
xxx_messageInfo_KeyPath.DiscardUnknown(m)
}
var xxx_messageInfo_KeyPath proto.InternalMessageInfo
// Key defines a proof Key
type Key struct {
Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Enc KeyEncoding `protobuf:"varint,2,opt,name=enc,proto3,enum=ibc.core.commitment.v1.KeyEncoding" json:"enc,omitempty"`
}
func (m *Key) Reset() { *m = Key{} }
func (m *Key) String() string { return proto.CompactTextString(m) }
func (*Key) ProtoMessage() {}
func (*Key) Descriptor() ([]byte, []int) {
return fileDescriptor_7921d88972a41469, []int{5}
}
func (m *Key) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Key) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Key.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Key) XXX_Merge(src proto.Message) {
xxx_messageInfo_Key.Merge(m, src)
}
func (m *Key) XXX_Size() int {
return m.Size()
}
func (m *Key) XXX_DiscardUnknown() {
xxx_messageInfo_Key.DiscardUnknown(m)
}
var xxx_messageInfo_Key proto.InternalMessageInfo
func init() {
proto.RegisterEnum("ibc.core.commitment.v1.KeyEncoding", KeyEncoding_name, KeyEncoding_value)
proto.RegisterType((*MerkleRoot)(nil), "ibc.core.commitment.v1.MerkleRoot")
proto.RegisterType((*MerklePrefix)(nil), "ibc.core.commitment.v1.MerklePrefix")
proto.RegisterType((*MerklePath)(nil), "ibc.core.commitment.v1.MerklePath")
proto.RegisterType((*MerkleProof)(nil), "ibc.core.commitment.v1.MerkleProof")
proto.RegisterType((*KeyPath)(nil), "ibc.core.commitment.v1.KeyPath")
proto.RegisterType((*Key)(nil), "ibc.core.commitment.v1.Key")
}
func init() {
@ -322,39 +215,28 @@ func init() {
}
var fileDescriptor_7921d88972a41469 = []byte{
// 499 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0x4f, 0x6f, 0xd3, 0x30,
0x18, 0xc6, 0x13, 0x5a, 0xe8, 0x70, 0x27, 0x28, 0x16, 0x7f, 0xaa, 0xc2, 0x92, 0x2a, 0x48, 0x30,
0x90, 0x16, 0xab, 0x1d, 0x5c, 0xca, 0x05, 0x75, 0x0d, 0xac, 0xea, 0xe8, 0x2a, 0x4f, 0x45, 0x03,
0x09, 0x55, 0x69, 0xea, 0x26, 0x51, 0x96, 0x38, 0x4a, 0xcc, 0xb4, 0x7c, 0x83, 0x89, 0x13, 0x47,
0x2e, 0x48, 0x48, 0x7c, 0x99, 0x1d, 0x77, 0xe4, 0x54, 0xa1, 0xf6, 0x1b, 0xec, 0x13, 0x20, 0xdb,
0x2d, 0x0b, 0x12, 0x70, 0xca, 0x6b, 0xbd, 0xbf, 0x37, 0xcf, 0xe3, 0xc7, 0x2f, 0x78, 0xec, 0x8f,
0x1d, 0xe4, 0xd0, 0x84, 0x20, 0x87, 0x86, 0xa1, 0xcf, 0x42, 0x12, 0x31, 0x74, 0xdc, 0xc8, 0x9d,
0xcc, 0x38, 0xa1, 0x8c, 0xc2, 0xbb, 0xfe, 0xd8, 0x31, 0x39, 0x68, 0xe6, 0x5a, 0xc7, 0x8d, 0xda,
0x6d, 0x97, 0xba, 0x54, 0x20, 0x88, 0x57, 0x92, 0xae, 0x6d, 0x30, 0x12, 0x4d, 0x48, 0x12, 0xfa,
0x11, 0x43, 0x4e, 0x92, 0xc5, 0x8c, 0xa2, 0x38, 0xa1, 0x74, 0x2a, 0xdb, 0xc6, 0x23, 0x00, 0xde,
0x90, 0x24, 0x38, 0x22, 0x98, 0x52, 0x06, 0x21, 0x28, 0x7a, 0x76, 0xea, 0x55, 0xd5, 0xba, 0xba,
0xb9, 0x8e, 0x45, 0xdd, 0x2a, 0x9e, 0x7e, 0xd3, 0x15, 0xa3, 0x03, 0xd6, 0x25, 0x37, 0x48, 0xc8,
0xd4, 0x3f, 0x81, 0xcf, 0x00, 0x08, 0x48, 0x36, 0x8a, 0xc5, 0x49, 0xf2, 0xed, 0x3b, 0x17, 0x33,
0xfd, 0x56, 0x66, 0x87, 0x47, 0x2d, 0xe3, 0xb2, 0x67, 0xe0, 0xeb, 0x01, 0xc9, 0xe4, 0x94, 0xe1,
0xae, 0xd4, 0x06, 0x36, 0xf3, 0xe0, 0x01, 0x58, 0x13, 0x9c, 0xcd, 0xa4, 0x62, 0xb9, 0xa9, 0x9b,
0x7f, 0xbf, 0x9b, 0xd9, 0x23, 0x19, 0x1f, 0x69, 0xdf, 0x3b, 0x9b, 0xe9, 0xca, 0xc5, 0x4c, 0xbf,
0x99, 0x93, 0xb1, 0x99, 0x67, 0xe0, 0x52, 0x20, 0x89, 0x56, 0xf1, 0x0b, 0xb7, 0xfb, 0x12, 0x94,
0x57, 0x76, 0x29, 0x9d, 0xc2, 0x06, 0xb8, 0x2a, 0x2e, 0xbd, 0x94, 0xb9, 0x6f, 0x5e, 0x86, 0x62,
0xca, 0x50, 0x4c, 0x01, 0xee, 0xc7, 0x29, 0x96, 0xa4, 0xd1, 0x01, 0xa5, 0xa5, 0x28, 0x44, 0xa0,
0x18, 0x90, 0x2c, 0xad, 0xaa, 0xf5, 0x82, 0x18, 0xfe, 0xb7, 0x47, 0x2c, 0xc0, 0xd6, 0x1a, 0x8f,
0x4c, 0xf8, 0x78, 0x0b, 0x0a, 0x3d, 0x92, 0xf1, 0x5c, 0x23, 0x3b, 0x24, 0xab, 0x5c, 0x79, 0x0d,
0x9f, 0x83, 0x02, 0x89, 0x9c, 0xea, 0x95, 0xba, 0xba, 0x79, 0xa3, 0xf9, 0xf0, 0x3f, 0x3f, 0xb5,
0x22, 0x87, 0x4e, 0xfc, 0xc8, 0xc5, 0x9c, 0x97, 0xcf, 0xf1, 0xf4, 0x03, 0x28, 0xe7, 0x3a, 0xf0,
0x09, 0x78, 0xd0, 0xb3, 0xde, 0x8d, 0xac, 0xfe, 0xce, 0x7e, 0xa7, 0xdb, 0x7f, 0x3d, 0x1a, 0xe2,
0xbd, 0xd1, 0xb0, 0x7f, 0x30, 0xb0, 0x76, 0xba, 0xaf, 0xba, 0x56, 0xa7, 0xa2, 0xd4, 0x4a, 0x9f,
0xbe, 0xd6, 0x0b, 0x43, 0xbc, 0x07, 0x37, 0x40, 0xe5, 0x0f, 0x74, 0xd7, 0x3a, 0xac, 0xa8, 0xb2,
0xbd, 0x6b, 0x1d, 0xd6, 0x8a, 0xa7, 0xdf, 0x35, 0xa5, 0x3d, 0x3c, 0x9b, 0x6b, 0xea, 0xf9, 0x5c,
0x53, 0x7f, 0xce, 0x35, 0xf5, 0xf3, 0x42, 0x53, 0xce, 0x17, 0x9a, 0xf2, 0x63, 0xa1, 0x29, 0xef,
0x5f, 0xb8, 0x3e, 0xf3, 0x3e, 0x8e, 0xb9, 0x4b, 0xe4, 0xd0, 0x34, 0xa4, 0xe9, 0xf2, 0xb3, 0x95,
0x4e, 0x02, 0x74, 0x82, 0x7e, 0x2f, 0x71, 0x73, 0x7b, 0x2b, 0xb7, 0xc7, 0x2c, 0x8b, 0x49, 0x3a,
0xbe, 0x26, 0x76, 0x6e, 0xfb, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8a, 0x78, 0x4b, 0x97, 0xeb,
0x02, 0x00, 0x00,
// 334 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0xcf, 0x4e, 0xfa, 0x40,
0x10, 0xc7, 0xdb, 0xfc, 0x08, 0x3f, 0x59, 0x48, 0x8c, 0x45, 0x89, 0xe1, 0x50, 0x4c, 0x0f, 0xca,
0x85, 0xdd, 0x00, 0x9e, 0x30, 0x5e, 0xaa, 0x57, 0x13, 0xd2, 0xc4, 0x8b, 0x17, 0xd3, 0xae, 0x5b,
0xba, 0x29, 0x65, 0x9a, 0xee, 0x4a, 0xe8, 0x1b, 0x78, 0xf4, 0xe8, 0xd1, 0xc7, 0xf1, 0xc8, 0xd1,
0x13, 0x31, 0xf0, 0x06, 0x3c, 0x81, 0xe9, 0x2e, 0x60, 0x4f, 0x3b, 0xb3, 0xf3, 0x99, 0x7f, 0xdf,
0x41, 0x57, 0x3c, 0xa0, 0x84, 0x42, 0xc6, 0x08, 0x85, 0x24, 0xe1, 0x32, 0x61, 0x33, 0x49, 0xe6,
0xfd, 0x92, 0x87, 0xd3, 0x0c, 0x24, 0x58, 0x2d, 0x1e, 0x50, 0x5c, 0x80, 0xb8, 0x14, 0x9a, 0xf7,
0xdb, 0xa7, 0x13, 0x98, 0x80, 0x42, 0x48, 0x61, 0x69, 0xba, 0xdd, 0xa4, 0x30, 0x0b, 0x39, 0x90,
0x34, 0x03, 0x08, 0x85, 0xfe, 0x74, 0x2e, 0x11, 0x7a, 0x60, 0x59, 0x3c, 0x65, 0x1e, 0x80, 0xb4,
0x2c, 0x54, 0x89, 0x7c, 0x11, 0x9d, 0x9b, 0x17, 0x66, 0xb7, 0xe1, 0x29, 0x7b, 0x54, 0x79, 0xfb,
0xec, 0x18, 0xce, 0x3d, 0x6a, 0x68, 0x6e, 0x9c, 0xb1, 0x90, 0x2f, 0xac, 0x6b, 0x84, 0x62, 0x96,
0x3f, 0xa7, 0xca, 0xd3, 0xbc, 0x7b, 0xb6, 0x5d, 0x75, 0x4e, 0x72, 0x3f, 0x99, 0x8e, 0x9c, 0xbf,
0x98, 0xe3, 0xd5, 0x62, 0x96, 0xeb, 0x2c, 0xc7, 0xdd, 0x77, 0x1b, 0xfb, 0x32, 0xb2, 0x30, 0x3a,
0x52, 0x9c, 0x2f, 0x8b, 0x8e, 0xff, 0xba, 0x35, 0xb7, 0xb9, 0x5d, 0x75, 0x8e, 0x4b, 0x15, 0x7c,
0x19, 0x39, 0xde, 0xff, 0x22, 0xdf, 0x97, 0xd1, 0xa8, 0xf2, 0x51, 0x4c, 0x72, 0x8b, 0xea, 0xfb,
0x49, 0x00, 0x42, 0x0b, 0xa3, 0xaa, 0x5e, 0x48, 0x95, 0xa8, 0x0f, 0x5a, 0x98, 0x53, 0x31, 0x18,
0xe2, 0xbb, 0x83, 0x22, 0x8a, 0xf3, 0x76, 0x94, 0xfb, 0xf8, 0xb5, 0xb6, 0xcd, 0xe5, 0xda, 0x36,
0x7f, 0xd6, 0xb6, 0xf9, 0xbe, 0xb1, 0x8d, 0xe5, 0xc6, 0x36, 0xbe, 0x37, 0xb6, 0xf1, 0x74, 0x33,
0xe1, 0x32, 0x7a, 0x0d, 0x0a, 0x2d, 0x09, 0x05, 0x91, 0x80, 0xd8, 0x3d, 0x3d, 0xf1, 0x12, 0x93,
0x05, 0x39, 0x5c, 0x65, 0x30, 0xec, 0x95, 0x0e, 0x23, 0xf3, 0x94, 0x89, 0xa0, 0xaa, 0xe4, 0x1c,
0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xe7, 0x68, 0xd0, 0xbc, 0x01, 0x00, 0x00,
}
func (m *MerkleRoot) Marshal() (dAtA []byte, err error) {
@ -437,16 +319,15 @@ func (m *MerklePath) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
{
size, err := m.KeyPath.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
if len(m.KeyPath) > 0 {
for iNdEx := len(m.KeyPath) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.KeyPath[iNdEx])
copy(dAtA[i:], m.KeyPath[iNdEx])
i = encodeVarintCommitment(dAtA, i, uint64(len(m.KeyPath[iNdEx])))
i--
dAtA[i] = 0xa
}
i -= size
i = encodeVarintCommitment(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
@ -470,45 +351,10 @@ func (m *MerkleProof) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.Proof != nil {
{
size, err := m.Proof.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintCommitment(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *KeyPath) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *KeyPath) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *KeyPath) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Keys) > 0 {
for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- {
if len(m.Proofs) > 0 {
for iNdEx := len(m.Proofs) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Keys[iNdEx].MarshalToSizedBuffer(dAtA[:i])
size, err := m.Proofs[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
@ -522,41 +368,6 @@ func (m *KeyPath) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *Key) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Key) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Key) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.Enc != 0 {
i = encodeVarintCommitment(dAtA, i, uint64(m.Enc))
i--
dAtA[i] = 0x10
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintCommitment(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintCommitment(dAtA []byte, offset int, v uint64) int {
offset -= sovCommitment(v)
base := offset
@ -600,8 +411,12 @@ func (m *MerklePath) Size() (n int) {
}
var l int
_ = l
l = m.KeyPath.Size()
n += 1 + l + sovCommitment(uint64(l))
if len(m.KeyPath) > 0 {
for _, s := range m.KeyPath {
l = len(s)
n += 1 + l + sovCommitment(uint64(l))
}
}
return n
}
@ -611,21 +426,8 @@ func (m *MerkleProof) Size() (n int) {
}
var l int
_ = l
if m.Proof != nil {
l = m.Proof.Size()
n += 1 + l + sovCommitment(uint64(l))
}
return n
}
func (m *KeyPath) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if len(m.Keys) > 0 {
for _, e := range m.Keys {
if len(m.Proofs) > 0 {
for _, e := range m.Proofs {
l = e.Size()
n += 1 + l + sovCommitment(uint64(l))
}
@ -633,22 +435,6 @@ func (m *KeyPath) Size() (n int) {
return n
}
func (m *Key) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sovCommitment(uint64(l))
}
if m.Enc != 0 {
n += 1 + sovCommitment(uint64(m.Enc))
}
return n
}
func sovCommitment(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
@ -862,7 +648,7 @@ func (m *MerklePath) Unmarshal(dAtA []byte) error {
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field KeyPath", wireType)
}
var msglen int
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCommitment
@ -872,24 +658,23 @@ func (m *MerklePath) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthCommitment
}
postIndex := iNdEx + msglen
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthCommitment
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.KeyPath.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
m.KeyPath = append(m.KeyPath, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
@ -946,7 +731,7 @@ func (m *MerkleProof) Unmarshal(dAtA []byte) error {
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field Proofs", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
@ -973,10 +758,8 @@ func (m *MerkleProof) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Proof == nil {
m.Proof = &crypto.ProofOps{}
}
if err := m.Proof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
m.Proofs = append(m.Proofs, &_go.CommitmentProof{})
if err := m.Proofs[len(m.Proofs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
@ -1004,199 +787,6 @@ func (m *MerkleProof) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *KeyPath) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCommitment
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: KeyPath: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: KeyPath: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Keys", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCommitment
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthCommitment
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthCommitment
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Keys = append(m.Keys, &Key{})
if err := m.Keys[len(m.Keys)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipCommitment(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthCommitment
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthCommitment
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Key) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCommitment
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Key: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Key: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCommitment
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthCommitment
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthCommitment
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = append(m.Name[:0], dAtA[iNdEx:postIndex]...)
if m.Name == nil {
m.Name = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Enc", wireType)
}
m.Enc = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowCommitment
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Enc |= KeyEncoding(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipCommitment(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthCommitment
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthCommitment
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipCommitment(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0

View File

@ -1,40 +0,0 @@
package types
import (
fmt "fmt"
"net/url"
)
// AppendKey appends a new key to a KeyPath
func (pth KeyPath) AppendKey(key []byte, enc KeyEncoding) KeyPath {
pth.Keys = append(pth.Keys, &Key{Name: key, Enc: enc})
return pth
}
// String implements the fmt.Stringer interface
func (pth *KeyPath) String() string {
res := ""
for _, key := range pth.Keys {
switch key.Enc {
case URL:
res += "/" + url.PathEscape(string(key.Name))
case HEX:
res += "/x:" + fmt.Sprintf("%X", key.Name)
default:
panic("unexpected key encoding type")
}
}
return res
}
// GetKey returns the bytes representation of key at given index
// Passing in a positive index return the key at index in forward order
// from the highest key to the lowest key
// Passing in a negative index will return the key at index in reverse order
// from the lowest key to the highest key. This is the order for proof verification,
// since we prove lowest key first before moving to key of higher subtrees
func (pth *KeyPath) GetKey(i int) []byte {
total := len(pth.Keys)
index := (total + i) % total
return pth.Keys[index].Name
}

View File

@ -2,6 +2,7 @@ package types
import (
"bytes"
"fmt"
"net/url"
ics23 "github.com/confio/ics23/go"
@ -9,7 +10,6 @@ import (
tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
host "github.com/cosmos/cosmos-sdk/x/ibc/core/24-host"
"github.com/cosmos/cosmos-sdk/x/ibc/core/exported"
)
@ -68,51 +68,62 @@ func (mp MerklePrefix) Empty() bool {
var _ exported.Path = (*MerklePath)(nil)
// NewMerklePath creates a new MerklePath instance
func NewMerklePath(keyPathStr []string) MerklePath {
merkleKeyPath := KeyPath{}
for _, keyStr := range keyPathStr {
merkleKeyPath = merkleKeyPath.AppendKey([]byte(keyStr), URL)
}
// The keys must be passed in from root-to-leaf order
func NewMerklePath(keyPath ...string) MerklePath {
return MerklePath{
KeyPath: merkleKeyPath,
KeyPath: keyPath,
}
}
// String implements fmt.Stringer.
// This represents the path in the same way the tendermint KeyPath will
// represent a key path. The backslashes partition the key path into
// the respective stores they belong to.
func (mp MerklePath) String() string {
return mp.KeyPath.String()
pathStr := ""
for _, k := range mp.KeyPath {
pathStr += "/" + url.PathEscape(k)
}
return pathStr
}
// Pretty returns the unescaped path of the URL string.
// This function will unescape any backslash within a particular store key.
// This makes the keypath more human-readable while removing information
// about the exact partitions in the key path.
func (mp MerklePath) Pretty() string {
path, err := url.PathUnescape(mp.KeyPath.String())
path, err := url.PathUnescape(mp.String())
if err != nil {
panic(err)
}
return path
}
// Empty returns true if the path is empty
func (mp MerklePath) Empty() bool {
return len(mp.KeyPath.Keys) == 0
// GetKey will return a byte representation of the key
// after URL escaping the key element
func (mp MerklePath) GetKey(i uint64) ([]byte, error) {
if i >= uint64(len(mp.KeyPath)) {
return nil, fmt.Errorf("index out of range. %d (index) >= %d (len)", i, len(mp.KeyPath))
}
key, err := url.PathUnescape(mp.KeyPath[i])
if err != nil {
return nil, err
}
return []byte(key), nil
}
// ApplyPrefix constructs a new commitment path from the arguments. It interprets
// the path argument in the context of the prefix argument.
//
// CONTRACT: provided path string MUST be a well formated path. See ICS24 for
// reference.
func ApplyPrefix(prefix exported.Prefix, path string) (MerklePath, error) {
err := host.PathValidator(path)
if err != nil {
return MerklePath{}, err
}
// Empty returns true if the path is empty
func (mp MerklePath) Empty() bool {
return len(mp.KeyPath) == 0
}
// ApplyPrefix constructs a new commitment path from the arguments. It prepends the prefix key
// with the given path.
func ApplyPrefix(prefix exported.Prefix, path MerklePath) (MerklePath, error) {
if prefix == nil || prefix.Empty() {
return MerklePath{}, sdkerrors.Wrap(ErrInvalidPrefix, "prefix can't be empty")
}
return NewMerklePath([]string{string(prefix.Bytes()), path}), nil
return NewMerklePath(append([]string{string(prefix.Bytes())}, path.KeyPath...)...), nil
}
var _ exported.Proof = (*MerkleProof)(nil)
@ -128,22 +139,17 @@ func (proof MerkleProof) VerifyMembership(specs []*ics23.ProofSpec, root exporte
if !ok {
return sdkerrors.Wrapf(ErrInvalidProof, "path %v is not of type MerklePath", path)
}
if len(mpath.KeyPath.Keys) != len(specs) {
if len(mpath.KeyPath) != len(specs) {
return sdkerrors.Wrapf(ErrInvalidProof, "path length %d not same as proof %d",
len(mpath.KeyPath.Keys), len(specs))
len(mpath.KeyPath), len(specs))
}
if len(value) == 0 {
return sdkerrors.Wrap(ErrInvalidProof, "empty value in membership proof")
}
// Convert Proof to []CommitmentProof
proofs, err := convertProofs(proof)
if err != nil {
return err
}
// Since every proof in chain is a membership proof we can chain from index 0
if err := verifyChainedMembershipProof(root.GetHash(), specs, proofs, mpath.KeyPath, value, 0); err != nil {
// Since every proof in chain is a membership proof we can use verifyChainedMembershipProof from index 0
// to validate entire proof
if err := verifyChainedMembershipProof(root.GetHash(), specs, proof.Proofs, mpath, value, 0); err != nil {
return err
}
return nil
@ -162,31 +168,37 @@ func (proof MerkleProof) VerifyNonMembership(specs []*ics23.ProofSpec, root expo
if !ok {
return sdkerrors.Wrapf(ErrInvalidProof, "path %v is not of type MerkleProof", path)
}
if len(mpath.KeyPath.Keys) != len(specs) {
if len(mpath.KeyPath) != len(specs) {
return sdkerrors.Wrapf(ErrInvalidProof, "path length %d not same as proof %d",
len(mpath.KeyPath.Keys), len(specs))
len(mpath.KeyPath), len(specs))
}
// Convert Proof to []CommitmentProof
proofs, err := convertProofs(proof)
if err != nil {
return err
}
switch proof.Proofs[0].Proof.(type) {
case *ics23.CommitmentProof_Nonexist:
// VerifyNonMembership will verify the absence of key in lowest subtree, and then chain inclusion proofs
// of all subroots up to final root
subroot, err := proof.Proofs[0].Calculate()
if err != nil {
return sdkerrors.Wrapf(ErrInvalidProof, "could not calculate root for proof index 0, merkle tree is likely empty. %v", err)
}
key, err := mpath.GetKey(uint64(len(mpath.KeyPath) - 1))
if err != nil {
return sdkerrors.Wrapf(ErrInvalidProof, "could not retrieve key bytes for key: %s", mpath.KeyPath[len(mpath.KeyPath)-1])
}
if ok := ics23.VerifyNonMembership(specs[0], subroot, proof.Proofs[0], key); !ok {
return sdkerrors.Wrapf(ErrInvalidProof, "could not verify absence of key %s. Please ensure that the path is correct.", string(key))
}
// VerifyNonMembership will verify the absence of key in lowest subtree, and then chain inclusion proofs
// of all subroots up to final root
subroot, err := proofs[0].Calculate()
if err != nil {
sdkerrors.Wrapf(ErrInvalidProof, "could not calculate root for proof index 0. %v", err)
}
key := mpath.KeyPath.GetKey(-1)
if ok := ics23.VerifyNonMembership(specs[0], subroot, proofs[0], key); !ok {
return sdkerrors.Wrapf(ErrInvalidProof, "could not verify absence of key %s", string(key))
}
// Verify chained membership proof starting from index 1 with value = subroot
if err := verifyChainedMembershipProof(root.GetHash(), specs, proofs, mpath.KeyPath, subroot, 1); err != nil {
return err
// Verify chained membership proof starting from index 1 with value = subroot
if err := verifyChainedMembershipProof(root.GetHash(), specs, proof.Proofs, mpath, subroot, 1); err != nil {
return err
}
case *ics23.CommitmentProof_Exist:
return sdkerrors.Wrapf(ErrInvalidProof,
"got ExistenceProof in VerifyNonMembership. If this is unexpected, please ensure that proof was queried with the correct key.")
default:
return sdkerrors.Wrapf(ErrInvalidProof,
"expected proof type: %T, got: %T", &ics23.CommitmentProof_Exist{}, proof.Proofs[0].Proof)
}
return nil
}
@ -208,7 +220,7 @@ func (proof MerkleProof) BatchVerifyNonMembership(specs []*ics23.ProofSpec, root
// The proofs and specs are passed in from lowest subtree to the highest subtree, but the keys are passed in from highest subtree to lowest.
// The index specifies what index to start chaining the membership proofs, this is useful since the lowest proof may not be a membership proof, thus we
// will want to start the membership proof chaining from index 1 with value being the lowest subroot
func verifyChainedMembershipProof(root []byte, specs []*ics23.ProofSpec, proofs []*ics23.CommitmentProof, keys KeyPath, value []byte, index int) error {
func verifyChainedMembershipProof(root []byte, specs []*ics23.ProofSpec, proofs []*ics23.CommitmentProof, keys MerklePath, value []byte, index int) error {
var (
subroot []byte
err error
@ -218,42 +230,45 @@ func verifyChainedMembershipProof(root []byte, specs []*ics23.ProofSpec, proofs
// In this case, there may be no intermediate proofs to verify and we just check that lowest proof root equals final root
subroot = value
for i := index; i < len(proofs); i++ {
subroot, err = proofs[i].Calculate()
if err != nil {
return sdkerrors.Wrapf(ErrInvalidProof, "could not calculate proof root at index %d. %v", i, err)
switch proofs[i].Proof.(type) {
case *ics23.CommitmentProof_Exist:
subroot, err = proofs[i].Calculate()
if err != nil {
return sdkerrors.Wrapf(ErrInvalidProof, "could not calculate proof root at index %d, merkle tree may be empty. %v", i, err)
}
// Since keys are passed in from highest to lowest, we must grab their indices in reverse order
// from the proofs and specs which are lowest to highest
key, err := keys.GetKey(uint64(len(keys.KeyPath) - 1 - i))
if err != nil {
return sdkerrors.Wrapf(ErrInvalidProof, "could not retrieve key bytes for key %s: %v", keys.KeyPath[len(keys.KeyPath)-1-i], err)
}
// verify membership of the proof at this index with appropriate key and value
if ok := ics23.VerifyMembership(specs[i], subroot, proofs[i], key, value); !ok {
return sdkerrors.Wrapf(ErrInvalidProof,
"chained membership proof failed to verify membership of value: %X in subroot %X at index %d. Please ensure the path and value are both correct.",
value, subroot, i)
}
// Set value to subroot so that we verify next proof in chain commits to this subroot
value = subroot
case *ics23.CommitmentProof_Nonexist:
return sdkerrors.Wrapf(ErrInvalidProof,
"chained membership proof contains nonexistence proof at index %d. If this is unexpected, please ensure that proof was queried from the height that contained the value in store and was queried with the correct key.",
i)
default:
return sdkerrors.Wrapf(ErrInvalidProof,
"expected proof type: %T, got: %T", &ics23.CommitmentProof_Exist{}, proofs[i].Proof)
}
// Since keys are passed in from highest to lowest, we must grab their indices in reverse order
// from the proofs and specs which are lowest to highest
key := keys.GetKey(-1 * (i + 1))
if ok := ics23.VerifyMembership(specs[i], subroot, proofs[i], key, value); !ok {
return sdkerrors.Wrapf(ErrInvalidProof, "chained membership proof failed to verify membership of value: %X in subroot %X at index %d for proof %v",
value, subroot, i, proofs[i])
}
// Set value to subroot so that we verify next proof in chain commits to this subroot
value = subroot
}
// Check that chained proof root equals passed-in root
if !bytes.Equal(root, subroot) {
return sdkerrors.Wrapf(ErrInvalidProof, "proof did not commit to expected root: %X, got: %X", root, subroot)
return sdkerrors.Wrapf(ErrInvalidProof,
"proof did not commit to expected root: %X, got: %X. Please ensure proof was submitted with correct proofHeight and to the correct chain.",
root, subroot)
}
return nil
}
// convertProofs converts a MerkleProof into []*ics23.CommitmentProof
func convertProofs(mproof MerkleProof) ([]*ics23.CommitmentProof, error) {
// Unmarshal all proof ops to CommitmentProof
proofs := make([]*ics23.CommitmentProof, len(mproof.Proof.Ops))
for i, op := range mproof.Proof.Ops {
var p ics23.CommitmentProof
err := p.Unmarshal(op.Data)
if err != nil {
return nil, sdkerrors.Wrapf(ErrInvalidMerkleProof, "could not unmarshal proof op into CommitmentProof at index: %d", i)
}
proofs[i] = &p
}
return proofs, nil
}
// Empty returns true if the root is empty
func (proof MerkleProof) Empty() bool {
return proto.Equal(&proof, nil) || proto.Equal(&proof, &MerkleProof{}) || proto.Equal(&proof, &tmcrypto.ProofOps{})
@ -277,10 +292,10 @@ func (proof MerkleProof) validateVerificationArgs(specs []*ics23.ProofSpec, root
return sdkerrors.Wrap(ErrInvalidMerkleProof, "root cannot be empty")
}
if len(specs) != len(proof.Proof.Ops) {
if len(specs) != len(proof.Proofs) {
return sdkerrors.Wrapf(ErrInvalidMerkleProof,
"length of specs: %d not equal to length of proof: %d",
len(specs), len(proof.Proof.Ops))
len(specs), len(proof.Proofs))
}
for i, spec := range specs {

View File

@ -6,8 +6,6 @@ import (
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
tmmerkle "github.com/tendermint/tendermint/proto/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types"
)
@ -23,9 +21,9 @@ func (suite *MerkleTestSuite) TestVerifyMembership() {
})
require.NotNil(suite.T(), res.ProofOps)
proof := types.MerkleProof{
Proof: res.ProofOps,
}
proof, err := types.ConvertProofs(res.ProofOps)
require.NoError(suite.T(), err)
suite.Require().NoError(proof.ValidateBasic())
suite.Require().Error(types.MerkleProof{}.ValidateBasic())
@ -49,9 +47,7 @@ func (suite *MerkleTestSuite) TestVerifyMembership() {
{"nil root", []byte(nil), []string{suite.storeKey.Name(), "MYKEY"}, []byte("MYVALUE"), func() {}, false}, // invalid proof with nil root
{"proof is wrong length", cid.Hash, []string{suite.storeKey.Name(), "MYKEY"}, []byte("MYVALUE"), func() {
proof = types.MerkleProof{
Proof: &tmmerkle.ProofOps{
Ops: res.ProofOps.Ops[1:],
},
Proofs: proof.Proofs[1:],
}
}, false}, // invalid proof with wrong length
@ -63,7 +59,7 @@ func (suite *MerkleTestSuite) TestVerifyMembership() {
tc.malleate()
root := types.NewMerkleRoot(tc.root)
path := types.NewMerklePath(tc.pathArr)
path := types.NewMerklePath(tc.pathArr...)
err := proof.VerifyMembership(types.GetSDKSpecs(), &root, path, tc.value)
@ -91,9 +87,9 @@ func (suite *MerkleTestSuite) TestVerifyNonMembership() {
})
require.NotNil(suite.T(), res.ProofOps)
proof := types.MerkleProof{
Proof: res.ProofOps,
}
proof, err := types.ConvertProofs(res.ProofOps)
require.NoError(suite.T(), err)
suite.Require().NoError(proof.ValidateBasic())
cases := []struct {
@ -114,9 +110,7 @@ func (suite *MerkleTestSuite) TestVerifyNonMembership() {
{"nil root", []byte(nil), []string{suite.storeKey.Name(), "MYABSENTKEY"}, func() {}, false}, // invalid proof with nil root
{"proof is wrong length", cid.Hash, []string{suite.storeKey.Name(), "MYKEY"}, func() {
proof = types.MerkleProof{
Proof: &tmcrypto.ProofOps{
Ops: res.ProofOps.Ops[1:],
},
Proofs: proof.Proofs[1:],
}
}, false}, // invalid proof with wrong length
@ -129,7 +123,7 @@ func (suite *MerkleTestSuite) TestVerifyNonMembership() {
tc.malleate()
root := types.NewMerkleRoot(tc.root)
path := types.NewMerklePath(tc.pathArr)
path := types.NewMerklePath(tc.pathArr...)
err := proof.VerifyNonMembership(types.GetSDKSpecs(), &root, path)
@ -149,16 +143,30 @@ func TestApplyPrefix(t *testing.T) {
prefix := types.NewMerklePrefix([]byte("storePrefixKey"))
pathStr := "pathone/pathtwo/paththree/key"
path := types.MerklePath{
KeyPath: []string{pathStr},
}
prefixedPath, err := types.ApplyPrefix(prefix, pathStr)
require.Nil(t, err, "valid prefix returns error")
prefixedPath, err := types.ApplyPrefix(prefix, path)
require.NoError(t, err, "valid prefix returns error")
require.Equal(t, "/storePrefixKey/"+pathStr, prefixedPath.Pretty(), "Prefixed path incorrect")
require.Equal(t, "/storePrefixKey/pathone%2Fpathtwo%2Fpaththree%2Fkey", prefixedPath.String(), "Prefixed scaped path incorrect")
// invalid prefix contains non-alphanumeric character
invalidPathStr := "invalid-path/doesitfail?/hopefully"
invalidPath, err := types.ApplyPrefix(prefix, invalidPathStr)
require.NotNil(t, err, "invalid prefix does not returns error")
require.Equal(t, types.MerklePath{}, invalidPath, "invalid prefix returns valid Path on ApplyPrefix")
require.Equal(t, "/storePrefixKey/pathone%2Fpathtwo%2Fpaththree%2Fkey", prefixedPath.String(), "Prefixed escaped path incorrect")
}
func TestString(t *testing.T) {
path := types.NewMerklePath("rootKey", "storeKey", "path/to/leaf")
require.Equal(t, "/rootKey/storeKey/path%2Fto%2Fleaf", path.String(), "path String returns unxpected value")
require.Equal(t, "/rootKey/storeKey/path/to/leaf", path.Pretty(), "path's pretty string representation is incorrect")
onePath := types.NewMerklePath("path/to/leaf")
require.Equal(t, "/path%2Fto%2Fleaf", onePath.String(), "one element path does not have correct string representation")
require.Equal(t, "/path/to/leaf", onePath.Pretty(), "one element path has incorrect pretty string representation")
zeroPath := types.NewMerklePath()
require.Equal(t, "", zeroPath.String(), "zero element path does not have correct string representation")
require.Equal(t, "", zeroPath.Pretty(), "zero element path does not have correct pretty string representation")
}

View File

@ -0,0 +1,27 @@
package types
import (
ics23 "github.com/confio/ics23/go"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
crypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
)
// ConvertProofs converts crypto.ProofOps into MerkleProof
func ConvertProofs(tmProof *crypto.ProofOps) (MerkleProof, error) {
if tmProof == nil {
return MerkleProof{}, sdkerrors.Wrapf(ErrInvalidMerkleProof, "tendermint proof is nil")
}
// Unmarshal all proof ops to CommitmentProof
proofs := make([]*ics23.CommitmentProof, len(tmProof.Ops))
for i, op := range tmProof.Ops {
var p ics23.CommitmentProof
err := p.Unmarshal(op.Data)
if err != nil || p.Proof == nil {
return MerkleProof{}, sdkerrors.Wrapf(ErrInvalidMerkleProof, "could not unmarshal proof op into CommitmentProof at index %d: %v", i, err)
}
proofs[i] = &p
}
return MerkleProof{
Proofs: proofs,
}, nil
}

View File

@ -0,0 +1,97 @@
package types_test
import (
"fmt"
"github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
crypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
)
func (suite *MerkleTestSuite) TestConvertProofs() {
suite.iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE"))
cid := suite.store.Commit()
root := types.NewMerkleRoot(cid.Hash)
existsPath := types.NewMerklePath(suite.storeKey.Name(), "MYKEY")
nonexistPath := types.NewMerklePath(suite.storeKey.Name(), "NOTMYKEY")
value := []byte("MYVALUE")
var proofOps *crypto.ProofOps
testcases := []struct {
name string
malleate func()
keyExists bool
expPass bool
}{
{
"success for ExistenceProof",
func() {
res := suite.store.Query(abci.RequestQuery{
Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof
Data: []byte("MYKEY"),
Prove: true,
})
require.NotNil(suite.T(), res.ProofOps)
proofOps = res.ProofOps
},
true, true,
},
{
"success for NonexistenceProof",
func() {
res := suite.store.Query(abci.RequestQuery{
Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof
Data: []byte("NOTMYKEY"),
Prove: true,
})
require.NotNil(suite.T(), res.ProofOps)
proofOps = res.ProofOps
},
false, true,
},
{
"nil proofOps",
func() {
proofOps = nil
},
true, false,
},
{
"proof op data is nil",
func() {
res := suite.store.Query(abci.RequestQuery{
Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof
Data: []byte("MYKEY"),
Prove: true,
})
require.NotNil(suite.T(), res.ProofOps)
proofOps = res.ProofOps
proofOps.Ops[0].Data = nil
},
true, false,
},
}
for _, tc := range testcases {
tc.malleate()
proof, err := types.ConvertProofs(proofOps)
if tc.expPass {
suite.Require().NoError(err, "ConvertProofs unexpectedly returned error for case: %s", tc.name)
if tc.keyExists {
err := proof.VerifyMembership(types.GetSDKSpecs(), &root, existsPath, value)
suite.Require().NoError(err, "converted proof failed to verify membership for case: %s", tc.name)
} else {
err := proof.VerifyNonMembership(types.GetSDKSpecs(), &root, nonexistPath)
suite.Require().NoError(err, "converted proof failed to verify membership for case: %s", tc.name)
}
} else {
suite.Require().Error(err, "ConvertProofs passed on invalid case for case: %s", tc.name)
}
}
}

View File

@ -50,8 +50,9 @@ func QueryTendermintProof(clientCtx client.Context, key []byte) ([]byte, []byte,
return nil, nil, clienttypes.Height{}, err
}
merkleProof := commitmenttypes.MerkleProof{
Proof: res.ProofOps,
merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps)
if err != nil {
return nil, nil, clienttypes.Height{}, err
}
cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry)

View File

@ -41,6 +41,9 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))
// commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1)
suite.coordinator.CommitNBlocks(suite.chainA, 2)
suite.coordinator.CommitNBlocks(suite.chainB, 2)
}
func TestIBCTestSuite(t *testing.T) {

View File

@ -100,7 +100,7 @@ func (cs ClientState) VerifyClientState(
return err
}
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath()
clientPrefixedPath := commitmenttypes.NewMerklePath("clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath())
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
if err != nil {
return err
@ -138,7 +138,7 @@ func (cs ClientState) VerifyClientConsensusState(
return err
}
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight)
clientPrefixedPath := commitmenttypes.NewMerklePath("clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight))
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
if err != nil {
return err
@ -175,7 +175,8 @@ func (cs ClientState) VerifyConnectionState(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(connectionID))
connectionPath := commitmenttypes.NewMerklePath(host.ConnectionPath(connectionID))
path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath)
if err != nil {
return err
}
@ -212,7 +213,8 @@ func (cs ClientState) VerifyChannelState(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(portID, channelID))
channelPath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, channelPath)
if err != nil {
return err
}
@ -250,7 +252,8 @@ func (cs ClientState) VerifyPacketCommitment(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(portID, channelID, packetSequence))
commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, packetSequence))
path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath)
if err != nil {
return err
}
@ -288,7 +291,8 @@ func (cs ClientState) VerifyPacketAcknowledgement(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, packetSequence))
ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, packetSequence))
path, err := commitmenttypes.ApplyPrefix(prefix, ackPath)
if err != nil {
return err
}
@ -326,7 +330,8 @@ func (cs ClientState) VerifyPacketReceiptAbsence(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketReceiptPath(portID, channelID, packetSequence))
receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, packetSequence))
path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath)
if err != nil {
return err
}
@ -363,7 +368,8 @@ func (cs ClientState) VerifyNextSequenceRecv(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(portID, channelID))
nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath)
if err != nil {
return err
}

View File

@ -180,7 +180,7 @@ func (cs ClientState) VerifyClientState(
return err
}
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath()
clientPrefixedPath := commitmenttypes.NewMerklePath("clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath())
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
if err != nil {
return err
@ -220,7 +220,7 @@ func (cs ClientState) VerifyClientConsensusState(
return err
}
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight)
clientPrefixedPath := commitmenttypes.NewMerklePath("clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight))
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
if err != nil {
return err
@ -263,7 +263,8 @@ func (cs ClientState) VerifyConnectionState(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(connectionID))
connectionPath := commitmenttypes.NewMerklePath(host.ConnectionPath(connectionID))
path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath)
if err != nil {
return err
}
@ -302,7 +303,8 @@ func (cs ClientState) VerifyChannelState(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(portID, channelID))
channelPath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, channelPath)
if err != nil {
return err
}
@ -342,7 +344,8 @@ func (cs ClientState) VerifyPacketCommitment(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(portID, channelID, sequence))
commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath)
if err != nil {
return err
}
@ -372,7 +375,8 @@ func (cs ClientState) VerifyPacketAcknowledgement(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, sequence))
ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, ackPath)
if err != nil {
return err
}
@ -402,7 +406,8 @@ func (cs ClientState) VerifyPacketReceiptAbsence(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketReceiptPath(portID, channelID, sequence))
receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath)
if err != nil {
return err
}
@ -431,7 +436,8 @@ func (cs ClientState) VerifyNextSequenceRecv(
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(portID, channelID))
nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath)
if err != nil {
return err
}

View File

@ -60,6 +60,9 @@ func (suite *TendermintTestSuite) SetupTest() {
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))
// commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1)
suite.coordinator.CommitNBlocks(suite.chainA, 2)
suite.coordinator.CommitNBlocks(suite.chainB, 2)
// TODO: deprecate usage in favor of testing package
checkTx := false

View File

@ -117,5 +117,5 @@ func constructUpgradeMerklePath(upgradePath string, upgradeHeight exported.Heigh
// append upgradeHeight to last key in merkle path
// this will create the IAVL key that is used to store client in upgrade store
upgradeKeys[len(upgradeKeys)-1] = fmt.Sprintf("%s/%d", lastKey, upgradeHeight.GetVersionHeight())
return commitmenttypes.NewMerklePath(upgradeKeys), nil
return commitmenttypes.NewMerklePath(upgradeKeys...), nil
}

View File

@ -197,9 +197,8 @@ func (chain *TestChain) QueryProof(key []byte) ([]byte, clienttypes.Height) {
Prove: true,
})
merkleProof := commitmenttypes.MerkleProof{
Proof: res.ProofOps,
}
merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps)
require.NoError(chain.t, err)
proof, err := chain.App.AppCodec().MarshalBinaryBare(&merkleProof)
require.NoError(chain.t, err)
@ -222,9 +221,8 @@ func (chain *TestChain) QueryUpgradeProof(key []byte, height uint64) ([]byte, cl
Prove: true,
})
merkleProof := commitmenttypes.MerkleProof{
Proof: res.ProofOps,
}
merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps)
require.NoError(chain.t, err)
proof, err := chain.App.AppCodec().MarshalBinaryBare(&merkleProof)
require.NoError(chain.t, err)

View File

@ -251,7 +251,7 @@ func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte {
// GetClientStatePath returns the commitment path for the client state.
func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypes.MerklePath {
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath()
clientPrefixedPath := commitmenttypes.NewMerklePath("clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath())
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
require.NoError(solo.t, err)
@ -260,7 +260,7 @@ func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string)
// GetConsensusStatePath returns the commitment path for the consensus state.
func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier string, consensusHeight exported.Height) commitmenttypes.MerklePath {
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight)
clientPrefixedPath := commitmenttypes.NewMerklePath("clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight))
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
require.NoError(solo.t, err)
@ -269,7 +269,8 @@ func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier stri
// GetConnectionStatePath returns the commitment path for the connection state.
func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypes.MerklePath {
path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(connID))
connectionPath := commitmenttypes.NewMerklePath(host.ConnectionPath(connID))
path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath)
require.NoError(solo.t, err)
return path
@ -277,7 +278,8 @@ func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypes.M
// GetChannelStatePath returns the commitment path for that channel state.
func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmenttypes.MerklePath {
path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(portID, channelID))
channelPath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, channelPath)
require.NoError(solo.t, err)
return path
@ -285,7 +287,8 @@ func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmen
// GetPacketCommitmentPath returns the commitment path for a packet commitment.
func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string) commitmenttypes.MerklePath {
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(portID, channelID, solo.Sequence))
commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, solo.Sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath)
require.NoError(solo.t, err)
return path
@ -293,7 +296,8 @@ func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string) commi
// GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement.
func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string) commitmenttypes.MerklePath {
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, solo.Sequence))
ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, solo.Sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, ackPath)
require.NoError(solo.t, err)
return path
@ -302,7 +306,8 @@ func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string)
// GetPacketReceiptPath returns the commitment path for a packet receipt
// and an absent receipts.
func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string) commitmenttypes.MerklePath {
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketReceiptPath(portID, channelID, solo.Sequence))
receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, solo.Sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath)
require.NoError(solo.t, err)
return path
@ -310,7 +315,8 @@ func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string) commitme
// GetNextSequenceRecvPath returns the commitment path for the next sequence recv counter.
func (solo *Solomachine) GetNextSequenceRecvPath(portID, channelID string) commitmenttypes.MerklePath {
path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(portID, channelID))
nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath)
require.NoError(solo.t, err)
return path

File diff suppressed because it is too large Load Diff