diff --git a/cmd/proofgen/main.go b/cmd/proofgen/main.go index 9a5bb228..ba56f62e 100644 --- a/cmd/proofgen/main.go +++ b/cmd/proofgen/main.go @@ -402,9 +402,9 @@ func staticConsistencyProbes(dir string) error { tests := []consistencyProbe{ {0, 0, root1, root2, proof1, "sizes are equal (zero) but roots are not", true}, {1, 1, root1, root2, proof1, "sizes are equal (one) but roots are not", true}, - // Sizes that are always consistent. - {0, 0, root1, root1, proof1, "sizes are equal (zero) and proof is empty", false}, + {0, 0, root1, root1, proof1, "sizes are equal (zero) and proof is empty", true}, {0, 1, root1, root2, proof1, "size1 is zero and does not equal size2", true}, + // Sizes that are always consistent. {1, 1, root2, root2, proof1, "sizes are equal (one) and proof is empty", false}, // Time travel to the past. {1, 0, root1, root2, proof1, "size1 is greater than size2", true}, @@ -418,6 +418,7 @@ func staticConsistencyProbes(dir string) error { {0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "sizes match but proof is not empty and sizes are zero", true}, {1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "sizes match but proof is not empty and sizes are one", true}, // Fail to validate empty tree + {0, 1, sha256EmptyTreeHash, root2, proof1, "size1 is zero and size2 is not zero", true}, {0, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "consistency check on empty tree (size1 is zero) is useless", true}, } diff --git a/proof/proof.go b/proof/proof.go index df528770..24c221bd 100644 --- a/proof/proof.go +++ b/proof/proof.go @@ -82,12 +82,15 @@ func SubtreeInclusion(index, start, end uint64) (Nodes, error) { // Consistency returns the information on how to fetch and construct a // consistency proof between the two given tree sizes of a log Merkle tree. It -// requires 0 <= size1 <= size2. +// requires 0 < size1 <= size2. func Consistency(size1, size2 uint64) (Nodes, error) { if size1 > size2 { return Nodes{}, fmt.Errorf("tree size %d > %d", size1, size2) } - if size1 == size2 || size1 == 0 { + if size1 == 0 { + return Nodes{}, fmt.Errorf("consistency proof from empty tree is meaningless") + } + if size1 == size2 { return Nodes{IDs: []compact.NodeID{}}, nil } diff --git a/proof/proof_test.go b/proof/proof_test.go index 2984a702..41393fc2 100644 --- a/proof/proof_test.go +++ b/proof/proof_test.go @@ -341,6 +341,8 @@ func TestConsistency(t *testing.T) { // Errors. {size1: 5, size2: 0, wantErr: true}, {size1: 9, size2: 8, wantErr: true}, + {size1: 0, size2: 5, wantErr: true}, + {size1: 0, size2: 0, wantErr: true}, {size1: 1, size2: 2, want: nodes(id(0, 1))}, // b {size1: 1, size2: 4, want: nodes(id(0, 1), id(1, 1))}, // b h diff --git a/proof/verify.go b/proof/verify.go index 1c9186d4..24d9e049 100644 --- a/proof/verify.go +++ b/proof/verify.go @@ -112,13 +112,13 @@ func RootFromConsistencyProof(hasher merkle.LogHasher, size1, size2 uint64, proo switch { case size2 < size1: return nil, fmt.Errorf("size2 (%d) < size1 (%d)", size1, size2) + case size1 == 0: + return nil, errors.New("consistency proof from empty tree is meaningless") case size1 == size2: if len(proof) > 0 { return nil, errors.New("size1=size2, but proof is not empty") } return root1, nil - case size1 == 0: - return nil, errors.New("consistency proof from empty tree is meaningless") case len(proof) == 0: return nil, errors.New("empty proof") } diff --git a/testdata/consistency/additional/size1-is-zero-and-size2-is-not-zero.json b/testdata/consistency/additional/size1-is-zero-and-size2-is-not-zero.json new file mode 100644 index 00000000..a054a889 --- /dev/null +++ b/testdata/consistency/additional/size1-is-zero-and-size2-is-not-zero.json @@ -0,0 +1,9 @@ +{ + "size1": 0, + "size2": 1, + "root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", + "root2": "ZG9uJ3QgY2FyZSAy", + "proof": [], + "desc": "size1 is zero and size2 is not zero", + "wantErr": true +} \ No newline at end of file diff --git a/testdata/consistency/additional/sizes-are-equal-zero-and-proof-is-empty.json b/testdata/consistency/additional/sizes-are-equal-zero-and-proof-is-empty.json index 7c4ac437..6e5706e3 100644 --- a/testdata/consistency/additional/sizes-are-equal-zero-and-proof-is-empty.json +++ b/testdata/consistency/additional/sizes-are-equal-zero-and-proof-is-empty.json @@ -5,5 +5,5 @@ "root2": "ZG9uJ3QgY2FyZSAx", "proof": [], "desc": "sizes are equal (zero) and proof is empty", - "wantErr": false + "wantErr": true } \ No newline at end of file diff --git a/testonly/tree_fuzz_test.go b/testonly/tree_fuzz_test.go index 392dfdd3..fe0a9968 100644 --- a/testonly/tree_fuzz_test.go +++ b/testonly/tree_fuzz_test.go @@ -28,7 +28,7 @@ func FuzzConsistencyProofAndVerify(f *testing.F) { if begin > end || end > size { return } - if begin == 0 && end > 0 { + if begin == 0 { return } tree := newTree(genEntries(size)) @@ -176,6 +176,9 @@ func FuzzConsistencyProofAgainstReferenceImplementation(f *testing.F) { if begin > end || end > size { return } + if begin == 0 { + return + } entries := genEntries(size) tree := newTree(entries) got, err := tree.ConsistencyProof(begin, end) diff --git a/testonly/tree_test.go b/testonly/tree_test.go index a6120545..8eb1108b 100644 --- a/testonly/tree_test.go +++ b/testonly/tree_test.go @@ -154,8 +154,11 @@ func TestTreeConsistencyProof(t *testing.T) { if _, err := mt.ConsistencyProof(6, 3); err == nil { t.Error("ConsistencyProof(6, 3) succeeded unexpectedly") } + if _, err := mt.ConsistencyProof(0, 3); err == nil { + t.Error("ConsistencyProof(0, 3) succeeded unexpectedly") + } - for size1 := range uint64(8) + 1 { + for size1 := uint64(1); size1 <= 8; size1++ { for size2 := size1; size2 <= 8; size2++ { t.Run(fmt.Sprintf("%d:%d", size1, size2), func(t *testing.T) { got, err := mt.ConsistencyProof(size1, size2) @@ -178,8 +181,8 @@ func TestTreeConsistencyProofFuzz(t *testing.T) { for treeSize := uint64(1); treeSize <= 256; treeSize++ { mt := newTree(entries[:treeSize]) for range 8 { - size2 := rand.Uint64N(treeSize + 1) - size1 := rand.Uint64N(size2 + 1) + size2 := rand.Uint64N(treeSize) + 1 + size1 := rand.Uint64N(size2) + 1 got, err := mt.ConsistencyProof(size1, size2) if err != nil {