Skip to content

Commit 425f42c

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

4 files changed

Lines changed: 171 additions & 50 deletions

File tree

cosignature.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ func subtreeCosignedMessage(name string, t uint64, origin string, start, end int
156156
})
157157
b.AddUint64(uint64(start))
158158
b.AddUint64(uint64(end))
159+
if start == end && hash != emptyHash {
160+
return nil, errors.New("the hash of an empty subtree must be the hash of the empty string")
161+
}
159162
b.AddBytes(hash[:])
160163
return b.Bytes()
161164
}

cosignature_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,37 @@ func TestSubtreeRoundtrip(t *testing.T) {
134134
if !v.VerifySubtree("example.com/log", 0, 123, th, []byte(checkpointSig)) {
135135
t.Fatal("checkpoint cosignature did not verify as subtree cosignature")
136136
}
137+
138+
// Empty subtrees can be signed and verified, but only with the hash of the
139+
// empty string.
140+
empty, err := tlog.TreeHash(0, nil)
141+
if err != nil {
142+
t.Fatal(err)
143+
}
144+
emptySig, err := s.SignSubtree("example.com/log", 5, 5, empty)
145+
if err != nil {
146+
t.Fatal(err)
147+
}
148+
if !v.VerifySubtree("example.com/log", 5, 5, empty, emptySig) {
149+
t.Fatal("empty subtree signature did not verify")
150+
}
151+
if _, err := s.SignSubtree("example.com/log", 5, 5, th); err == nil {
152+
t.Fatal("expected error signing empty subtree with wrong hash")
153+
}
154+
if v.VerifySubtree("example.com/log", 5, 5, th, emptySig) {
155+
t.Fatal("expected failure verifying empty subtree with wrong hash")
156+
}
157+
158+
// A checkpoint for an empty tree can be cosigned, but only with the hash
159+
// of the empty string.
160+
emptyCheckpoint := "example.com/log\n0\n" + base64.StdEncoding.EncodeToString(empty[:]) + "\n"
161+
if _, err := note.Sign(&note.Note{Text: emptyCheckpoint}, s); err != nil {
162+
t.Fatal(err)
163+
}
164+
badEmptyCheckpoint := "example.com/log\n0\n" + base64.StdEncoding.EncodeToString(th[:]) + "\n"
165+
if _, err := note.Sign(&note.Note{Text: badEmptyCheckpoint}, s); err == nil {
166+
t.Fatal("expected error signing empty-tree checkpoint with wrong hash")
167+
}
137168
}
138169

139170
func testSignerRoundtrip(t *testing.T, k crypto.Signer, extensions bool) {

subtree.go

Lines changed: 40 additions & 16 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

@@ -12,7 +13,7 @@ import (
1213
// where the subtree has start index 0. A [tlog.RecordProof] is a special case
1314
// of a SubtreeProof where the subtree has size 1.
1415
//
15-
// draft-ietf-plants-merkle-tree-certs-04 calls this a "Subtree Consistency
16+
// draft-ietf-plants-merkle-tree-certs calls this a "Subtree Consistency
1617
// Proof".
1718
type SubtreeProof []tlog.Hash
1819

@@ -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
@@ -44,7 +49,7 @@ func ProveSubtree(t, start, end int64, r tlog.HashReader) (SubtreeProof, error)
4449
// subtreeProofIndex builds the list of indexes needed to construct the proof
4550
// that the subtree [start, end) is contained in the node with leaves [lo, hi).
4651
// 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
52+
// draft-ietf-plants-merkle-tree-certs, Section 4.4. b reports whether the
4853
// verifier already knows the hash of the subtree portion in [lo, hi); it starts
4954
// true and becomes false past the first straddled node.
5055
func subtreeProofIndex(lo, hi, start, end int64, b bool, need []int64) []int64 {
@@ -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
@@ -179,7 +192,7 @@ func runSubtreeProof(p SubtreeProof, lo, hi, start, end int64, b bool, sh tlog.H
179192
// contains a particular record. A [tlog.RecordProof] is a special case of a
180193
// RecordInSubtreeProof where the subtree has start index 0.
181194
//
182-
// draft-ietf-plants-merkle-tree-certs-04 calls this a "Subtree Inclusion Proof".
195+
// draft-ietf-plants-merkle-tree-certs calls this a "Subtree Inclusion Proof".
183196
type RecordInSubtreeProof []tlog.Hash
184197

185198
// ProveRecordInSubtree returns the proof that the subtree [start, end) contains
@@ -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
//
232-
// See draft-ietf-plants-merkle-tree-certs-04, Section 4.
246+
// See draft-ietf-plants-merkle-tree-certs, 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
//
254-
// See draft-ietf-plants-merkle-tree-certs-04, Section 4.
272+
// See draft-ietf-plants-merkle-tree-certs, 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, 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

0 commit comments

Comments
 (0)