refactor(core): Split off bitcoin/paths.py from bitcoin/keychain.py.

andrewkozlik/account-naming
Andrew Kozlik 2 years ago
parent 7c1c5b4a44
commit 22b992a018

@ -269,6 +269,8 @@ apps.bitcoin.multisig
import apps.bitcoin.multisig
apps.bitcoin.ownership
import apps.bitcoin.ownership
apps.bitcoin.paths
import apps.bitcoin.paths
apps.bitcoin.readers
import apps.bitcoin.readers
apps.bitcoin.scripts

@ -12,7 +12,8 @@ from apps.common.paths import PathSchema, validate_path
from .authorization import FEE_RATE_DECIMALS
from .common import BIP32_WALLET_DEPTH
from .keychain import PATTERN_SLIP25, with_keychain
from .keychain import with_keychain
from .paths import PATTERN_SLIP25
if TYPE_CHECKING:
from apps.common.coininfo import CoinInfo

@ -9,8 +9,9 @@ from apps.common.address_mac import get_address_mac
from apps.common.paths import address_n_to_str, validate_path
from . import addresses
from .keychain import validate_path_against_script_type, with_keychain
from .keychain import with_keychain
from .multisig import multisig_pubkey_index
from .paths import validate_path_against_script_type
if TYPE_CHECKING:
from trezor.messages import GetAddress

@ -7,8 +7,9 @@ from trezor.messages import GetOwnershipId, OwnershipId
from apps.common.paths import validate_path
from . import addresses, common, scripts
from .keychain import validate_path_against_script_type, with_keychain
from .keychain import with_keychain
from .ownership import get_identifier
from .paths import validate_path_against_script_type
if TYPE_CHECKING:
from apps.common.coininfo import CoinInfo

@ -8,8 +8,9 @@ from trezor.ui.layouts import confirm_action, confirm_blob
from apps.common.paths import validate_path
from . import addresses, common, scripts
from .keychain import validate_path_against_script_type, with_keychain
from .keychain import with_keychain
from .ownership import generate_proof, get_identifier
from .paths import validate_path_against_script_type
if TYPE_CHECKING:
from apps.common.coininfo import CoinInfo

