Skip to content

Commit b6ac8b0

Browse files
committed
torchwood: update subtree validity definition to allow empty subtrees
For ietf-plants-wg/merkle-tree-certs#275
1 parent bfa5abc commit b6ac8b0

2 files changed

Lines changed: 132 additions & 45 deletions

File tree

subtree.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package torchwood
22

33
import (
4+
"crypto/sha256"
45
"fmt"
56
"math/bits"
67

@@ -22,6 +23,10 @@ func ProveSubtree(t, start, end int64, r tlog.HashReader) (SubtreeProof, error)
2223
if t < 0 || t > maxN || end > t || !ValidSubtree(start, end) {
2324
return nil, fmt.Errorf("tlog: invalid inputs in ProveSubtree")
2425
}
26+
if start == end {
27+
// The proof for an empty subtree is empty.
28+
return SubtreeProof{}, nil
29+
}
2530
indexes := subtreeProofIndex(0, t, start, end, true, nil)
2631
if len(indexes) == 0 {
2732
return SubtreeProof{}, nil
@@ -115,6 +120,14 @@ func CheckSubtree(p SubtreeProof, t int64, th tlog.Hash, start, end int64, sh tl
115120
if t < 0 || t > maxN || end > t || !ValidSubtree(start, end) {
116121
return fmt.Errorf("tlog: invalid inputs in CheckSubtree")
117122
}
123+
if start == end {
124+
// An empty subtree is contained in every tree, so th is not checked:
125+
// the proof must be empty and sh must be the hash of the empty tree.
126+
if len(p) != 0 || sh != emptyHash {
127+
return errProofFailed
128+
}
129+
return nil
130+
}
118131
sh2, th2, err := runSubtreeProof(p, 0, t, start, end, true, sh)
119132
if err != nil {
120133
return err
@@ -224,16 +237,20 @@ func CheckRecordInSubtree(p RecordInSubtreeProof, start, end int64, sh tlog.Hash
224237
}
225238

226239
// 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.
240+
// HashReader to obtain previously stored hashes. SubtreeHash makes at most a
241+
// single call to ReadHash requesting at most 1 + log₂(end - start) hashes.
229242
//
230243
// A [tlog.TreeHash] is a special case of a SubtreeHash where start is 0.
244+
// The hash of an empty subtree is the hash of the empty string.
231245
//
232246
// See draft-ietf-plants-merkle-tree-certs-04, Section 4.
233247
func SubtreeHash(start, end int64, r tlog.HashReader) (tlog.Hash, error) {
234248
if !ValidSubtree(start, end) {
235249
return tlog.Hash{}, fmt.Errorf("tlog: invalid inputs in SubtreeHash")
236250
}
251+
if start == end {
252+
return emptyHash, nil
253+
}
237254
indexes := subTreeIndex(start, end, nil)
238255
hashes, err := r.ReadHashes(indexes)
239256
if err != nil {
@@ -249,31 +266,38 @@ func SubtreeHash(start, end int64, r tlog.HashReader) (tlog.Hash, error) {
249266
return hash, nil
250267
}
251268

252-
// ValidSubtree reports whether [start, end) is a valid subtree.
269+
// ValidSubtree reports whether [start, end) is a valid subtree. Note that
270+
// empty subtrees [x, x) are valid.
253271
//
254272
// See draft-ietf-plants-merkle-tree-certs-04, Section 4.
255273
func ValidSubtree(start, end int64) bool {
256-
if start < 0 || end <= start || end-start > maxN {
274+
if start < 0 || end < start || end-start > maxN {
257275
return false
258276
}
259-
return start&(bitCeil(end-start)-1) == 0
277+
return start == end || start&(bitCeil(end-start)-1) == 0
260278
}
261279

280+
// emptyHash is the hash of the empty tree and of empty subtrees, per
281+
// RFC 6962, Section 2.1. It is the hash of the empty string.
282+
var emptyHash = tlog.Hash(sha256.Sum256(nil))
283+
262284
// CoverInterval returns leftStart and mid for the two subtrees [leftStart, mid)
263285
// and [mid, end) that cover the interval [start, end) as efficiently as
264286
// possible.
265287
//
266288
// 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.
289+
// before start. If the interval has size zero or one, the first subtree is the
290+
// interval itself and the second is empty. If [start, end) is a subtree of size
291+
// larger than one, the two subtrees are its children. See
292+
// draft-ietf-plants-merkle-tree-certs-04, Section 4.5.
268293
//
269-
// It is an error if start < 0, end <= start, or [start, end) is already a
270-
// subtree.
294+
// It is an error if start < 0 or end < start.
271295
func CoverInterval(start, end int64) (leftStart, mid int64, err error) {
272-
if start < 0 || end <= start {
296+
if start < 0 || end < start {
273297
return 0, 0, fmt.Errorf("tlog: invalid interval in CoverInterval")
274298
}
275-
if ValidSubtree(start, end) {
276-
return 0, 0, fmt.Errorf("tlog: interval is already a subtree in CoverInterval")
299+
if end-start <= 1 {
300+
return start, end, nil
277301
}
278302
last := end - 1
279303
split := bits.Len64(uint64(start^last)) - 1

subtree_test.go

Lines changed: 97 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ func TestAccumulatedSubtreeProof(t *testing.T) {
1717
for n := int64(0); n <= 130; n++ {
1818
th, err := tlog.TreeHash(n, tree)
1919
fatalIfErr(t, err)
20-
for end := int64(1); end <= n; end++ {
21-
for start := int64(0); start < end; start++ {
20+
for end := int64(0); end <= n; end++ {
21+
for start := int64(0); start <= end; start++ {
2222
if !torchwood.ValidSubtree(start, end) {
2323
continue
2424
}
@@ -43,7 +43,13 @@ func TestAccumulatedSubtreeProof(t *testing.T) {
4343
}
4444

4545
err = torchwood.CheckSubtree(p, n, flip(th), start, end, sh)
46-
if err == nil {
46+
if start == end {
47+
// An empty subtree is contained in every tree,
48+
// so the tree hash is not checked.
49+
if err != nil {
50+
t.Errorf("CheckSubtree rejected wrong th for [%d, %d) in tree of size %d", start, end, n)
51+
}
52+
} else if err == nil {
4753
t.Errorf("CheckSubtree accepted wrong th for [%d, %d) in tree of size %d", start, end, n)
4854
}
4955

@@ -76,7 +82,7 @@ func TestAccumulatedSubtreeProof(t *testing.T) {
7682
}
7783
}
7884
got := o.Sum(nil)
79-
want := "c586ebbb73a5621baf2140095d87dde934e3b6503a562a1a5215b8209edd083d"
85+
want := "10fa99b37bf9bf9ffa26b412fbd98bd75363256d0b75d61bc4538b9c9c5a0a74"
8086
if fmt.Sprintf("%x", got) != want {
8187
t.Errorf("AccumulatedSubtreeProof hash = %x; want %s", got, want)
8288
}
@@ -85,8 +91,8 @@ func TestAccumulatedSubtreeProof(t *testing.T) {
8591
func TestAccumulatedRecordInSubtreeProof(t *testing.T) {
8692
o := sha256.New()
8793
leaves, tree := buildTree(t, 130)
88-
for end := int64(1); end <= 130; end++ {
89-
for start := int64(0); start < end; start++ {
94+
for end := int64(0); end <= 130; end++ {
95+
for start := int64(0); start <= end; start++ {
9096
if !torchwood.ValidSubtree(start, end) {
9197
continue
9298
}
@@ -150,8 +156,8 @@ func mangleProof(p []tlog.Hash, i int) []tlog.Hash {
150156
func TestAccumulatedSubtreeHash(t *testing.T) {
151157
o := sha256.New()
152158
_, tree := buildTree(t, 130)
153-
for end := int64(1); end <= 130; end++ {
154-
for start := int64(0); start < end; start++ {
159+
for end := int64(0); end <= 130; end++ {
160+
for start := int64(0); start <= end; start++ {
155161
if !torchwood.ValidSubtree(start, end) {
156162
continue
157163
}
@@ -161,27 +167,23 @@ func TestAccumulatedSubtreeHash(t *testing.T) {
161167
}
162168
}
163169
got := o.Sum(nil)
164-
want := "94a95384a8c69acea9b50d035a58285b3a777cb7a724005faa5e1f1e1190007f"
170+
want := "b82806ad4265bb151c1119c0f4db437bb4d1a1f887b3a7fba1cd4ebf552e3e81"
165171
if fmt.Sprintf("%x", got) != want {
166172
t.Errorf("AccumulatedSubtreeHash hash = %x; want %s", got, want)
167173
}
168174
}
169175

170176
func TestAccumulatedCoverInterval(t *testing.T) {
171177
o := sha256.New()
172-
for end := int64(1); end <= 130; end++ {
173-
for start := int64(0); start < end; start++ {
174-
if torchwood.ValidSubtree(start, end) {
175-
fmt.Fprintf(o, "[%d, %d)\n", start, end)
176-
} else {
177-
ls, mid, err := torchwood.CoverInterval(start, end)
178-
fatalIfErr(t, err)
179-
fmt.Fprintf(o, "[%d, %d) [%d, %d)\n", ls, mid, mid, end)
180-
}
178+
for end := int64(0); end <= 130; end++ {
179+
for start := int64(0); start <= end; start++ {
180+
ls, mid, err := torchwood.CoverInterval(start, end)
181+
fatalIfErr(t, err)
182+
fmt.Fprintf(o, "[%d, %d) [%d, %d)\n", ls, mid, mid, end)
181183
}
182184
}
183185
got := o.Sum(nil)
184-
want := "e0aecb912a10c57d753b6ecc64db73217f9bc4ed10fcb4e9062be3b6fbe1ebfd"
186+
want := "7fd9c8b926e9d2b5cf831560e8ce295a5ef97ad5c5ede4ea0dea28a8c8fc8bb0"
185187
if fmt.Sprintf("%x", got) != want {
186188
t.Errorf("AccumulatedCoverInterval hash = %x; want %s", got, want)
187189
}
@@ -202,7 +204,8 @@ func TestValidSubtree(t *testing.T) {
202204
{5, 13, false}, // size 8, 5 not a multiple of 8
203205
{1, 7, false}, // size 6, 1 not a multiple of 8
204206
{7, 9, false}, // size 2, 7 not a multiple of 2
205-
{0, 0, false}, // empty
207+
{0, 0, true}, // empty
208+
{5, 5, true}, // empty, non-zero start
206209
{5, 3, false}, // end < start
207210
{-1, 3, false}, // negative start
208211
{0, 1 << 62, true}, // size 2^62, the maximum span
@@ -218,8 +221,8 @@ func TestValidSubtree(t *testing.T) {
218221
// refFindSubtrees is a direct port of the find_subtrees Python reference in
219222
// draft-ietf-plants-merkle-tree-certs, Section 4.5.
220223
func refFindSubtrees(start, end int64) [][2]int64 {
221-
if end-start == 1 {
222-
return [][2]int64{{start, end}}
224+
if end-start <= 1 {
225+
return [][2]int64{{start, end}, {end, end}}
223226
}
224227
last := end - 1
225228
split := bits.Len64(uint64(start^last)) - 1
@@ -231,8 +234,8 @@ func refFindSubtrees(start, end int64) [][2]int64 {
231234
}
232235

233236
func TestCoverInterval(t *testing.T) {
234-
// Error cases: bad range, and intervals that are already subtrees.
235-
for _, tt := range [][2]int64{{-1, 5}, {5, 5}, {5, 3}, {4, 8}, {7, 8}, {0, 16}} {
237+
// Error cases: bad ranges.
238+
for _, tt := range [][2]int64{{-1, 5}, {5, 3}} {
236239
if _, _, err := torchwood.CoverInterval(tt[0], tt[1]); err == nil {
237240
t.Errorf("CoverInterval(%d, %d) = nil error; want error", tt[0], tt[1])
238241
}
@@ -243,32 +246,44 @@ func TestCoverInterval(t *testing.T) {
243246
t.Errorf("CoverInterval(5, 13) = %d, %d, %v; want 4, 8, nil", ls, mid, err)
244247
}
245248

249+
// Size-one and empty intervals are covered by themselves and an
250+
// empty subtree.
251+
if ls, mid, err := torchwood.CoverInterval(7, 8); err != nil || ls != 7 || mid != 8 {
252+
t.Errorf("CoverInterval(7, 8) = %d, %d, %v; want 7, 8, nil", ls, mid, err)
253+
}
254+
if ls, mid, err := torchwood.CoverInterval(5, 5); err != nil || ls != 5 || mid != 5 {
255+
t.Errorf("CoverInterval(5, 5) = %d, %d, %v; want 5, 5, nil", ls, mid, err)
256+
}
257+
258+
// A subtree of size larger than one is covered by its two children.
259+
if ls, mid, err := torchwood.CoverInterval(4, 8); err != nil || ls != 4 || mid != 6 {
260+
t.Errorf("CoverInterval(4, 8) = %d, %d, %v; want 4, 6, nil", ls, mid, err)
261+
}
262+
if ls, mid, err := torchwood.CoverInterval(0, 13); err != nil || ls != 0 || mid != 8 {
263+
t.Errorf("CoverInterval(0, 13) = %d, %d, %v; want 0, 8, nil", ls, mid, err)
264+
}
265+
246266
for start := range int64(64) {
247-
for end := start + 1; end <= 64; end++ {
267+
for end := start; end <= 64; end++ {
248268
ls, mid, err := torchwood.CoverInterval(start, end)
249-
if torchwood.ValidSubtree(start, end) {
250-
if err == nil {
251-
t.Errorf("CoverInterval(%d, %d) = nil error for a valid subtree", start, end)
252-
}
253-
continue
254-
}
255269
if err != nil {
256270
t.Errorf("CoverInterval(%d, %d) = %v", start, end, err)
257271
continue
258272
}
259273
// Matches the spec reference.
260-
if want := refFindSubtrees(start, end); ls != want[0][0] || mid != want[0][1] || want[1][1] != end {
274+
if want := refFindSubtrees(start, end); ls != want[0][0] || mid != want[0][1] ||
275+
want[1][0] != mid || want[1][1] != end {
261276
t.Errorf("CoverInterval(%d, %d) = %d, %d; want %v", start, end, ls, mid, want)
262277
}
263278
// Both halves are valid subtrees.
264279
if !torchwood.ValidSubtree(ls, mid) || !torchwood.ValidSubtree(mid, end) {
265280
t.Errorf("CoverInterval(%d, %d) = %d, %d: halves not valid subtrees", start, end, ls, mid)
266281
}
267282
// Coverage and bounded excess.
268-
if !(ls <= start && start < mid && mid < end) {
283+
if !(ls <= start && start <= mid && mid <= end) {
269284
t.Errorf("CoverInterval(%d, %d) = %d, %d: bad coverage", start, end, ls, mid)
270285
}
271-
if !(start-ls < mid-start) {
286+
if start < end && !(start-ls < mid-start) {
272287
t.Errorf("CoverInterval(%d, %d) = %d, %d: excess %d not less than half of left %d",
273288
start, end, ls, mid, start-ls, mid-ls)
274289
}
@@ -429,6 +444,54 @@ func TestRecordInSubtree(t *testing.T) {
429444
}
430445
}
431446

447+
func TestEmptySubtree(t *testing.T) {
448+
_, r := buildTree(t, 16)
449+
th, err := tlog.TreeHash(16, r)
450+
fatalIfErr(t, err)
451+
452+
// An empty subtree hashes to the empty tree hash.
453+
sh, err := torchwood.SubtreeHash(5, 5, r)
454+
fatalIfErr(t, err)
455+
if empty, err := tlog.TreeHash(0, r); err != nil || sh != empty {
456+
t.Errorf("SubtreeHash(5, 5) = %v, %v; want empty tree hash %v", sh, err, empty)
457+
}
458+
459+
// The proof for an empty subtree is empty, and does not depend on the tree.
460+
p, err := torchwood.ProveSubtree(16, 5, 5, r)
461+
fatalIfErr(t, err)
462+
if len(p) != 0 {
463+
t.Errorf("ProveSubtree(16, 5, 5) = %v; want empty proof", p)
464+
}
465+
if err := torchwood.CheckSubtree(p, 16, th, 5, 5, sh); err != nil {
466+
t.Errorf("CheckSubtree(16, [5, 5)) = %v", err)
467+
}
468+
if err := torchwood.CheckSubtree(p, 16, flip(th), 5, 5, sh); err != nil {
469+
t.Errorf("CheckSubtree(16, [5, 5)) with different th = %v", err)
470+
}
471+
if torchwood.CheckSubtree(p, 16, th, 5, 5, flip(sh)) == nil {
472+
t.Errorf("CheckSubtree accepted wrong hash for empty subtree")
473+
}
474+
if torchwood.CheckSubtree(torchwood.SubtreeProof{th}, 16, th, 5, 5, sh) == nil {
475+
t.Errorf("CheckSubtree accepted non-empty proof for empty subtree")
476+
}
477+
478+
// An empty subtree must still be within the tree.
479+
if _, err := torchwood.ProveSubtree(16, 17, 17, r); err == nil {
480+
t.Errorf("ProveSubtree(16, 17, 17) = nil error")
481+
}
482+
if err := torchwood.CheckSubtree(nil, 16, th, 17, 17, sh); err == nil {
483+
t.Errorf("CheckSubtree(16, [17, 17)) = nil error")
484+
}
485+
486+
// No record is contained in an empty subtree.
487+
if _, err := torchwood.ProveRecordInSubtree(5, 5, 5, r); err == nil {
488+
t.Errorf("ProveRecordInSubtree(5, 5, 5) = nil error")
489+
}
490+
if err := torchwood.CheckRecordInSubtree(nil, 5, 5, sh, 5, tlog.Hash{}); err == nil {
491+
t.Errorf("CheckRecordInSubtree([5, 5)) = nil error")
492+
}
493+
}
494+
432495
func TestSubtreeInvalidInputs(t *testing.T) {
433496
_, r := buildTree(t, 16)
434497
// end > t

0 commit comments

Comments
 (0)