Skip to content

fix(bip137): wallet script type bip137 header #866

Draft
qlrd wants to merge 4 commits into
selfcustody:developfrom
qlrd:fix/bip137
Draft

fix(bip137): wallet script type bip137 header #866
qlrd wants to merge 4 commits into
selfcustody:developfrom
qlrd:fix/bip137

Conversation

@qlrd
Copy link
Copy Markdown
Member

@qlrd qlrd commented May 29, 2026

What is this PR for?

Minor bug fix.

src/krux/pages/home_pages/sign_message_ui.pybip137.pyproduces a P2PKH-compressed header31for P2SH-P2WPKH and P2WPKH addresses. While signing correctly, this is not fully compliant with BIP-137. This PR add asrc/krux/bip137.py` module moving this logic with those fixes.

Changes made to:

  • Code
  • Tests
  • Docs
  • CHANGELOG

Did you build the code and tested on device?

  • Yes, build and tested on Amigo and Simulator

What is the purpose of this pull request?

  • Bug fix
  • New feature
  • Docs update
  • Other

@qlrd qlrd changed the base branch from main to develop May 29, 2026 00:52
@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 85.26316% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.06%. Comparing base (07805a5) to head (5d5b2eb).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
src/krux/pages/home_pages/sign_message_ui.py 78.37% 16 Missing ⚠️
src/krux/bip322.py 87.09% 12 Missing ⚠️

❌ Your patch check has failed because the patch coverage (85.26%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #866      +/-   ##
===========================================
- Coverage    97.30%   97.06%   -0.24%     
===========================================
  Files           83       85       +2     
  Lines        10798    10980     +182     
===========================================
+ Hits         10507    10658     +151     
- Misses         291      322      +31     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qlrd qlrd changed the title Fix/bip137 fix(bip137): wallet script type bip137 header May 29, 2026
@qlrd qlrd force-pushed the fix/bip137 branch 3 times, most recently from e35223d to 4c95cf7 Compare May 29, 2026 13:09
@qlrd qlrd marked this pull request as ready for review May 29, 2026 13:10
Copy link
Copy Markdown
Member

@odudex odudex left a comment

Choose a reason for hiding this comment

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

The header-byte fix is correct: single-sig P2SH-P2WPKH and P2WPKH wallets were
emitting legacy-message signatures with the P2PKH-compressed header byte (31-34)
instead of the BIP-137 script-type headers (35-38 / 39-42). Signing now routes
through bip137.sign, which preserves the recovery id and rebases the header on
the script type. Verified against the regenerated vectors:

  • bc1qgl5... (P2WPKH): 32 -> 40 (39 + recid) OK
  • 38CahkV... (P2SH-P2WPKH): 31 -> 35 (35 + recid) OK
  • tb1qynp... (P2WPKH tnet): 32 -> 40 (39 + recid) OK

sign_at emits 31 + recid, so recovery_id = raw_header - 31 is correct and
the re-encoding only swaps the script-type base. Tests pass (5 passed).

Blocking: taproot message signing shouldn't stop working

This PR makes single-sig taproot (m/86') message signing fail. From a user's
perspective this is a regression / introduced bug, not a tightening: taproot
signing worked before and Sparrow verified the result as valid.

Mechanism: get_script_type_from_path("m/86'/...") returns "p2tr", which is
passed to bip137.sign -> build_header. build_header has no p2tr branch, so
it raises ValueError, which the menu catches and shows as an error screen.

Why it really was working (so this is a real loss of function, not removal of a
broken path):

  • Krux signs the standard BIP-137 commitment with an ECDSA-recoverable
    signature using the taproot internal key.
  • Lenient verifiers (Sparrow / Electrum-style) recover the pubkey and
    reconstruct the address of the claimed type from it - for taproot they apply
    the taproot tweak (same as script.p2tr) and the recovered+tweaked key matches
    the bc1p... address. They effectively ignore the header's script-type bits.
  • So the signature genuinely proves control of the taproot key and verifies in
    practice. It's just not a formally specified scheme (BIP-137 has no taproot;
    BIP-322 is the eventual standard).

Required outcome: taproot message signing must keep working. Acceptable options:

  1. Keep the previous behavior - give build_header a p2tr branch that returns a
    valid recoverable header so Krux keeps emitting the Sparrow-verifiable
    signature (this is the no-regression path). The segwit header fix (#865) is
    independent and does not require dropping taproot.
  2. Implement proper BIP-322 signing for taproot.

What is NOT acceptable: shipping a version where taproot signing errors out.

Other required changes

  1. Misleading error message in build_header.
    "%s legacy sign not supported" implies the script type itself is
    unsupported. p2tr is a supported wallet type; the limitation is that BIP-137
    legacy message signing has no taproot scheme. If any reject path remains,
    reword to make that clear and point at BIP-322. Also drop the trailing space
    (currently shown verbatim as ValueError('p2tr legacy sign not supported ')).

  2. Add the MIT license header to src/krux/bip137.py.
    Every other src/krux/*.py file opens with the MIT block; this module starts
    straight at P2PKH_HEADER = 31.

  3. Dead module-level constants. P2PKH_HEADER / P2SH_P2WPKH_HEADER / P2WPKH_HEADER
    are defined but never used; build_header hardcodes the literals 31/35/39.
    Use the constants or drop them so they cannot drift.

  4. Unreachable assertion in test_sign_message_p2tr_bip137.
    The assert ctx.input.wait_for_button.call_count == len(btn_seq) sits after
    the raising call inside the with pytest.raises(...) block, so it never runs.
    Move it after the block or delete it. (This test should change anyway once
    taproot signing is restored.)

Suggested (non-blocking)

  1. Commit says "add test vectors for bip137 module" but there are no direct unit
    tests for bip137.py; coverage is only via the UI. The API-boundary guards in
    build_header / sign are never exercised. Add tests/test_bip137.py.

@qlrd qlrd marked this pull request as draft June 1, 2026 21:36
qlrd added 4 commits June 3, 2026 14:51
This commit fix a P2PKH-compressed header byte
(`31..34`) for legacy signed message for `singlesig` wallets with
P2SH-P2WPKH. Also added support to bip322 (simple) message sign. Tested
with sparrow.

Fix selfcustody#865
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.

2 participants