Skip to content

subtreeconsistency#2

Open
phbnf wants to merge 3 commits into
mainfrom
subtreeconsistency
Open

subtreeconsistency#2
phbnf wants to merge 3 commits into
mainfrom
subtreeconsistency

Conversation

@phbnf

@phbnf phbnf commented May 29, 2026

Copy link
Copy Markdown
Owner
  • SubtreeInclusion
  • SubtreeInclusionProof Tests
  • SubtreeConsistency
  • spcial case when a subtree len is a pow of 2
  • fold use SubtreeConsistency for Consistency

@phbnf

phbnf commented May 29, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SubtreeInclusion and SubtreeConsistency functions to proof/proof.go to support fetching and constructing inclusion and consistency proofs for subtrees of a log Merkle tree, along with comprehensive unit tests in proof/proof_test.go. The reviewer identified a critical issue in both new functions where a large difference between end and start (greater than 1 << 63) can cause a shift of 64 or more, leading to undefined behavior and a potential division-by-zero panic during the modulo operation. Code suggestions were provided to safely handle this edge case.

Comment thread proof/proof.go Outdated
Comment on lines +65 to +67
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If 'end - start' is large enough (specifically, 'end - start > 1 << 63'), then 'end - start - 1' will have its 64th bit set, meaning 'bits.Len64(end - start - 1)' returns '64'. Shifting a 64-bit integer by 64 ('uint64(1) << 64') is undefined behavior in Go and can result in '0' (or other platform-dependent values), which then causes a division-by-zero panic during the modulo operation 'start % bc'.

To prevent this, we should check if the shift is 64 or greater. If it is, the only valid 'start' value that is a multiple of '1 << 64' is '0'.

Suggested change
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But both end and starts are uint64, and end > start. So it's not possible.

Comment thread proof/proof.go Outdated
Comment on lines 98 to 100
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to 'SubtreeInclusion', if 'end - start > 1 << 63', 'bits.Len64(end - start - 1)' returns '64'. Shifting a 64-bit integer by 64 is undefined behavior in Go and can result in '0', leading to a division-by-zero panic during 'start % bc'.

We should safely handle the case where the shift is 64 or greater.

Suggested change
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces 'SubtreeInclusion' and 'SubtreeConsistency' functions to the 'proof' package, allowing inclusion and consistency proofs to be generated for subtrees. It also refactors the existing 'Consistency' function to leverage 'SubtreeConsistency' and adds comprehensive unit tests covering these new functions and their edge cases. The review feedback highlights a potential integer division by zero panic in both 'SubtreeInclusion' and 'SubtreeConsistency' when 'end - start' is greater than '1 << 63' (causing a shift overflow to 0), and provides code suggestions to safely handle this case.

Comment thread proof/proof.go Outdated
Comment on lines +65 to +67
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If end - start is greater than 1 << 63, then end - start - 1 is at least 1 << 63, which means bits.Len64(end - start - 1) will return 64. Shifting uint64(1) << 64 results in 0 in Go, which then causes a runtime panic (integer divide by zero) when evaluating start % bc.

To prevent this panic, we should explicitly handle the case where shift == 64. Since start is a uint64, the only multiple of 1 << 64 is 0. Thus, if shift == 64, start must be 0.

Suggested change
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}
shift := bits.Len64(end - start - 1)
if shift == 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Comment thread proof/proof.go Outdated
Comment on lines 98 to 100
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If end - start is greater than 1 << 63, then end - start - 1 is at least 1 << 63, which means bits.Len64(end - start - 1) will return 64. Shifting uint64(1) << 64 results in 0 in Go, which then causes a runtime panic (integer divide by zero) when evaluating start % bc.

To prevent this panic, we should explicitly handle the case where shift == 64. Since start is a uint64, the only multiple of 1 << 64 is 0. Thus, if shift == 64, start must be 0.

Suggested change
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}
shift := bits.Len64(end - start - 1)
if shift == 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d is not a multiple of bit_ceil(end - start) = %d", start, bc)
}

@phbnf phbnf force-pushed the subtreeconsistency branch 2 times, most recently from 473dce7 to f602ae6 Compare May 29, 2026 18:04
@phbnf

phbnf commented May 29, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SubtreeInclusion and SubtreeConsistency functions to support inclusion and consistency proofs for log Merkle subtrees, along with extensive unit tests. The review feedback suggests refactoring duplicated subtree validation logic into a shared helper function, fixing a minor typo in a comment, and expanding test coverage in TestSubtreeConsistencySucceedsUpToTreeSize by adjusting a loop boundary to include the end == size case.

Comment thread proof/proof.go
Comment on lines +59 to +71
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}
if index < start || index >= end {
return Nodes{}, fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
}
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The subtree validation logic is duplicated between SubtreeInclusion and SubtreeConsistency. Extracting this validation into a helper function improves maintainability and readability.

Suggested change
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}
if index < start || index >= end {
return Nodes{}, fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
}
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
}
if err := validateSubtreeRange(start, end); err != nil {
return Nodes{}, err
}
if index < start || index >= end {
return Nodes{}, fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
}

Comment thread proof/proof.go Outdated
Comment on lines +82 to +83
return p.skipFirst(), nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Define the validateSubtreeRange helper function here to centralize the subtree validation logic.

	return p.skipFirst(), nil
}

