Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multisig_xpub_magic option to GetPublicKey #4305

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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 common/protob/messages-bitcoin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ message GetPublicKey {
optional string coin_name = 4 [default='Bitcoin']; // coin to use for verifying
optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
optional bool ignore_xpub_magic = 6; // ignore SLIP-0132 XPUB magic, use xpub/tpub prefix for all account types
optional bool multisig_xpub_magic = 7; // use Ypub/Zpub prefix for SegWit script types
}

/**
Expand Down
1 change: 1 addition & 0 deletions core/.changelog.d/2658.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add multisig_xpub_magic option to GetPublicKey.
35 changes: 35 additions & 0 deletions core/src/apps/bitcoin/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ def input_is_external_unverified(txi: TxInput) -> bool:
)


def sanitize_input_script_type(coin: CoinInfo, script_type: InputScriptType) -> None:
if script_type in SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit:
raise wire.DataError("Segwit not enabled on this coin.")

if script_type == InputScriptType.SPENDTAPROOT and not coin.taproot:
raise wire.DataError("Taproot not enabled on this coin")


def tagged_hashwriter(tag: bytes) -> HashWriter:
from trezor.crypto.hashlib import sha256
from trezor.utils import HashWriter
Expand Down Expand Up @@ -263,3 +271,30 @@ def _polymod(c: int, val: int) -> int:
for j in range(0, 8):
ret[j] = CHECKSUM_CHARSET[(c >> (5 * (7 - j))) & 31]
return "".join(ret)


def get_xpub_magic(
coin: CoinInfo,
script_type: InputScriptType,
ignore_xpub_magic: bool | None,
is_multisig: bool | None,
) -> int:
from trezor.enums import InputScriptType

xpub_magic = None
if not ignore_xpub_magic:
# Handle SegWit v0 script types.
# When coin.segwit is true, cointool.py guarantees that the corresponding xpub_magic_* attributes not None.
if not is_multisig:
if script_type == InputScriptType.SPENDP2SHWITNESS:
xpub_magic = coin.xpub_magic_segwit_p2sh
elif script_type == InputScriptType.SPENDWITNESS:
xpub_magic = coin.xpub_magic_segwit_native
else:
if script_type == InputScriptType.SPENDWITNESS:
xpub_magic = coin.xpub_magic_multisig_segwit_native
elif script_type == InputScriptType.SPENDP2SHWITNESS:
xpub_magic = coin.xpub_magic_multisig_segwit_p2sh

# SPENDADDRESS, SPENDMULTISIG, SPENDTAPROOT, ignore_xpub_magic or fallback.
return xpub_magic or coin.xpub_magic
20 changes: 6 additions & 14 deletions core/src/apps/bitcoin/get_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
from apps.common.paths import address_n_to_str, validate_path

from . import addresses
from .common import get_xpub_magic
from .keychain import (
address_n_to_name_or_unknown,
validate_path_against_script_type,
Expand Down Expand Up @@ -72,20 +73,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
address_case_sensitive = False # cashaddr address

mac: bytes | None = None
multisig_xpub_magic = coin.xpub_magic
if multisig:
if coin.segwit and not msg.ignore_xpub_magic:
if (
script_type == InputScriptType.SPENDWITNESS
and coin.xpub_magic_multisig_segwit_native is not None
):
multisig_xpub_magic = coin.xpub_magic_multisig_segwit_native
elif (
script_type == InputScriptType.SPENDP2SHWITNESS
and coin.xpub_magic_multisig_segwit_p2sh is not None
):
multisig_xpub_magic = coin.xpub_magic_multisig_segwit_p2sh
else:
if not multisig:
# Attach a MAC for single-sig addresses, but only if the path is standard
# or if the user explicitly confirms a non-standard path.
if msg.show_display or (
Expand All @@ -97,6 +85,10 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
if msg.show_display:
path = address_n_to_str(address_n)
if multisig:
multisig_xpub_magic = get_xpub_magic(
coin, script_type, msg.ignore_xpub_magic, is_multisig=True
)

if multisig.nodes:
pubnodes = multisig.nodes
else:
Expand Down
7 changes: 1 addition & 6 deletions core/src/apps/bitcoin/get_ownership_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
async def get_ownership_id(
msg: GetOwnershipId, keychain: Keychain, coin: CoinInfo
) -> OwnershipId:
from trezor.enums import InputScriptType
from trezor.messages import OwnershipId
from trezor.wire import DataError

Expand All @@ -34,11 +33,7 @@ async def get_ownership_id(
if script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES:
raise DataError("Invalid script type")

if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit:
raise DataError("Segwit not enabled on this coin")

if script_type == InputScriptType.SPENDTAPROOT and not coin.taproot:
raise DataError("Taproot not enabled on this coin")
common.sanitize_input_script_type(coin, script_type)

node = keychain.derive(msg.address_n)
address = addresses.get_address(script_type, coin, node, msg.multisig)
Expand Down
7 changes: 1 addition & 6 deletions core/src/apps/bitcoin/get_ownership_proof.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ async def get_ownership_proof(
authorization: CoinJoinAuthorization | None = None,
) -> OwnershipProof:
from trezor import TR
from trezor.enums import InputScriptType
from trezor.messages import OwnershipProof
from trezor.ui.layouts import confirm_action, confirm_blob
from trezor.wire import DataError, ProcessError
Expand All @@ -46,11 +45,7 @@ async def get_ownership_proof(
if script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES:
raise DataError("Invalid script type")

if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit:
raise DataError("Segwit not enabled on this coin")

if script_type == InputScriptType.SPENDTAPROOT and not coin.taproot:
raise DataError("Taproot not enabled on this coin")
common.sanitize_input_script_type(coin, script_type)

node = keychain.derive(msg.address_n)
address = addresses.get_address(script_type, coin, node, msg.multisig)
Expand Down
43 changes: 10 additions & 33 deletions core/src/apps/bitcoin/get_public_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@
async def get_public_key(
msg: GetPublicKey, auth_msg: MessageType | None = None
) -> PublicKey:
from trezor import TR, wire
from trezor import TR
from trezor.enums import InputScriptType
from trezor.messages import HDNodeType, PublicKey, UnlockPath

from apps.common import coininfo, paths
from apps.common.keychain import FORBIDDEN_KEY_PATH, get_keychain

from .common import get_xpub_magic, sanitize_input_script_type

coin_name = msg.coin_name or "Bitcoin"
script_type = msg.script_type or InputScriptType.SPENDADDRESS
coin = coininfo.by_name(coin_name)
curve_name = msg.ecdsa_curve_name or coin.curve_name
address_n = msg.address_n # local_cache_attribute
ignore_xpub_magic = msg.ignore_xpub_magic # local_cache_attribute
xpub_magic = coin.xpub_magic # local_cache_attribute

if address_n and address_n[0] == paths.SLIP25_PURPOSE:
# UnlockPath is required to access SLIP25 paths.
Expand All @@ -38,36 +38,13 @@ async def get_public_key(

node = keychain.derive(address_n)

if (
script_type
in (
InputScriptType.SPENDADDRESS,
InputScriptType.SPENDMULTISIG,
InputScriptType.SPENDTAPROOT,
)
and xpub_magic is not None
):
node_xpub = node.serialize_public(xpub_magic)
elif (
coin.segwit
and script_type == InputScriptType.SPENDP2SHWITNESS
and (ignore_xpub_magic or coin.xpub_magic_segwit_p2sh is not None)
):
assert coin.xpub_magic_segwit_p2sh is not None
node_xpub = node.serialize_public(
xpub_magic if ignore_xpub_magic else coin.xpub_magic_segwit_p2sh
)
elif (
coin.segwit
and script_type == InputScriptType.SPENDWITNESS
and (ignore_xpub_magic or coin.xpub_magic_segwit_native is not None)
):
assert coin.xpub_magic_segwit_native is not None
node_xpub = node.serialize_public(
xpub_magic if ignore_xpub_magic else coin.xpub_magic_segwit_native
sanitize_input_script_type(coin, script_type)

node_xpub = node.serialize_public(
get_xpub_magic(
coin, script_type, msg.ignore_xpub_magic, msg.multisig_xpub_magic
)
else:
raise wire.DataError("Invalid combination of coin and script_type")
)

pubkey = node.public_key()
# For curve25519 and ed25519, the public key has the prefix 0x00, as specified by SLIP-10. However, since this prefix is non-standard, it may be removed in the future.
Expand All @@ -79,7 +56,7 @@ async def get_public_key(
public_key=pubkey,
)
descriptor = _xpub_descriptor(
node, xpub_magic, address_n, script_type, keychain.root_fingerprint()
node, coin.xpub_magic, address_n, script_type, keychain.root_fingerprint()
)

if msg.show_display:
Expand Down
2 changes: 2 additions & 0 deletions core/src/trezor/messages.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions legacy/firmware/.changelog.d/2658.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add multisig_xpub_magic option to GetPublicKey.
82 changes: 40 additions & 42 deletions legacy/firmware/fsm_msg_coin.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,35 @@
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

static bool fsm_checkScriptType(const CoinInfo *coin,
InputScriptType script_type);

static int fsm_getXpubMagic(const CoinInfo *coin, InputScriptType script_type,
bool ignore_xpub_magic, bool is_multisig) {
uint32_t xpub_magic = 0;
if (!ignore_xpub_magic) {
// Handle SegWit v0 script types.
// When coin.segwit is true, cointool.py guarantees that the corresponding
// xpub_magic_* attributes are defined.
if (!is_multisig) {
if (script_type == InputScriptType_SPENDP2SHWITNESS) {
xpub_magic = coin->xpub_magic_segwit_p2sh;
} else if (script_type == InputScriptType_SPENDWITNESS) {
xpub_magic = coin->xpub_magic_segwit_native;
}
} else {
if (script_type == InputScriptType_SPENDWITNESS) {
xpub_magic = coin->xpub_magic_multisig_segwit_native;
} else if (script_type == InputScriptType_SPENDP2SHWITNESS) {
xpub_magic = coin->xpub_magic_multisig_segwit_p2sh;
}
}
}

// SPENDADDRESS, SPENDMULTISIG, SPENDTAPROOT, ignore_xpub_magic or fallback.
return (xpub_magic != 0) ? xpub_magic : coin->xpub_magic;
}

void fsm_msgGetPublicKey(const GetPublicKey *msg) {
RESP_INIT(PublicKey);

Expand Down Expand Up @@ -80,39 +109,17 @@ void fsm_msgGetPublicKey(const GetPublicKey *msg) {
// removed in the future.
memcpy(resp->node.public_key.bytes, node->public_key, 33);

if (coin->xpub_magic && (script_type == InputScriptType_SPENDADDRESS ||
script_type == InputScriptType_SPENDMULTISIG)) {
hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub,
sizeof(resp->xpub));
} else if (coin->has_segwit &&
script_type == InputScriptType_SPENDP2SHWITNESS &&
!msg->ignore_xpub_magic && coin->xpub_magic_segwit_p2sh) {
hdnode_serialize_public(node, fingerprint, coin->xpub_magic_segwit_p2sh,
resp->xpub, sizeof(resp->xpub));
} else if (coin->has_segwit &&
script_type == InputScriptType_SPENDP2SHWITNESS &&
msg->ignore_xpub_magic && coin->xpub_magic) {
hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub,
sizeof(resp->xpub));
} else if (coin->has_segwit && script_type == InputScriptType_SPENDWITNESS &&
!msg->ignore_xpub_magic && coin->xpub_magic_segwit_native) {
hdnode_serialize_public(node, fingerprint, coin->xpub_magic_segwit_native,
resp->xpub, sizeof(resp->xpub));
} else if (coin->has_segwit && script_type == InputScriptType_SPENDWITNESS &&
msg->ignore_xpub_magic && coin->xpub_magic) {
hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub,
sizeof(resp->xpub));
} else if (coin->has_taproot && script_type == InputScriptType_SPENDTAPROOT &&
coin->xpub_magic) {
hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub,
sizeof(resp->xpub));
} else {
fsm_sendFailure(FailureType_Failure_DataError,
_("Invalid combination of coin and script_type"));
if (!fsm_checkScriptType(coin, msg->script_type)) {
layoutHome();
return;
}

uint32_t xpub_magic = fsm_getXpubMagic(
coin, script_type, msg->ignore_xpub_magic, msg->multisig_xpub_magic);

hdnode_serialize_public(node, fingerprint, xpub_magic, resp->xpub,
sizeof(resp->xpub));

if (msg->has_show_display && msg->show_display) {
for (int page = 0; page < 2; page++) {
layoutXPUB(resp->xpub, page);
Expand Down Expand Up @@ -234,7 +241,8 @@ bool fsm_checkCoinPath(const CoinInfo *coin, InputScriptType script_type,
return true;
}

bool fsm_checkScriptType(const CoinInfo *coin, InputScriptType script_type) {
static bool fsm_checkScriptType(const CoinInfo *coin,
InputScriptType script_type) {
if (!is_internal_input_script_type(script_type)) {
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid script type"));
return false;
Expand Down Expand Up @@ -312,18 +320,8 @@ void fsm_msgGetAddress(const GetAddress *msg) {
strlcpy(desc, _("Address:"), sizeof(desc));
}

uint32_t multisig_xpub_magic = coin->xpub_magic;
if (msg->has_multisig && coin->has_segwit) {
if (!msg->has_ignore_xpub_magic || !msg->ignore_xpub_magic) {
if (msg->script_type == InputScriptType_SPENDWITNESS &&
coin->xpub_magic_segwit_native) {
multisig_xpub_magic = coin->xpub_magic_segwit_native;
} else if (msg->script_type == InputScriptType_SPENDP2SHWITNESS &&
coin->xpub_magic_segwit_p2sh) {
multisig_xpub_magic = coin->xpub_magic_segwit_p2sh;
}
}
}
uint32_t multisig_xpub_magic = fsm_getXpubMagic(
coin, msg->script_type, msg->ignore_xpub_magic, msg->has_multisig);

bool is_cashaddr = coin->cashaddr_prefix != NULL;
if (!fsm_layoutAddress(address, desc, is_cashaddr,
Expand Down
1 change: 1 addition & 0 deletions python/.changelog.d/2658.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support multisig in get-public-node.
2 changes: 2 additions & 0 deletions python/src/trezorlib/btc.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def get_public_node(
coin_name: Optional[str] = None,
script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS,
ignore_xpub_magic: bool = False,
multisig_xpub_magic: bool = False,
unlock_path: Optional[List[int]] = None,
unlock_path_mac: Optional[bytes] = None,
) -> "MessageType":
Expand All @@ -130,6 +131,7 @@ def get_public_node(
coin_name=coin_name,
script_type=script_type,
ignore_xpub_magic=ignore_xpub_magic,
multisig_xpub_magic=multisig_xpub_magic,
)
)

Expand Down
Loading
Loading