mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-25 16:08:32 +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:
|
class EcdsaSigner:
|
||||||
from trezor.crypto.curve import secp256k1
|
def __init__(self, node: bip32.HDNode, digest: bytes) -> None:
|
||||||
from trezor.crypto.signature import encode_der_signature
|
self.node = node
|
||||||
|
self.digest = digest
|
||||||
|
self.private_key = node.private_key()
|
||||||
|
|
||||||
sig = secp256k1.sign_recoverable(node.private_key(), digest)
|
def commit_nonce(self, entropy_commitment: bytes) -> bytes:
|
||||||
sigder = encode_der_signature(sig)
|
from trezor.crypto.curve import secp256k1
|
||||||
return sigder
|
|
||||||
|
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:
|
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 apps.common.writers import write_compact_size
|
||||||
|
|
||||||
from .. import addresses, common, multisig, scripts, writers
|
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 ..ownership import verify_nonownership
|
||||||
from ..verification import SignatureVerifier
|
from ..verification import SignatureVerifier
|
||||||
from . import helpers
|
from . import helpers
|
||||||
from .approvers import CoinJoinApprover
|
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 .progress import progress
|
||||||
from .tx_info import OriginalTxInfo
|
from .tx_info import OriginalTxInfo
|
||||||
|
|
||||||
@ -610,7 +610,7 @@ class Bitcoin:
|
|||||||
|
|
||||||
self.write_tx_input_derived(self.serialized_tx, txi, key_sign_pub, b"")
|
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:
|
if self.taproot_only:
|
||||||
# Prevents an attacker from bypassing prev tx checking by providing a different
|
# Prevents an attacker from bypassing prev tx checking by providing a different
|
||||||
# script type than the one that was provided during the confirmation phase.
|
# script type than the one that was provided during the confirmation phase.
|
||||||
@ -635,13 +635,30 @@ class Bitcoin:
|
|||||||
self.get_hash_type(txi),
|
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
|
return public_key, signature
|
||||||
|
|
||||||
def sign_taproot_input(self, i: int, txi: TxInput) -> bytes:
|
def sign_taproot_input(self, i: int, txi: TxInput) -> bytes:
|
||||||
from ..common import bip340_sign
|
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(
|
sigmsg_digest = self.tx_info.sig_hasher.hash341(
|
||||||
i,
|
i,
|
||||||
self.tx_info.tx,
|
self.tx_info.tx,
|
||||||
@ -666,7 +683,7 @@ class Bitcoin:
|
|||||||
self.serialized_tx, signature, self.get_sighash_type(txi)
|
self.serialized_tx, signature, self.get_sighash_type(txi)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
public_key, signature = self.sign_bip143_input(i, txi)
|
public_key, signature = await self.sign_bip143_input(i, txi)
|
||||||
if self.serialize:
|
if self.serialize:
|
||||||
if txi.multisig:
|
if txi.multisig:
|
||||||
# find out place of our signature based on the pubkey
|
# 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)
|
tx_digest, txi, node = await self.get_legacy_tx_digest(i, self.tx_info)
|
||||||
assert node is not None
|
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
|
# 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:
|
if self.serialize:
|
||||||
# serialize input with correct signature
|
# serialize input with correct signature
|
||||||
|
@ -25,7 +25,7 @@ class Bitcoinlike(Bitcoin):
|
|||||||
|
|
||||||
if txi.script_type not in NONSEGWIT_INPUT_SCRIPT_TYPES:
|
if txi.script_type not in NONSEGWIT_INPUT_SCRIPT_TYPES:
|
||||||
raise wire.ProcessError("Transaction has changed during signing")
|
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 multisig, do a sanity check to ensure we are signing with a key that is included in the multisig
|
||||||
if txi.multisig:
|
if txi.multisig:
|
||||||
|
@ -209,8 +209,10 @@ class Decred(Bitcoin):
|
|||||||
async def step4_serialize_inputs(self) -> None:
|
async def step4_serialize_inputs(self) -> None:
|
||||||
from trezor.enums import DecredStakingSpendType
|
from trezor.enums import DecredStakingSpendType
|
||||||
|
|
||||||
|
from apps.bitcoin.sign_tx.helpers import request_entropy
|
||||||
|
|
||||||
from .. import multisig
|
from .. import multisig
|
||||||
from ..common import SigHashType, ecdsa_sign
|
from ..common import EcdsaSigner, SigHashType
|
||||||
from .progress import progress
|
from .progress import progress
|
||||||
|
|
||||||
inputs_count = self.tx_info.tx.inputs_count # local_cache_attribute
|
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)
|
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)
|
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
|
# serialize input with correct signature
|
||||||
self.set_serialized_signature(i_sign, 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)
|
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]
|
def request_tx_finish(tx_req: TxRequest) -> Awaitable[None]: # type: ignore [awaitable-return-type]
|
||||||
tx_req.request_type = RequestType.TXFINISHED
|
tx_req.request_type = RequestType.TXFINISHED
|
||||||
yield None, tx_req # type: ignore [awaitable-return-type]
|
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.tx_hash = None
|
||||||
details.extra_data_len = None
|
details.extra_data_len = None
|
||||||
details.extra_data_offset = None
|
details.extra_data_offset = None
|
||||||
|
details.nonce_commitment = None
|
||||||
serialized.signature = None
|
serialized.signature = None
|
||||||
serialized.signature_index = None
|
serialized.signature_index = None
|
||||||
# typechecker thinks serialized_tx is `bytes`, which is immutable
|
# 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:
|
async def sign_nonsegwit_input(self, i_sign: int) -> None:
|
||||||
await self.sign_nonsegwit_bip143_input(i_sign)
|
await self.sign_nonsegwit_bip143_input(i_sign)
|
||||||
|
|
||||||
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]:
|
||||||
from apps.bitcoin.common import ecdsa_sign
|
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)
|
node = self.keychain.derive(txi.address_n)
|
||||||
signature_digest = self.tx_info.sig_hasher.hash_zip244(
|
signature_digest = self.tx_info.sig_hasher.hash_zip244(
|
||||||
txi, self.input_derive_script(txi, node)
|
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
|
return node.public_key(), signature
|
||||||
|
|
||||||
async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None:
|
async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None:
|
||||||
|
Loading…
Reference in New Issue
Block a user