Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ set(sources
#trezor-firmware/crypto/tests/test_openssl.c
#trezor-firmware/crypto/tests/test_speed.c
trezor-firmware/crypto/secp256k1.c
trezor-firmware/crypto/bip340.c
trezor-firmware/crypto/bignum.c
trezor-firmware/crypto/segwit_addr.c
trezor-firmware/crypto/ripemd160.c
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# 7.15.0-rc26 — Taproot witness serialization and long-address display

Gate-3 OLED evidence for the two defects rc25 shipped, both fixed in rc26.

Captured from the emulator built at this branch, driven by python-keepkey with
`KEEPKEY_SCREENSHOT=1`. Seed throughout is the standard BIP-39 test vector
`abandon abandon ... about`, so every address below is a published BIP-86
value and can be checked independently.

> Emulator captures. Per the release SOP these do **not** substitute for
> on-device Gate-3 proof; they establish that the rendering logic is correct
> before anyone picks up a device.

## What rc25 got wrong

**1. Addresses longer than one line were silently truncated.**
`draw_string()` stopped at the bottom of the canvas and dropped the rest, so the
user verified a 42-character prefix while the QR code beside it encoded the whole
address — the two disagreed with no indication. This became urgent the moment
rc25 shipped Taproot, because every `bc1p` address is 62 characters.

`01` shows `m/86'/0'/0'/0/0` rendering the complete
`bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr`
across two lines, matching the QR. `02` shows the same fix for P2WSH multisig,
which had the identical defect before Taproot existed.

**2. The witness and locktime never reached the host.**
The Taproot signing branch set `has_signature` but not `has_serialized_tx`, so
nanopb omitted `serialized_tx` entirely and 70 bytes — the 66-byte witness plus
the 4-byte locktime footer — were dropped on the wire. The signature itself was
always correct, which is why every test passed.

`03`–`08` are the confirm screens for the three signing flows that now also
assert the full BIP-144 serialization. They prove the amount, destination and
fee shown to the user are unchanged by that fix.

## Frames

| File | Screen |
|---|---|
| `01-p2tr-address-full-62-chars.png` | BIP-86 receive address, complete, with QR |
| `02-p2wsh-address-full.png` | P2WSH multisig address, complete |
| `03-p2tr-spend-recipient.png` | `Send 0.0009 BTC to 1BitcoinEater…` |
| `04-p2tr-spend-fee.png` | Total `0.001 BTC`, fee `0.0001 BTC` |
| `05-p2tr-change-recipient.png` | Recipient, device-derived P2TR change omitted |
| `06-p2tr-change-fee.png` | Fee confirmation, change flow |
| `07-mixed-p2tr-legacy-recipient.png` | Recipient, mixed Taproot + legacy inputs |
| `08-mixed-p2tr-legacy-fee.png` | Fee confirmation, mixed inputs |

## Reproducing

```sh
cd scripts/emulator
docker compose build kkemu && docker compose up -d kkemu
docker compose run --rm -v "$PWD/out:/out" --entrypoint /bin/sh python-keepkey -c '
cd /kkemu/deps/python-keepkey/tests && export PYTHONPATH=".."
KEEPKEY_SCREENSHOT=1 SCREENSHOT_DIR=/out \
KK_TRANSPORT_MAIN=kkemu:11044 KK_TRANSPORT_DEBUG=kkemu:11045 \
python3 -m pytest test_msg_signtx_taproot.py test_msg_getaddress_taproot.py \
test_taproot_screens.py -q'
```

Frames land in `$SCREENSHOT_DIR/<module>/<test_name>/btnNNNNN.png`.

## Still owed

On-device Gate-3 for both flows: display a `bc1p` receive address and compare it
character-for-character against the QR, then spend a P2TR input and confirm the
amount, destination and fee screens appear and require a press.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"candidate": "7.15.0-rc26",
"seed": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"capture": {
"source": "emulator",
"method": "python-keepkey with KEEPKEY_SCREENSHOT=1",
"note": "Emulator captures. Per the release SOP these do not substitute for on-device Gate-3 proof."
},
"defects_covered": [
{
"pr": 331,
"title": "addresses longer than one line were silently truncated",
"detail": "draw_string() stopped at the bottom of the canvas and dropped the remainder, so the rendered text and the QR code disagreed with no indication.",
"frames": [
"01-p2tr-address-full-62-chars.png",
"02-p2wsh-address-full.png"
]
},
{
"pr": 337,
"title": "taproot witness and locktime footer never reached the host",
"detail": "The taproot signing branch set has_signature but not has_serialized_tx, so nanopb omitted serialized_tx and 70 bytes (66-byte witness + 4-byte locktime) were dropped on the wire.",
"frames": [
"03-p2tr-spend-recipient.png",
"04-p2tr-spend-fee.png",
"05-p2tr-change-recipient.png",
"06-p2tr-change-fee.png",
"07-mixed-p2tr-legacy-recipient.png",
"08-mixed-p2tr-legacy-fee.png"
]
}
],
"verified_values": {
"bip86_receive_address_m_86h_0h_0h_0_0": "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
"bip86_address_characters": 62,
"serialized_tx_bytes": {
"p2tr": 153,
"p2tr_with_change": 196,
"mixed_p2tr_and_legacy": 301
}
},
"tests": {
"files": [
"test_msg_signtx_taproot.py",
"test_msg_getaddress_taproot.py",
"test_taproot_screens.py"
],
"result": "9 passed"
},
"frames": [
{
"file": "01-p2tr-address-full-62-chars.png",
"sha256": "b1a4174f8e4c88b30a41ce337d0e45ded840c104f6637dcc98a7dfc89d27e073",
"bytes": 996
},
{
"file": "02-p2wsh-address-full.png",
"sha256": "7cf807b25d8a2a6a67bc83f887184b89221a0bfd9d8a2a24872d3bc954669b17",
"bytes": 1057
},
{
"file": "03-p2tr-spend-recipient.png",
"sha256": "91390aec2d647e5dcd5c9cfc767e72fcc62a41e710aae9d8d42abe4af9451fd5",
"bytes": 574
},
{
"file": "04-p2tr-spend-fee.png",
"sha256": "c635bd2cb4c52b4553e4ad0ccbf53ae0e6fa3c61f9dbe60bfbf43d18692228fb",
"bytes": 793
},
{
"file": "05-p2tr-change-recipient.png",
"sha256": "fb1e137d2463b5ba44decd151f5380f649b3e884323c7c54f6f66e87cc3288a0",
"bytes": 579
},
{
"file": "06-p2tr-change-fee.png",
"sha256": "7260c9c0ee5cc85c61356b41b91aa4d30a091bf1be60b7516bff0e4b25b225aa",
"bytes": 790
},
{
"file": "07-mixed-p2tr-legacy-recipient.png",
"sha256": "6964d237e5208ec5cf2ca03ca35a3515cbd522eb4d0799e372285b5262cf1f6c",
"bytes": 587
},
{
"file": "08-mixed-p2tr-legacy-fee.png",
"sha256": "8ff0582a2bb578485b1c4973f042f8f7e1454473b7995174afdfb4e72cb56f1e",
"bytes": 796
}
]
}
2 changes: 2 additions & 0 deletions include/keepkey/firmware/coins.def
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//coin_name coin_shortcut address_type maxfee_kb p2sh signed_message_header bip44_account_path forkid/chain_id decimals contract_address xpub_magic segwit force_bip143 curve_name cashaddr_prefix bech32_prefix decred xpub_magic_segwit_p2sh xpub_mmagic_segwit_native nanoaddr_prefix taproot
X(true, "Bitcoin", true, "BTC", true, 0, true, 100000, true, 5, true, "Bitcoin Signed Message:\n", true, 0x80000000, false, 0, true, 8, false, NO_CONTRACT, true, 76067358, true, true, true, false, true, SECP256K1_STRING, false, "", true, "bc", false, false, true, 77429938, true, 78792518, false, "", true, true )
X(true, "Testnet", true, "TEST", true, 111, true, 10000000, true, 196, true, "Bitcoin Signed Message:\n", true, 0x80000001, false, 0, true, 8, false, NO_CONTRACT, true, 70617039, true, true, true, false, true, SECP256K1_STRING, false, "", true, "tb", false, false, true, 71979618, true, 73342198, false, "", true, true )
#if !BITCOIN_ONLY
X(true, "BitcoinCash", true, "BCH", true, 0, true, 500000, true, 5, true, "Bitcoin Signed Message:\n", true, 0x80000091, true, 0, true, 8, false, NO_CONTRACT, true, 76067358, true, false, true, true, true, SECP256K1_STRING, true, "bitcoincash", false, "", false, false, false, 0, false, 0, false, "", true, false )
X(true, "Namecoin", true, "NMC", true, 52, true, 10000000, true, 5, true, "Namecoin Signed Message:\n", true, 0x80000007, false, 0, true, 8, false, NO_CONTRACT, true, 27108450, true, false, true, false, true, SECP256K1_STRING, false, "", false, "", false, false, false, 0, false, 0, false, "", true, false )
X(true, "Litecoin", true, "LTC", true, 48, true, 1000000, true, 50, true, "Litecoin Signed Message:\n", true, 0x80000002, false, 0, true, 8, false, NO_CONTRACT, true, 27108450, true, true, true, false, true, SECP256K1_STRING, false, "", true, "ltc", false, false, true, 28471030, true, 78792518, false, "", true, false )
Expand Down Expand Up @@ -47,6 +48,7 @@ X(true, "Terra", true, "LUNA", false, NA, false, NA, false, N
X(true, "Kava", true, "KAVA", false, NA, false, NA, false, NA, false, {0}, true, 0x800001cb, false, 0, true, 6, false, NO_CONTRACT, false, 0, false, false, false, false, true, SECP256K1_STRING, false, "", false, "kava", false, false, false, 0, false, 0, false, "", true, false )
X(true, "Secret", true, "SCRT", false, NA, false, NA, false, NA, false, {0}, true, 0x80000211, false, 0, true, 6, false, NO_CONTRACT, false, 0, false, false, false, false, true, SECP256K1_STRING, false, "", false, "secret", false, false, false, 0, false, 0, false, "", true, false )
X(true, "MAYAChain", true, "CACAO", false, NA, false, NA, false, NA, false, {0}, true, 0x800003a3, false, 0, true, 10, false, NO_CONTRACT, false, 0, false, false, false, false, true, SECP256K1_STRING, false, "", false, "maya", false, false, false, 0, false, 0, false, "", true, false )
#endif // !BITCOIN_ONLY

#undef X
#undef NO_CONTRACT
2 changes: 2 additions & 0 deletions include/keepkey/firmware/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ enum {
CONCAT(CoinIndex, __COUNTER__),
#include "keepkey/firmware/coins.def"

#if !BITCOIN_ONLY // ERC-20 tokens excluded from the bitcoin-only image
#define X(INDEX, NAME, SYMBOL, DECIMALS, CONTRACT_ADDRESS) \
CONCAT(CoinIndex, __COUNTER__),
#include "keepkey/firmware/tokens.def"
#endif

CoinIndexLast,
CoinIndexFirst = 0
Expand Down
8 changes: 8 additions & 0 deletions include/keepkey/firmware/signing.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@
#include "trezor/crypto/bip32.h"
#include "keepkey/transport/interface.h"

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
/// Exposed for unit tests: pure predicate, no signing state involved.
bool isCrossAccountSegwitChangeForbidden(const uint32_t* lhs_address_n,
size_t lhs_address_n_count,
const uint32_t* rhs_address_n,
size_t rhs_address_n_count,
OutputScriptType rhs_script_type);

void signing_init(const SignTx* msg, const CoinType* _coin,
const HDNode* _root);
void signing_abort(void);
Expand Down
9 changes: 9 additions & 0 deletions include/keepkey/firmware/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ uint32_t serialize_script_multisig(const CoinType* coin,
int compile_output(const CoinType* coin, const HDNode* root, TxOutputType* in,
TxOutputBinType* out, bool needs_confirm);

bool address_to_script_pubkey(const CoinType* coin, const char* address,
uint8_t* script_pubkey, size_t* script_pubkey_len,
size_t script_pubkey_size);

bool fill_input_script_pubkey(const CoinType* coin, const HDNode* root,
const TxInputType* in, uint8_t* script_pubkey,
size_t* script_pubkey_len,
size_t script_pubkey_size);

uint32_t tx_prevout_hash(Hasher* hasher, const TxInputType* input);
uint32_t tx_script_hash(Hasher* hasher, uint32_t size, const uint8_t* data);
uint32_t tx_sequence_hash(Hasher* hasher, const TxInputType* input);
Expand Down
18 changes: 18 additions & 0 deletions lib/firmware/coins.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const CoinType coins[COINS_COUNT] = {
TAPROOT},
#include "keepkey/firmware/coins.def"

#if !BITCOIN_ONLY // ERC-20 tokens excluded from the bitcoin-only image
#define X(INDEX, NAME, SYMBOL, DECIMALS, CONTRACT_ADDRESS) \
{ \
true, \
Expand Down Expand Up @@ -131,6 +132,7 @@ const CoinType coins[COINS_COUNT] = {
false, /* has_taproot, taproot*/ \
},
#include "keepkey/firmware/tokens.def"
#endif // !BITCOIN_ONLY
};

_Static_assert(sizeof(coins) / sizeof(coins[0]) == COINS_COUNT,
Expand Down Expand Up @@ -226,6 +228,22 @@ static bool path_mismatched(const CoinType* coin, const uint32_t* address_n,
return mismatch;
}

// m/86' : BIP86 Taproot
// m / purpose' / bip44_account_path' / account' / change / address_index
if (address_n[0] == (0x80000000 + 86)) {
mismatch |= !coin->has_segwit || !coin->segwit;
mismatch |= !coin->has_bech32_prefix;
mismatch |= !coin->has_taproot || !coin->taproot;
mismatch |= (address_n_count != (whole_account ? 3 : 5));
mismatch |= (address_n[1] != coin->bip44_account_path);
mismatch |= (address_n[2] & 0x80000000) == 0;
if (!whole_account) {
mismatch |= (address_n[3] & 0x80000000) == 0x80000000;
mismatch |= (address_n[4] & 0x80000000) == 0x80000000;
}
return mismatch;
}

return false;
}

Expand Down
6 changes: 6 additions & 0 deletions lib/firmware/fsm_msg_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ void fsm_msgGetFeatures(GetFeatures* msg) {
resp->has_model = true;
strlcpy(resp->model, model(), sizeof(resp->model));

/* Taproot capability. Reported directly so a host does not have to infer
P2TR support from a firmware version -- that inference breaks whenever the
feature is retargeted to a different release. */
resp->has_supports_taproot = true;
resp->supports_taproot = true;

/* Variant Name */
resp->has_firmware_variant = true;
strlcpy(resp->firmware_variant, variant_getName(),
Expand Down
Loading
Loading