Skip to content

subtreeinclusion - #1

Open
phbnf wants to merge 5 commits into
mainfrom
subtreeinclusion
Open

subtreeinclusion#1
phbnf wants to merge 5 commits into
mainfrom
subtreeinclusion

Conversation

@phbnf

@phbnf phbnf commented May 29, 2026

Copy link
Copy Markdown
Owner
  • SubtreeInclusion
  • SubtreeInclusionProof Tests

@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 adds the SubtreeInclusion function to compute inclusion proofs for a leaf index in a log Merkle subtree, along with comprehensive unit tests. The review identified a critical bug in the bounds check of SubtreeInclusion that allows index == end, potentially causing integer overflow. Additionally, a bug was found in TestInclusionSubtreeSucceedsUpToTreeSize where the innermost loop never executes, and suggestions were provided to fix the loop logic and skip invalid test parameters.

Comment thread proof/proof.go Outdated
Comment on lines +62 to +64
if index < start || index > end {
return Nodes{}, fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The bounds check allows index == end because index > end is used instead of index >= end. If index == end, it will bypass this check and call nodes(end-start, 0, end-start), which leads to index == size in nodes. This causes inner to be computed as -1, leading to integer overflow/underflow when converted to uint and causing undefined behavior or panics.

Suggested change
if index < start || index > end {
return Nodes{}, fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
}
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_test.go
Comment on lines +424 to +432
for sbe := uint64(1); sbe <= maxSize; sbe++ {
for sbs := uint64(1); sbs < sbe; sbs++ {
for i := sbe; i < sbe; i++ {
if _, err := SubtreeInclusion(i, sbs, sbe); err != nil {
t.Errorf("SubtreeInclusion(ts:%d, i:%d) = %v", sbe, i, err)
}
}
}
}

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

There are two major issues with this test:

  1. The innermost loop for i := sbe; i < sbe; i++ never executes because i is initialized to sbe and the condition is i < sbe.
  2. If the loop is fixed to start at sbs, the test will fail for many combinations of sbs and sbe because SubtreeInclusion requires sbs to be a multiple of bit_ceil(sbe - sbs). We should skip invalid combinations and also allow sbs to start at 0 to cover more cases.
Suggested change
for sbe := uint64(1); sbe <= maxSize; sbe++ {
for sbs := uint64(1); sbs < sbe; sbs++ {
for i := sbe; i < sbe; i++ {
if _, err := SubtreeInclusion(i, sbs, sbe); err != nil {
t.Errorf("SubtreeInclusion(ts:%d, i:%d) = %v", sbe, i, err)
}
}
}
}
for sbe := uint64(1); sbe <= maxSize; sbe++ {
for sbs := uint64(0); sbs < sbe; sbs++ {
bc := uint64(1) << bits.Len64(sbe-sbs-1)
if sbs%bc != 0 {
continue
}
for i := sbs; i < sbe; i++ {
if _, err := SubtreeInclusion(i, sbs, sbe); err != nil {
t.Errorf("SubtreeInclusion(i:%d, sbs:%d, sbe:%d) = %v", i, sbs, sbe, err)
}
}
}
}

@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 the SubtreeInclusion function to calculate inclusion proofs for a leaf index within a Merkle subtree, along with corresponding unit tests. The review identified two critical issues: an incorrect boundary check in SubtreeInclusion that allows index == end and can lead to undefined behavior or panics, and a loop initialization bug in TestInclusionSubtreeSucceedsUpToTreeSize that prevents the test from executing.

Comment thread proof/proof.go Outdated
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}
if index < start || index > end {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The boundary check for index is incorrect. The comment states that 0 <= start <= index < end, meaning index must be strictly less than end. However, the check index > end allows index == end to pass.

If index == end, index - start equals end - start (the subtree size). Passing an index equal to the size to nodes() results in inner being -1, leading to undefined behavior, extremely large levels (uint(-1)), and an infinite loop or panic in nodes().

Change the condition to index >= end.

Suggested change
if index < start || index > end {
if index < start || index >= end {

Comment thread proof/proof_test.go Outdated
const maxSize = uint64(555)
for sbe := uint64(1); sbe <= maxSize; sbe++ {
for sbs := uint64(1); sbs < sbe; sbs++ {
for i := sbe; i < sbe; i++ {

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 variable i is initialized to sbe and the loop condition is i < sbe. This means the loop body never executes, and no subtree inclusion proofs are actually tested in this function.

It should be initialized to sbs to test all valid leaf indices within the subtree.

Suggested change
for i := sbe; i < sbe; i++ {
for i := sbs; i < sbe; i++ {

@phbnf
phbnf force-pushed the subtreeinclusion branch 4 times, most recently from 54dfa13 to baaac88 Compare May 29, 2026 17:59
@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 the SubtreeInclusion function in proof/proof.go to construct inclusion proofs for a leaf index within a log Merkle subtree, along with comprehensive unit tests in proof/proof_test.go. The reviewer provided valuable feedback on the tests, recommending a reduction of maxSize in TestInclusionSubtreeSucceedsUpToTreeSize from 555 to 128 to prevent slow test execution due to cubic complexity, and suggesting that the error message parameters be reordered to match the signature of SubtreeInclusion for clearer debugging.

Comment thread proof/proof_test.go
}

func TestInclusionSubtreeSucceedsUpToTreeSize(t *testing.T) {
const maxSize = uint64(555)

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 loop in TestInclusionSubtreeSucceedsUpToTreeSize has cubic complexity $O(N^3)$ where $N$ is maxSize. With maxSize = 555, this results in over 28 million iterations, which can significantly slow down the unit test execution. Reducing maxSize to 128 is sufficient to cover all tree shapes and alignments up to level 7, while reducing the iteration count to around 350,000 (an ~80x speedup).

Suggested change
const maxSize = uint64(555)
const maxSize = uint64(128)

Comment thread proof/proof_test.go
Comment on lines +431 to +439
for i := sbs; i < sbe; i++ {
_, err := SubtreeInclusion(i, sbs, sbe)
if !wantErr && err != nil {
t.Errorf("SubtreeInclusion(sbs:%d, sbe: %d, i:%d) = %v", sbs, sbe, i, err)
}
if wantErr && err == nil {
t.Errorf("SubtreeInclusion(sbs:%d, sbe: %d, i:%d) = %v", sbs, sbe, i, err)
}
}

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 error messages in t.Errorf print the parameters in the order (sbs, sbe, i), but the actual signature of SubtreeInclusion is SubtreeInclusion(index, start, end). This mismatch can be highly confusing when debugging test failures. Updating the format string to match the parameter names and order of the function signature improves clarity.

Suggested change
for i := sbs; i < sbe; i++ {
_, err := SubtreeInclusion(i, sbs, sbe)
if !wantErr && err != nil {
t.Errorf("SubtreeInclusion(sbs:%d, sbe: %d, i:%d) = %v", sbs, sbe, i, err)
}
if wantErr && err == nil {
t.Errorf("SubtreeInclusion(sbs:%d, sbe: %d, i:%d) = %v", sbs, sbe, i, err)
}
}
for i := sbs; i < sbe; i++ {
_, err := SubtreeInclusion(i, sbs, sbe)
if !wantErr && err != nil {
t.Errorf("SubtreeInclusion(index:%d, start:%d, end:%d) = %v", i, sbs, sbe, err)
}
if wantErr && err == nil {
t.Errorf("SubtreeInclusion(index:%d, start:%d, end:%d) = %v", i, sbs, sbe, err)
}
}

@phbnf
phbnf force-pushed the subtreeinclusion branch from baaac88 to 2097c8d 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 the SubtreeInclusion function to fetch and construct inclusion proofs for log Merkle subtrees, along with comprehensive unit tests covering various subtree configurations and edge cases. The feedback suggests improving the documentation of SubtreeInclusion to clarify the alignment requirements for the start parameter, correcting a grammatical error in an error message, and optimizing the function by calling skipFirst() earlier to avoid unnecessary index shifting on the discarded first node.

Comment thread proof/proof.go Outdated
Comment on lines +55 to +57
// SubtreeInclusion returns the information on how to fetch and construct an inclusion
// proof for the given leaf index in a log Merkle subtree covering [start, end).
// It requires 0 <= start <= index < end.

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 documentation for SubtreeInclusion does not mention the alignment requirement for start (i.e., that start must be a multiple of the smallest power of two greater than or equal to end - start). Since violating this requirement results in an error, it should be explicitly documented to help users of the API.

// SubtreeInclusion returns the information on how to fetch and construct an inclusion
// proof for the given leaf index in a log Merkle subtree covering [start, end).
// It requires 0 <= start <= index < end, and start must be a multiple of the smallest
// power of two greater than or equal to (end - start).

Comment thread proof/proof.go
Comment on lines +59 to +61
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}

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 error message uses the grammatically incorrect phrase 'larger or equal than'. It should be 'greater than or equal to' or 'larger than or equal to' for clarity and standard terminology.

Suggested change
if start >= end {
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
}
if start >= end {
return Nodes{}, fmt.Errorf("start %d greater than or equal to end %d", start, end)
}

Comment thread proof/proof.go Outdated
Comment on lines +73 to +82
// Shift the subtree to the left, such that it starts at 0.
p := nodes(index-start, 0, end-start)

// Shift nodes back to the right, in line with the original subtree position.
for n := range p.IDs {
p.IDs[n].Index += start >> p.IDs[n].Level
}
p.ephem.Index += start >> p.ephem.Level

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

We can call skipFirst() immediately after retrieving the nodes from nodes(...). This avoids performing the index shifting operation on the first node, which is immediately discarded by skipFirst() anyway.

Suggested change
// Shift the subtree to the left, such that it starts at 0.
p := nodes(index-start, 0, end-start)
// Shift nodes back to the right, in line with the original subtree position.
for n := range p.IDs {
p.IDs[n].Index += start >> p.IDs[n].Level
}
p.ephem.Index += start >> p.ephem.Level
return p.skipFirst(), nil
// Shift the subtree to the left, such that it starts at 0.
p := nodes(index-start, 0, end-start).skipFirst()
// Shift nodes back to the right, in line with the original subtree position.
for n := range p.IDs {
p.IDs[n].Index += start >> p.IDs[n].Level
}
p.ephem.Index += start >> p.ephem.Level
return p, nil

@phbnf
phbnf force-pushed the subtreeinclusion branch 2 times, most recently from 6e018c6 to 143671d Compare May 29, 2026 18:19
@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 the SubtreeInclusion function to calculate inclusion proofs for a leaf index within a log Merkle subtree, along with comprehensive unit tests. Feedback recommends reducing the maxSize constant in TestInclusionSubtreeSucceedsUpToTreeSize from 555 to 128 to prevent excessive memory allocation and slow test execution caused by the cubic complexity of the nested loops.

Comment thread proof/proof_test.go
}

func TestInclusionSubtreeSucceedsUpToTreeSize(t *testing.T) {
const maxSize = uint64(555)

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 nested loops in TestInclusionSubtreeSucceedsUpToTreeSize run in $O(\text{maxSize}^3)$ time. With maxSize = 555, this results in exactly 28,645,600 iterations. Since each iteration performs slice allocations in SubtreeInclusion and nodes, this test will allocate gigabytes of memory and take several seconds to complete, significantly slowing down the test suite.\n\nReducing maxSize to 128 (which is a power of two and sufficient to cover all boundary conditions) reduces the total iterations to 182,304, making the test run in milliseconds while maintaining excellent coverage.

Suggested change
const maxSize = uint64(555)
const maxSize = uint64(128)

@phbnf
phbnf force-pushed the subtreeinclusion branch from 143671d to 75d2e0e Compare May 29, 2026 18:26
@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 the SubtreeInclusion function to calculate inclusion proofs for a leaf index within a log Merkle subtree, along with comprehensive unit tests. A critical issue was identified in the TestInclusionSubtreeSucceedsUpToTreeSize test, where an incorrect condition (bc != 0) causes the test loop to be silently skipped. Correcting this to sbs%bc != 0 is necessary to ensure the tests actually run.

Comment thread proof/proof_test.go Outdated
Comment on lines +429 to +431
if bc := uint64(1) << bits.Len64(sbe-sbs-1); bc != 0 {
continue
}

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

In TestInclusionSubtreeSucceedsUpToTreeSize, the condition bc != 0 is always true because bc is a power of two and sbe - sbs <= 555 (so the shift is at most 10). As a result, the continue statement is always executed, and the entire test loop is silently skipped without running any assertions.\n\nTo fix this and ensure the test actually runs, the condition should check whether sbs (the subtree start index) is a multiple of bc (the bit ceiling of the subtree length), i.e., sbs % bc != 0.

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

@phbnf
phbnf force-pushed the subtreeinclusion branch from 75d2e0e to 596109d Compare May 29, 2026 21:53
@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 the SubtreeInclusion function in proof/proof.go to fetch and construct inclusion proofs for a leaf index within a specific log Merkle subtree [start, end). It also adds corresponding unit tests in proof/proof_test.go to verify subtree inclusion, correctness across various tree sizes, and ephemeral node calculations. There are no review comments, and I have no feedback to provide.

@phbnf
phbnf force-pushed the subtreeinclusion branch from 596109d to 4443702 Compare June 1, 2026 09:03
@phbnf
phbnf force-pushed the subtreeinclusion branch 4 times, most recently from 99fdb32 to 7b03e29 Compare June 2, 2026 16:56
@phbnf
phbnf force-pushed the subtreeinclusion branch from 7b03e29 to ebffc12 Compare June 2, 2026 17:03
@phbnf
phbnf force-pushed the subtreeinclusion branch from ebffc12 to ac00877 Compare June 3, 2026 09:02
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