func validateSubtreeRange(start, end uint64) error {
	if start >= end {
		return fmt.Errorf("start %d larger or equal than end %d", start, end)
	}
	if shift := bits.Len64(end - start - 1); shift >= 64 {
		if start != 0 {
			return fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
		}
	} else if bc := uint64(1) << shift; start%bc != 0 {
		return fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
	}
	return nil
}

Comment thread proof/proof.go Outdated
Comment on lines 96 to 108
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}
if end > size {
return Nodes{}, fmt.Errorf("subtree end %d strictly larger than tree size %d", end, size)
}
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the validateSubtreeRange helper function here to avoid code duplication.

Suggested change
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}
if end > size {
return Nodes{}, fmt.Errorf("subtree end %d strictly larger than tree size %d", end, size)
}
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
}
if err := validateSubtreeRange(start, end); err != nil {
return Nodes{}, err
}
if end > size {
return Nodes{}, fmt.Errorf("subtree end %d strictly larger than tree size %d", end, size)
}

Comment thread proof/proof.go Outdated

// Handle the case when size1 is a power of 2.
if index == 0 {
// Handle the case when the subtree size if a power of 2.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Fix typo in comment: 'if' -> 'is'.

Suggested change
// Handle the case when the subtree size if a power of 2.
// Handle the case when the subtree size is a power of 2.

Comment thread proof/proof_test.go
func TestSubtreeConsistencySucceedsUpToTreeSize(t *testing.T) {
const maxSize = uint64(100)
for sbe := uint64(1); sbe < maxSize; sbe++ {
for s := sbe + 1; s <= maxSize; s++ {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

By starting s at sbe instead of sbe + 1, we can also test the case where end == size (i.e., sbe == s) for subtrees with start > 0. This significantly improves test coverage for subtree consistency proofs.

Suggested change
for s := sbe + 1; s <= maxSize; s++ {
for s := sbe; s <= maxSize; s++ {

@phbnf phbnf force-pushed the subtreeconsistency branch from f602ae6 to c7bc29b Compare May 29, 2026 21:54
@phbnf

phbnf commented May 29, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SubtreeInclusion and SubtreeConsistency functions to support generating inclusion and consistency proofs for log Merkle subtrees, along with comprehensive unit tests. Two issues were identified in the review: first, an incorrect loop condition in TestSubtreeConsistencySucceedsUpToTreeSize causes the test cases to be silently skipped; second, SubtreeConsistency does not correctly handle empty subtrees when start > 0 and start == end due to an incorrect boundary check.

Comment thread proof/proof_test.go Outdated
for sbe := uint64(1); sbe < maxSize; sbe++ {
for s := sbe + 1; s <= maxSize; s++ {
for sbs := uint64(0); sbs < sbe; sbs++ {
if bc := uint64(1) << bits.Len64(sbe-sbs-1); bc != 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The loop condition bc != 0 is always true because bc is a power of two (and cannot overflow to 0 given the maxSize of 100). This causes the loop to always continue and skip all test cases, meaning SubtreeConsistency is never actually tested. It should be sbs%bc != 0 instead, matching the logic in TestInclusionSubtreeSucceedsUpToTreeSize.

Suggested change
if bc := uint64(1) << bits.Len64(sbe-sbs-1); bc != 0 {
if bc := uint64(1) << bits.Len64(sbe-sbs-1); sbs%bc != 0 {

Comment thread proof/proof.go Outdated
return Nodes{}, fmt.Errorf("subtree end %d strictly greater than tree size %d", end, size)
}
if size1 == size2 || size1 == 0 {
if (end == 0) || (start == 0 && end == size) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When start > 0 and start == end, the subtree is empty, so the consistency proof should be empty. However, because end == 0 is checked instead of start == end, it proceeds to the alignment check and incorrectly returns an error. Changing the check to start == end correctly handles empty subtrees for any starting index.

Suggested change
if (end == 0) || (start == 0 && end == size) {
if (start == end) || (start == 0 && end == size) {

@phbnf phbnf force-pushed the subtreeconsistency branch from c7bc29b to 062f0d9 Compare May 29, 2026 22:00
@phbnf

phbnf commented May 29, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces subtree-based inclusion and consistency proof generation by adding the SubtreeInclusion and SubtreeConsistency functions to the proof package, along with comprehensive unit tests. The feedback suggests extracting the duplicated subtree alignment validation logic shared between these two new functions into a single helper function to improve maintainability.

Comment thread proof/proof.go Outdated
Comment on lines +111 to +117
if shift := bits.Len64(end - start - 1); shift >= 64 {
if start != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
}
} else if bc := uint64(1) << shift; start%bc != 0 {
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The subtree alignment validation logic is duplicated between SubtreeInclusion (lines 68-74) and SubtreeConsistency (lines 111-117).

To improve maintainability and adhere to DRY (Don't Repeat Yourself) principles, consider extracting this logic into a private helper function, such as:

func checkSubtreeAlignment(start, end uint64) error {
	shift := bits.Len64(end - start - 1)
	if shift >= 64 {
		if start != 0 {
			return fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
		}
	} else if bc := uint64(1) << shift; start%bc != 0 {
		return fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
	}
	return nil
}

Then you can simply call it in both functions:

if err := checkSubtreeAlignment(start, end); err != nil {
	return Nodes{}, err
}
	if err := checkSubtreeAlignment(start, end); err != nil {
		return Nodes{}, err
	}

@phbnf phbnf force-pushed the subtreeconsistency branch from 062f0d9 to 7a4802a Compare June 9, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant