1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-19 00:29:03 +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:
internal_private_key = node.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
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
async def get_ownership_proof(
msg: GetOwnershipProof,
@ -28,7 +39,7 @@ async def get_ownership_proof(
from . import addresses, common, scripts
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
ownership_ids = msg.ownership_ids # local_cache_attribute
@ -83,7 +94,7 @@ async def get_ownership_proof(
TR.bitcoin__commitment_data,
)
ownership_proof, signature = generate_proof(
proof_generator = ProofGenerator(
node,
script_type,
msg.multisig,
@ -94,4 +105,13 @@ async def get_ownership_proof(
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.crypto.hashlib import sha256
from trezor.enums import InputScriptType
from trezor.utils import HashWriter
from trezor.wire import DataError
from apps.bitcoin.writers import write_bytes_prefixed
from apps.common.readers import read_compact_size
from .common import EcdsaSigner
from .scripts import read_bip322_signature_proof
if TYPE_CHECKING:
from trezor.crypto import bip32
from trezor.enums import InputScriptType
from trezor.messages import MultisigRedeemScriptType
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"]
def generate_proof(
node: bip32.HDNode,
script_type: InputScriptType,
multisig: MultisigRedeemScriptType | None,
coin: CoinInfo,
user_confirmed: bool,
ownership_ids: list[bytes],
script_pubkey: bytes,
commitment_data: bytes,
) -> tuple[bytes, bytes]:
from trezor.enums import InputScriptType
class ProofGenerator:
@staticmethod
def is_ecdsa_script_type(script_type: InputScriptType) -> bool:
return script_type in (
InputScriptType.SPENDADDRESS,
InputScriptType.SPENDMULTISIG,
InputScriptType.SPENDWITNESS,
InputScriptType.SPENDP2SHWITNESS,
)
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
from .scripts import write_bip322_signature_proof
flags = 0
if user_confirmed:
flags |= _FLAG_USER_CONFIRMED
flags = 0
if user_confirmed:
flags |= _FLAG_USER_CONFIRMED
proof = utils.empty_bytearray(
4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN
)
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)
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)
sighash = HashWriter(sha256(proof))
write_bytes_prefixed(sighash, script_pubkey)
write_bytes_prefixed(sighash, commitment_data)
digest = sighash.get_digest()
sighash = HashWriter(sha256(proof))
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, digest
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(

View File

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