diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 6d5db6cf5a..c867a806cf 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -104,13 +104,35 @@ NONSEGWIT_INPUT_SCRIPT_TYPES = ( ) -def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes: - from trezor.crypto.curve import secp256k1 - from trezor.crypto.signature import encode_der_signature +class EcdsaSigner: + def __init__(self, node: bip32.HDNode, digest: bytes) -> None: + self.node = node + self.digest = digest + self.private_key = node.private_key() - sig = secp256k1.sign_recoverable(node.private_key(), digest) - sigder = encode_der_signature(sig) - return sigder + def commit_nonce(self, entropy_commitment: bytes) -> bytes: + from trezor.crypto.curve import secp256k1 + + return secp256k1.anti_exfil_commit_nonce( + self.private_key, self.digest, entropy_commitment + ) + + def sign(self, entropy: bytes | None = None) -> bytes: + # If entropy is provided, uses anti-exfil signing and returns a signature that has 64 bytes. + # Otherwise, uses standard signing and returns a DER-encoded signature. + from trezor.crypto.curve import secp256k1 + from trezor.crypto.signature import encode_der_signature + + if entropy is not None: + return secp256k1.anti_exfil_sign(self.private_key, self.digest, entropy) + else: + return encode_der_signature( + secp256k1.sign_recoverable(self.private_key, self.digest) + ) + + +def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes: + return EcdsaSigner(node, digest).sign() def bip340_sign(node: bip32.HDNode, digest: bytes) -> bytes: diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index 08c3aea391..48e390993b 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -10,12 +10,12 @@ from trezor.wire import DataError, ProcessError from apps.common.writers import write_compact_size from .. import addresses, common, multisig, scripts, writers -from ..common import SigHashType, ecdsa_sign, input_is_external +from ..common import EcdsaSigner, SigHashType, input_is_external from ..ownership import verify_nonownership from ..verification import SignatureVerifier from . import helpers from .approvers import CoinJoinApprover -from .helpers import request_tx_input, request_tx_output +from .helpers import request_entropy, request_tx_input, request_tx_output from .progress import progress from .tx_info import OriginalTxInfo @@ -610,7 +610,7 @@ class Bitcoin: self.write_tx_input_derived(self.serialized_tx, txi, key_sign_pub, b"") - def sign_bip143_input(self, i: int, txi: TxInput) -> tuple[bytes, bytes]: + async def sign_bip143_input(self, i: int, txi: TxInput) -> tuple[bytes, bytes]: if self.taproot_only: # Prevents an attacker from bypassing prev tx checking by providing a different # script type than the one that was provided during the confirmation phase. @@ -635,13 +635,30 @@ class Bitcoin: self.get_hash_type(txi), ) - signature = ecdsa_sign(node, hash143_digest) + if txi.entropy_commitment is not None and self.serialize: + # If host uses the anti-exfil protocol, it should not rely on device + # to serialize the transaction correctly. + raise ProcessError( + "Anti-exfil is not supported together with serialization" + ) + + ecdsa_signer = EcdsaSigner(node, hash143_digest) + if txi.entropy_commitment is not None: + # use anti-exfil protocol + nonce_commitment = ecdsa_signer.commit_nonce(txi.entropy_commitment) + entropy = await request_entropy(self.tx_req, i, nonce_commitment) + signature = ecdsa_signer.sign(entropy) + else: + signature = ecdsa_signer.sign() return public_key, signature def sign_taproot_input(self, i: int, txi: TxInput) -> bytes: from ..common import bip340_sign + if txi.entropy_commitment is not None: + raise ProcessError("Anti-exfil is not supported for taproot inputs") + sigmsg_digest = self.tx_info.sig_hasher.hash341( i, self.tx_info.tx, @@ -666,7 +683,7 @@ class Bitcoin: self.serialized_tx, signature, self.get_sighash_type(txi) ) else: - public_key, signature = self.sign_bip143_input(i, txi) + public_key, signature = await self.sign_bip143_input(i, txi) if self.serialize: if txi.multisig: # find out place of our signature based on the pubkey @@ -776,8 +793,22 @@ class Bitcoin: tx_digest, txi, node = await self.get_legacy_tx_digest(i, self.tx_info) assert node is not None + if txi.entropy_commitment is not None and self.serialize: + # If host uses the anti-exfil protocol, it should not rely on device + # to serialize the transaction correctly. + raise ProcessError( + "Anti-exfil is not supported together with serialization" + ) + # compute the signature from the tx digest - signature = ecdsa_sign(node, tx_digest) + ecdsa_signer = EcdsaSigner(node, tx_digest) + if txi.entropy_commitment is not None: + # use anti-exfil protocol + nonce_commitment = ecdsa_signer.commit_nonce(txi.entropy_commitment) + entropy = await request_entropy(self.tx_req, i, nonce_commitment) + signature = ecdsa_signer.sign(entropy) + else: + signature = ecdsa_signer.sign() if self.serialize: # serialize input with correct signature diff --git a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py index d5af224736..4bf85de60b 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoinlike.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoinlike.py @@ -25,7 +25,7 @@ class Bitcoinlike(Bitcoin): if txi.script_type not in NONSEGWIT_INPUT_SCRIPT_TYPES: raise wire.ProcessError("Transaction has changed during signing") - public_key, signature = self.sign_bip143_input(i_sign, txi) + public_key, signature = await self.sign_bip143_input(i_sign, txi) # if multisig, do a sanity check to ensure we are signing with a key that is included in the multisig if txi.multisig: diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index 39b99380ab..9b42b4345f 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -209,8 +209,10 @@ class Decred(Bitcoin): async def step4_serialize_inputs(self) -> None: from trezor.enums import DecredStakingSpendType + from apps.bitcoin.sign_tx.helpers import request_entropy + from .. import multisig - from ..common import SigHashType, ecdsa_sign + from ..common import EcdsaSigner, SigHashType from .progress import progress inputs_count = self.tx_info.tx.inputs_count # local_cache_attribute @@ -276,7 +278,24 @@ class Decred(Bitcoin): writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE) sig_hash = writers.get_tx_hash(h_sign, double=coin.sign_hash_double) - signature = ecdsa_sign(key_sign, sig_hash) + + if txi_sign.entropy_commitment is not None and self.serialize: + # If host uses the anti-exfil protocol, it should not rely on device + # to serialize the transaction correctly. + raise ProcessError( + "Anti-exfil is not supported together with serialization" + ) + + ecdsa_signer = EcdsaSigner(key_sign, sig_hash) + if txi_sign.entropy_commitment is not None: + # use anti-exfil protocol + nonce_commitment = ecdsa_signer.commit_nonce( + txi_sign.entropy_commitment + ) + entropy = await request_entropy(self.tx_req, i_sign, nonce_commitment) + signature = ecdsa_signer.sign(entropy) + else: + signature = ecdsa_signer.sign() # serialize input with correct signature self.set_serialized_signature(i_sign, signature) diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index 8142c3b35e..0fa3e84941 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -404,6 +404,18 @@ def request_payment_req(tx_req: TxRequest, i: int) -> Awaitable[TxAckPaymentRequ return _sanitize_payment_req(ack) +def request_entropy(tx_req: TxRequest, i: int, nonce_commitment: bytes) -> Awaitable[bytes]: # type: ignore [awaitable-return-type] + from trezor.messages import TxAckEntropy + + assert tx_req.details is not None + tx_req.request_type = RequestType.TXENTROPY + tx_req.details.nonce_commitment = nonce_commitment + tx_req.details.request_index = i + ack = yield TxAckEntropy, tx_req # type: ignore [awaitable-return-type] + _clear_tx_request(tx_req) + return ack.tx.entropy.entropy + + def request_tx_finish(tx_req: TxRequest) -> Awaitable[None]: # type: ignore [awaitable-return-type] tx_req.request_type = RequestType.TXFINISHED yield None, tx_req # type: ignore [awaitable-return-type] @@ -422,6 +434,7 @@ def _clear_tx_request(tx_req: TxRequest) -> None: details.tx_hash = None details.extra_data_len = None details.extra_data_offset = None + details.nonce_commitment = None serialized.signature = None serialized.signature_index = None # typechecker thinks serialized_tx is `bytes`, which is immutable diff --git a/core/src/apps/zcash/signer.py b/core/src/apps/zcash/signer.py index f97110e5c6..bc9466129c 100644 --- a/core/src/apps/zcash/signer.py +++ b/core/src/apps/zcash/signer.py @@ -64,14 +64,33 @@ class Zcash(Bitcoinlike): async def sign_nonsegwit_input(self, i_sign: int) -> None: await self.sign_nonsegwit_bip143_input(i_sign) - def sign_bip143_input(self, i: int, txi: TxInput) -> tuple[bytes, bytes]: - from apps.bitcoin.common import ecdsa_sign + async def sign_bip143_input(self, i: int, txi: TxInput) -> tuple[bytes, bytes]: + from trezor.wire import ProcessError + + from apps.bitcoin.common import EcdsaSigner + from apps.bitcoin.sign_tx.helpers import request_entropy node = self.keychain.derive(txi.address_n) signature_digest = self.tx_info.sig_hasher.hash_zip244( txi, self.input_derive_script(txi, node) ) - signature = ecdsa_sign(node, signature_digest) + + if txi.entropy_commitment is not None and self.serialize: + # If host uses the anti-exfil protocol, it should not rely on device + # to serialize the transaction correctly. + raise ProcessError( + "Anti-exfil is not supported together with serialization" + ) + + ecdsa_signer = EcdsaSigner(node, signature_digest) + if txi.entropy_commitment is not None: + # use anti-exfil protocol + nonce_commitment = ecdsa_signer.commit_nonce(txi.entropy_commitment) + entropy = await request_entropy(self.tx_req, i, nonce_commitment) + signature = ecdsa_signer.sign(entropy) + else: + signature = ecdsa_signer.sign() + return node.public_key(), signature async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None: