bip360: bound merkle depth at 128 and replace validation asserts in ref-impl #2273

pull let-the-dreamers-rise wants to merge 1 commits into bitcoin:master from let-the-dreamers-rise:bip360-refimpl-depth-bound-and-raises changing 1 files +60 −6
  1. let-the-dreamers-rise commented at 11:36 PM on September 2, 2026: none

    Two conformance/robustness fixes to bip-0360/ref-impl/python/p2mr.py, found by differential testing against an independent implementation (byte-for-byte agreement with the reference on all 16 official construction vectors and on 2000 seeded random script trees before divergence analysis). Reported to the BIP authors by email first; opening it here at @EthanHeilman's request so it is trackable — issues are disabled on this repo, so a PR is the closest equivalent.

    1. No depth bound — the ref-impl can emit consensus-invalid control blocks

    BIP 360 Script Validation: the control block "must have length 1 + 32 * m, for a value of m that is an integer between 0 and 128, inclusive." compute_merkle_root / compute_control_block applied no bound, so a 129-deep tree produced a normal-looking root and a 4129-byte control block (m = 129) — an output consensus must reject as unspendable.

    import p2mr
    leaf = lambda s: {"leafVersion": 0xC0, "script": s}
    tree = leaf("51")
    for _ in range(129):
        tree = [tree, leaf("52")]
    print(len(p2mr.compute_control_block(0, tree)))   # 4129 before this PR
    

    Now refused with ValueError; m = 128 is still accepted.

    2. Validation asserts are stripped under python -O

    assert len(tree) == 2 vanishes under -O, so a malformed 3-child branch silently returned the root of its first two leaves — the third script leaf dropped from the commitment with no error:

    $ python3 -O
    >>> import p2mr
    >>> l = lambda s: {"leafVersion": 0xC0, "script": s}
    >>> p2mr.compute_merkle_root([l("51"), l("52"), l("53")]).hex()
    # identical to the 2-leaf root before this PR, no error
    

    Validation in the construction path now raises explicitly, which survives -O. assert is left in place for genuine internal invariants.

    Tests

    Adds negative_structure_tests() — ternary branch refused, m = 128 accepted, m = 129 refused for both the root and the control block — run from BIP360_tests(). The existing 9/9 vector suite passes with and without -O.

    Two related things I'd rather ask than patch: tapleaf_hash refuses empty scripts (deliberate, or defensive?), and construction silently masks odd leaf versions 0xc1 -> 0xc0 per v = c[0] & 0xfe (would you rather refuse odd versions than rewrite them?). Happy to follow up either way.

    Full graded writeup with method and repro instructions: https://github.com/let-the-dreamers-rise/p2mr-assurance-lab/blob/main/FINDINGS.md

  2. bip360: bound merkle depth at 128 and replace validation asserts
    The control block length rule in Script Validation caps m at 128, but
    compute_merkle_root and compute_control_block applied no depth bound, so a
    129-deep tree produced a 4129-byte control block for an output consensus
    must reject as unspendable.
    
    Structural validation also used assert, which python -O strips: a malformed
    3-child branch silently returned the root of its first two leaves rather
    than raising, dropping a script leaf from the commitment.
    
    Adds negative_structure_tests covering both, run from BIP360_tests.
    3c8190e11e
  3. let-the-dreamers-rise force-pushed on Sep 2, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bips. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-14 22:10 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me