feat(taproot): P2TR addresses via GetAddress - #328
Closed
BitHighlander wants to merge 2 commits into
Closed
Conversation
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.
4 tasks
Owner
Author
|
Superseded — this content is already on develop. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of three steps. Stacked on #327 — base is
feature/taproot-bip340, so the diff here is just the address work.Removes the
return 0attransaction.c:188. GetAddress now returnsbc1p…form/86'paths.What changed
compute_address()handlesSPENDTAPROOT: tweak the x-only internal key per BIP-86, bech32m encode at witness version 1path_mismatched()gains anm/86'branch, in both copies —fsm_msg_coin.handcoins.ccarry a "PLEASE keep both copies in sync" noteTwo guards that would otherwise have failed silently
Taproot multisig is rejected up front. Without it a
SPENDTAPROOT+ multisig request fell through thehas_multisigchain 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-lookingbc1p.Pre-existing bug fixed: the PAYTOTAPROOT output gate
signing.ctested!coin->has_taproot. Buthas_taprootis the nanopb presence flag — every coin incoins.defsets it. Only thetaprootvalue distinguishes them:taproot=true: Bitcoin, Testnettaproot=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
developtoday.Cost
Retained in
firmware.keepkey.elf: 306 bytes (bip340_tweak_pubkey+ the two tagged-hash helpers).bip340_signandbip340_verifystill 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:
mnemonic_to_seed→m/86'/0'/0'/change/index→public_key + 1→ tweak → bech32mThe second one matters:
compute_address()feedsnode->public_key + 1to 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
bc1phas been read off an OLED yet.GetPublicKeywithSPENDTAPROOTstill errors. BIP-86 uses plain xpubs, so a host asking for them/86'account xpub usesSPENDADDRESSand that already works — but if you want the script_type accepted there too, say so.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-branchauto-closes its dependents, so re-point bases rather than deleting branches.