Skip to content

test(bitcoin): prove Taproot production paths in release PDF - #204

Merged
BitHighlander merged 7 commits into
keepkey:reconcile/upstream-syncfrom
BitHighlander:agent/taproot-tests-staging
Aug 2, 2026
Merged

test(bitcoin): prove Taproot production paths in release PDF#204
BitHighlander merged 7 commits into
keepkey:reconcile/upstream-syncfrom
BitHighlander:agent/taproot-tests-staging

Conversation

@BitHighlander

Copy link
Copy Markdown
Contributor

Adds BIP-86 address vectors and capability gating, exact independent BIP-340/341 witnesses, real P2TR key-path spend, device-derived P2TR change, mixed P2TR/legacy signing, and missing-amount rejection. Corrects the report catalog so P2TR output creation is not mislabeled as a Taproot spend. The PDF catalog now has explicit Taproot cases and OLED requirements, consumes CI's branch@SHA label, and rejects missing catalog evidence. Also reconciles the stale ClearSign v2 fallback catalog name so the release report has zero unexplained pending cases.\n\nTarget is the existing upstream staging branch; no package publication.\n\nLocal preflight: py_compile passed; report catalog validation is 310 passed, 3 intentional skips, 0 failed, 0 pending against the canonical Docker JUnit artifact. Canonical Docker CI and its generated PDF remain the merge gate.

Drives the emulator through the full GetAddress path for SPENDTAPROOT --
fsm_msgGetAddress, path_mismatched's m/86' branch, compute_address, the
BIP-86 tweak and bech32m encoding -- none of which the firmware's C unit
tests reach.

Expected values are the three official BIP-86 vectors.  BIP-86 publishes
them against the "abandon abandon ... about" mnemonic, which is exactly
what setup_mnemonic_abandon loads, so these are the spec's constants and
not values our implementation produced.

Verified against a locally built emulator: 1 passed.  Also mutation
checked -- flipping one character of the first expected address makes it
fail, so the assertions are not vacuous.

NOTE: gated at 7.16.0 via TAPROOT_FIRMWARE_VERSION.  develop is currently
7.15.0, so this SKIPS until the project version bumps.  A gate that is
never reached is a test that is silently green forever -- keep the
constant in step with CMakeLists.txt.
Spends a P2TR input on the emulator and compares the 64-byte witness byte
for byte against a signature computed outside the firmware.

The expected value is not a round trip through our own verifier -- that
would pass even if the device committed to the wrong transaction.  It comes
from a standalone Python implementation of BIP-340/341 written from the
specs, keyed from BIP-86's own published xprv for m/86'/0'/0'/0/0, and
self-checked against BIP-86's published internal and output keys before
being used.  BIP-340 signing is deterministic given aux_rand, so equality is
meaningful.

This caught a real bug.  BIP-143 hashes prevouts/sequences/outputs with
DOUBLE sha256 (hasher_sign is HASHER_SHA2D for Bitcoin) while BIP-341
specifies SINGLE sha256, so reusing the BIP-143 accumulators produced a
cryptographically valid signature over the wrong commitment -- exactly the
failure a self-consistent test cannot see.

Includes a synthetic prev-tx fixture in txcache so the test runs offline.
Both taproot tests gated on requires_firmware("7.16.0") while CMakeLists
said 7.15.0, so they skipped -- and which release taproot ships in is still
undecided.  A gate that is never reached is a test that is silently green
forever, which is the failure mode that looks exactly like passing.

Replaces it with requires_taproot(), which asks the device.  The tests now
run whenever the firmware reports the capability, whichever release that
turns out to be, and retargeting the release no longer touches them.

  - common.py: requires_taproot() helper, alongside requires_firmware and
    requires_message
  - regenerated messages_pb2.py for Features.supports_taproot (field 27)
  - device-protocol bumped to the commit adding it

Adds test_taproot_screens.py for Gate-3 OLED capture: the two places a
62-character bech32m address actually reaches the display -- verifying a
receive address, and the p2wsh multisig case that shares the length.  That
capture is what found the address truncation fixed in the firmware repo.
@BitHighlander
BitHighlander marked this pull request as ready for review August 2, 2026 21:48
@BitHighlander
BitHighlander merged commit b6e8f01 into keepkey:reconcile/upstream-sync Aug 2, 2026
2 of 3 checks passed
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