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:
parent
c58f1e6ec1
commit
723ca105d8
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user