Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions cmd/proofgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,18 +744,42 @@ func invalidSubtreeConsistencyProof(size1, size2 uint64, root1, root2 []byte, pr
}

func staticSubtreeConsistencyProbes(dir string) error {
for _, p := range staticConsistencyProbes() {
sp := subtreeConsistencyProbe{
Start: 0,
End: p.Size1,
Size: p.Size2,
Root1: p.Root1,
Root2: p.Root2,
Proof: p.Proof,
Desc: p.Desc,
WantError: p.WantError,
}
if err := writeSubtreeConsistencyProbe(dir, sp); err != nil {
root1 := []byte("don't care 1")
root2 := []byte("don't care 2")
proof1 := [][]byte{}
proof2 := [][]byte{sha256EmptyTreeHash}

for _, p := range []subtreeConsistencyProbe{
{0, 0, 0, root1, root2, proof1, "sizes are equal (zero) but roots are not", true},
{0, 1, 1, root1, root2, proof1, "sizes are equal (one) but roots are not", true},
{0, 0, 1, root1, root2, proof1, "size1 is zero and does not equal size2", true},
// Sizes that are always consistent.
{0, 1, 1, root2, root2, proof1, "sizes are equal (one) and proof is empty", false},
// Empty subtree
{0, 0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty sizes are equal (zero) subtree root valid proof is empty", false},
{0, 0, 0, sha256EmptyTreeHash, root1, proof1, "subtree is empty sizes are equal (zero) subtree root valid tree root random proof is empty", false},
{0, 0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "subtree is empty sizes are equal (zero) roots valid but proof is not empty", true},
{0, 0, 0, root1, root1, proof1, "subtree is empty sizes are equal (zero) roots match but not valid", true},
{1, 1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty sizes are equal (one) subtree root valid proof is empty", false},
{1, 1, 1, sha256EmptyTreeHash, root1, proof1, "subtree is empty sizes are equal (one) subtree root valid tree root random proof is empty", false},
{1, 1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "subtree is empty sizes are equal (one) roots valid but proof is not empty", true},
{1, 1, 1, root1, root1, proof1, "subtree is empty sizes are equal (one) roots match but not valid", true},
{1, 1, 2, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty subtree root valid proof is empty", false},
{1, 1, 2, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty subtree root valid tree root random proof is empty", false},
{1, 1, 2, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "subtree is empty roots valid but proof is not empty", true},
{1, 1, 2, root1, root1, proof1, "subtree is empty roots match but not valid", true},
// Time travel to the past.
{0, 1, 0, root1, root2, proof1, "size1 is greater than size2", true},
{0, 2, 1, root1, root2, proof1, "size1 is greater than size2 again", true},
// Empty proof.
{0, 1, 2, root1, root2, proof1, "sizes do not watch and proof is empty", true},
// Roots don't match.
{0, 1, 1, sha256EmptyTreeHash, root2, proof1, "roots do not not match and sizes are one", true},
// Sizes match but the proof is not empty.
{0, 0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "sizes match but proof is not empty and sizes are zero", true},
{0, 1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "sizes match but proof is not empty and sizes are one", true},
} {
if err := writeSubtreeConsistencyProbe(dir, p); err != nil {
return err
}
}
Expand Down
18 changes: 12 additions & 6 deletions proof/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ func subtreeConsistency(start, end, size uint64) (Nodes, error) {
if start == 0 && end == size {
return Nodes{IDs: []compact.NodeID{}}, nil
}
if start == end {
return Nodes{IDs: []compact.NodeID{}}, nil
}

// If end == size, prove inclusion of [start, end) into the tree.
if end == size {
Expand Down Expand Up @@ -286,11 +289,11 @@ type Subtree struct {
// - There are no "extra" entries covered past end, but there may be covered entries prior to start.
// - The number of entries covered before start is always less than half the size of the first returned subtree.
func FindSubtrees(start, end uint64) ([]Subtree, error) {
if start >= end {
return nil, fmt.Errorf("start %d must be strictly less than end %d", start, end)
if start > end {
return nil, fmt.Errorf("start %d must be less than or equal to end %d", start, end)
}
if end-start == 1 || isSubtreeValid(start, end) == nil {
return []Subtree{{Start: start, End: end}}, nil
if end-start <= 1 {
return []Subtree{{Start: start, End: end}, {Start: end, End: end}}, nil
}
last := end - 1
// Find where start and last's tree paths diverge.
Expand All @@ -313,12 +316,15 @@ func FindSubtrees(start, end uint64) ([]Subtree, error) {
// - no extra node to the left of the subtree
// - potentially extra nodes to the right of the subtree
func isSubtreeValid(start, end uint64) error {
if start >= end {
return fmt.Errorf("start %d must be strictly less than end %d", start, end)
if start > end {
return fmt.Errorf("start %d must be less than or equal to end %d", start, end)
}
if start == 0 {
return nil
}
if start == end {
return nil
}

l := end - start

Expand Down
27 changes: 14 additions & 13 deletions proof/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,6 @@ func TestSubtreeConsistency(t *testing.T) {
wantErr bool
}{
// Errors.
{start: 0, end: 0, size: 0, wantErr: true}, // start = end = 0
{start: 1, end: 1, size: 1, wantErr: true}, // start = end
{start: 2, end: 1, size: 0, wantErr: true}, // start > end
{start: 0, end: 5, size: 0, wantErr: true}, // end > size
{start: 0, end: 9, size: 8, wantErr: true}, // end > size
Expand All @@ -467,6 +465,7 @@ func TestSubtreeConsistency(t *testing.T) {

// Small trees.
// start = 0
{start: 0, end: 0, size: 0, want: Nodes{IDs: []compact.NodeID{}}}, // start = end = 0
{start: 0, end: 1, size: 2, want: nodes(id(0, 1))}, // b
{start: 0, end: 1, size: 4, want: nodes(id(0, 1), id(1, 1))}, // b bb
{start: 0, end: 1, size: 6, want: rehash(2, 3, id(0, 1), id(1, 1), id(1, 2))}, // b bb cc
Expand All @@ -485,12 +484,14 @@ func TestSubtreeConsistency(t *testing.T) {
{start: 0, end: 7, size: 8, want: nodes(
id(0, 6), id(0, 7), id(1, 2), id(2, 0))}, // g h cc aaa
// start > 0
{start: 1, end: 1, size: 1, want: Nodes{IDs: []compact.NodeID{}}}, // start = end
{start: 1, end: 2, size: 3, want: rehash(1, 2, id(0, 0), id(0, 2))}, // a c
{start: 1, end: 2, size: 5, want: rehash(2, 3, id(0, 0), id(1, 1), id(0, 4))}, // a bb e
{start: 2, end: 4, size: 5, want: rehash(1, 2, id(1, 0), id(0, 4))}, // aa e
{start: 1, end: 2, size: 7, want: rehash(2, 4, id(0, 0), id(1, 1), id(0, 6), id(1, 2))}, // a bb g cc
{start: 2, end: 4, size: 10, want: rehash(2, 3, id(1, 0), id(2, 1), id(1, 4))}, // aa bbb ee
{start: 4, end: 6, size: 10, want: rehash(2, 3, id(1, 3), id(2, 0), id(1, 4))}, // dd aaa ee
{start: 4, end: 4, size: 10, want: Nodes{IDs: []compact.NodeID{}}}, // start = end
{start: 4, end: 7, size: 11, want: rehash(4, 6, // ccc=hash(ee,k)
id(0, 6), id(0, 7), id(1, 2), id(2, 0), id(0, 10), id(1, 4))}, // g h cc aaa k ee
{start: 4, end: 8, size: 11, want: rehash(1, 3, // ccc=hash(ee,k)
Expand Down Expand Up @@ -599,16 +600,16 @@ func TestSubtreeConsistency(t *testing.T) {

func TestInclusionSucceedsUpToTreeSize(t *testing.T) {
const maxSize = uint64(555)
for ts := uint64(1); ts <= maxSize; ts++ {
for i := ts; i < ts; i++ {
for ts := range maxSize + 1 {
for i := range ts {
if _, err := Inclusion(i, ts); err != nil {
t.Errorf("Inclusion(ts:%d, i:%d) = %v", ts, i, err)
}
}
}
}

func TestInclusionSubtreeSucceedsUpToTreeSize(t *testing.T) {
func TestSubtreeInclusionSucceedsUpToTreeSize(t *testing.T) {
const maxSize = uint64(555)
for sbe := uint64(1); sbe <= maxSize; sbe++ {
for sbs := range sbe {
Expand Down Expand Up @@ -637,9 +638,9 @@ func TestConsistencySucceedsUpToTreeSize(t *testing.T) {

func TestSubtreeConsistencySucceedsUpToTreeSize(t *testing.T) {
const maxSize = uint64(100)
for s := uint64(1); s <= maxSize; s++ {
for sbe := uint64(1); sbe <= s; sbe++ {
for sbs := range sbe {
for s := range maxSize + 1 {
for sbe := range s + 1 {
for sbs := range sbe + 1 {
if err := isSubtreeValid(sbs, sbe); err != nil {
continue
}
Expand Down Expand Up @@ -829,16 +830,16 @@ func TestFindSubtrees(t *testing.T) {
}{
// Already-valid subtrees are returned as-is.
// Single entry subtrees:
{start: 0, end: 1, want: []Subtree{{Start: 0, End: 1}}},
{start: 3, end: 4, want: []Subtree{{Start: 3, End: 4}}},
{start: 0, end: 1, want: []Subtree{{Start: 0, End: 1}, {Start: 1, End: 1}}},
{start: 3, end: 4, want: []Subtree{{Start: 3, End: 4}, {Start: 4, End: 4}}},
// Perfectly aligned subtrees:
{start: 4, end: 6, want: []Subtree{{Start: 4, End: 6}}},
{start: 16, end: 32, want: []Subtree{{Start: 16, End: 32}}},
{start: 4, end: 6, want: []Subtree{{Start: 4, End: 5}, {Start: 5, End: 6}}},
{start: 16, end: 32, want: []Subtree{{Start: 16, End: 24}, {Start: 24, End: 32}}},
// Non-perfect trees are split into two:
{start: 5, end: 13, want: []Subtree{{Start: 4, End: 8}, {Start: 8, End: 13}}},
{start: 7, end: 9, want: []Subtree{{Start: 7, End: 8}, {Start: 8, End: 9}}},
// Invalid inputs:
{start: 5, end: 5, wantErr: true},
{start: 5, end: 5, want: []Subtree{{Start: 5, End: 5}, {Start: 5, End: 5}}},
{start: 6, end: 5, wantErr: true},
} {
t.Run(fmt.Sprintf("%d:%d", tc.start, tc.end), func(t *testing.T) {
Expand Down
24 changes: 22 additions & 2 deletions proof/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,22 @@ func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]b
// VerifySubtreeConsistency checks that the passed-in subtree consistency proof
// is valid between the passed in subtree indices and parent tree size, with
// respect to the corresponding subtree root node hash. It Requires:
// - 0 <= start < end <= size.
// - 0 <= start <= end <= size.
// - start to be a multiple of the smallest power of two greater than or equal to
// (end - start)
func VerifySubtreeConsistency(hasher merkle.LogHasher, start, end, size uint64, proof [][]byte, subRoot, parentRoot []byte) error {
if start == end {
if end > size {
return fmt.Errorf("size (%d) < end (%d)", size, end)
}
if len(proof) > 0 {
return errors.New("start=end, but proof is not empty")
}
if !bytes.Equal(subRoot, hasher.EmptyRoot()) {
return errors.New("start=end, but subRoot is not empty root")
}
return nil
}
hash2, err := RootFromSubtreeConsistencyProof(hasher, start, end, size, proof, subRoot)
if err != nil {
return err
Expand Down Expand Up @@ -144,7 +156,7 @@ func RootFromConsistencyProof(hasher merkle.LogHasher, size1, size2 uint64, proo
// a consistency proof.
//
// It requires:
// - 0 <= start < end <= size.
// - 0 <= start <= end <= size.
// - start to be a multiple of the smallest power of two greater than or equal to
// (end - start)
//
Expand All @@ -156,6 +168,14 @@ func RootFromSubtreeConsistencyProof(hasher merkle.LogHasher, start, end, size u
return nil, fmt.Errorf("subtree invalid: %v", err)
case size < end:
return nil, fmt.Errorf("size (%d) < end (%d)", size, end)
case start == end && size == 0:
if len(proof) > 0 {
return nil, errors.New("start=end, but proof is not empty")
}
if !bytes.Equal(subRoot, hasher.EmptyRoot()) {
return nil, errors.New("start=end, but subRoot is not empty root")
}
return hasher.EmptyRoot(), nil
case start == 0 && size == end:
if len(proof) > 0 {
return nil, errors.New("start=0 and end=size, but proof is not empty")
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"start": 0,
"end": 0,
"size": 0,
"start": 1,
"end": 1,
"size": 2,
"root1": "ZG9uJ3QgY2FyZSAx",
"root2": "ZG9uJ3QgY2FyZSAx",
"proof": [],
"desc": "sizes are equal (zero) and proof is empty",
"desc": "subtree is empty roots match but not valid",
"wantErr": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"start": 1,
"end": 1,
"size": 2,
"root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"root2": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"proof": [
"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
],
"desc": "subtree is empty roots valid but proof is not empty",
"wantErr": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"start": 1,
"end": 1,
"size": 1,
"root1": "ZG9uJ3QgY2FyZSAx",
"root2": "ZG9uJ3QgY2FyZSAx",
"proof": [],
"desc": "subtree is empty sizes are equal (one) roots match but not valid",
"wantErr": true
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"start": 0,
"end": 0,
"start": 1,
"end": 1,
"size": 1,
"root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"root2": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"proof": [
"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
],
"desc": "consistency check on empty tree (size1 is zero) is useless",
"desc": "subtree is empty sizes are equal (one) roots valid but proof is not empty",
"wantErr": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"start": 1,
"end": 1,
"size": 1,
"root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"root2": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"proof": [],
"desc": "subtree is empty sizes are equal (one) subtree root valid proof is empty",
"wantErr": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"start": 1,
"end": 1,
"size": 1,
"root1": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"root2": "ZG9uJ3QgY2FyZSAx",
"proof": [],
"desc": "subtree is empty sizes are equal (one) subtree root valid tree root random proof is empty",
"wantErr": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"root1": "ZG9uJ3QgY2FyZSAx",
"root2": "ZG9uJ3QgY2FyZSAx",
"proof": [],
"desc": "sizes are equal (zero) and proof is empty",
"desc": "subtree is empty sizes are equal (zero) roots match but not valid",
"wantErr": true
}
Loading
Loading