Skip to content

feat(taproot): P2TR addresses via GetAddress - #328

Closed
BitHighlander wants to merge 2 commits into
feature/taproot-bip340from
feature/taproot-address
Closed

feat(taproot): P2TR addresses via GetAddress#328
BitHighlander wants to merge 2 commits into
feature/taproot-bip340from
feature/taproot-address

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

Second of three steps. Stacked on #327base is feature/taproot-bip340, so the diff here is just the address work.

Removes the return 0 at transaction.c:188. GetAddress now returns bc1p… for m/86' paths.

What changed

  • compute_address() handles SPENDTAPROOT: tweak the x-only internal key per BIP-86, bech32m encode at witness version 1
  • path_mismatched() gains an m/86' branch, in both copies — fsm_msg_coin.h and coins.c carry a "PLEASE keep both copies in sync" note
  • three official BIP-86 vectors

Two guards that would otherwise have failed silently

Taproot multisig is rejected up front. Without it a SPENDTAPROOT + multisig request fell through the has_multisig chain to the p2sh branch and returned a p2sh address for a taproot ask — a wrong address, not an error.

Exactly 32 bytes go to segwit_addr_encode(). It only length-checks the witness program for version 0 (segwit_addr.c:182), so any other length would have encoded into a plausible-looking bc1p.

Pre-existing bug fixed: the PAYTOTAPROOT output gate

signing.c tested !coin->has_taproot. But has_taproot is the nanopb presence flag — every coin in coins.def sets it. Only the taproot value distinguishes them:

  • taproot=true: Bitcoin, Testnet
  • taproot=false: the other 41 coins — BitcoinCash, Litecoin, Dogecoin, Dash, Zcash, …

All 41 were passing the gate, so the firmware would build a P2TR output for chains that cannot spend one. Now tests the value. This is not new to Taproot work — it is live on develop today.

Cost

Retained in firmware.keepkey.elf: 306 bytes (bip340_tweak_pubkey + the two tagged-hash helpers). bip340_sign and bip340_verify still garbage-collect out — nothing references them until step 3.

Testing

Device build: MAKE_EXIT=0, zero errors, zero warnings, all variants (firmware.keepkey, blupdater, bootloader, bootstrap, and the 5 variant images).

11/11 unit tests green. The three BIP-86 vectors are driven two ways:

  1. from the published internal keys → output keys → addresses
  2. end to end from the mnemonic: mnemonic_to_seedm/86'/0'/0'/change/indexpublic_key + 1 → tweak → bech32m

The second one matters: compute_address() feeds node->public_key + 1 to the tweak, and an off-by-one there produces a valid-looking wrong address rather than a failure. Driving it from the mnemonic pins the offset convention against published addresses.

Plus rejection of an off-curve internal key and one past the field size.

Not done here

  • On-device verification. These are host tests; no bc1p has been read off an OLED yet.
  • GetPublicKey with SPENDTAPROOT still errors. BIP-86 uses plain xpubs, so a host asking for the m/86' account xpub uses SPENDADDRESS and that already works — but if you want the script_type accepted there too, say so.
  • Spending from P2TR. That is step 3: BIP-341 sighash, the signing path, confirm UX, Gate-3 OLED.

Merge order

Depends on keepkey/trezor-firmware#6, which is itself stacked on #5. Merge order is #5#6#327 → this. Note that merging a stacked PR with --delete-branch auto-closes its dependents, so re-point bases rather than deleting branches.

Second of three steps.  Removes the `return 0` at transaction.c:188 and
makes GetAddress return bc1p addresses for m/86' paths.

  - compute_address() handles SPENDTAPROOT: tweak the x-only internal key
    per BIP-86, then bech32m encode it at witness version 1
  - path_mismatched() gains an m/86' branch, in BOTH copies (fsm_msg_coin.h
    and coins.c, per the keep-in-sync note above them)
  - three official BIP-86 vectors, driven from the published internal keys
    and again end to end from the mnemonic

Two guards worth calling out, because both would have failed silently:

  - taproot multisig is rejected up front.  Without it the request fell
    through to the p2sh branch and returned a p2sh address for a taproot
    ask -- a wrong address, not an error.
  - exactly 32 bytes are passed to segwit_addr_encode(), which only
    length-checks the witness program for version 0 (segwit_addr.c:182).
    Any other length would have encoded into a plausible-looking bc1p.

Also fixes a pre-existing bug in the PAYTOTAPROOT output gate.  It tested
`!coin->has_taproot`, but has_taproot is the nanopb presence flag and every
coin in coins.def sets it -- only the `taproot` VALUE distinguishes them.
Bitcoin and Testnet have taproot=true; the other 41 coins have taproot=false
and were all passing the gate, building p2tr outputs for chains that cannot
spend them.  Now tests the value.

Device build clean (MAKE_EXIT=0, no warnings, all variants).  Retained cost
in firmware.keepkey.elf is 306 bytes; bip340_sign and bip340_verify still
garbage-collect out until step 3 references them.

11/11 unit tests green.  Depends on keepkey/trezor-firmware#6.
Bumps deps/python-keepkey by one commit to pick up
tests/test_msg_getaddress_taproot.py (BitHighlander/python-keepkey#28),
which drives the emulator through the full SPENDTAPROOT GetAddress path
and asserts the three official BIP-86 addresses.

Verified against a locally built kkemu: 1 passed, and mutation checked so
the assertions are known not to be vacuous.

The test gates on firmware 7.16.0 and CMakeLists is 7.15.0, so it SKIPS
until the project version bumps.  Deliberate: gating at 7.15.0 would make
released 7.15.0-rcN firmware without taproot fail rather than skip.
@BitHighlander

Copy link
Copy Markdown
Owner Author

Superseded — this content is already on develop. compare/develop...feature/taproot-address reports ahead_by=0, behind_by=22, i.e. every commit here is reachable from develop; P2TR GetAddress landed via the taproot stack (#327 / #330 / #333) and ships in release/7.15.0-rc25. Closing to keep the queue honest; nothing is lost.

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