def get_schemas_for_coin(coin: coininfo.CoinInfo) -> Iterable[PathSchema]: # basic patterns patterns = [ PATTERN_BIP44, PATTERN_BIP48_RAW, ] # 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, PATTERN_UNCHAINED_DEPRECATED, ) ) # segwit patterns if coin.segwit: patterns.extend( ( PATTERN_BIP49, PATTERN_BIP84, PATTERN_BIP48_P2SHSEGWIT, PATTERN_BIP48_SEGWIT, ) ) 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. 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]
def test_verify_path(self): schemas = ( PathSchema("m/44'/coin_type'", slip44_id=134), PathSchema("m/44'/coin_type'", slip44_id=11), ) keychain = Keychain(b"", "secp256k1", schemas) correct = ( [H_(44), H_(134)], [H_(44), H_(11)], ) for path in correct: keychain.verify_path(path) fails = ( [H_(44), 134], # path does not match [44, 134], # path does not match (non-hardened items) [H_(44), H_(13)], # invalid second item ) for f in fails: with self.assertRaises(wire.DataError): keychain.verify_path(f) # turn off restrictions safety_checks.apply_setting(SafetyCheckLevel.PromptTemporarily) for path in correct + fails: keychain.verify_path(path)
async def _fail_or_warn_if_invalid_path(ctx: wire.Context, schema: PathSchema, path: List[int], path_name: str) -> None: if not schema.match(path): if safety_checks.is_strict(): raise wire.DataError("Invalid %s" % path_name.lower()) else: await show_warning_path(ctx, path, path_name)
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.extend( ( PATTERN_GREENADDRESS_A, PATTERN_GREENADDRESS_B, PATTERN_GREENADDRESS_SIGN_A, PATTERN_GREENADDRESS_SIGN_B, PATTERN_CASA, PATTERN_UNCHAINED_HARDENED, PATTERN_UNCHAINED_UNHARDENED, PATTERN_UNCHAINED_DEPRECATED, ) ) # segwit patterns if coin.segwit: patterns.extend( ( PATTERN_BIP49, PATTERN_BIP84, PATTERN_PURPOSE48_P2SHSEGWIT, PATTERN_PURPOSE48_SEGWIT, ) ) 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) gc.collect() return [schema.copy() for schema in schemas]
def validate_path_against_script_type( coin: coininfo.CoinInfo, msg: MsgWithAddressScriptType | None = None, address_n: Bip32Path | None = None, script_type: InputScriptType | None = None, multisig: bool = False, ) -> bool: patterns = [] if msg is not None: assert address_n is None and script_type is None address_n = msg.address_n script_type = msg.script_type or InputScriptType.SPENDADDRESS multisig = bool(getattr(msg, "multisig", False)) else: assert address_n is not None and script_type is not None if script_type == InputScriptType.SPENDADDRESS and not multisig: patterns.append(PATTERN_BIP44) if coin.coin_name in BITCOIN_NAMES: patterns.append(PATTERN_GREENADDRESS_A) patterns.append(PATTERN_GREENADDRESS_B) elif (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: patterns.append(PATTERN_GREENADDRESS_A) patterns.append(PATTERN_GREENADDRESS_B) patterns.append(PATTERN_UNCHAINED_HARDENED) patterns.append(PATTERN_UNCHAINED_UNHARDENED) patterns.append(PATTERN_UNCHAINED_DEPRECATED) elif coin.segwit and script_type == InputScriptType.SPENDP2SHWITNESS: patterns.append(PATTERN_BIP49) if multisig: patterns.append(PATTERN_PURPOSE48_P2SHSEGWIT) if coin.coin_name in BITCOIN_NAMES: patterns.append(PATTERN_GREENADDRESS_A) patterns.append(PATTERN_GREENADDRESS_B) 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: patterns.append(PATTERN_GREENADDRESS_A) patterns.append(PATTERN_GREENADDRESS_B) return any( PathSchema.parse(pattern, coin.slip44).match(address_n) for pattern in patterns)
def test_verify_path_special_ed25519(self): schema = PathSchema("m/44'/coin_type'/*", slip44_id=134) k = Keychain(b"", "ed25519-keccak", [schema]) # OK case k.verify_path([H_(44), H_(134)]) # failing case: non-hardened component with ed25519-like derivation with self.assertRaises(wire.DataError): k.verify_path([H_(44), H_(134), 1])
def test_get_keychain(self): seed = bip39.seed(" ".join(["all"] * 12), "") cache.set(cache.APP_COMMON_SEED, seed) schema = PathSchema("m/44'/1'", 0) keychain = await_result( get_keychain(wire.DUMMY_CONTEXT, "secp256k1", [schema])) # valid path: self.assertIsNotNone(keychain.derive([H_(44), H_(1)])) # invalid path: with self.assertRaises(wire.DataError): keychain.derive([44])
from micropython import const from apps.common import HARDENED from apps.common.paths import PathSchema SLIP44_ID = 1815 BYRON_ROOT = [44 | HARDENED, SLIP44_ID | HARDENED] SHELLEY_ROOT = [1852 | HARDENED, SLIP44_ID | HARDENED] # fmt: off SCHEMA_PUBKEY = PathSchema("m/[44,1852]'/coin_type'/account'/*", SLIP44_ID) SCHEMA_ADDRESS = PathSchema( "m/[44,1852]'/coin_type'/account'/[0,1,2]/address_index", SLIP44_ID) # staking is only allowed on Shelley paths with suffix /2/0 SCHEMA_STAKING = PathSchema("m/1852'/coin_type'/account'/2/0", SLIP44_ID) SCHEMA_STAKING_ANY_ACCOUNT = PathSchema( "m/1852'/coin_type'/[0-%s]'/2/0" % (HARDENED - 1), SLIP44_ID) # fmt: on # the maximum allowed change address. this should be large enough for normal # use and still allow to quickly brute-force the correct bip32 path MAX_SAFE_CHANGE_ADDRESS_INDEX = const(1_000_000) MAX_SAFE_ACCOUNT_INDEX = const(100) | HARDENED ACCOUNT_PATH_INDEX = const(2) BIP_PATH_LENGTH = const(5) CHANGE_OUTPUT_PATH_NAME = "Change output path" CHANGE_OUTPUT_STAKING_PATH_NAME = "Change output staking path" CERTIFICATE_PATH_NAME = "Certificate path" POOL_OWNER_STAKING_PATH_NAME = "Pool owner staking path"
from micropython import const from apps.common.paths import HARDENED, PathSchema SLIP44_ID = 1815 BYRON_ROOT = [44 | HARDENED, SLIP44_ID | HARDENED] SHELLEY_ROOT = [1852 | HARDENED, SLIP44_ID | HARDENED] MULTISIG_ROOT = [1854 | HARDENED, SLIP44_ID | HARDENED] MINTING_ROOT = [1855 | HARDENED, SLIP44_ID | HARDENED] # fmt: off SCHEMA_PUBKEY = PathSchema.parse("m/[44,1852,1854]'/coin_type'/account'/*", SLIP44_ID) # minting has a specific schema for key derivation - see CIP-1855 SCHEMA_MINT = PathSchema.parse(f"m/1855'/coin_type'/[0-{HARDENED - 1}]'", SLIP44_ID) SCHEMA_PAYMENT = PathSchema.parse("m/[44,1852]'/coin_type'/account'/[0,1]/address_index", SLIP44_ID) # staking is only allowed on Shelley paths with suffix /2/0 SCHEMA_STAKING = PathSchema.parse("m/1852'/coin_type'/account'/2/0", SLIP44_ID) SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse(f"m/1852'/coin_type'/[0-{HARDENED - 1}]'/2/0", SLIP44_ID) # fmt: on ACCOUNT_PATH_INDEX = const(2) ACCOUNT_PATH_LENGTH = const(3) CHAIN_STAKING_KEY = const(2) CHANGE_OUTPUT_PATH_NAME = "Change output path" CHANGE_OUTPUT_STAKING_PATH_NAME = "Change output staking path" CERTIFICATE_PATH_NAME = "Certificate path" POOL_OWNER_STAKING_PATH_NAME = "Pool owner staking path" WITNESS_PATH_NAME = "Witness path"
async def _fail_or_warn_if_invalid_path(ctx: wire.Context, schema: PathSchema, path: list[int], path_name: str) -> None: if not schema.match(path): await _fail_or_warn_path(ctx, path, path_name)
from micropython import const from apps.common import HARDENED from apps.common.paths import PathSchema SLIP44_ID = 1815 BYRON_ROOT = [44 | HARDENED, SLIP44_ID | HARDENED] SHELLEY_ROOT = [1852 | HARDENED, SLIP44_ID | HARDENED] # fmt: off SCHEMA_PUBKEY = PathSchema("m/[44,1852]'/coin_type'/account'/*", SLIP44_ID) SCHEMA_ADDRESS = PathSchema( "m/[44,1852]'/coin_type'/account'/[0,1,2]/address_index", SLIP44_ID) # staking is only allowed on Shelley paths with suffix /2/0 SCHEMA_STAKING = PathSchema("m/1852'/coin_type'/account'/2/0", SLIP44_ID) # fmt: on # the maximum allowed change address. this should be large enough for normal # use and still allow to quickly brute-force the correct bip32 path MAX_CHANGE_ADDRESS_INDEX = const(1_000_000) ACCOUNT_PATH_INDEX = const(2) BIP_PATH_LENGTH = const(5) def unharden(item: int) -> int: return item ^ (item & HARDENED)
async def _fail_or_warn_if_invalid_path(self, schema: PathSchema, path: list[int], path_name: str) -> None: if not schema.match(path): await self._fail_or_warn_path(path, path_name)