Skip to content

Commit

Permalink
Support calculating root from consistency proof
Browse files Browse the repository at this point in the history
This is useful for other teams in the transparency space and was requested via the transparency-dev Slack channel. The new method is similar in essence to RootFromInclusionProof so fits in within the API.

As noted in the CHANGELOG, this change fixes a logical bug in the previous code that would have successfully verified an _empty_ proof from a tree size of 0 to any other tree size. In this change, trying to verify a consistency from a tree size of 0 to any other size than 0 will be considered an error, no matter what proof is provided.
  • Loading branch information
mhutchinson committed Sep 18, 2024
1 parent 406fabc commit 391939b
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## HEAD

* Breaking change: consistency proofs from `size1 = 0` to `size2 != 0` now always fail
* Previously, this could succeed if the empty proof was provided
* Bump Go version from 1.19 to 1.20

## v0.0.2
Expand Down
28 changes: 16 additions & 12 deletions proof/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,26 @@ func RootFromInclusionProof(hasher merkle.LogHasher, index, size uint64, leafHas
// between the passed in tree sizes, with respect to the corresponding root
// hashes. Requires 0 <= size1 <= size2.
func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]byte, root1, root2 []byte) error {
hash2, err := RootFromConsistencyProof(hasher, size1, size2, proof, root1)
if err != nil {
return err
}
return verifyMatch(hash2, root2)
}

func RootFromConsistencyProof(hasher merkle.LogHasher, size1, size2 uint64, proof [][]byte, root1 []byte) ([]byte, error) {
switch {
case size2 < size1:
return fmt.Errorf("size2 (%d) < size1 (%d)", size1, size2)
return nil, fmt.Errorf("size2 (%d) < size1 (%d)", size1, size2)
case size1 == size2:
if len(proof) > 0 {
return errors.New("size1=size2, but proof is not empty")
return nil, errors.New("size1=size2, but proof is not empty")
}
return verifyMatch(root1, root2)
return root1, nil
case size1 == 0:
// Any size greater than 0 is consistent with size 0.
if len(proof) > 0 {
return fmt.Errorf("expected empty proof, but got %d components", len(proof))
}
return nil // Proof OK.
return nil, errors.New("Consistency proof from empty tree are meaningless")
case len(proof) == 0:
return errors.New("empty proof")
return nil, errors.New("empty proof")
}

inner, border := decompInclProof(size1-1, size2)
Expand All @@ -104,7 +108,7 @@ func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]b
seed, start = root1, 0
}
if got, want := len(proof), start+inner+border; got != want {
return fmt.Errorf("wrong proof size %d, want %d", got, want)
return nil, fmt.Errorf("wrong proof size %d, want %d", got, want)
}
proof = proof[start:]
// Now len(proof) == inner+border, and proof is effectively a suffix of
Expand All @@ -115,13 +119,13 @@ func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]b
hash1 := chainInnerRight(hasher, seed, proof[:inner], mask)
hash1 = chainBorderRight(hasher, hash1, proof[inner:])
if err := verifyMatch(hash1, root1); err != nil {
return err
return nil, err
}

// Verify the second root.
hash2 := chainInner(hasher, seed, proof[:inner], mask)
hash2 = chainBorderRight(hasher, hash2, proof[inner:])
return verifyMatch(hash2, root2)
return hash2, nil
}

// decompInclProof breaks down inclusion proof for a leaf at the specified
Expand Down
2 changes: 1 addition & 1 deletion proof/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func TestVerifyConsistency(t *testing.T) {
{1, 1, root1, root2, proof1, true},
// Sizes that are always consistent.
{0, 0, root1, root1, proof1, false},
{0, 1, root1, root2, proof1, false},
{0, 1, root1, root2, proof1, true},
{1, 1, root2, root2, proof1, false},
// Time travel to the past.
{1, 0, root1, root2, proof1, true},
Expand Down
6 changes: 3 additions & 3 deletions testonly/tree_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (

// Compute and verify consistency proofs
func FuzzConsistencyProofAndVerify(f *testing.F) {
for size := 0; size <= 8; size++ {
for end := 0; end <= size; end++ {
for begin := 0; begin <= end; begin++ {
for size := 1; size <= 8; size++ {
for end := 1; end <= size; end++ {
for begin := 1; begin <= end; begin++ {
f.Add(uint64(size), uint64(begin), uint64(end))
}
}
Expand Down

0 comments on commit 391939b

Please sign in to comment.