From 7495528fb8345d92d7fe8649aecd1e2ffff00fba Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 12 Jul 2018 21:57:37 -0700 Subject: [PATCH] Commit KeyPath --- crypto/merkle/proof.go | 15 +++-- crypto/merkle/proof_key_path.go | 106 ++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 crypto/merkle/proof_key_path.go diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 33159186..ed16b997 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -32,10 +32,13 @@ type ProofOperator interface { type ProofOperators []ProofOperator +// XXX Reorder value/keys. +// XXX Replace keys with keyString (the result of KeyPath.String()) func (poz ProofOperators) VerifyValue(root []byte, value []byte, keys ...string) (err error) { return poz.Verify(root, [][]byte{value}, keys...) } +// XXX Replace keys with keyString (the result of KeyPath.String()) func (poz ProofOperators) Verify(root []byte, args [][]byte, keys ...string) (err error) { for i, op := range poz { key := op.GetKey() @@ -99,22 +102,22 @@ func (prt *ProofRuntime) DecodeProof(proof *Proof) (poz ProofOperators, err erro return } -// XXX Reorder value/keys, and figure out how to merge keys into a single string -// after figuring out encoding between bytes/string. +// XXX Reorder value/keys. +// XXX Replace keys with keyString (the result of KeyPath.String()). func (prt *ProofRuntime) VerifyValue(proof *Proof, root []byte, value []byte, keys ...string) (err error) { return prt.Verify(proof, root, [][]byte{value}, keys...) } -// XXX Reorder value/keys, and figure out how to merge keys into a single string -// after figuring out encoding between bytes/string. +// XXX Reorder value/keys. +// XXX Replace keys with keyString (the result of KeyPath.String()). // 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 *Proof, root []byte, keys ...string) (err error) { return prt.Verify(proof, root, nil, keys...) } -// XXX Reorder value/keys, and figure out how to merge keys into a single string -// after figuring out encoding between bytes/string. +// XXX Reorder value/keys. +// XXX Replace keys with keyString (the result of KeyPath.String()). func (prt *ProofRuntime) Verify(proof *Proof, root []byte, args [][]byte, keys ...string) (err error) { poz, err := prt.DecodeProof(proof) if err != nil { diff --git a/crypto/merkle/proof_key_path.go b/crypto/merkle/proof_key_path.go new file mode 100644 index 00000000..823283e2 --- /dev/null +++ b/crypto/merkle/proof_key_path.go @@ -0,0 +1,106 @@ +package merkle + +import ( + "encoding/hex" + "fmt" + "net/url" + "strings" + + cmn "github.com/tendermint/tmlibs/common" +) + +/* + + For generalized Merkle proofs, each layer of the proof may require an + optional key. The key may be encoded either by URL-encoding or + (upper-case) hex-encoding. + TODO: In the future, more encodings may be supported, like base32 (e.g. + /32:) + + For example, for a Cosmos-SDK application where the first two proof layers + are SimpleValueOps, and the third proof layer is an IAVLValueOp, the keys + might look like: + + 0: []byte("App") + 1: []byte("IBC") + 2: []byte{0x01, 0x02, 0x03} + + Assuming that we know that the first two layers are always ASCII texts, we + probably want to use URLEncoding for those, whereas the third layer will + require HEX encoding for efficient representation. + + kp := new(KeyPath) + kp.AppendKey([]byte("App"), KeyEncodingURL) + kp.AppendKey([]byte("IBC"), KeyEncodingURL) + kp.AppendKey([]byte{0x01, 0x02, 0x03}, KeyEncodingURL) + kp.String() // Should return "/App/IBC/x:010203" + + NOTE: All encodings *MUST* work compatibly, such that you can choose to use + whatever encoding, and the decoded keys will always be the same. In other + words, it's just as good to encode all three keys using URL encoding or HEX + encoding... it just wouldn't be optimal in terms of readability or space + efficiency. + + NOTE: Punycode will never be supported here, because not all values can be + decoded. For example, no string decodes to the string "xn--blah" in + Punycode. + +*/ + +type keyEncoding int + +const ( + KeyEncodingURL keyEncoding = iota + KeyEncodingHex +) + +type KeyPath struct { + keys [][]byte + encs []keyEncoding +} + +func (pth *KeyPath) AppendKey(key []byte, enc keyEncoding) { + pth.keys = append(pth.keys, key) + pth.encs = append(pth.encs, enc) +} + +func (pth *KeyPath) String() string { + res := "" + for i := 0; i < len(pth.keys); i++ { + key, enc := pth.keys[i], pth.encs[i] + switch enc { + case KeyEncodingURL: + res += "/" + url.PathEscape(string(key)) + case KeyEncodingHex: + res += "/x:" + fmt.Sprintf("%X", key) + default: + panic("unexpected key encoding type") + } + } + return res +} + +func KeyPathToKeys(path string) (keys [][]byte, err error) { + if path == "" || path[0] != '/' { + return nil, cmn.NewError("key path string must start with a forward slash '/'") + } + parts := strings.Split(path[1:], "/") + keys = make([][]byte, len(parts)) + for i, part := range parts { + if strings.HasPrefix(part, "x:") { + hexPart := part[2:] + key, err := hex.DecodeString(hexPart) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding hex-encoded part #%d: /%s", i, part) + } + keys[i] = key + } else { + key, err := url.PathUnescape(part) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding url-encoded part #%d: /%s", i, part) + } + keys[i] = []byte(key) // TODO Test this with random bytes, I'm not sure that it works for arbitrary bytes... + } + } + return keys, nil +}