You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
3.0 KiB

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