package merkle import ( "bytes" "errors" "fmt" tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) //---------------------------------------- // ProofOp gets converted to an instance of ProofOperator: // ProofOperator is a layer for calculating intermediate Merkle roots // when a series of Merkle trees are chained together. // Run() takes leaf values from a tree and returns the Merkle // root for the corresponding tree. It takes and returns a list of bytes // to allow multiple leaves to be part of a single proof, for instance in a range proof. // ProofOp() encodes the ProofOperator in a generic way so it can later be // decoded with OpDecoder. type ProofOperator interface { Run([][]byte) ([][]byte, error) GetKey() []byte ProofOp() tmcrypto.ProofOp } //---------------------------------------- // Operations on a list of ProofOperators // ProofOperators is a slice of ProofOperator(s). // Each operator will be applied to the input value sequentially // and the last Merkle root will be verified with already known data type ProofOperators []ProofOperator func (poz ProofOperators) VerifyValue(root []byte, keypath string, value []byte) (err error) { return poz.Verify(root, keypath, [][]byte{value}) } func (poz ProofOperators) Verify(root []byte, keypath string, args [][]byte) (err error) { keys, err := KeyPathToKeys(keypath) if err != nil { return } for i, op := range poz { key := op.GetKey() if len(key) != 0 { if len(keys) == 0 { return fmt.Errorf("key path has insufficient # of parts: expected no more keys but got %+v", string(key)) } lastKey := keys[len(keys)-1] if !bytes.Equal(lastKey, key) { return fmt.Errorf("key mismatch on operation #%d: expected %+v but got %+v", i, string(lastKey), string(key)) } keys = keys[:len(keys)-1] } args, err = op.Run(args) if err != nil { return } } if !bytes.Equal(root, args[0]) { return fmt.Errorf("calculated root hash is invalid: expected %X but got %X", root, args[0]) } if len(keys) != 0 { return errors.New("keypath not consumed all") } return nil } //---------------------------------------- // ProofRuntime - main entrypoint type OpDecoder func(tmcrypto.ProofOp) (ProofOperator, error) type ProofRuntime struct { decoders map[string]OpDecoder } func NewProofRuntime() *ProofRuntime { return &ProofRuntime{ decoders: make(map[string]OpDecoder), } } func (prt *ProofRuntime) RegisterOpDecoder(typ string, dec OpDecoder) { _, ok := prt.decoders[typ] if ok { panic("already registered for type " + typ) } prt.decoders[typ] = dec } func (prt *ProofRuntime) Decode(pop tmcrypto.ProofOp) (ProofOperator, error) { decoder := prt.decoders[pop.Type] if decoder == nil { return nil, fmt.Errorf("unrecognized proof type %v", pop.Type) } return decoder(pop) } func (prt *ProofRuntime) DecodeProof(proof *tmcrypto.ProofOps) (ProofOperators, error) { poz := make(ProofOperators, 0, len(proof.Ops)) for _, pop := range proof.Ops { operator, err := prt.Decode(pop) if err != nil { return nil, fmt.Errorf("decoding a proof operator: %w", err) } poz = append(poz, operator) } return poz, nil } func (prt *ProofRuntime) VerifyValue(proof *tmcrypto.ProofOps, root []byte, keypath string, value []byte) (err error) { return prt.Verify(proof, root, keypath, [][]byte{value}) } // TODO In the long run we'll need a method of classifcation of ops, // whether existence or absence or perhaps a third? func (prt *ProofRuntime) VerifyAbsence(proof *tmcrypto.ProofOps, root []byte, keypath string) (err error) { return prt.Verify(proof, root, keypath, nil) } func (prt *ProofRuntime) Verify(proof *tmcrypto.ProofOps, root []byte, keypath string, args [][]byte) (err error) { poz, err := prt.DecodeProof(proof) if err != nil { return fmt.Errorf("decoding proof: %w", err) } return poz.Verify(root, keypath, args) } // DefaultProofRuntime only knows about value proofs. // To use e.g. IAVL proofs, register op-decoders as // defined in the IAVL package. func DefaultProofRuntime() (prt *ProofRuntime) { prt = NewProofRuntime() prt.RegisterOpDecoder(ProofOpValue, ValueOpDecoder) return }