Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

merkle_test.gno

10.80 Kb · 298 lines
  1package merkle
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/hex"
  6	"strconv"
  7	"testing"
  8)
  9
 10// leaves returns n deterministic leaves, "leaf-0" … "leaf-n-1". The golden
 11// vectors below were produced from these exact inputs by tm2's own
 12// merkle.SimpleProofsFromByteSlices, so a mismatch means this package has
 13// diverged from Tendermint, not that the expectation is stale.
 14func leaves(n int) [][]byte {
 15	out := make([][]byte, n)
 16	for i := 0; i < n; i++ {
 17		out[i] = []byte("leaf-" + strconv.Itoa(i))
 18	}
 19	return out
 20}
 21
 22var goldenRoots = map[int]string{
 23	1: "305df59f9590c3c9ac63d2b2743c388e3792449078cebf7fb3dbe6471643b2b7",
 24	2: "60a53eed0de87a90c8e59427c59c46253c33a76a09502a51801300927b7e6bdc",
 25	3: "cf763a041c81ceef1578a6083f75c61bef2e0014f2a3e683a97fcfca5be7f19a",
 26	4: "bdd1c5ff55b19cb6b0e7c761bf9a6ccaa27fbbfc07b74f1fabb6e911a0bd2ab3",
 27	5: "00d21829a5503145348abcf712513eacf2a274211ad83e970202bb5b6d80b286",
 28	7: "0b007fb915eb9b2a146f54b1c86ec53b664f8e455b7660b0b6ee13edc0d921c0",
 29}
 30
 31// goldenAunts[n][i] is the comma-separated hex sibling list tm2 produces for
 32// leaf i of an n-leaf tree.
 33var goldenAunts = map[int][]string{
 34	1: {""},
 35	2: {
 36		"3145c409f259b7c53e32036090ff76751025a2498ba9823ef718cac50b4e616f",
 37		"305df59f9590c3c9ac63d2b2743c388e3792449078cebf7fb3dbe6471643b2b7",
 38	},
 39	3: {
 40		"3145c409f259b7c53e32036090ff76751025a2498ba9823ef718cac50b4e616f,fca89f57c9f8c8eb4047a7ff9d333acf9e0f3384b20b255bceab0f216dcca267",
 41		"305df59f9590c3c9ac63d2b2743c388e3792449078cebf7fb3dbe6471643b2b7,fca89f57c9f8c8eb4047a7ff9d333acf9e0f3384b20b255bceab0f216dcca267",
 42		"60a53eed0de87a90c8e59427c59c46253c33a76a09502a51801300927b7e6bdc",
 43	},
 44	4: {
 45		"3145c409f259b7c53e32036090ff76751025a2498ba9823ef718cac50b4e616f,bd45ff28796704d88bdac51b1df553fda59837b616d6d1cb2114dbc3b087ff69",
 46		"305df59f9590c3c9ac63d2b2743c388e3792449078cebf7fb3dbe6471643b2b7,bd45ff28796704d88bdac51b1df553fda59837b616d6d1cb2114dbc3b087ff69",
 47		"f76836325aec5699d8d71f8e42e9d47c5c29b08059ba296384f7ca40ad3a40ae,60a53eed0de87a90c8e59427c59c46253c33a76a09502a51801300927b7e6bdc",
 48		"fca89f57c9f8c8eb4047a7ff9d333acf9e0f3384b20b255bceab0f216dcca267,60a53eed0de87a90c8e59427c59c46253c33a76a09502a51801300927b7e6bdc",
 49	},
 50	7: {
 51		"3145c409f259b7c53e32036090ff76751025a2498ba9823ef718cac50b4e616f,bd45ff28796704d88bdac51b1df553fda59837b616d6d1cb2114dbc3b087ff69,8eae6bd3b3a07f1f75ee72a531629e6eb31e42e62f760e47de52a53c3641ef23",
 52		"305df59f9590c3c9ac63d2b2743c388e3792449078cebf7fb3dbe6471643b2b7,bd45ff28796704d88bdac51b1df553fda59837b616d6d1cb2114dbc3b087ff69,8eae6bd3b3a07f1f75ee72a531629e6eb31e42e62f760e47de52a53c3641ef23",
 53		"f76836325aec5699d8d71f8e42e9d47c5c29b08059ba296384f7ca40ad3a40ae,60a53eed0de87a90c8e59427c59c46253c33a76a09502a51801300927b7e6bdc,8eae6bd3b3a07f1f75ee72a531629e6eb31e42e62f760e47de52a53c3641ef23",
 54		"fca89f57c9f8c8eb4047a7ff9d333acf9e0f3384b20b255bceab0f216dcca267,60a53eed0de87a90c8e59427c59c46253c33a76a09502a51801300927b7e6bdc,8eae6bd3b3a07f1f75ee72a531629e6eb31e42e62f760e47de52a53c3641ef23",
 55		"8f1593cb92f429d9340b9bbc1f0bb122adf8026c42a4a42142e2168931727236,676f3782f5b3a5fb4370ed49572cedc523f4a66322269c85f2af0509d17b0a4d,bdd1c5ff55b19cb6b0e7c761bf9a6ccaa27fbbfc07b74f1fabb6e911a0bd2ab3",
 56		"ea9fc1a1b6e191b460d0d6306e3e870c173f39330f13cda1b70cfc72bdc398ba,676f3782f5b3a5fb4370ed49572cedc523f4a66322269c85f2af0509d17b0a4d,bdd1c5ff55b19cb6b0e7c761bf9a6ccaa27fbbfc07b74f1fabb6e911a0bd2ab3",
 57		"985bb5d36b927800876871da925a7e82abe83a9ddba5882920a007a55ea2b376,bdd1c5ff55b19cb6b0e7c761bf9a6ccaa27fbbfc07b74f1fabb6e911a0bd2ab3",
 58	},
 59}
 60
 61func TestRootMatchesTendermint(t *testing.T) {
 62	for _, n := range []int{1, 2, 3, 4, 5, 7} {
 63		got := New(leaves(n)).RootHex()
 64		if got != goldenRoots[n] {
 65			t.Errorf("n=%d: root = %s, tm2 says %s", n, got, goldenRoots[n])
 66		}
 67	}
 68}
 69
 70func TestProofMatchesTendermint(t *testing.T) {
 71	for n, want := range goldenAunts {
 72		tree := New(leaves(n))
 73		for i, w := range want {
 74			p, err := tree.Proof(i)
 75			if err != nil {
 76				t.Fatalf("n=%d i=%d: Proof: %v", n, i, err)
 77			}
 78			if got := p.Hex(); got != w {
 79				t.Errorf("n=%d i=%d:\n got  %s\n want %s", n, i, got, w)
 80			}
 81		}
 82	}
 83}
 84
 85// Every leaf of every tree shape up to 33 must produce a proof the native
 86// verifier accepts. This is the property that matters; the golden vectors only
 87// pin the encoding.
 88func TestEveryProofVerifies(t *testing.T) {
 89	for n := 1; n <= 33; n++ {
 90		ls := leaves(n)
 91		tree := New(ls)
 92		root := tree.Root()
 93		for i := 0; i < n; i++ {
 94			p, err := tree.Proof(i)
 95			if err != nil {
 96				t.Fatalf("n=%d i=%d: Proof: %v", n, i, err)
 97			}
 98			if !p.Verify(root, ls[i]) {
 99				t.Errorf("n=%d i=%d: valid proof rejected", n, i)
100			}
101		}
102	}
103}
104
105func TestRejects(t *testing.T) {
106	ls := leaves(8)
107	tree := New(ls)
108	root := tree.Root()
109	p0, _ := tree.Proof(0)
110	p1, _ := tree.Proof(1)
111
112	tests := []struct {
113		name  string
114		proof Proof
115		root  []byte
116		leaf  []byte
117	}{
118		{"forged leaf", p0, root, []byte("not-a-leaf")},
119		{"another real leaf at the wrong index", p0, root, ls[1]},
120		{"proof replayed at a different index", Proof{Index: 1, Total: p0.Total, Siblings: p0.Siblings}, root, ls[1]},
121		{"sibling list of another leaf", Proof{Index: 0, Total: 8, Siblings: p1.Siblings}, root, ls[0]},
122		{"wrong root", p0, LeafHash([]byte("nope")), ls[0]},
123		{"index beyond total", Proof{Index: 8, Total: 8, Siblings: p0.Siblings}, root, ls[0]},
124		{"negative index", Proof{Index: -1, Total: 8, Siblings: p0.Siblings}, root, ls[0]},
125		{"zero total", Proof{Index: 0, Total: 0, Siblings: p0.Siblings}, root, ls[0]},
126		{"truncated proof", Proof{Index: 0, Total: 8, Siblings: p0.Siblings[:1]}, root, ls[0]},
127		{"short root", p0, root[:31], ls[0]},
128	}
129	for _, tc := range tests {
130		if tc.proof.Verify(tc.root, tc.leaf) {
131			t.Errorf("%s: accepted, must be rejected", tc.name)
132		}
133	}
134}
135
136func TestMaxDepthCap(t *testing.T) {
137	sibs := make([][]byte, MaxDepth+1)
138	for i := range sibs {
139		sibs[i] = make([]byte, HashSize)
140	}
141	p := Proof{Index: 0, Total: 1 << 20, Siblings: sibs}
142	if p.Verify(make([]byte, HashSize), []byte("x")) {
143		t.Error("proof deeper than MaxDepth accepted")
144	}
145	if !VerifySorted(make([]byte, HashSize), make([]byte, HashSize), sibs[:MaxDepth]) == false {
146		t.Error("unreachable")
147	}
148	if VerifySorted(make([]byte, HashSize), make([]byte, HashSize), sibs) {
149		t.Error("sorted proof deeper than MaxDepth accepted")
150	}
151}
152
153func TestParseProofRoundTrip(t *testing.T) {
154	tree := New(leaves(7))
155	for i := 0; i < 7; i++ {
156		want, _ := tree.Proof(i)
157		got, err := ParseProof(i, 7, want.Hex())
158		if err != nil {
159			t.Fatalf("i=%d: ParseProof: %v", i, err)
160		}
161		if got.Hex() != want.Hex() || got.Index != want.Index || got.Total != want.Total {
162			t.Errorf("i=%d: round trip lost data", i)
163		}
164		if !got.Verify(tree.Root(), []byte("leaf-"+strconv.Itoa(i))) {
165			t.Errorf("i=%d: reparsed proof does not verify", i)
166		}
167	}
168}
169
170func TestParseProofErrors(t *testing.T) {
171	tests := []struct {
172		name string
173		in   string
174		want error
175	}{
176		{"not hex", "zz", ErrBadHex},
177		{"wrong size", "abcd", ErrBadHashSize},
178		{"too deep", tooDeepHex(), ErrProofTooDeep},
179	}
180	for _, tc := range tests {
181		if _, err := ParseProof(0, 2, tc.in); err != tc.want {
182			t.Errorf("%s: err = %v, want %v", tc.name, err, tc.want)
183		}
184	}
185	// An empty sibling list is the one-leaf proof, not an error.
186	p, err := ParseProof(0, 1, "  ")
187	if err != nil {
188		t.Fatalf("empty siblings: %v", err)
189	}
190	one := New(leaves(1))
191	if !p.Verify(one.Root(), []byte("leaf-0")) {
192		t.Error("one-leaf proof with no siblings rejected")
193	}
194}
195
196func tooDeepHex() string {
197	h := hex.EncodeToString(make([]byte, HashSize))
198	out := h
199	for i := 0; i < MaxDepth; i++ {
200		out += "," + h
201	}
202	return out
203}
204
205func TestTreeErrors(t *testing.T) {
206	if _, err := New(nil).Proof(0); err != ErrEmptyTree {
207		t.Errorf("empty tree: err = %v, want %v", err, ErrEmptyTree)
208	}
209	if New(nil).Root() != nil {
210		t.Error("empty tree root should be nil, like Tendermint")
211	}
212	tree := New(leaves(3))
213	for _, i := range []int{-1, 3, 99} {
214		if _, err := tree.Proof(i); err != ErrIndexRange {
215			t.Errorf("index %d: err = %v, want %v", i, err, ErrIndexRange)
216		}
217	}
218}
219
220// New must copy its input: a caller mutating the slice afterwards cannot be
221// allowed to change what the tree committed to.
222func TestNewCopiesLeaves(t *testing.T) {
223	ls := [][]byte{[]byte("a"), []byte("b")}
224	tree := New(ls)
225	before := tree.RootHex()
226	ls[0][0] = 'z'
227	if tree.RootHex() != before {
228		t.Error("mutating the caller's slice changed the committed root")
229	}
230}
231
232// The reason this package exists. In a scheme without domain separation an
233// INNER NODE hash is also a valid LEAF hash, so anyone who can choose a
234// 64-byte leaf preimage can prove membership of a leaf that was never in the
235// tree. This test performs that forgery against VerifySorted, then shows the
236// Tendermint scheme refuses the same move.
237func TestSecondPreimageForgery(t *testing.T) {
238	// A 4-leaf commutative tree, built the OpenZeppelin way.
239	h := make([][]byte, 4)
240	for i := range h {
241		s := sha256.Sum256([]byte("leaf-" + strconv.Itoa(i)))
242		h[i] = s[:]
243	}
244	n01 := hashSortedPair(h[0], h[1])
245	n23 := hashSortedPair(h[2], h[3])
246	root := hashSortedPair(n01, n23)
247
248	// Sanity: a real leaf really does verify.
249	if !VerifySorted(root, h[0], [][]byte{h[1], n23}) {
250		t.Fatal("honest sorted proof rejected")
251	}
252
253	// The forgery: present the inner node n01 as if it were a leaf hash. Its
254	// preimage is the 64 bytes sorted(h0, h1), so an application whose leaves
255	// can be 64 bytes long hands the attacker a membership proof for data that
256	// was never committed.
257	if !VerifySorted(root, n01, [][]byte{n23}) {
258		t.Fatal("expected the second-preimage forgery to succeed against VerifySorted")
259	}
260
261	// Same shape, Tendermint scheme. Leaves are tagged 0x00 and inner nodes
262	// 0x01, so no leaf preimage can ever hash to an inner node, and the proof
263	// is bound to an index besides.
264	tree := New(leaves(4))
265	inner := InnerHash(LeafHash(tree.leaves[0]), LeafHash(tree.leaves[1]))
266	forged := Proof{Index: 0, Total: 2, Siblings: [][]byte{InnerHash(LeafHash(tree.leaves[2]), LeafHash(tree.leaves[3]))}}
267	if forged.Verify(tree.Root(), inner) {
268		t.Error("domain separation failed: an inner node was accepted as a leaf")
269	}
270}
271
272func TestLeafAndInnerHashAreTagged(t *testing.T) {
273	leafOfEmpty := LeafHash(nil)
274	taggedLeaf := sha256.Sum256([]byte{0x00})
275	if hex.EncodeToString(leafOfEmpty) != hex.EncodeToString(taggedLeaf[:]) {
276		t.Error("LeafHash is not SHA256(0x00 || leaf)")
277	}
278	l := make([]byte, 32)
279	r := make([]byte, 32)
280	r[0] = 1
281	got := InnerHash(l, r)
282	want := sha256.Sum256(append(append([]byte{0x01}, l...), r...))
283	if hex.EncodeToString(got) != hex.EncodeToString(want[:]) {
284		t.Error("InnerHash is not SHA256(0x01 || left || right)")
285	}
286}
287
288func TestSplitPointMatchesTendermint(t *testing.T) {
289	// largest power of two strictly below length
290	tests := []struct{ in, want int }{
291		{2, 1}, {3, 2}, {4, 2}, {5, 4}, {7, 4}, {8, 4}, {9, 8}, {16, 8}, {17, 16},
292	}
293	for _, tc := range tests {
294		if got := splitPoint(tc.in); got != tc.want {
295			t.Errorf("splitPoint(%d) = %d, want %d", tc.in, got, tc.want)
296		}
297	}
298}