1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-25 09:22:33 +00:00

feat(core): support ownership proof anti-exfil protocol

This commit is contained in:
Ondřej Vejpustek 2025-03-21 14:12:26 +01:00
parent cb7ac84d8a
commit 12ccfcd43c
4 changed files with 137 additions and 72 deletions

View File

@ -131,10 +131,6 @@ class EcdsaSigner:
) )
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:
internal_private_key = node.private_key() internal_private_key = node.private_key()
output_private_key = bip340.tweak_secret_key(internal_private_key) output_private_key = bip340.tweak_secret_key(internal_private_key)

View File

@ -11,6 +11,17 @@ if TYPE_CHECKING:
from .authorization import CoinJoinAuthorization from .authorization import CoinJoinAuthorization
async def send_request_entropy(nonce_commitment: bytes) -> bytes:
from trezor.messages import OwnershipProofEntropy, OwnershipProofNonceCommitment
from trezor.wire.context import call
req = OwnershipProofNonceCommitment()
req.nonce_commitment = nonce_commitment
resp = await call(req, OwnershipProofEntropy)
assert resp.entropy is not None
return resp.entropy
@with_keychain @with_keychain
async def get_ownership_proof( async def get_ownership_proof(
msg: GetOwnershipProof, msg: GetOwnershipProof,
@ -28,7 +39,7 @@ async def get_ownership_proof(
from . import addresses, common, scripts from . import addresses, common, scripts
from .keychain import validate_path_against_script_type from .keychain import validate_path_against_script_type
from .ownership import generate_proof, get_identifier from .ownership import ProofGenerator, get_identifier
script_type = msg.script_type # local_cache_attribute script_type = msg.script_type # local_cache_attribute
ownership_ids = msg.ownership_ids # local_cache_attribute ownership_ids = msg.ownership_ids # local_cache_attribute
@ -83,7 +94,7 @@ async def get_ownership_proof(
TR.bitcoin__commitment_data, TR.bitcoin__commitment_data,
) )
ownership_proof, signature = generate_proof( proof_generator = ProofGenerator(
node, node,
script_type, script_type,
msg.multisig, msg.multisig,
@ -94,4 +105,13 @@ async def get_ownership_proof(
msg.commitment_data, msg.commitment_data,
) )
return OwnershipProof(ownership_proof=ownership_proof, signature=signature) if msg.entropy_commitment:
# use anti-exfil protocol
nonce_commitment = proof_generator.commit_nonce(msg.entropy_commitment)
entropy = await send_request_entropy(nonce_commitment)
_, signature = proof_generator.sign(entropy)
return OwnershipProof(ownership_proof=b"", signature=signature)
else:
ownership_proof, signature = proof_generator.sign()
assert ownership_proof is not None
return OwnershipProof(ownership_proof=ownership_proof, signature=signature)

View File

@ -3,17 +3,18 @@ from typing import TYPE_CHECKING
from trezor import utils from trezor import utils
from trezor.crypto.hashlib import sha256 from trezor.crypto.hashlib import sha256
from trezor.enums import InputScriptType
from trezor.utils import HashWriter from trezor.utils import HashWriter
from trezor.wire import DataError from trezor.wire import DataError
from apps.bitcoin.writers import write_bytes_prefixed from apps.bitcoin.writers import write_bytes_prefixed
from apps.common.readers import read_compact_size from apps.common.readers import read_compact_size
from .common import EcdsaSigner
from .scripts import read_bip322_signature_proof from .scripts import read_bip322_signature_proof
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.crypto import bip32 from trezor.crypto import bip32
from trezor.enums import InputScriptType
from trezor.messages import MultisigRedeemScriptType from trezor.messages import MultisigRedeemScriptType
from apps.common.coininfo import CoinInfo from apps.common.coininfo import CoinInfo
@ -29,55 +30,103 @@ _OWNERSHIP_ID_LEN = const(32)
_OWNERSHIP_ID_KEY_PATH = [b"SLIP-0019", b"Ownership identification key"] _OWNERSHIP_ID_KEY_PATH = [b"SLIP-0019", b"Ownership identification key"]
def generate_proof( class ProofGenerator:
node: bip32.HDNode, @staticmethod
script_type: InputScriptType, def is_ecdsa_script_type(script_type: InputScriptType) -> bool:
multisig: MultisigRedeemScriptType | None, return script_type in (
coin: CoinInfo, InputScriptType.SPENDADDRESS,
user_confirmed: bool, InputScriptType.SPENDMULTISIG,
ownership_ids: list[bytes], InputScriptType.SPENDWITNESS,
script_pubkey: bytes, InputScriptType.SPENDP2SHWITNESS,
commitment_data: bytes, )
) -> tuple[bytes, bytes]:
from trezor.enums import InputScriptType
from apps.bitcoin.writers import write_bytes_fixed, write_compact_size, write_uint8 @staticmethod
def get_proof_body(
user_confirmed: bool,
ownership_ids: list[bytes],
script_pubkey: bytes,
commitment_data: bytes,
) -> tuple[bytearray, bytes]:
from apps.bitcoin.writers import (
write_bytes_fixed,
write_compact_size,
write_uint8,
)
from . import common flags = 0
from .scripts import write_bip322_signature_proof if user_confirmed:
flags |= _FLAG_USER_CONFIRMED
flags = 0 proof = utils.empty_bytearray(
if user_confirmed: 4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN
flags |= _FLAG_USER_CONFIRMED )
proof = utils.empty_bytearray(4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN) write_bytes_fixed(proof, _VERSION_MAGIC, 4)
write_uint8(proof, flags)
write_compact_size(proof, len(ownership_ids))
for ownership_id in ownership_ids:
write_bytes_fixed(proof, ownership_id, _OWNERSHIP_ID_LEN)
write_bytes_fixed(proof, _VERSION_MAGIC, 4) sighash = HashWriter(sha256(proof))
write_uint8(proof, flags) write_bytes_prefixed(sighash, script_pubkey)
write_compact_size(proof, len(ownership_ids)) write_bytes_prefixed(sighash, commitment_data)
for ownership_id in ownership_ids: digest = sighash.get_digest()
write_bytes_fixed(proof, ownership_id, _OWNERSHIP_ID_LEN)
sighash = HashWriter(sha256(proof)) return proof, digest
write_bytes_prefixed(sighash, script_pubkey)
write_bytes_prefixed(sighash, commitment_data)
if script_type in (
InputScriptType.SPENDADDRESS,
InputScriptType.SPENDMULTISIG,
InputScriptType.SPENDWITNESS,
InputScriptType.SPENDP2SHWITNESS,
):
signature = common.ecdsa_sign(node, sighash.get_digest())
elif script_type == InputScriptType.SPENDTAPROOT:
signature = common.bip340_sign(node, sighash.get_digest())
else:
raise DataError("Unsupported script type.")
public_key = node.public_key()
write_bip322_signature_proof(
proof, script_type, multisig, coin, public_key, signature
)
return proof, signature def __init__(
self,
node: bip32.HDNode,
script_type: InputScriptType,
multisig: MultisigRedeemScriptType | None,
coin: CoinInfo,
user_confirmed: bool,
ownership_ids: list[bytes],
script_pubkey: bytes,
commitment_data: bytes,
) -> None:
self.proof_body, self.digest = self.get_proof_body(
user_confirmed, ownership_ids, script_pubkey, commitment_data
)
self.node = node
self.multisig = multisig
self.coin = coin
self.script_type = script_type
if self.is_ecdsa_script_type(script_type):
self.signer = EcdsaSigner(self.node, self.digest)
def commit_nonce(self, entropy_commitment: bytes) -> bytes:
if not self.is_ecdsa_script_type(self.script_type):
raise DataError("Unsupported script type.")
return self.signer.commit_nonce(entropy_commitment)
def sign(self, entropy: bytes | None = None) -> tuple[bytes | None, bytes]:
from . import common
from .scripts import write_bip322_signature_proof
if self.is_ecdsa_script_type(self.script_type):
signature = self.signer.sign(entropy)
elif self.script_type == InputScriptType.SPENDTAPROOT:
signature = common.bip340_sign(self.node, self.digest)
else:
raise DataError("Unsupported script type.")
if entropy is None:
public_key = self.node.public_key()
write_bip322_signature_proof(
self.proof_body,
self.script_type,
self.multisig,
self.coin,
public_key,
signature,
)
return self.proof_body, signature
else:
return None, signature
def verify_nonownership( def verify_nonownership(

View File

@ -43,7 +43,7 @@ class TestOwnershipProof(unittest.TestCase):
), ),
) )
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=node, node=node,
script_type=InputScriptType.SPENDWITNESS, script_type=InputScriptType.SPENDWITNESS,
multisig=None, multisig=None,
@ -52,7 +52,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=[ownership_id], ownership_ids=[ownership_id],
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -95,7 +95,7 @@ class TestOwnershipProof(unittest.TestCase):
), ),
) )
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=node, node=node,
script_type=InputScriptType.SPENDP2SHWITNESS, script_type=InputScriptType.SPENDP2SHWITNESS,
multisig=None, multisig=None,
@ -104,7 +104,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=[ownership_id], ownership_ids=[ownership_id],
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -146,7 +146,7 @@ class TestOwnershipProof(unittest.TestCase):
), ),
) )
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=node, node=node,
script_type=InputScriptType.SPENDTAPROOT, script_type=InputScriptType.SPENDTAPROOT,
multisig=None, multisig=None,
@ -155,7 +155,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=[ownership_id], ownership_ids=[ownership_id],
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -197,7 +197,7 @@ class TestOwnershipProof(unittest.TestCase):
), ),
) )
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=node, node=node,
script_type=InputScriptType.SPENDADDRESS, script_type=InputScriptType.SPENDADDRESS,
multisig=None, multisig=None,
@ -206,7 +206,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=[ownership_id], ownership_ids=[ownership_id],
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -338,7 +338,7 @@ class TestOwnershipProof(unittest.TestCase):
) )
# Sign with the first key. # Sign with the first key.
_, signature = ownership.generate_proof( _, signature = ownership.ProofGenerator(
node=keychains[0].derive([84 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0]), node=keychains[0].derive([84 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0]),
script_type=InputScriptType.SPENDWITNESS, script_type=InputScriptType.SPENDWITNESS,
multisig=multisig, multisig=multisig,
@ -347,7 +347,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -357,7 +357,7 @@ class TestOwnershipProof(unittest.TestCase):
multisig.signatures[0] = signature multisig.signatures[0] = signature
# Sign with the third key. # Sign with the third key.
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=keychain.derive([84 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0]), node=keychain.derive([84 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0]),
script_type=InputScriptType.SPENDWITNESS, script_type=InputScriptType.SPENDWITNESS,
multisig=multisig, multisig=multisig,
@ -366,7 +366,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -429,7 +429,7 @@ class TestOwnershipProof(unittest.TestCase):
) )
# Sign with the second key. # Sign with the second key.
_, signature = ownership.generate_proof( _, signature = ownership.ProofGenerator(
node=keychain.derive([49 | HARDENED, 0 | HARDENED, 2 | HARDENED, 0, 1]), node=keychain.derive([49 | HARDENED, 0 | HARDENED, 2 | HARDENED, 0, 1]),
script_type=InputScriptType.SPENDP2SHWITNESS, script_type=InputScriptType.SPENDP2SHWITNESS,
multisig=multisig, multisig=multisig,
@ -438,7 +438,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -448,7 +448,7 @@ class TestOwnershipProof(unittest.TestCase):
multisig.signatures[1] = signature multisig.signatures[1] = signature
# Sign with the fourth key. # Sign with the fourth key.
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=keychain.derive([49 | HARDENED, 0 | HARDENED, 4 | HARDENED, 0, 1]), node=keychain.derive([49 | HARDENED, 0 | HARDENED, 4 | HARDENED, 0, 1]),
script_type=InputScriptType.SPENDP2SHWITNESS, script_type=InputScriptType.SPENDP2SHWITNESS,
multisig=multisig, multisig=multisig,
@ -457,7 +457,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -467,7 +467,7 @@ class TestOwnershipProof(unittest.TestCase):
multisig.signatures[3] = signature multisig.signatures[3] = signature
# Sign with the fifth key. # Sign with the fifth key.
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=keychain.derive([49 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 1]), node=keychain.derive([49 | HARDENED, 0 | HARDENED, 5 | HARDENED, 0, 1]),
script_type=InputScriptType.SPENDP2SHWITNESS, script_type=InputScriptType.SPENDP2SHWITNESS,
multisig=multisig, multisig=multisig,
@ -476,7 +476,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -539,7 +539,7 @@ class TestOwnershipProof(unittest.TestCase):
) )
# Sign with the first key. # Sign with the first key.
_, signature = ownership.generate_proof( _, signature = ownership.ProofGenerator(
node=keychain.derive([48 | HARDENED, 0 | HARDENED, 1 | HARDENED, 0, 0]), node=keychain.derive([48 | HARDENED, 0 | HARDENED, 1 | HARDENED, 0, 0]),
script_type=InputScriptType.SPENDMULTISIG, script_type=InputScriptType.SPENDMULTISIG,
multisig=multisig, multisig=multisig,
@ -548,7 +548,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(
@ -558,7 +558,7 @@ class TestOwnershipProof(unittest.TestCase):
multisig.signatures[0] = signature multisig.signatures[0] = signature
# Sign with the third key. # Sign with the third key.
proof, signature = ownership.generate_proof( proof, signature = ownership.ProofGenerator(
node=keychain.derive([48 | HARDENED, 0 | HARDENED, 2 | HARDENED, 0, 0]), node=keychain.derive([48 | HARDENED, 0 | HARDENED, 2 | HARDENED, 0, 0]),
script_type=InputScriptType.SPENDMULTISIG, script_type=InputScriptType.SPENDMULTISIG,
multisig=multisig, multisig=multisig,
@ -567,7 +567,7 @@ class TestOwnershipProof(unittest.TestCase):
ownership_ids=ownership_ids, ownership_ids=ownership_ids,
script_pubkey=script_pubkey, script_pubkey=script_pubkey,
commitment_data=commitment_data, commitment_data=commitment_data,
) ).sign()
self.assertEqual( self.assertEqual(
signature, signature,
unhexlify( unhexlify(