github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/crypto/merkle/proof_key_path.go (about) 1 package merkle 2 3 import ( 4 "encoding/hex" 5 "errors" 6 "fmt" 7 "net/url" 8 "strings" 9 ) 10 11 /* 12 13 For generalized Merkle proofs, each layer of the proof may require an 14 optional key. The key may be encoded either by URL-encoding or 15 (upper-case) hex-encoding. 16 TODO: In the future, more encodings may be supported, like base32 (e.g. 17 /32:) 18 19 For example, for a Cosmos-SDK application where the first two proof layers 20 are ValueOps, and the third proof layer is an IAVLValueOp, the keys 21 might look like: 22 23 0: []byte("App") 24 1: []byte("IBC") 25 2: []byte{0x01, 0x02, 0x03} 26 27 Assuming that we know that the first two layers are always ASCII texts, we 28 probably want to use URLEncoding for those, whereas the third layer will 29 require HEX encoding for efficient representation. 30 31 kp := new(KeyPath) 32 kp.AppendKey([]byte("App"), KeyEncodingURL) 33 kp.AppendKey([]byte("IBC"), KeyEncodingURL) 34 kp.AppendKey([]byte{0x01, 0x02, 0x03}, KeyEncodingURL) 35 kp.String() // Should return "/App/IBC/x:010203" 36 37 NOTE: Key paths must begin with a `/`. 38 39 NOTE: All encodings *MUST* work compatibly, such that you can choose to use 40 whatever encoding, and the decoded keys will always be the same. In other 41 words, it's just as good to encode all three keys using URL encoding or HEX 42 encoding... it just wouldn't be optimal in terms of readability or space 43 efficiency. 44 45 NOTE: Punycode will never be supported here, because not all values can be 46 decoded. For example, no string decodes to the string "xn--blah" in 47 Punycode. 48 49 */ 50 51 type keyEncoding int 52 53 const ( 54 KeyEncodingURL keyEncoding = iota 55 KeyEncodingHex 56 KeyEncodingMax // Number of known encodings. Used for testing 57 ) 58 59 type Key struct { 60 name []byte 61 enc keyEncoding 62 } 63 64 type KeyPath []Key 65 66 func (pth KeyPath) AppendKey(key []byte, enc keyEncoding) KeyPath { 67 return append(pth, Key{key, enc}) 68 } 69 70 func (pth KeyPath) String() string { 71 res := "" 72 for _, key := range pth { 73 switch key.enc { 74 case KeyEncodingURL: 75 res += "/" + url.PathEscape(string(key.name)) 76 case KeyEncodingHex: 77 res += "/x:" + fmt.Sprintf("%X", key.name) 78 default: 79 panic("unexpected key encoding type") 80 } 81 } 82 return res 83 } 84 85 // Decode a path to a list of keys. Path must begin with `/`. 86 // Each key must use a known encoding. 87 func KeyPathToKeys(path string) (keys [][]byte, err error) { 88 if path == "" || path[0] != '/' { 89 return nil, errors.New("key path string must start with a forward slash '/'") 90 } 91 parts := strings.Split(path[1:], "/") 92 keys = make([][]byte, len(parts)) 93 for i, part := range parts { 94 if strings.HasPrefix(part, "x:") { 95 hexPart := part[2:] 96 key, err := hex.DecodeString(hexPart) 97 if err != nil { 98 return nil, fmt.Errorf("decoding hex-encoded part #%d: /%s: %w", i, part, err) 99 } 100 keys[i] = key 101 } else { 102 key, err := url.PathUnescape(part) 103 if err != nil { 104 return nil, fmt.Errorf("decoding url-encoded part #%d: /%s: %w", i, part, err) 105 } 106 keys[i] = []byte(key) // TODO Test this with random bytes, I'm not sure that it works for arbitrary bytes... 107 } 108 } 109 return keys, nil 110 }