@ -1,21 +1,16 @@
import gc
from micropython import const
from typing import TYPE_CHECKING
from trezor import wire
from trezor.enums import InputScriptType
from trezor.messages import AuthorizeCoinJoin, SignTx
from apps.common import coininfo
from apps.common.keychain import get_keychain
from apps.common.paths import PATTERN_BIP44, PathSchema
from . import authorization
from .common import BITCOIN_NAMES
from .paths import get_schemas_for_coin
if TYPE_CHECKING:
from typing import Awaitable, Callable, Iterable, TypeVar
from typing_extensions import Protocol
from typing import Awaitable, Callable, TypeVar
from trezor.protobuf import MessageType
@ -29,7 +24,6 @@ if TYPE_CHECKING:
)
from apps.common.keychain import Keychain, MsgOut, Handler
from apps.common.paths import Bip32Path
BitcoinMessage = (
AuthorizeCoinJoin
@ -42,196 +36,9 @@ if TYPE_CHECKING:
| VerifyMessage
)
class MsgWithAddressScriptType(Protocol):
address_n: Bip32Path
script_type: InputScriptType
MsgIn = TypeVar("MsgIn", bound=BitcoinMessage)
HandlerWithCoinInfo = Callable[..., Awaitable[MsgOut]]
# BIP-45 for multisig: https://github.com/bitcoin/bips/blob/master/bip-0045.mediawiki
PATTERN_BIP45 = "m/45'/[0-100]/change/address_index"
# BIP-48 for multisig: https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki
# The raw script type is not part of the BIP (and Electrum, as a notable implementation,
# does not use it), it is included here for completeness.
PATTERN_BIP48_RAW = "m/48'/coin_type'/account'/0'/change/address_index"
PATTERN_BIP48_P2SHSEGWIT = "m/48'/coin_type'/account'/1'/change/address_index"
PATTERN_BIP48_SEGWIT = "m/48'/coin_type'/account'/2'/change/address_index"
# BIP-49 for segwit-in-P2SH: https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki
PATTERN_BIP49 = "m/49'/coin_type'/account'/change/address_index"
# BIP-84 for segwit: https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki
PATTERN_BIP84 = "m/84'/coin_type'/account'/change/address_index"
# BIP-86 for taproot: https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
PATTERN_BIP86 = "m/86'/coin_type'/account'/change/address_index"
# SLIP-25 for CoinJoin: https://github.com/satoshilabs/slips/blob/master/slip-0025.md
# Only account=0 and script_type=1 are supported for now.
PATTERN_SLIP25 = "m/10025'/coin_type'/0'/1'/change/address_index"
PATTERN_SLIP25_EXTERNAL = "m/10025'/coin_type'/0'/1'/0/address_index"
# compatibility patterns, will be removed in the future
PATTERN_GREENADDRESS_A = "m/[1,4]/address_index"
PATTERN_GREENADDRESS_B = "m/3'/[1-100]'/[1,4]/address_index"
PATTERN_GREENADDRESS_SIGN_A = "m/1195487518"
PATTERN_GREENADDRESS_SIGN_B = "m/1195487518/6/address_index"
PATTERN_CASA = "m/49/coin_type/account/change/address_index"
PATTERN_UNCHAINED_HARDENED = (
"m/45'/coin_type'/account'/[0-1000000]/change/address_index"
)
PATTERN_UNCHAINED_UNHARDENED = (
"m/45'/coin_type/account/[0-1000000]/change/address_index"
)
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,
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.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
elif (
script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG)
and multisig
):
patterns.append(PATTERN_BIP48_RAW)
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)
elif coin.segwit and script_type == InputScriptType.SPENDP2SHWITNESS:
patterns.append(PATTERN_BIP49)
if multisig:
patterns.append(PATTERN_BIP48_P2SHSEGWIT)
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_BIP48_SEGWIT)
if coin.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
elif coin.taproot and script_type == InputScriptType.SPENDTAPROOT:
patterns.append(PATTERN_BIP86)
patterns.append(PATTERN_SLIP25)
return any(
PathSchema.parse(pattern, coin.slip44).match(address_n) for pattern in patterns
)
def get_schemas_for_coin(
coin: coininfo.CoinInfo, allow_slip25_internal: bool = False
) -> 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,
)
)
# taproot patterns
if coin.taproot:
patterns.append(PATTERN_BIP86)
if allow_slip25_internal:
patterns.append(PATTERN_SLIP25)
else:
patterns.append(PATTERN_SLIP25_EXTERNAL)
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 get_coin_by_name(coin_name: str | None) -> coininfo.CoinInfo:
if coin_name is None:

