-
Notifications
You must be signed in to change notification settings - Fork 74
Check for placeholder nodes in proof verification #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0f8d2d4
c96dcaf
d91ed00
d2555d7
ac9a7b8
a1f90d0
8450bb8
8a4b4d9
05b8a53
548b3f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -210,27 +210,27 @@ func (p *NonExistenceProof) Verify(spec *ProofSpec, root CommitmentRoot, key []b | |
return nil | ||
} | ||
|
||
// IsLeftMost returns true if this is the left-most path in the tree | ||
// IsLeftMost returns true if this is the left-most path in the tree, excluding placeholder (empty child) nodes | ||
func IsLeftMost(spec *InnerSpec, path []*InnerOp) bool { | ||
minPrefix, maxPrefix, suffix := getPadding(spec, 0) | ||
|
||
// ensure every step has a prefix and suffix defined to be leftmost | ||
// ensure every step has a prefix and suffix defined to be leftmost, unless it is a placeholder node | ||
for _, step := range path { | ||
if !hasPadding(step, minPrefix, maxPrefix, suffix) { | ||
if !hasPadding(step, minPrefix, maxPrefix, suffix) && !leftBranchesAreEmpty(spec, step, 0) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// IsRightMost returns true if this is the left-most path in the tree | ||
// IsRightMost returns true if this is the left-most path in the tree, excluding placeholder (empty child) nodes | ||
func IsRightMost(spec *InnerSpec, path []*InnerOp) bool { | ||
last := len(spec.ChildOrder) - 1 | ||
minPrefix, maxPrefix, suffix := getPadding(spec, int32(last)) | ||
|
||
// ensure every step has a prefix and suffix defined to be rightmost | ||
// ensure every step has a prefix and suffix defined to be rightmost, unless it is a placeholder node | ||
for _, step := range path { | ||
if !hasPadding(step, minPrefix, maxPrefix, suffix) { | ||
if !hasPadding(step, minPrefix, maxPrefix, suffix) && !rightBranchesAreEmpty(spec, step, int32(last)) { | ||
return false | ||
} | ||
} | ||
|
@@ -285,6 +285,7 @@ func isLeftStep(spec *InnerSpec, left *InnerOp, right *InnerOp) bool { | |
return rightidx == leftidx+1 | ||
} | ||
|
||
// checks if an op has the expected padding | ||
func hasPadding(op *InnerOp, minPrefix, maxPrefix, suffix int) bool { | ||
if len(op.Prefix) < minPrefix { | ||
return false | ||
|
@@ -309,6 +310,51 @@ func getPadding(spec *InnerSpec, branch int32) (minPrefix, maxPrefix, suffix int | |
return | ||
} | ||
|
||
// leftBranchesAreEmpty returns true if the padding bytes correspond to all empty children | ||
// on the left side of this branch, ie. it's a valid placeholder on a leftmost path | ||
func leftBranchesAreEmpty(spec *InnerSpec, op *InnerOp, branch int32) bool { | ||
idx := getPosition(spec.ChildOrder, branch) | ||
// count branches to left of this | ||
leftBranches := idx | ||
if leftBranches == 0 { | ||
return false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's an odd choice. (The true case would fall out after the loop anyway, but fine to explicitly return early. Not sure why you want to return false here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the index is 0, the op describes a left-branch, but the op contains the data of the siblings of that branch, right? So There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, as this function is only used to check "leftmost" of a path, I would do this differently. But no need to repeat myself |
||
} | ||
// compare prefix with the expected number of empty branches | ||
actualPrefix := len(op.Prefix) - leftBranches*int(spec.ChildSize) | ||
if actualPrefix < 0 { | ||
return false | ||
} | ||
for i := 0; i < leftBranches; i++ { | ||
from := actualPrefix + i*int(spec.ChildSize) | ||
if !bytes.Equal(spec.EmptyChild, op.Prefix[from:from+int(spec.ChildSize)]) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// rightBranchesAreEmpty returns true if the padding bytes correspond to all empty children | ||
// on the right side of this branch, ie. it's a valid placeholder on a rightmost path | ||
func rightBranchesAreEmpty(spec *InnerSpec, op *InnerOp, branch int32) bool { | ||
idx := getPosition(spec.ChildOrder, branch) | ||
// count branches to right of this one | ||
rightBranches := len(spec.ChildOrder) - 1 - idx | ||
if rightBranches == 0 { | ||
return false | ||
} | ||
// compare suffix with the expected number of empty branches | ||
if len(op.Suffix) != rightBranches*int(spec.ChildSize) { | ||
return false // sanity check | ||
} | ||
for i := 0; i < rightBranches; i++ { | ||
from := i * int(spec.ChildSize) | ||
if !bytes.Equal(spec.EmptyChild, op.Suffix[from:from+int(spec.ChildSize)]) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// getPosition checks where the branch is in the order and returns | ||
// the index of this branch | ||
func getPosition(order []int32, branch int32) int { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,3 +69,115 @@ func CheckAgainstSpecTestData(t *testing.T) map[string]CheckAgainstSpecTestStruc | |
} | ||
return cases | ||
} | ||
|
||
var SpecWithEmptyChild = &ProofSpec{ | ||
LeafSpec: &LeafOp{ | ||
Prefix: []byte{0}, | ||
Hash: HashOp_SHA256, | ||
PrehashValue: HashOp_SHA256, | ||
}, | ||
InnerSpec: &InnerSpec{ | ||
ChildOrder: []int32{0, 1}, | ||
ChildSize: 32, | ||
MinPrefixLength: 1, | ||
MaxPrefixLength: 1, | ||
EmptyChild: []byte("32_empty_child_placeholder_bytes"), | ||
Hash: HashOp_SHA256, | ||
}, | ||
} | ||
|
||
type EmptyBranchTestStruct struct { | ||
Op *InnerOp | ||
Spec *ProofSpec | ||
IsLeft bool | ||
IsRight bool | ||
} | ||
|
||
func EmptyBranchTestData(t *testing.T) []EmptyBranchTestStruct { | ||
var emptyChild = SpecWithEmptyChild.InnerSpec.EmptyChild | ||
|
||
return []EmptyBranchTestStruct{ | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: append([]byte{1}, emptyChild...), | ||
Suffix: nil, | ||
Hash: SpecWithEmptyChild.InnerSpec.Hash, | ||
}, | ||
Spec: SpecWithEmptyChild, | ||
IsLeft: true, | ||
IsRight: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this also be |
||
}, | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: []byte{1}, | ||
Suffix: emptyChild, | ||
Hash: SpecWithEmptyChild.InnerSpec.Hash, | ||
}, | ||
Spec: SpecWithEmptyChild, | ||
IsLeft: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, this is leftmost from the previous definition |
||
IsRight: true, | ||
}, | ||
// non-empty cases | ||
EmptyBranchTestStruct{ | ||
roysc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Op: &InnerOp{ | ||
Prefix: append([]byte{1}, make([]byte, 32)...), | ||
Suffix: nil, | ||
Hash: SpecWithEmptyChild.InnerSpec.Hash, | ||
}, | ||
Spec: SpecWithEmptyChild, | ||
IsLeft: false, | ||
IsRight: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expect: IsRight: true |
||
}, | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: []byte{1}, | ||
Suffix: make([]byte, 32), | ||
Hash: SpecWithEmptyChild.InnerSpec.Hash, | ||
}, | ||
Spec: SpecWithEmptyChild, | ||
IsLeft: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expect: IsLeft: true |
||
IsRight: false, | ||
}, | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: append(append([]byte{1}, emptyChild[0:28]...), []byte("xxxx")...), | ||
Suffix: nil, | ||
Hash: SpecWithEmptyChild.InnerSpec.Hash, | ||
}, | ||
Spec: SpecWithEmptyChild, | ||
IsLeft: false, | ||
IsRight: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expect: IsRight: true |
||
}, | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: []byte{1}, | ||
Suffix: append(append([]byte(nil), emptyChild[0:28]...), []byte("xxxx")...), | ||
Hash: SpecWithEmptyChild.InnerSpec.Hash, | ||
}, | ||
Spec: SpecWithEmptyChild, | ||
IsLeft: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expect: IsLeft: true |
||
IsRight: false, | ||
}, | ||
// some cases using a spec with no empty child | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: append([]byte{1}, make([]byte, 32)...), | ||
Suffix: nil, | ||
Hash: TendermintSpec.InnerSpec.Hash, | ||
}, | ||
Spec: TendermintSpec, | ||
IsLeft: false, | ||
IsRight: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected: IsRight: true |
||
}, | ||
EmptyBranchTestStruct{ | ||
Op: &InnerOp{ | ||
Prefix: []byte{1}, | ||
Suffix: make([]byte, 32), | ||
Hash: TendermintSpec.InnerSpec.Hash, | ||
}, | ||
Spec: TendermintSpec, | ||
IsLeft: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected IsLeft: true |
||
IsRight: false, | ||
}, | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.