You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
mpt: shorten, generalize proofs (API and wire format change) (#79)
As suggested in rsc/tmp#21 and #75, shorten proofs
to remove the mptproof prefix, which costs 8 bytes per proof for little benefit.
In fact, if we are going to strip non-essential information, then the
single-byte prefix at the start of the proof is also unnecessary,
so this commit removes that too.
While we are changing the proof format, also use a varint-encoded length
prefix ahead of keys and values, so that the wire format of proofs applies
to any size keys and values, even though this API still assumes [32]byte.
(Followup work will revise the package API to allow variable-size keys and values.)
The handling of variable-length keys is slightly subtle since we have to
pad keys of different lengths to make them comparable for insertion in
the tree, but we want to avoid introducing any ambiguity where two different
keys pad to the same bit sequence. The answer is to pad with a sequence
that always differs from itself when not exactly aligned (so not all 0s or all 1s
or any other purely repeating sequence). Working within that constraint,
the chosen key padding is a 0x00 byte followed by as many 0xFF bytes as needed.
The 0x00 ensures that NUL-free text keys sort in the usual order in the tree,
while the 0xFFs that follow ensure that keys of other lengths
(even keys ending in 0x00 or 0xFF) will not have the same padding at the same
positions.
The previous Prove and Verify signatures were:
Prove(key Key) (proof Proof, err error)
Verify(snap Snapshot, key Key, proof Proof) (val Val, ok bool, err error)
Now they are:
Prove(key Key) (val Val, ok bool, proof Proof, err error)
Verify(snap Snapshot, key Key, val Val, ok bool, proof Proof) error
The invariant maintained is that Verify is passed the snapshot
plus all the results of Prove. The difference is that now the val, ok
are returned by Prove instead of Verify. Of course, they should
not be trusted until Prove has succeeded.
This commit also adds VerifyPresent and VerifyNotPresent helpers
wrapping Verify, which can improve clarity at some call sites.
This commit also adds test vectors for Verify in testdata/verify.txt,
for easier use by other implementations.
Copy file name to clipboardExpand all lines: mpt/DESIGN.md
+9-176Lines changed: 9 additions & 176 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Analogous to a [transparent log](https://research.swtch.com/tlog),
8
8
an MPT can cryptographically prove that a given key-value pair exists
9
9
(or that a key does not exist) in a given tree root.
10
10
By recording the sequence of tree roots in a transparent log,
11
-
a server can publish an record of the history of a key-value database,
11
+
a server can publish a record of the history of a key-value database,
12
12
in such a way that auditors can check that the database was correct
13
13
at all times, and clients can be sure the responses they received
14
14
came from the recorded database history.
@@ -67,80 +67,16 @@ After describing the fully in-memory version, we describe the hybrid version.
67
67
68
68
## Merkle Patricia Tree Overview {#mpt}
69
69
70
-
An MPT starts with the concept of a binary tree of depth 256, where the key-value
71
-
pairs are stored in the leaves at depth 256, and a lookup proceeds by walking
72
-
left or right according to each of the 256 key bits. The root node represents
73
-
the empty key prefix, its children represent key bit prefixes 0 and 1, their
74
-
children represent key bit prefixes 00, 01, 10, 11, and so on: at depth d,
75
-
the nodes represent key prefixes of d bits.
76
-
The original Key Transparency system at Google used exactly this data structure,
77
-
a [Merkle-hashed binary radix tree](https://github.com/google/keytransparency/blob/master/docs/overview.md).
78
-
Since then, the transparency community has realized that it works better
79
-
to apply Merkle hashing to a Patricia tree,
80
-
which adds three optimizations to the binary radix tree.
81
-
82
-
First, the tree is “path-compressed,” by removing inner nodes with a single child:
83
-
a node that would have pointed at a single-child node is replaced by its child,
84
-
recursively. Every node is therefore either a leaf or an inner node with two children.
85
-
The path compression ensures that there are exactly _N_ inner nodes for a tree with _N_+1 leaf nodes.
86
-
87
-
Second, unlike in a normal binary tree, an inner node stores only the bit position
88
-
that determines whether a lookup should proceed to the left or right child.
89
-
A lookup walks inner nodes down to some leaf, checking one bit at each step.
90
-
Only upon reaching the leaf does it do a full key comparison.
91
-
If it takes _O_(_K_) time to compare two keys, a normal binary tree would
92
-
take _O_(_K_ log _N_) time for a walk; this optimization
93
-
cuts the time to _O_(_K_ + log _N_).
94
-
Furthermore, inner nodes need not store associated keys,
95
-
cutting the number of stored keys by a factor of two.
96
-
97
-
Third, nodes are “joined” by merging one inner node and one leaf node into
98
-
a single stored node.
99
-
(After joining _N_ inner nodes to _N_ leaf nodes, that leaves one “leaf-only”
100
-
node not paired to an inner node,
101
-
but the node is still stored using the joined representation.)
102
-
Whether a stored node represents an inner node or leaf node depends
103
-
on how it is reached
104
-
while walking the tree.
105
-
This trick is not essential, but it simplifies storage management
106
-
to have only one type of stored node.
107
-
108
-
The path-compression optimization implies that an inner node for key prefix _p_ exists
109
-
if and only if the tree contains at least one key with prefix _p_0 and at least one key with prefix _p_1.
110
-
That is, the specific inner nodes that exist in a Patricia depend only on which
111
-
keys are present in the tree, not on their insertion order.
112
-
This implies that we can batch or otherwise reorder insertions of distinct keys
113
-
without affecting the final tree structure.
114
-
115
-
For more about standard Patricia trees, see TODO REFERENCE.
116
-
117
-
Cominbing the Merkle and Patricia pieces, a Merkle Patricia Tree provides the following operations:
70
+
See the doc comment at the top of tree.go for details about the MPT data structure and proofs.
71
+
72
+
A Merkle Patricia Tree provides the following operations:
118
73
119
74
- Set(key, val): add a new key-value pair to the map.
120
-
- Snap(version): set the tree's version and return the tree root's key prefix and hash.
121
-
- Prove(key): return a proof of the result of looking up a given key in the current snapshot. A separate library function `Verify` verifies a proof and returns the lookup result (whether the key was found and, if so, its associated value).
75
+
- Snap(version): set the tree's version and return the tree root's hash.
76
+
- Prove(key): return a lookup result (a value and whether the key was found), along with proof of the result.
77
+
A separate library function `Verify` verifies the result using the proof.
122
78
- Sync(): flush recent changes to disk.
123
-
124
-
The recursive hash of an MPT is defined as follows:
125
-
126
-
- The hash of a leaf node is the hash of its key and value.
127
-
- The hash of an inner node is the hash of its bit position and its left and right children's hashes.
128
-
129
-
A proof confirming that a key-value pair exists in an MPT with a given recursive hash
130
-
is the value followed by the bit position and sibling hash for every inner node along the path
131
-
back to the root.
132
-
The key-value pair can be hashed to obtain the hash of the leaf node,
133
-
and then the running hash can be hashed with the parent's bit position
134
-
and sibling hash to obtain the hash of the next inner node toward the root.
135
-
(Whether the running hash is the left or right child is determined by checking
136
-
the specified bit of the key.)
137
-
Recomputing the root's actual hash proves the lookup.
138
-
139
-
A proof denying that a target key exists in an MPT is almost identical.
140
-
It consists of the “other key” whose leaf would be found by looking up the key in the tree,
141
-
followed by the proof that that other key is in the tree.
142
-
The verification checks that the other key's proof is valid and also that
143
-
the target key and other key agree at every relevant bit position.
79
+
- Predict(keyvals): return the snapshot hash that would result from adding all the keyvals to the map.
144
80
145
81
An in-memory MPT implementation is in [mem.go](mem.go).
146
82
It was useful to write and debug that version before adding the
@@ -150,112 +86,9 @@ that version first.
150
86
It may also be useful read and understand that implementation
151
87
before proceeding to the disk implementation.
152
88
153
-
## Encoding Details {#encoding}
154
-
155
-
Any MPT implementation must define the exact encodings it uses.
156
-
The encodings used by this package are as follows.
157
-
158
-
### Tree Hashes
159
-
160
-
An empty tree is a special case that is otherwise independent
161
-
of the tree hash definition. In this implementation,
162
-
the hash of an empty tree is SHA256(_e_), the hash of the empty string (e3b0c442...7852b855).
163
-
164
-
The hash of a leaf node is the hash of the concatenation of the key and value
165
-
(both fixed-size 32-byte sequences).
166
-
167
-
The hash of an inner node at bit position _b_ with left and right child hashes _left_ and _right_
168
-
is SHA256(_left_ || _right_ || _b_) where _left_ and _right_ are 32-byte values and _b_ is a one-byte value.
169
-
170
-
### Proofs
171
-
172
-
Proofs are variable length strings beginning with the 8-byte sequence `mptproof`.
0 commit comments