@ -0,0 +1,204 @@
import gc
from micropython import const
from typing import TYPE_CHECKING
from trezor.enums import InputScriptType
from apps.common.paths import PATTERN_BIP44, PathSchema
from .common import BITCOIN_NAMES
if TYPE_CHECKING:
from typing import Iterable
from typing_extensions import Protocol
from apps.common.coininfo import CoinInfo
from apps.common.paths import Bip32Path
class MsgWithAddressScriptType(Protocol):
address_n: Bip32Path
script_type: InputScriptType
# BIP-45 for multisig: https://github.com/bitcoin/bips/blob/master/bip-0045.mediawiki
PATTERN_BIP45 = "m/45'/[0-100]/change/address_index"
# BIP-48 for multisig: https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki
# The raw script type is not part of the BIP (and Electrum, as a notable implementation,
# does not use it), it is included here for completeness.
PATTERN_BIP48_RAW = "m/48'/coin_type'/account'/0'/change/address_index"
PATTERN_BIP48_P2SHSEGWIT = "m/48'/coin_type'/account'/1'/change/address_index"
PATTERN_BIP48_SEGWIT = "m/48'/coin_type'/account'/2'/change/address_index"
# BIP-49 for segwit-in-P2SH: https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki
PATTERN_BIP49 = "m/49'/coin_type'/account'/change/address_index"
# BIP-84 for segwit: https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki
PATTERN_BIP84 = "m/84'/coin_type'/account'/change/address_index"
# BIP-86 for taproot: https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
PATTERN_BIP86 = "m/86'/coin_type'/account'/change/address_index"
# SLIP-25 for CoinJoin: https://github.com/satoshilabs/slips/blob/master/slip-0025.md
# Only account=0 and script_type=1 are supported for now.
PATTERN_SLIP25 = "m/10025'/coin_type'/0'/1'/change/address_index"
PATTERN_SLIP25_EXTERNAL = "m/10025'/coin_type'/0'/1'/0/address_index"
# compatibility patterns, will be removed in the future
PATTERN_GREENADDRESS_A = "m/[1,4]/address_index"
PATTERN_GREENADDRESS_B = "m/3'/[1-100]'/[1,4]/address_index"
PATTERN_GREENADDRESS_SIGN_A = "m/1195487518"
PATTERN_GREENADDRESS_SIGN_B = "m/1195487518/6/address_index"
PATTERN_CASA = "m/49/coin_type/account/change/address_index"
PATTERN_UNCHAINED_HARDENED = (
"m/45'/coin_type'/account'/[0-1000000]/change/address_index"
)
PATTERN_UNCHAINED_UNHARDENED = (
"m/45'/coin_type/account/[0-1000000]/change/address_index"
)
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,
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.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
elif (
script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG)
and multisig
):
patterns.append(PATTERN_BIP48_RAW)
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)
elif coin.segwit and script_type == InputScriptType.SPENDP2SHWITNESS:
patterns.append(PATTERN_BIP49)
if multisig:
patterns.append(PATTERN_BIP48_P2SHSEGWIT)
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_BIP48_SEGWIT)
if coin.slip44 == SLIP44_BITCOIN:
patterns.append(PATTERN_GREENADDRESS_A)
patterns.append(PATTERN_GREENADDRESS_B)
elif coin.taproot and script_type == InputScriptType.SPENDTAPROOT:
patterns.append(PATTERN_BIP86)
patterns.append(PATTERN_SLIP25)
return any(
PathSchema.parse(pattern, coin.slip44).match(address_n) for pattern in patterns
)
def get_schemas_for_coin(
coin: CoinInfo, allow_slip25_internal: bool = False
) -> 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,
)
)
# taproot patterns
if coin.taproot:
patterns.append(PATTERN_BIP86)
if allow_slip25_internal:
patterns.append(PATTERN_SLIP25)
else:
patterns.append(PATTERN_SLIP25_EXTERNAL)
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]

@ -10,7 +10,8 @@ from apps.common.paths import validate_path
from apps.common.signverify import decode_message, message_digest
from .addresses import address_short, get_address
from .keychain import validate_path_against_script_type, with_keychain
from .keychain import with_keychain
from .paths import validate_path_against_script_type
if TYPE_CHECKING:
from trezor.messages import SignMessage

@ -9,7 +9,7 @@ from apps.common import safety_checks
from ..authorization import FEE_RATE_DECIMALS
from ..common import input_is_external_unverified
from ..keychain import validate_path_against_script_type
from ..paths import validate_path_against_script_type
from . import helpers, tx_weight
from .payment_request import PaymentRequestVerifier
from .tx_info import OriginalTxInfo, TxInfo

@ -6,7 +6,7 @@ from trezor.utils import HashWriter
from apps.common import coins
from apps.bitcoin import scripts
from apps.bitcoin.addresses import *
from apps.bitcoin.keychain import validate_path_against_script_type
from apps.bitcoin.paths import validate_path_against_script_type
from apps.bitcoin.writers import *

@ -28,7 +28,7 @@ from trezor import wire
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import helpers, bitcoin

@ -27,7 +27,7 @@ from trezor.enums import OutputScriptType
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import bitcoinlike, helpers

@ -28,7 +28,7 @@ from trezor import wire
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import bitcoin, helpers

@ -27,7 +27,7 @@ from trezor.enums import OutputScriptType
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import bitcoinlike, helpers

@ -26,7 +26,7 @@ from trezor.enums import OutputScriptType
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import bitcoin, helpers

@ -26,7 +26,7 @@ from trezor.enums import OutputScriptType
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import decred, helpers

@ -26,7 +26,7 @@ from trezor.enums import OutputScriptType
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.paths import get_schemas_for_coin
from apps.bitcoin.sign_tx import bitcoinlike, helpers

Loading…
Cancel
Save