Commit KeyPath

This commit is contained in:
Jae Kwon 2018-07-12 21:57:37 -07:00
parent 3a5a7a776e
commit 7495528fb8
2 changed files with 115 additions and 6 deletions

View File

@ -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 {

View File

@ -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
}