diff --git a/common/protob/messages-bitcoin.proto b/common/protob/messages-bitcoin.proto index c40eda7fc2..5357bd597f 100644 --- a/common/protob/messages-bitcoin.proto +++ b/common/protob/messages-bitcoin.proto @@ -356,6 +356,10 @@ message TxAck { optional bytes orig_hash = 10; // tx_hash of the original transaction where this output was present (used when creating a replacement transaction) optional uint32 orig_index = 11; // index of the output in the original transaction (used when creating a replacement transaction) optional uint32 payment_req_index = 12 [(experimental_field)=true]; // index of the PaymentRequest containing this output + optional string label = 13; // label stored in Suite + optional bytes label_sig = 14; // sig(label|label_pk) stored in Suite, previously computed by Trezor + optional string label_pk = 15; // pubkey of the contact corresponding to the label + optional bytes address_pk_sig = 16; // signature of the address (signed by the receiver) } } } diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index a803a22d0c..836358c5a1 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -181,6 +181,41 @@ class BasicApprover(Approver): await super()._add_output(txo, script_pubkey) + async def confirm_output_label(self, txo: TxOutput) -> None: + from trezor.crypto.curve import secp256k1 + from apps.common.signverify import message_digest + from apps.bitcoin.addresses import address_pkh + from apps.bitcoin.keychain import get_keychain_for_coin + from apps.common import coins + + coin_testnet = coins.by_name("Testnet") + + # Derive master public key + FIRST_TESTNET_ADDRESS_PATH = [2147483692, 2147483649, 2147483648, 0, 0] + keychain_testnet = await get_keychain_for_coin(coin_testnet) + master_node = keychain_testnet.derive(FIRST_TESTNET_ADDRESS_PATH) + master_pk = master_node.public_key() + + if not txo.label or not txo.label_sig: + raise DataError("Missing label or label signature.") + + # Verify that txo.label_sig matches label_message signed by my master key + label_message = txo.label + "/" + (txo.label_pk or txo.address) + label_message_digest = message_digest(coin_testnet, label_message.encode()) + if not secp256k1.verify(master_pk, txo.label_sig, label_message_digest): + raise DataError("Invalid signature of label.") + + # Verify that txo.address_pk_sig matches txo.address signed by txo.label_pk + if txo.label_pk and txo.address_pk_sig: + address_digest = message_digest(self.coin, txo.address.encode()) + address_pk_sig_rec = secp256k1.verify_recover(txo.address_pk_sig, address_digest) + if txo.label_pk != address_pkh(address_pk_sig_rec, coin_testnet): + raise DataError("Invalid signature of address.") + elif not txo.label_pk and not txo.address_pk_sig: + pass + else: + raise DataError("Both label public key and address public key signature must be present.") + async def add_change_output(self, txo: TxOutput, script_pubkey: bytes) -> None: await super().add_change_output(txo, script_pubkey) self.change_count += 1 @@ -196,6 +231,9 @@ class BasicApprover(Approver): await super().add_external_output(txo, script_pubkey, tx_info, orig_txo) + if txo.label: + await self.confirm_output_label(txo) + if orig_txo: if txo.amount < orig_txo.amount: # Replacement transactions may need to decrease the value of external outputs to diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index 8f9fed1e51..4f564f8bed 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -96,7 +96,9 @@ async def confirm_output( title = None address_label = None - if output.address_n and not output.multisig: + if output.label: + address_label = output.label + elif output.address_n and not output.multisig: from trezor import utils # Showing the account string only for model_tr layout diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 865c2cbcf4..705fc1c5ef 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -2046,6 +2046,10 @@ class TxOutputType(protobuf.MessageType): 10: protobuf.Field("orig_hash", "bytes", repeated=False, required=False, default=None), 11: protobuf.Field("orig_index", "uint32", repeated=False, required=False, default=None), 12: protobuf.Field("payment_req_index", "uint32", repeated=False, required=False, default=None), + 13: protobuf.Field("label", "string", repeated=False, required=False, default=None), + 14: protobuf.Field("label_sig", "bytes", repeated=False, required=False, default=None), + 15: protobuf.Field("label_pk", "string", repeated=False, required=False, default=None), + 16: protobuf.Field("address_pk_sig", "bytes", repeated=False, required=False, default=None), } def __init__( @@ -2060,6 +2064,10 @@ class TxOutputType(protobuf.MessageType): orig_hash: Optional["bytes"] = None, orig_index: Optional["int"] = None, payment_req_index: Optional["int"] = None, + label: Optional["str"] = None, + label_sig: Optional["bytes"] = None, + label_pk: Optional["str"] = None, + address_pk_sig: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.amount = amount @@ -2070,6 +2078,10 @@ class TxOutputType(protobuf.MessageType): self.orig_hash = orig_hash self.orig_index = orig_index self.payment_req_index = payment_req_index + self.label = label + self.label_sig = label_sig + self.label_pk = label_pk + self.address_pk_sig = address_pk_sig class PaymentRequestMemo(protobuf.MessageType):