Merge branch 'release/21.07'

pull/1711/head
Martin Milata 3 years ago
commit 0d251fc01a

@ -21,7 +21,7 @@
"xpub_magic_multisig_segwit_native": 70617039,
"bech32_prefix": "tbtg",
"cashaddr_prefix": null,
"slip44": 156,
"slip44": 1,
"segwit": true,
"decred": false,
"fork_id": 79,

@ -21,7 +21,7 @@
"xpub_magic_multisig_segwit_native": null,
"bech32_prefix": null,
"cashaddr_prefix": null,
"slip44": 224,
"slip44": 1,
"segwit": false,
"decred": false,
"fork_id": null,

@ -184,6 +184,9 @@ def validate_btc(coin):
if not coin["max_address_length"] >= coin["min_address_length"]:
errors.append("max address length must not be smaller than min address length")
if "testnet" in coin["coin_name"].lower() and coin["slip44"] != 1:
errors.append("testnet coins must use slip44 coin type 1")
if coin["segwit"]:
if coin["bech32_prefix"] is None:
errors.append("bech32_prefix must be defined for segwit-enabled coin")

@ -26,6 +26,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix red screen on shutdown. [#1658]
- Empty passphrase is properly cached in Cardano functions [#1659]
### Security
- Ensure that all testnet coins use SLIP-44 coin type 1.
- Disable all testnet coins from accessing Bitcoin paths.
- Restrict BIP-45 paths to Bitcoin and coins with strong replay protection.
- Fix operation source account encoding in Stellar.
## 2.4.0 [9th June 2021]

@ -1,4 +1,5 @@
import gc
from micropython import const
from trezor import wire
from trezor.enums import InputScriptType
@ -61,6 +62,12 @@ PATTERN_UNCHAINED_UNHARDENED = (
)
PATTERN_UNCHAINED_DEPRECATED = "m/45'/coin_type'/account'/[0-1000000]/address_index"
# SLIP-44 coin type for Bitcoin
SLIP44_BITCOIN = const(0)
# SLIP-44 coin type for all Testnet coins
SLIP44_TESTNET = const(1)
def validate_path_against_script_type(
coin: coininfo.CoinInfo,
@ -82,7 +89,7 @@ def validate_path_against_script_type(
if script_type == InputScriptType.SPENDADDRESS and not multisig:
patterns.append(PATTERN_BIP44)
if coin.coin_name in BITCOIN_NAMES:
if coin.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
@ -90,11 +97,15 @@ def validate_path_against_script_type(
script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG)
and multisig
):
patterns.append(PATTERN_BIP45)
patterns.append(PATTERN_PURPOSE48_RAW)
if coin.coin_name in BITCOIN_NAMES:
if coin.slip44 == SLIP44_BITCOIN or (
coin.fork_id is not None and coin.slip44 != SLIP44_TESTNET
):
patterns.append(PATTERN_BIP45)
if coin.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
if coin.coin_name in BITCOIN_NAMES:
patterns.append(PATTERN_UNCHAINED_HARDENED)
patterns.append(PATTERN_UNCHAINED_UNHARDENED)
patterns.append(PATTERN_UNCHAINED_DEPRECATED)
@ -103,16 +114,17 @@ def validate_path_against_script_type(
patterns.append(PATTERN_BIP49)
if multisig:
patterns.append(PATTERN_PURPOSE48_P2SHSEGWIT)
if coin.coin_name in BITCOIN_NAMES:
if coin.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
if coin.coin_name in BITCOIN_NAMES:
patterns.append(PATTERN_CASA)
elif coin.segwit and script_type == InputScriptType.SPENDWITNESS:
patterns.append(PATTERN_BIP84)
if multisig:
patterns.append(PATTERN_PURPOSE48_SEGWIT)
if coin.coin_name in BITCOIN_NAMES:
if coin.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
@ -125,18 +137,29 @@ def get_schemas_for_coin(coin: coininfo.CoinInfo) -> Iterable[PathSchema]:
# basic patterns
patterns = [
PATTERN_BIP44,
PATTERN_BIP45,
PATTERN_PURPOSE48_RAW,
]
# compatibility patterns
if coin.coin_name in BITCOIN_NAMES:
# patterns without coin_type field must be treated as if coin_type == 0
if coin.slip44 == SLIP44_BITCOIN or (
coin.fork_id is not None and coin.slip44 != SLIP44_TESTNET
):
patterns.append(PATTERN_BIP45)
if coin.slip44 == SLIP44_BITCOIN:
patterns.extend(
(
PATTERN_GREENADDRESS_A,
PATTERN_GREENADDRESS_B,
PATTERN_GREENADDRESS_SIGN_A,
PATTERN_GREENADDRESS_SIGN_B,
)
)
# compatibility patterns
if coin.coin_name in BITCOIN_NAMES:
patterns.extend(
(
PATTERN_CASA,
PATTERN_UNCHAINED_HARDENED,
PATTERN_UNCHAINED_UNHARDENED,
@ -157,11 +180,16 @@ def get_schemas_for_coin(coin: coininfo.CoinInfo) -> Iterable[PathSchema]:
schemas = [PathSchema.parse(pattern, coin.slip44) for pattern in patterns]
# some wallets such as Electron-Cash (BCH) store coins on Bitcoin paths
# we can allow spending these coins from Bitcoin paths if the coin has
# implemented strong replay protection via SIGHASH_FORKID
if coin.fork_id is not None:
schemas.extend(PathSchema.parse(pattern, 0) for pattern in patterns)
# Some wallets such as Electron-Cash (BCH) store coins on Bitcoin paths.
# We can allow spending these coins from Bitcoin paths if the coin has
# implemented strong replay protection via SIGHASH_FORKID. However, we
# cannot allow spending any testnet coins from Bitcoin paths, because
# otherwise an attacker could trick the user into spending BCH on a Bitcoin
# path by signing a seemingly harmless BCH Testnet transaction.
if coin.fork_id is not None and coin.slip44 != SLIP44_TESTNET:
schemas.extend(
PathSchema.parse(pattern, SLIP44_BITCOIN) for pattern in patterns
)
gc.collect()
return [schema.copy() for schema in schemas]

@ -390,7 +390,7 @@ def by_name(name: str) -> CoinInfo:
xpub_magic_multisig_segwit_native=0x043587cf,
bech32_prefix="tbtg",
cashaddr_prefix=None,
slip44=156,
slip44=1,
segwit=True,
fork_id=79,
force_bip143=True,
@ -1482,7 +1482,7 @@ def by_name(name: str) -> CoinInfo:
xpub_magic_multisig_segwit_native=None,
bech32_prefix=None,
cashaddr_prefix=None,
slip44=224,
slip44=1,
segwit=False,
fork_id=None,
force_bip143=False,

@ -138,8 +138,9 @@ def _write_set_options_int(w, value: int):
def write_account(w, source_account: str):
if source_account is None:
writers.write_bool(w, False)
return
writers.write_pubkey(w, source_account)
else:
writers.write_bool(w, True)
writers.write_pubkey(w, source_account)
def _write_asset_code(w, asset_type: int, asset_code: str):

@ -53,15 +53,11 @@ class TestBitcoinKeychain(unittest.TestCase):
valid_addresses = (
[H_(44), H_(1), H_(0), 0, 0],
[H_(45), 99, 1, 1000],
[H_(48), H_(1), H_(0), H_(2), 1, 1000],
[H_(49), H_(1), H_(0), 0, 10],
[H_(84), H_(1), H_(0), 0, 10],
# Casa:
[49, 1, 0, 0, 10],
# Green:
[1, 1000],
[H_(3), H_(10), 4, 1000],
)
invalid_addresses = (
[H_(43), H_(1), H_(0), 0, 0],
@ -69,6 +65,10 @@ class TestBitcoinKeychain(unittest.TestCase):
[44, 1, 0, 0, 0],
[H_(44), H_(1), H_(0)],
[H_(44), H_(1), H_(0), 0, 0, 0],
[H_(45), 99, 1, 1000],
# Green:
[1, 1000],
[H_(3), H_(10), 4, 1000],
)
for addr in valid_addresses:
@ -141,7 +141,6 @@ class TestAltcoinKeychains(unittest.TestCase):
self.assertTrue(coin.segwit)
valid_addresses = (
[H_(44), H_(2), H_(0), 0, 0],
[H_(45), 99, 1, 1000],
[H_(48), H_(2), H_(0), H_(2), 1, 1000],
[H_(49), H_(2), H_(0), 0, 10],
[H_(84), H_(2), H_(0), 0, 10],
@ -150,6 +149,7 @@ class TestAltcoinKeychains(unittest.TestCase):
[H_(43), H_(2), H_(0), 0, 0],
# Bitcoin paths:
[H_(44), H_(0), H_(0), 0, 0],
[H_(45), 99, 1, 1000],
[H_(49), H_(0), H_(0), 0, 0],
[H_(84), H_(0), H_(0), 0, 0],

@ -10,6 +10,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Removed support for Firo [#1647]
- Removed support for Hatch [#1650]
### Fixed
- Allow non-standard paths used by Unchained Capital, Green Address and Casa. [#1660]
### Security
- Ensure that all testnet coins use SLIP-44 coin type 1.
- Restrict BIP-45 paths to Bitcoin and coins with strong replay protection.
- Don't show addresses that have an unrecognized path.
- Disable all testnet coins from accessing Bitcoin paths.
- Restrict the BIP-32 path ranges of `account`, `change` and `address_index` fields.
- Fix operation source account encoding in Stellar.
## 1.10.1 [9th June 2021]
@ -400,3 +411,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[#1627]: https://github.com/trezor/trezor-firmware/issues/1627
[#1647]: https://github.com/trezor/trezor-firmware/issues/1647
[#1650]: https://github.com/trezor/trezor-firmware/issues/1650
[#1660]: https://github.com/trezor/trezor-firmware/issues/1660

@ -33,6 +33,16 @@
#include "segwit_addr.h"
#include "sha2.h"
#define PATH_MAX_ACCOUNT 100
#define PATH_MAX_CHANGE 1
#define PATH_MAX_ADDRESS_INDEX 1000000
// SLIP-44 hardened coin type for Bitcoin
#define SLIP44_BITCOIN 0x80000000
// SLIP-44 hardened coin type for all Testnet coins
#define SLIP44_TESTNET 0x80000001
uint32_t ser_length(uint32_t len, uint8_t *out) {
if (len < 253) {
out[0] = len & 0xFF;
@ -512,10 +522,14 @@ static bool check_cointype(const CoinInfo *coin, uint32_t slip44, bool full) {
(void)full;
#else
if (!full) {
// some wallets such as Electron-Cash (BCH) store coins on Bitcoin paths
// we can allow spending these coins from Bitcoin paths if the coin has
// implemented strong replay protection via SIGHASH_FORKID
if (slip44 == 0x80000000 && coin->has_fork_id) {
// Some wallets such as Electron-Cash (BCH) store coins on Bitcoin paths.
// We can allow spending these coins from Bitcoin paths if the coin has
// implemented strong replay protection via SIGHASH_FORKID. However, we
// cannot allow spending any testnet coins from Bitcoin paths, because
// otherwise an attacker could trick the user into spending BCH on a Bitcoin
// path by signing a seemingly harmless BCH Testnet transaction.
if (slip44 == SLIP44_BITCOIN && coin->has_fork_id &&
coin->coin_type != SLIP44_TESTNET) {
return true;
}
}
@ -523,38 +537,103 @@ static bool check_cointype(const CoinInfo *coin, uint32_t slip44, bool full) {
return coin->coin_type == slip44;
}
bool coin_known_path_check(const CoinInfo *coin, InputScriptType script_type,
uint32_t address_n_count, const uint32_t *address_n,
bool full) {
bool coin_path_check(const CoinInfo *coin, InputScriptType script_type,
uint32_t address_n_count, const uint32_t *address_n,
bool has_multisig, CoinPathCheckLevel level) {
// For level BASIC this function checks that a coin without strong replay
// protection doesn't access paths that are known to be used by another coin.
// Used by SignTx to ensure that a user cannot be coerced into signing a
// testnet transaction or a Litecoin transaction which in fact spends Bitcoin.
// For level KNOWN this function checks that the path is a recognized path for
// the given coin. Used by GetAddress to prevent ransom attacks where a user
// could be coerced to use an address with an unenumerable path.
// For level SCRIPT_TYPE this function makes the same checks as in level
// KNOWN, but includes script type checks.
const bool check_known = (level >= CoinPathCheckLevel_KNOWN);
const bool check_script_type = (level >= CoinPathCheckLevel_SCRIPT_TYPE);
bool valid = true;
// m/44' : BIP44 Legacy
// m / purpose' / coin_type' / account' / change / address_index
if (address_n_count > 0 && address_n[0] == (0x80000000 + 44)) {
if (full) {
valid &= (address_n_count == 5);
if (check_known) {
valid = valid && (address_n_count == 5);
} else {
valid &= (address_n_count >= 2);
valid = valid && (address_n_count >= 2);
}
valid = valid && check_cointype(coin, address_n[1], check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDADDRESS);
valid = valid && (!has_multisig);
}
valid &= check_cointype(coin, address_n[1], full);
if (full) {
valid &= (script_type == InputScriptType_SPENDADDRESS);
valid &= (address_n[2] & 0x80000000) == 0x80000000;
valid &= (address_n[3] & 0x80000000) == 0;
valid &= (address_n[4] & 0x80000000) == 0;
if (check_known) {
valid = valid && ((address_n[2] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[2] & 0x7fffffff) <= PATH_MAX_ACCOUNT);
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
}
return valid;
}
// m/45' - BIP45 Copay Abandoned Multisig P2SH
// m / purpose' / cosigner_index / change / address_index
if (address_n_count > 0 && address_n[0] == (0x80000000 + 45)) {
if (full) {
valid &= (script_type == InputScriptType_SPENDMULTISIG);
valid &= (address_n_count == 4);
valid &= (address_n[1] & 0x80000000) == 0;
valid &= (address_n[2] & 0x80000000) == 0;
valid &= (address_n[3] & 0x80000000) == 0;
if (check_script_type) {
valid = valid && has_multisig;
}
if (address_n_count == 4) {
// m/45' - BIP45 Copay Abandoned Multisig P2SH
// m / purpose' / cosigner_index / change / address_index
// Patterns without a coin_type field must be treated as Bitcoin paths.
valid = valid && check_cointype(coin, SLIP44_BITCOIN, check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDMULTISIG);
}
if (check_known) {
valid = valid && (address_n[1] <= 100);
valid = valid && (address_n[2] <= PATH_MAX_CHANGE);
valid = valid && (address_n[3] <= PATH_MAX_ADDRESS_INDEX);
}
} else if (address_n_count == 5) {
// Unchained Capital compatibility pattern. Will be removed in the
// future.
// m / 45' / coin_type' / account' / [0-1000000] / address_index
valid = valid && check_cointype(coin, address_n[1], check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDADDRESS ||
script_type == InputScriptType_SPENDMULTISIG);
}
if (check_known) {
valid = valid && ((address_n[2] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[2] & 0x7fffffff) <= PATH_MAX_ACCOUNT);
valid = valid && (address_n[3] <= 1000000);
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
}
} else if (address_n_count == 6) {
// Unchained Capital compatibility pattern. Will be removed in the
// future.
// m/45'/coin_type'/account'/[0-1000000]/change/address_index
// m/45'/coin_type/account/[0-1000000]/change/address_index
valid =
valid && check_cointype(coin, 0x80000000 | address_n[1], check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDADDRESS ||
script_type == InputScriptType_SPENDMULTISIG);
}
if (check_known) {
valid = valid &&
((address_n[1] & 0x80000000) == (address_n[2] & 0x80000000));
valid = valid && ((address_n[2] & 0x7fffffff) <= PATH_MAX_ACCOUNT);
valid = valid && (address_n[3] <= 1000000);
valid = valid && (address_n[4] <= PATH_MAX_CHANGE);
valid = valid && (address_n[5] <= PATH_MAX_ADDRESS_INDEX);
}
} else {
if (check_known) {
return false;
}
}
return valid;
}
@ -563,18 +642,32 @@ bool coin_known_path_check(const CoinInfo *coin, InputScriptType script_type,
// Electrum:
// m / purpose' / coin_type' / account' / type' / change / address_index
if (address_n_count > 0 && address_n[0] == (0x80000000 + 48)) {
if (full) {
valid &= (address_n_count == 5) || (address_n_count == 6);
if (check_known) {
valid = valid && (address_n_count == 5 || address_n_count == 6);
} else {
valid &= (address_n_count >= 2);
valid = valid && (address_n_count >= 2);
}
valid = valid && check_cointype(coin, address_n[1], check_known);
if (check_script_type) {
valid = valid && has_multisig;
valid = valid && (script_type == InputScriptType_SPENDMULTISIG ||
script_type == InputScriptType_SPENDP2SHWITNESS ||
script_type == InputScriptType_SPENDWITNESS);
}
valid &= check_cointype(coin, address_n[1], full);
if (full) {
valid &= (script_type == InputScriptType_SPENDMULTISIG) ||
(script_type == InputScriptType_SPENDP2SHWITNESS) ||
(script_type == InputScriptType_SPENDWITNESS);
valid &= (address_n[2] & 0x80000000) == 0x80000000;
valid &= (address_n[4] & 0x80000000) == 0;
if (check_known) {
valid = valid && ((address_n[2] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[2] & 0x7fffffff) <= PATH_MAX_ACCOUNT);
if (address_n_count == 5) {
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
} else if (address_n_count == 6) {
valid = valid && ((address_n[3] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[3] & 0x7fffffff) <= 3);
valid = valid && (address_n[4] <= PATH_MAX_CHANGE);
valid = valid && (address_n[5] <= PATH_MAX_ADDRESS_INDEX);
} else {
return false;
}
}
return valid;
}
@ -582,18 +675,21 @@ bool coin_known_path_check(const CoinInfo *coin, InputScriptType script_type,
// m/49' : BIP49 SegWit
// m / purpose' / coin_type' / account' / change / address_index
if (address_n_count > 0 && address_n[0] == (0x80000000 + 49)) {
valid &= coin->has_segwit;
if (full) {
valid &= (address_n_count == 5);
valid = valid && coin->has_segwit;
if (check_known) {
valid = valid && (address_n_count == 5);
} else {
valid &= (address_n_count >= 2);
valid = valid && (address_n_count >= 2);
}
valid &= check_cointype(coin, address_n[1], full);
if (full) {
valid &= (script_type == InputScriptType_SPENDP2SHWITNESS);
valid &= (address_n[2] & 0x80000000) == 0x80000000;
valid &= (address_n[3] & 0x80000000) == 0;
valid &= (address_n[4] & 0x80000000) == 0;
valid = valid && check_cointype(coin, address_n[1], check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDP2SHWITNESS);
}
if (check_known) {
valid = valid && ((address_n[2] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[2] & 0x7fffffff) <= PATH_MAX_ACCOUNT);
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
}
return valid;
}
@ -601,23 +697,89 @@ bool coin_known_path_check(const CoinInfo *coin, InputScriptType script_type,
// m/84' : BIP84 Native SegWit
// m / purpose' / coin_type' / account' / change / address_index
if (address_n_count > 0 && address_n[0] == (0x80000000 + 84)) {
valid &= coin->has_segwit;
valid &= coin->bech32_prefix != NULL;
if (full) {
valid &= (address_n_count == 5);
valid = valid && coin->has_segwit;
valid = valid && (coin->bech32_prefix != NULL);
if (check_known) {
valid = valid && (address_n_count == 5);
} else {
valid = valid && (address_n_count >= 2);
}
valid = valid && check_cointype(coin, address_n[1], check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDWITNESS);
}
if (check_known) {
valid = valid && ((address_n[2] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[2] & 0x7fffffff) <= PATH_MAX_ACCOUNT);
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
}
return valid;
}
// Green Address compatibility pattern. Will be removed in the future.
// m / [1,4] / address_index
if (address_n_count > 0 && (address_n[0] == 1 || address_n[0] == 4)) {
valid = valid && (coin->coin_type == SLIP44_BITCOIN);
if (check_known) {
valid = valid && (address_n_count == 2);
valid = valid && (address_n[1] <= PATH_MAX_ADDRESS_INDEX);
}
return valid;
}
// Green Address compatibility pattern. Will be removed in the future.
// m / 3' / [1-100]' / [1,4] / address_index
if (address_n_count > 0 && address_n[0] == (0x80000000 + 3)) {
valid = valid && (coin->coin_type == SLIP44_BITCOIN);
if (check_known) {
valid = valid && (address_n_count == 4);
valid = valid && ((address_n[1] & 0x80000000) == 0x80000000);
valid = valid && ((address_n[1] & 0x7fffffff) <= 100);
valid = valid && (address_n[2] == 1 || address_n[2] == 4);
valid = valid && (address_n[3] <= PATH_MAX_ADDRESS_INDEX);
}
return valid;
}
// Green Address compatibility patterns. Will be removed in the future.
// m / 1195487518
// m / 1195487518 / 6 / address_index
if (address_n_count > 0 && address_n[0] == 1195487518) {
valid = valid && (coin->coin_type == SLIP44_BITCOIN);
if (check_known) {
if (address_n_count == 3) {
valid = valid && (address_n[1] == 6);
valid = valid && (address_n[2] <= PATH_MAX_ADDRESS_INDEX);
} else if (address_n_count != 1) {
return false;
}
}
return valid;
}
// Casa compatibility pattern. Will be removed in the future.
// m / 49 / coin_type / account / change / address_index
if (address_n_count > 0 && address_n[0] == 49) {
if (check_known) {
valid = valid && (address_n_count == 5);
} else {
valid &= (address_n_count >= 2);
valid = valid && (address_n_count >= 2);
}
valid =
valid && check_cointype(coin, 0x80000000 | address_n[1], check_known);
if (check_script_type) {
valid = valid && (script_type == InputScriptType_SPENDP2SHWITNESS);
}
valid &= check_cointype(coin, address_n[1], full);
if (full) {
valid &= (script_type == InputScriptType_SPENDWITNESS);
valid &= (address_n[2] & 0x80000000) == 0x80000000;
valid &= (address_n[3] & 0x80000000) == 0;
valid &= (address_n[4] & 0x80000000) == 0;
if (check_known) {
valid = valid && ((address_n[1] & 0x80000000) == 0);
valid = valid && (address_n[2] <= PATH_MAX_ACCOUNT);
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
}
return valid;
}
// we don't check unknown paths
return true;
// we allow unknown paths only when a full check is not required
return level == CoinPathCheckLevel_BASIC;
}

@ -32,6 +32,12 @@
#include "messages-bitcoin.pb.h"
#include "messages-crypto.pb.h"
typedef enum _CoinPathCheckLevel {
CoinPathCheckLevel_BASIC = 0,
CoinPathCheckLevel_KNOWN = 1,
CoinPathCheckLevel_SCRIPT_TYPE = 2,
} CoinPathCheckLevel;
#define ser_length_size(len) ((len) < 253 ? 1 : (len) < 0x10000 ? 3 : 5)
uint32_t ser_length(uint32_t len, uint8_t *out);
@ -82,8 +88,8 @@ int cryptoMultisigFingerprint(const MultisigRedeemScriptType *multisig,
int cryptoIdentityFingerprint(const IdentityType *identity, uint8_t *hash);
bool coin_known_path_check(const CoinInfo *coin, InputScriptType script_type,
uint32_t address_n_count, const uint32_t *address_n,
bool full);
bool coin_path_check(const CoinInfo *coin, InputScriptType script_type,
uint32_t address_n_count, const uint32_t *address_n,
bool has_multisig, CoinPathCheckLevel level);
#endif

@ -183,9 +183,13 @@ void fsm_msgGetAddress(const GetAddress *msg) {
strlcpy(desc, _("Address:"), sizeof(desc));
}
if (!coin_known_path_check(coin, msg->script_type, msg->address_n_count,
msg->address_n, true)) {
if (config_getSafetyCheckLevel() == SafetyCheckLevel_Strict) {
if (!coin_path_check(coin, msg->script_type, msg->address_n_count,
msg->address_n, msg->has_multisig,
CoinPathCheckLevel_SCRIPT_TYPE)) {
if (config_getSafetyCheckLevel() == SafetyCheckLevel_Strict &&
!coin_path_check(coin, msg->script_type, msg->address_n_count,
msg->address_n, msg->has_multisig,
CoinPathCheckLevel_KNOWN)) {
fsm_sendFailure(FailureType_Failure_DataError, _("Forbidden key path"));
layoutHome();
return;

@ -685,8 +685,9 @@ bool compile_input_script_sig(TxInputType *tinput) {
return false;
}
}
if (!coin_known_path_check(coin, tinput->script_type, tinput->address_n_count,
tinput->address_n, false)) {
if (!coin_path_check(coin, tinput->script_type, tinput->address_n_count,
tinput->address_n, tinput->has_multisig,
CoinPathCheckLevel_BASIC)) {
if (config_getSafetyCheckLevel() == SafetyCheckLevel_Strict) {
return false;
}

@ -165,8 +165,8 @@ bool stellar_signingInit(const StellarSignTx *msg) {
bool stellar_confirmSourceAccount(bool has_source_account,
const char *str_account) {
stellar_hashupdate_bool(has_source_account);
if (!has_source_account) {
stellar_hashupdate_bool(false);
return true;
}

@ -17,6 +17,7 @@
import pytest
from trezorlib import btc, messages, tools
from trezorlib.exceptions import TrezorFailure
VECTORS = ( # path, script_type, address
(
@ -51,6 +52,17 @@ def test_show(client, path, script_type, address):
)
def test_show_unrecognized_path(client):
with pytest.raises(TrezorFailure):
btc.get_address(
client,
"Bitcoin",
tools.parse_path("m/24684621h/516582h/5156h/21/856"),
script_type=messages.InputScriptType.SPENDWITNESS,
show_display=True,
)
@pytest.mark.multisig
def test_show_multisig_3(client):
node = btc.get_public_node(

@ -234,7 +234,7 @@ def test_sign_tx_allow_trust_op(client):
def test_sign_tx_change_trust_op(client):
op = messages.StellarChangeTrustOp()
op.limit = 500111000
op.limit = 5000000000
op.source_account = "GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V"
op.asset = messages.StellarAssetType(
@ -248,7 +248,7 @@ def test_sign_tx_change_trust_op(client):
assert (
b64encode(response.signature)
== b"OZdDO/qW8o/xbV6nZaDM/D7z9/fqbrk+P4lSzzCqeD3C8nGOg+Jl33JqHek0zNNOW9Pn+tPpfdoQnuZWJzocCw=="
== b"B7UyKRCzVf6esTkzDJgac0vJ1YfI4Z7Ecq65/3TY0+D/VB3myZVg06LMjgIf10q8kF+GvJwN6XGKRd6q1wxHAw=="
)

@ -26,6 +26,9 @@ from .signtx import request_finished, request_input, request_meta, request_outpu
B = proto.ButtonRequestType
TX_API = TxCache("Testnet")
# NOTE: This test case was migrated from Testnet to Bitcoin, because we
# disabled testnet for BIP-45 paths. So we are still using the Testnet TxCache
# here, but everything else has been changed to Bitcoin mainnet.
TXHASH_16c6c8 = bytes.fromhex(
"16c6c8471b8db7a628f2b2bb86bfeefae1766463ce8692438c7fd3fce3f43ce5"
@ -40,25 +43,25 @@ TXHASH_b0946d = bytes.fromhex(
class TestMultisigChange:
node_ext1 = bip32.deserialize(
"tpubDADHV9u9Y6gkggintTdMjJE3be58zKNLhpxBQyuEM6Pwx3sN9JVLmMCMN4DNVwL9AKec27z5TaWcWuHzMXiGAtcra5DjwWbvppGX4gaEGVN"
"xpub69qexv5TppjJQtXwSGeGXNtgGWyUzvsHACMt4Rr61Be4CmCf55eFcuXX828aySNuNR7hQYUCvUgZpioNxfs2HTAZWUUSFywhErg7JfTPv3Y"
)
# m/1 => 02c0d0c5fee952620757c6128dbf327c996cd72ed3358d15d6518a1186099bc15e
# m/2 => 0375b9dfaad928ce1a7eed88df7c084e67d99e9ab74332419458a9a45779706801
node_ext2 = bip32.deserialize(
"tpubDADHV9u9Y6gkhWXBmDJ6TUhZajLWjvKukRe2w9FfhdbQpUux8Z8jnPHNAZqFRgHPg9sR7YR93xThM32M7NfRu8S5WyDtext7S62sqxeJNkd"
"xpub69qexv5TppjJRiLLK2K1FZNCFcErkXprCo3jabCXMiqX5CFF4LHedwcXvXkTuBL9tFLWVxuGWrdeerXjiWpC1gynTNUaySDsr8SU5xMpj5R"
)
# m/1 => 0388460dc439f4c8f5bcfc268c36e11b4375cad5c3535c336cfdf8c32c3afad5c1
# m/2 => 03a04f945d5a3685729dde697d574076de4bdf38e904f813b22a851548e1110fc0
node_ext3 = bip32.deserialize(
"tpubDADHV9u9Y6gkmM5ohWRGTswrc6fr7soH7e2D2ic5a86PDUaHc5Ln9EbER69cEr5bDZPa7EXguJ1MhWVzPZpZWVdG5fvoF3hfirXvRbpCCBg"
"xpub69qexv5TppjJVYtxFKSBFxcVGyaC8VJDa1RugAYwEDLVUBuaXrVgznvQB44piM8MRerfVf1pNCBK1L1NzhyKd4Ay25BVZX3S8twWfZDxmz7"
)
# m/1 => 02e0c21e2a7cf00b94c5421725acff97f9826598b91f2340c5ddda730caca7d648
# m/2 => 03928301ffb8c0d7a364b794914c716ba3107cc78a6fe581028b0d8638b22e8573
node_int = bip32.deserialize(
"tpubDADHV9u9Y6gke2Vw3rWE8KRXmeK8PTtsF5B3Cqjo6h3SoiyRtzxjnDVG1knxrqB8BpP1dMAd6MR3Ps5UXibiFDtQuWVPXLkJ3HvttZYbH12"
"xpub69qexv5TppjJNEK5bfX8vQ6ASXDUQ5PohSajrHgeknHZ4SJipn7edmpRmiiBLLDtPur71mekZFazhgas8rkUMnS7quk5qp64TLLV8ShrxZJ"
)
# m/1 => 03f91460d79e4e463d7d90cb75254bcd62b515a99a950574c721efdc5f711dff35
# m/2 => 038caebd6f753bbbd2bb1f3346a43cd32140648583673a31d62f2dfb56ad0ab9e3
@ -174,13 +177,13 @@ class TestMultisigChange:
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
def test_external_external(self, client):
out1 = proto.TxOutputType(
address="muevUcG1Bb8eM2nGUGhqmeujHRX7YXjSEu",
address="1F8yBZB2NZhPZvJekhjTwjhQRRvQeTjjXr",
amount=40000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
out2 = proto.TxOutputType(
address="mwdrpMVSJxxsM8f8xbnCHn9ERaRT1NG1UX",
address="1H7uXJQTVwXca2BXF2opTrvuZapk8Cm8zY",
amount=44000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -189,7 +192,7 @@ class TestMultisigChange:
client.set_expected_responses(self._responses(self.inp1, self.inp2))
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -205,7 +208,7 @@ class TestMultisigChange:
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
def test_external_internal(self, client):
out1 = proto.TxOutputType(
address="muevUcG1Bb8eM2nGUGhqmeujHRX7YXjSEu",
address="1F8yBZB2NZhPZvJekhjTwjhQRRvQeTjjXr",
amount=40000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -222,7 +225,7 @@ class TestMultisigChange:
)
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -244,7 +247,7 @@ class TestMultisigChange:
)
out2 = proto.TxOutputType(
address="mwdrpMVSJxxsM8f8xbnCHn9ERaRT1NG1UX",
address="1H7uXJQTVwXca2BXF2opTrvuZapk8Cm8zY",
amount=44000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -255,7 +258,7 @@ class TestMultisigChange:
)
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -271,13 +274,13 @@ class TestMultisigChange:
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
def test_multisig_external_external(self, client):
out1 = proto.TxOutputType(
address="2N2aFoogGntQFFwdUVPfRmutXD22ThcNTsR",
address="3B23k4kFBRtu49zvpG3Z9xuFzfpHvxBcwt",
amount=40000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
out2 = proto.TxOutputType(
address="2NFJjQcU8mw4Z3ywpbek8HL1VoJ27GDrkHw",
address="3PkXLsY7AUZCrCKGvX8FfP2EawowUBMbcg",
amount=44000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -286,7 +289,7 @@ class TestMultisigChange:
client.set_expected_responses(self._responses(self.inp1, self.inp2))
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -316,7 +319,7 @@ class TestMultisigChange:
)
out2 = proto.TxOutputType(
address="2NFJjQcU8mw4Z3ywpbek8HL1VoJ27GDrkHw",
address="3PkXLsY7AUZCrCKGvX8FfP2EawowUBMbcg",
amount=44000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -327,7 +330,7 @@ class TestMultisigChange:
)
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -350,7 +353,7 @@ class TestMultisigChange:
)
out1 = proto.TxOutputType(
address="2N2aFoogGntQFFwdUVPfRmutXD22ThcNTsR",
address="3B23k4kFBRtu49zvpG3Z9xuFzfpHvxBcwt",
amount=40000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -368,7 +371,7 @@ class TestMultisigChange:
)
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -391,7 +394,7 @@ class TestMultisigChange:
)
out1 = proto.TxOutputType(
address="2N2aFoogGntQFFwdUVPfRmutXD22ThcNTsR",
address="3B23k4kFBRtu49zvpG3Z9xuFzfpHvxBcwt",
amount=40000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -407,7 +410,7 @@ class TestMultisigChange:
client.set_expected_responses(self._responses(self.inp1, self.inp2))
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp2],
[out1, out2],
prev_txes=TX_API,
@ -437,7 +440,7 @@ class TestMultisigChange:
)
out2 = proto.TxOutputType(
address="2NFJjQcU8mw4Z3ywpbek8HL1VoJ27GDrkHw",
address="3PkXLsY7AUZCrCKGvX8FfP2EawowUBMbcg",
amount=65000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
@ -446,7 +449,7 @@ class TestMultisigChange:
client.set_expected_responses(self._responses(self.inp1, self.inp3))
_, serialized_tx = btc.sign_tx(
client,
"Testnet",
"Bitcoin",
[self.inp1, self.inp3],
[out1, out2],
prev_txes=TX_API,

@ -109,12 +109,6 @@ def test_getpublicnode(client, path, script_types):
assert res.xpub
# See https://github.com/trezor/trezor-firmware/issues/1660
# T1 fails on:
# test_getaddress[m/45'/0'/63'/1000000/0/255-script_types5]
# test_getaddress[m/45'/0'/63'/1000000/255-script_types7]
# test_getaddress[m/45'/0/63/1000000/0/255-script_types6]
@pytest.mark.skip_t1
@pytest.mark.parametrize("path, script_types", VECTORS)
def test_getaddress(client, path, script_types):
for script_type in script_types:
@ -170,9 +164,6 @@ def test_signtx(client, path, script_types):
assert serialized_tx.hex()
# See https://github.com/trezor/trezor-firmware/issues/1660
# T1 fails on the Unchained paths
@pytest.mark.skip_t1
@pytest.mark.multisig
@pytest.mark.parametrize("paths, address_index", VECTORS_MULTISIG)
def test_getaddress_multisig(client, paths, address_index):

@ -271,6 +271,7 @@
"test_msg_getaddress_show.py::test_show_multisig_xpubs[InputScriptType.SPENDP2SHWITNESS-1-3P-82103d63": "afcf421cbb68a5808e8184674dceb09152a08cdc1b04f9f59807be2221e1842a",
"test_msg_getaddress_show.py::test_show_multisig_xpubs[InputScriptType.SPENDWITNESS-2-bc1qqn-5a90fe2a": "5e0dc7790e1dea0839c8d5f44d1a9fb3463364866987cecda6eff01a2dd4fd1b",
"test_msg_getaddress_show.py::test_show_multisig_xpubs[InputScriptType.SPENDWITNESS-2-bc1qqn-b8a7ece8": "c34ab5baa05e59ca95c5ece02b816b0cc82a67f79462612da0d014d0724dd2fc",
"test_msg_getaddress_show.py::test_show_unrecognized_path": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_getecdhsessionkey.py-test_ecdh": "75fe462e6afa73742949ede4f3529d2e0ec08f8f1b67c04a57189c8657fcbdcd",
"test_msg_getentropy.py::test_entropy[128]": "a722fa2048fa3102889ec05558d25f837a364ef2a118e85975683e10a56f1356",
"test_msg_getentropy.py::test_entropy[129]": "a722fa2048fa3102889ec05558d25f837a364ef2a118e85975683e10a56f1356",
@ -592,7 +593,7 @@
"test_msg_stellar_sign_transaction.py::test_sign_tx_account_merge_op": "cba8c17ea77956cbe0cb77fdbaeac7408cdc934d60f1a3d0c2f1d4375ce49ebd",
"test_msg_stellar_sign_transaction.py::test_sign_tx_allow_trust_op": "e1585be0bbf49a9dc0902fca6e8df6fa5851cbbe27b22a88710944209ac29315",
"test_msg_stellar_sign_transaction.py::test_sign_tx_bump_sequence_op": "eaf6d92c7b897338959b20d4aa1281110d0a5179b4bd53ceab88ee50b2456b80",
"test_msg_stellar_sign_transaction.py::test_sign_tx_change_trust_op": "8d6cc0009b370753bdf0b674650543ae24e68cb420cb451e23631002e497790e",
"test_msg_stellar_sign_transaction.py::test_sign_tx_change_trust_op": "c3e3e56d684deb29a1558bbb35c13aad80e26dd58230aeed9ee3ddd127efcb52",
"test_msg_stellar_sign_transaction.py::test_sign_tx_create_account_op": "a1dcb3b4630fc45a771b4a6e65eab879adfe59c38624881e140cf08c1f83a9df",
"test_msg_stellar_sign_transaction.py::test_sign_tx_manage_offer_op": "32b68728958da31382907f8c0a76547011d264241ec4974fa85c487ffdb7a1b0",
"test_msg_stellar_sign_transaction.py::test_sign_tx_passive_offer_op": "ab6808486a4e98bbf990b55d60489a4f3464572428e9374a8f55d5352581a678",
@ -640,14 +641,14 @@
"test_multisig.py-test_2_of_3": "2be92556edf4ff8eed340d535f379ee6915eae34fef25d669ce865848e7b4705",
"test_multisig.py-test_attack_change_input": "89859fea184df09ce96df5281d405c0e85c87ba7efbafa4b3fdf7d9402c0fc44",
"test_multisig.py-test_missing_pubkey": "a69ccaba89fdb284243c476a5fae3551e8aacf52a59e73f41a1e9f0a38ab93f0",
"test_multisig_change.py-test_external_external": "0f9bc8153070c4bad85bf7ae9d1d30843752538b4d1f8964bc748015a07f00bc",
"test_multisig_change.py-test_external_internal": "f60554e040d8965b8f3efdf9eea1f940b64b5844f12d1ee802abbec8d15ea9bf",
"test_multisig_change.py-test_internal_external": "f6b695c7bd338a6d465d81c7ace92f8021b01932a42e6b8e52ec4261f10bcf33",
"test_multisig_change.py-test_multisig_change_match_first": "05b894da12a881b5000eea56eef50b3c3be619fc43474ebcbed8e3129d64929b",
"test_multisig_change.py-test_multisig_change_match_second": "a095e5f0e53c76850f6fee94623cf724e2aa2a61442aa771a4e7ee81370111f5",
"test_multisig_change.py-test_multisig_external_external": "ddecdadd659b0d1360a6a255c6f9dbf2c5b813039877b76d4062ddab765e1912",
"test_multisig_change.py-test_multisig_mismatch_change": "7cb243b20be31a587dced4aaaf782a2d8487595369dde66aacb1b9a76e89c4fe",
"test_multisig_change.py-test_multisig_mismatch_inputs": "64741bd84c5394e719125c1fbe8c34ef866ac63ca24ee1299e4268c59a199466",
"test_multisig_change.py-test_external_external": "c0f0e105f7361b79718c0f5e208a4cafaa18ab1c18289da4e8bba0be4938aa00",
"test_multisig_change.py-test_external_internal": "b8a981257afddcd57dfa6510632c1ecc38935b7d466dadc3ef2c05a02a3c059a",
"test_multisig_change.py-test_internal_external": "05bcf4ed8f908c2228d82075d1b40df43ced865e9e09df400a3a955f636ac2d1",
"test_multisig_change.py-test_multisig_change_match_first": "8216c87b1956e6852421bcea3b7fff6457e0e7726d6eec5312d1126a23635cbb",
"test_multisig_change.py-test_multisig_change_match_second": "a4974ba1b357d8ecd146e8a6112c13829e2812b5425cc7b8f2d417a05f99de51",
"test_multisig_change.py-test_multisig_external_external": "a7a18c6ff1a3afaf501abb7459c7ded8dc591dd406034e13f5ca83ffe9d4bab7",
"test_multisig_change.py-test_multisig_mismatch_change": "f25b0a640bf10c502f3c6484cebe4df4ad05a8e679891d40f863b9d40fc6a8b5",
"test_multisig_change.py-test_multisig_mismatch_inputs": "dc8960fe817c15b5262aaa2f6a83b37db7e27e196e8abdf7f76d47e4f68a6fec",
"test_nonstandard_paths.py::test_getaddress[m-1195487518-6-255-script_types3]": "4e1ff4e743e91769318fa032d10343c08ee016820df26c1b2a7c6e41b342b24c",
"test_nonstandard_paths.py::test_getaddress[m-1195487518-script_types2]": "fc89fc6ed34c87e94e45a4a0aeab1904009da2f52c5a54ca427209ae7ec0c968",
"test_nonstandard_paths.py::test_getaddress[m-3'-100'-4-255-script_types1]": "d16eecce052da865fe42416a7ab742f567cf7b508d1bb137de46116a513db52c",

Loading…
Cancel
Save