Skip to content

Commit cc3ad56

Browse files
committed
add accumulated test vectors from specs
1 parent 0845efa commit cc3ad56

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

testonly/vectors_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2026 Google LLC. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package testonly
16+
17+
import (
18+
"crypto/sha256"
19+
"fmt"
20+
"io"
21+
"math/bits"
22+
"testing"
23+
)
24+
25+
// These tests reproduce the accumulated test vectors from the "Subtree Test
26+
// Vectors" appendix of draft-ietf-plants-merkle-tree-certs. For trees of sizes
27+
// up to 130, they fold the output of each subtree algorithm over every valid
28+
// input into a single rolling SHA-256, which is compared against the value
29+
// published in the draft.
30+
31+
const subtreeVectorMax = 130
32+
33+
// subtreeVectorTree builds the tree D used by the test vectors, with leaf values
34+
// d[0] = 0x00, d[1] = 0x01, and so on.
35+
func subtreeVectorTree() *Tree {
36+
entries := make([][]byte, subtreeVectorMax)
37+
for i := range entries {
38+
entries[i] = []byte{byte(i)}
39+
}
40+
return newTree(entries)
41+
}
42+
43+
func isValidSubtree(start, end int) bool {
44+
if 0 > start || start >= end {
45+
return false
46+
}
47+
ceil := uint(1) << (bits.UintSize - bits.LeadingZeros(uint(end-start-1)))
48+
return uint(start)&(ceil-1) == 0
49+
}
50+
51+
// writeProofLine writes prefix followed by, for each hash in the concatenated
52+
// proof, a space and the hash's hexadecimal encoding, then a newline. An empty
53+
// proof contributes no hashes and so leaves no trailing space.
54+
func writeProofLine(w io.Writer, prefix string, proof [][]byte) {
55+
io.WriteString(w, prefix)
56+
for _, h := range proof {
57+
fmt.Fprintf(w, " %x", h)
58+
}
59+
io.WriteString(w, "\n")
60+
}
61+
62+
func TestSubtreeHashVectors(t *testing.T) {
63+
tree := subtreeVectorTree()
64+
h := sha256.New()
65+
for end := 1; end <= subtreeVectorMax; end++ {
66+
for start := 0; start < end; start++ {
67+
if !isValidSubtree(start, end) {
68+
continue
69+
}
70+
subtreeHash := tree.SubtreeHashAt(uint64(start), uint64(end))
71+
fmt.Fprintf(h, "[%d, %d) %x\n", start, end, subtreeHash)
72+
}
73+
}
74+
75+
const want = "94a95384a8c69acea9b50d035a58285b3a777cb7a724005faa5e1f1e1190007f"
76+
if got := fmt.Sprintf("%x", h.Sum(nil)); got != want {
77+
t.Errorf("subtree hash vector = %s, want %s", got, want)
78+
}
79+
}
80+
81+
func TestSubtreeInclusionProofVectors(t *testing.T) {
82+
tree := subtreeVectorTree()
83+
h := sha256.New()
84+
for end := 1; end <= subtreeVectorMax; end++ {
85+
for start := 0; start < end; start++ {
86+
if !isValidSubtree(start, end) {
87+
continue
88+
}
89+
for index := start; index < end; index++ {
90+
proof, err := tree.SubtreeInclusionProof(uint64(index), uint64(start), uint64(end))
91+
if err != nil {
92+
t.Fatalf("SubtreeInclusionProof(%d, %d, %d): %v", index, start, end, err)
93+
}
94+
writeProofLine(h, fmt.Sprintf("%d [%d, %d)", index, start, end), proof)
95+
}
96+
}
97+
}
98+
const want = "ac2a8f989e44d99e399db448050ff5f19757df53cfb716aa81015d3955d8163f"
99+
if got := fmt.Sprintf("%x", h.Sum(nil)); got != want {
100+
t.Errorf("subtree inclusion proof vector = %s, want %s", got, want)
101+
}
102+
}
103+
104+
func TestSubtreeConsistencyProofVectors(t *testing.T) {
105+
tree := subtreeVectorTree()
106+
h := sha256.New()
107+
for n := 0; n <= subtreeVectorMax; n++ {
108+
for end := 1; end <= n; end++ {
109+
for start := 0; start < end; start++ {
110+
if !isValidSubtree(start, end) {
111+
continue
112+
}
113+
proof, err := tree.SubtreeConsistencyProof(uint64(start), uint64(end), uint64(n))
114+
if err != nil {
115+
t.Fatalf("SubtreeConsistencyProof(%d, %d, %d): %v", start, end, n, err)
116+
}
117+
writeProofLine(h, fmt.Sprintf("[%d, %d) %d", start, end, n), proof)
118+
}
119+
}
120+
}
121+
const want = "c586ebbb73a5621baf2140095d87dde934e3b6503a562a1a5215b8209edd083d"
122+
if got := fmt.Sprintf("%x", h.Sum(nil)); got != want {
123+
t.Errorf("subtree consistency proof vector = %s, want %s", got, want)
124+
}
125+
}

0 commit comments

Comments
 (0)