Skip to content

Commit c7a2e11

Browse files
committed
torchwood: add SubtreeHash, ValidSubtree, CoverInterval, and subtree proofs
1 parent c903b96 commit c7a2e11

3 files changed

Lines changed: 846 additions & 99 deletions

File tree

subtree.go

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
package torchwood
2+
3+
import (
4+
"fmt"
5+
"math/bits"
6+
7+
"golang.org/x/mod/sumdb/tlog"
8+
)
9+
10+
// A SubtreeProof is a verifiable proof that a particular tree head contains a
11+
// particular subtree. A [tlog.TreeProof] is a special case of a SubtreeProof
12+
// where the subtree has start index 0. A [tlog.RecordProof] is a special case
13+
// of a SubtreeProof where the subtree has size 1.
14+
//
15+
// draft-ietf-plants-merkle-tree-certs-04 calls this a "Subtree Consistency
16+
// Proof".
17+
type SubtreeProof []tlog.Hash
18+
19+
// ProveSubtree returns the proof that the tree of size t contains the subtree
20+
// [start, end), where start and end are record indexes.
21+
func ProveSubtree(t, start, end int64, r tlog.HashReader) (SubtreeProof, error) {
22+
if t < 0 || end > t || !ValidSubtree(start, end) {
23+
return nil, fmt.Errorf("tlog: invalid inputs in ProveSubtree")
24+
}
25+
indexes := subtreeProofIndex(0, t, start, end, true, nil)
26+
if len(indexes) == 0 {
27+
return SubtreeProof{}, nil
28+
}
29+
hashes, err := r.ReadHashes(indexes)
30+
if err != nil {
31+
return nil, err
32+
}
33+
if len(hashes) != len(indexes) {
34+
return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
35+
}
36+
37+
p, hashes := subtreeProof(0, t, start, end, true, hashes)
38+
if len(hashes) != 0 {
39+
panic("tlog: bad index math in ProveSubtree")
40+
}
41+
return p, nil
42+
}
43+
44+
// subtreeProofIndex builds the list of indexes needed to construct the proof
45+
// that the subtree [start, end) is contained in the node with leaves [lo, hi).
46+
// It appends those indexes to need and returns the result. See
47+
// draft-ietf-plants-merkle-tree-certs-04, Section 4.4. b reports whether the
48+
// verifier already knows the hash of the subtree portion in [lo, hi); it starts
49+
// true and becomes false past the first straddled node.
50+
func subtreeProofIndex(lo, hi, start, end int64, b bool, need []int64) []int64 {
51+
if !(lo <= start && start < end && end <= hi) {
52+
panic("tlog: bad math in subtreeProofIndex")
53+
}
54+
if lo == start && hi == end {
55+
if b {
56+
return need
57+
}
58+
return subTreeIndex(lo, hi, need)
59+
}
60+
k, _ := maxpow2(hi - lo)
61+
switch {
62+
case end <= lo+k: // subtree in the left child
63+
need = subtreeProofIndex(lo, lo+k, start, end, b, need)
64+
need = subTreeIndex(lo+k, hi, need)
65+
case lo+k <= start: // subtree in the right child
66+
need = subTreeIndex(lo, lo+k, need)
67+
need = subtreeProofIndex(lo+k, hi, start, end, b, need)
68+
default: // subtree straddles the split, which implies start == lo
69+
if start != lo {
70+
panic("tlog: bad math in subtreeProofIndex")
71+
}
72+
need = subtreeProofIndex(lo+k, hi, lo+k, end, false, need)
73+
need = subTreeIndex(lo, lo+k, need)
74+
}
75+
return need
76+
}
77+
78+
// subtreeProof constructs the proof that the subtree [start, end) is contained
79+
// in the node with leaves [lo, hi). It returns any leftover hashes as well.
80+
func subtreeProof(lo, hi, start, end int64, b bool, hashes []tlog.Hash) (SubtreeProof, []tlog.Hash) {
81+
if !(lo <= start && start < end && end <= hi) {
82+
panic("tlog: bad math in subtreeProof")
83+
}
84+
if lo == start && hi == end {
85+
if b {
86+
// The verifier knows the subtree hash, so we don't need to send it.
87+
return SubtreeProof{}, hashes
88+
}
89+
th, hashes := subTreeHash(lo, hi, hashes)
90+
return SubtreeProof{th}, hashes
91+
}
92+
k, _ := maxpow2(hi - lo)
93+
var p SubtreeProof
94+
var th tlog.Hash
95+
switch {
96+
case end <= lo+k: // subtree in the left child
97+
p, hashes = subtreeProof(lo, lo+k, start, end, b, hashes)
98+
th, hashes = subTreeHash(lo+k, hi, hashes)
99+
case lo+k <= start: // subtree in the right child
100+
th, hashes = subTreeHash(lo, lo+k, hashes)
101+
p, hashes = subtreeProof(lo+k, hi, start, end, b, hashes)
102+
default: // subtree straddles the split, which implies start == lo
103+
if start != lo {
104+
panic("tlog: bad math in subtreeProof")
105+
}
106+
p, hashes = subtreeProof(lo+k, hi, lo+k, end, false, hashes)
107+
th, hashes = subTreeHash(lo, lo+k, hashes)
108+
}
109+
return append(p, th), hashes
110+
}
111+
112+
// CheckSubtree verifies that p is a valid proof that the tree of size t
113+
// with hash th contains the subtree [start, end) with hash sh.
114+
func CheckSubtree(p SubtreeProof, t int64, th tlog.Hash, start, end int64, sh tlog.Hash) error {
115+
if t < 0 || end > t || !ValidSubtree(start, end) {
116+
return fmt.Errorf("tlog: invalid inputs in CheckSubtree")
117+
}
118+
sh2, th2, err := runSubtreeProof(p, 0, t, start, end, true, sh)
119+
if err != nil {
120+
return err
121+
}
122+
if sh2 == sh && th2 == th {
123+
return nil
124+
}
125+
return errProofFailed
126+
}
127+
128+
// runSubtreeProof runs the proof p that the subtree [start, end) is contained in
129+
// the node with leaves [lo, hi). Running the proof means constructing and
130+
// returning the implied hashes of both the subtree and the node.
131+
func runSubtreeProof(p SubtreeProof, lo, hi, start, end int64, b bool, sh tlog.Hash) (subtreeHash, nodeHash tlog.Hash, err error) {
132+
if !(lo <= start && start < end && end <= hi) {
133+
panic("tlog: bad math in runSubtreeProof")
134+
}
135+
if lo == start && hi == end {
136+
if b {
137+
// The verifier knows the subtree hash, and it is the node hash.
138+
if len(p) != 0 {
139+
return tlog.Hash{}, tlog.Hash{}, errProofFailed
140+
}
141+
return sh, sh, nil
142+
}
143+
if len(p) != 1 {
144+
return tlog.Hash{}, tlog.Hash{}, errProofFailed
145+
}
146+
return p[0], p[0], nil
147+
}
148+
if len(p) == 0 {
149+
return tlog.Hash{}, tlog.Hash{}, errProofFailed
150+
}
151+
k, _ := maxpow2(hi - lo)
152+
switch {
153+
case end <= lo+k: // subtree in the left child
154+
sh2, nh, err := runSubtreeProof(p[:len(p)-1], lo, lo+k, start, end, b, sh)
155+
if err != nil {
156+
return tlog.Hash{}, tlog.Hash{}, err
157+
}
158+
return sh2, tlog.NodeHash(nh, p[len(p)-1]), nil
159+
case lo+k <= start: // subtree in the right child
160+
sh2, nh, err := runSubtreeProof(p[:len(p)-1], lo+k, hi, start, end, b, sh)
161+
if err != nil {
162+
return tlog.Hash{}, tlog.Hash{}, err
163+
}
164+
return sh2, tlog.NodeHash(p[len(p)-1], nh), nil
165+
default: // subtree straddles the split, which implies start == lo
166+
if start != lo {
167+
panic("tlog: bad math in runSubtreeProof")
168+
}
169+
left := p[len(p)-1]
170+
sh2, nh, err := runSubtreeProof(p[:len(p)-1], lo+k, hi, lo+k, end, false, sh)
171+
if err != nil {
172+
return tlog.Hash{}, tlog.Hash{}, err
173+
}
174+
return tlog.NodeHash(left, sh2), tlog.NodeHash(left, nh), nil
175+
}
176+
}
177+
178+
// A RecordInSubtreeProof is a verifiable proof that a particular subtree
179+
// contains a particular record. A [tlog.RecordProof] is a special case of a
180+
// RecordInSubtreeProof where the subtree has start index 0.
181+
//
182+
// draft-ietf-plants-merkle-tree-certs-04 calls this a "Subtree Inclusion Proof".
183+
type RecordInSubtreeProof []tlog.Hash
184+
185+
// ProveRecordInSubtree returns the proof that the subtree [start, end) contains
186+
// the record with index n.
187+
func ProveRecordInSubtree(start, end, n int64, r tlog.HashReader) (RecordInSubtreeProof, error) {
188+
if !ValidSubtree(start, end) || n < start || n >= end {
189+
return nil, fmt.Errorf("tlog: invalid inputs in ProveRecordInSubtree")
190+
}
191+
indexes := leafProofIndex(start, end, n, nil)
192+
if len(indexes) == 0 {
193+
return RecordInSubtreeProof{}, nil
194+
}
195+
hashes, err := r.ReadHashes(indexes)
196+
if err != nil {
197+
return nil, err
198+
}
199+
if len(hashes) != len(indexes) {
200+
return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
201+
}
202+
203+
p, hashes := leafProof(start, end, n, hashes)
204+
if len(hashes) != 0 {
205+
panic("tlog: bad index math in ProveRecordInSubtree")
206+
}
207+
return p, nil
208+
}
209+
210+
// CheckRecordInSubtree verifies that p is a valid proof that the subtree
211+
// [start, end) with hash sh contains the record with index n and hash h.
212+
func CheckRecordInSubtree(p RecordInSubtreeProof, start, end int64, sh tlog.Hash, n int64, h tlog.Hash) error {
213+
if !ValidSubtree(start, end) || n < start || n >= end {
214+
return fmt.Errorf("tlog: invalid inputs in CheckRecordInSubtree")
215+
}
216+
sh2, err := runRecordProof(p, start, end, n, h)
217+
if err != nil {
218+
return err
219+
}
220+
if sh2 == sh {
221+
return nil
222+
}
223+
return errProofFailed
224+
}
225+
226+
// SubtreeHash computes the hash for the subtree [start, end) using the
227+
// HashReader to obtain previously stored hashes. SubtreeHash makes a single
228+
// call to ReadHash requesting at most 1 + log₂(end - start) hashes.
229+
//
230+
// A [tlog.TreeHash] is a special case of a SubtreeHash where start is 0.
231+
//
232+
// See draft-ietf-plants-merkle-tree-certs-04, Section 4.
233+
func SubtreeHash(start, end int64, r tlog.HashReader) (tlog.Hash, error) {
234+
if !ValidSubtree(start, end) {
235+
return tlog.Hash{}, fmt.Errorf("tlog: invalid inputs in SubtreeHash")
236+
}
237+
indexes := subTreeIndex(start, end, nil)
238+
hashes, err := r.ReadHashes(indexes)
239+
if err != nil {
240+
return tlog.Hash{}, err
241+
}
242+
if len(hashes) != len(indexes) {
243+
return tlog.Hash{}, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
244+
}
245+
hash, hashes := subTreeHash(start, end, hashes)
246+
if len(hashes) != 0 {
247+
panic("tlog: bad index math in SubtreeHash")
248+
}
249+
return hash, nil
250+
}
251+
252+
// ValidSubtree reports whether [start, end) is a valid subtree.
253+
//
254+
// See draft-ietf-plants-merkle-tree-certs-04, Section 4.
255+
func ValidSubtree(start, end int64) bool {
256+
if start < 0 || end <= start {
257+
return false
258+
}
259+
return start&(bitCeil(end-start)-1) == 0
260+
}
261+
262+
// CoverInterval returns leftStart and mid for the two subtrees [leftStart, mid)
263+
// and [mid, end) that cover the interval [start, end) as efficiently as
264+
// possible.
265+
//
266+
// The subtrees are adjacent and the second ends at end, but the first may begin
267+
// before start. See draft-ietf-plants-merkle-tree-certs-04, Section 4.5.
268+
//
269+
// It is an error if start < 0, end <= start, or [start, end) is already a
270+
// subtree.
271+
func CoverInterval(start, end int64) (leftStart, mid int64, err error) {
272+
if start < 0 || end <= start {
273+
return 0, 0, fmt.Errorf("tlog: invalid interval in CoverInterval")
274+
}
275+
if ValidSubtree(start, end) {
276+
return 0, 0, fmt.Errorf("tlog: interval is already a subtree in CoverInterval")
277+
}
278+
last := end - 1
279+
split := bits.Len64(uint64(start^last)) - 1
280+
mask := int64(1)<<split - 1
281+
mid = last &^ mask
282+
leftSplit := bits.Len64(uint64(^start & mask))
283+
leftStart = start &^ (int64(1)<<leftSplit - 1)
284+
return leftStart, mid, nil
285+
}
286+
287+
// bitCeil returns the smallest power of two not smaller than n, for n >= 1.
288+
func bitCeil(n int64) int64 {
289+
return 1 << bits.Len64(uint64(n-1))
290+
}

0 commit comments

Comments
 (0)