1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-19 00:29:03 +00:00

feat(core): support bitcoin-like transaction signing anti-exfil protocol

This commit is contained in:
Ondřej Vejpustek 2025-03-24 15:33:14 +01:00
parent c58f1e6ec1
commit 723ca105d8
6 changed files with 122 additions and 18 deletions

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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: