mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-03 12:00:59 +00:00
core/bitcoin: Implement generation and verification of SLIP-0019 proofs of ownership.
This commit is contained in:
parent
d52de28704
commit
d4317d1536
@ -5,6 +5,7 @@ from trezor.messages import MessageType
|
|||||||
def boot() -> None:
|
def boot() -> None:
|
||||||
wire.add(MessageType.GetPublicKey, __name__, "get_public_key")
|
wire.add(MessageType.GetPublicKey, __name__, "get_public_key")
|
||||||
wire.add(MessageType.GetAddress, __name__, "get_address")
|
wire.add(MessageType.GetAddress, __name__, "get_address")
|
||||||
|
wire.add(MessageType.GetOwnershipProof, __name__, "get_ownership_proof")
|
||||||
wire.add(MessageType.SignTx, __name__, "sign_tx")
|
wire.add(MessageType.SignTx, __name__, "sign_tx")
|
||||||
wire.add(MessageType.SignMessage, __name__, "sign_message")
|
wire.add(MessageType.SignMessage, __name__, "sign_message")
|
||||||
wire.add(MessageType.VerifyMessage, __name__, "verify_message")
|
wire.add(MessageType.VerifyMessage, __name__, "verify_message")
|
||||||
|
@ -12,6 +12,9 @@ if False:
|
|||||||
from trezor.messages.TxInputType import EnumTypeInputScriptType
|
from trezor.messages.TxInputType import EnumTypeInputScriptType
|
||||||
from trezor.messages.TxOutputType import EnumTypeOutputScriptType
|
from trezor.messages.TxOutputType import EnumTypeOutputScriptType
|
||||||
|
|
||||||
|
# Default signature hash type in Bitcoin which signs all inputs and all outputs of the transaction.
|
||||||
|
SIGHASH_ALL = const(0x01)
|
||||||
|
|
||||||
# supported witness version for bech32 addresses
|
# supported witness version for bech32 addresses
|
||||||
_BECH32_WITVER = const(0x00)
|
_BECH32_WITVER = const(0x00)
|
||||||
|
|
||||||
|
92
core/src/apps/bitcoin/get_ownership_proof.py
Normal file
92
core/src/apps/bitcoin/get_ownership_proof.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
from ubinascii import hexlify
|
||||||
|
|
||||||
|
from trezor import ui, wire
|
||||||
|
from trezor.messages.GetOwnershipProof import GetOwnershipProof
|
||||||
|
from trezor.messages.OwnershipProof import OwnershipProof
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
|
from apps.common import coininfo
|
||||||
|
from apps.common.confirm import require_confirm
|
||||||
|
from apps.common.paths import validate_path
|
||||||
|
|
||||||
|
from . import addresses, common, scripts
|
||||||
|
from .keychain import with_keychain
|
||||||
|
from .ownership import generate_proof, get_identifier
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from apps.common.seed import Keychain
|
||||||
|
|
||||||
|
# Maximum number of characters per line in monospace font.
|
||||||
|
_MAX_MONO_LINE = 18
|
||||||
|
|
||||||
|
|
||||||
|
@with_keychain
|
||||||
|
async def get_ownership_proof(
|
||||||
|
ctx, msg: GetOwnershipProof, keychain: Keychain, coin: coininfo.CoinInfo
|
||||||
|
) -> OwnershipProof:
|
||||||
|
await validate_path(
|
||||||
|
ctx,
|
||||||
|
addresses.validate_full_path,
|
||||||
|
keychain,
|
||||||
|
msg.address_n,
|
||||||
|
coin.curve_name,
|
||||||
|
coin=coin,
|
||||||
|
script_type=msg.script_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES:
|
||||||
|
raise wire.DataError("Invalid script type")
|
||||||
|
|
||||||
|
if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit:
|
||||||
|
raise wire.DataError("Segwit not enabled on this coin")
|
||||||
|
|
||||||
|
node = keychain.derive(msg.address_n)
|
||||||
|
address = addresses.get_address(msg.script_type, coin, node, msg.multisig)
|
||||||
|
script_pubkey = scripts.output_derive_script(address, coin)
|
||||||
|
ownership_id = get_identifier(script_pubkey, keychain)
|
||||||
|
|
||||||
|
# If the scriptPubKey is multisig, then the caller has to provide
|
||||||
|
# ownership IDs, otherwise providing an ID is optional.
|
||||||
|
if msg.multisig:
|
||||||
|
if ownership_id not in msg.ownership_ids:
|
||||||
|
raise wire.DataError("Missing ownership identifier")
|
||||||
|
elif msg.ownership_ids:
|
||||||
|
if msg.ownership_ids != [ownership_id]:
|
||||||
|
raise wire.DataError("Invalid ownership identifier")
|
||||||
|
else:
|
||||||
|
msg.ownership_ids = [ownership_id]
|
||||||
|
|
||||||
|
# In order to set the "user confirmation" bit in the proof, the user must actually confirm.
|
||||||
|
if msg.user_confirmation:
|
||||||
|
text = Text("Proof of ownership", ui.ICON_CONFIG)
|
||||||
|
text.normal("Do you want to create a")
|
||||||
|
if not msg.commitment_data:
|
||||||
|
text.normal("proof of ownership?")
|
||||||
|
else:
|
||||||
|
hex_data = hexlify(msg.commitment_data).decode()
|
||||||
|
text.normal("proof of ownership for:")
|
||||||
|
if len(hex_data) > 3 * _MAX_MONO_LINE:
|
||||||
|
text.mono(hex_data[0:_MAX_MONO_LINE])
|
||||||
|
text.mono(
|
||||||
|
hex_data[_MAX_MONO_LINE : 3 * _MAX_MONO_LINE // 2 - 1]
|
||||||
|
+ "..."
|
||||||
|
+ hex_data[-3 * _MAX_MONO_LINE // 2 + 2 : -_MAX_MONO_LINE]
|
||||||
|
)
|
||||||
|
text.mono(hex_data[-_MAX_MONO_LINE:])
|
||||||
|
else:
|
||||||
|
text.mono(hex_data)
|
||||||
|
|
||||||
|
await require_confirm(ctx, text)
|
||||||
|
|
||||||
|
ownership_proof, signature = generate_proof(
|
||||||
|
node,
|
||||||
|
msg.script_type,
|
||||||
|
msg.multisig,
|
||||||
|
coin,
|
||||||
|
msg.user_confirmation,
|
||||||
|
msg.ownership_ids,
|
||||||
|
script_pubkey,
|
||||||
|
msg.commitment_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return OwnershipProof(ownership_proof=ownership_proof, signature=signature)
|
114
core/src/apps/bitcoin/ownership.py
Normal file
114
core/src/apps/bitcoin/ownership.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
from trezor import utils, wire
|
||||||
|
from trezor.crypto import bip32, hashlib, hmac
|
||||||
|
|
||||||
|
from apps.common import seed
|
||||||
|
from apps.common.readers import BytearrayReader, read_bitcoin_varint
|
||||||
|
from apps.common.writers import (
|
||||||
|
empty_bytearray,
|
||||||
|
write_bitcoin_varint,
|
||||||
|
write_bytes_fixed,
|
||||||
|
write_uint8,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import common
|
||||||
|
from .scripts import read_bip322_signature_proof, write_bip322_signature_proof
|
||||||
|
from .verification import SignatureVerifier
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType
|
||||||
|
from trezor.messages.TxInputType import EnumTypeInputScriptType
|
||||||
|
from apps.common.coininfo import CoinInfo
|
||||||
|
|
||||||
|
# This module implements the SLIP-0019 proof of ownership format.
|
||||||
|
|
||||||
|
_VERSION_MAGIC = b"SL\x00\x19"
|
||||||
|
_FLAG_USER_CONFIRMED = 0x01
|
||||||
|
_OWNERSHIP_ID_LEN = 32
|
||||||
|
_OWNERSHIP_ID_KEY_PATH = [b"SLIP-0019", b"Ownership identification key"]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_proof(
|
||||||
|
node: bip32.HDNode,
|
||||||
|
script_type: EnumTypeInputScriptType,
|
||||||
|
multisig: MultisigRedeemScriptType,
|
||||||
|
coin: CoinInfo,
|
||||||
|
user_confirmed: bool,
|
||||||
|
ownership_ids: List[bytes],
|
||||||
|
script_pubkey: bytes,
|
||||||
|
commitment_data: Optional[bytes],
|
||||||
|
) -> Tuple[bytes, bytes]:
|
||||||
|
flags = 0
|
||||||
|
if user_confirmed:
|
||||||
|
flags |= _FLAG_USER_CONFIRMED
|
||||||
|
|
||||||
|
proof = empty_bytearray(4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN)
|
||||||
|
|
||||||
|
write_bytes_fixed(proof, _VERSION_MAGIC, 4)
|
||||||
|
write_uint8(proof, flags)
|
||||||
|
write_bitcoin_varint(proof, len(ownership_ids))
|
||||||
|
for ownership_id in ownership_ids:
|
||||||
|
write_bytes_fixed(proof, ownership_id, _OWNERSHIP_ID_LEN)
|
||||||
|
|
||||||
|
sighash = hashlib.sha256(proof)
|
||||||
|
sighash.update(script_pubkey)
|
||||||
|
if commitment_data:
|
||||||
|
sighash.update(commitment_data)
|
||||||
|
signature = common.ecdsa_sign(node, sighash.digest())
|
||||||
|
public_key = node.public_key()
|
||||||
|
write_bip322_signature_proof(
|
||||||
|
proof, script_type, multisig, coin, public_key, signature
|
||||||
|
)
|
||||||
|
|
||||||
|
return proof, signature
|
||||||
|
|
||||||
|
|
||||||
|
def verify_nonownership(
|
||||||
|
proof: bytes,
|
||||||
|
script_pubkey: bytes,
|
||||||
|
commitment_data: bytes,
|
||||||
|
keychain: seed.Keychain,
|
||||||
|
coin: CoinInfo,
|
||||||
|
) -> bool:
|
||||||
|
try:
|
||||||
|
r = BytearrayReader(proof)
|
||||||
|
if r.read(4) != _VERSION_MAGIC:
|
||||||
|
raise wire.DataError("Unknown format of proof of ownership")
|
||||||
|
|
||||||
|
flags = r.get()
|
||||||
|
if flags & 0b1111_1110:
|
||||||
|
raise wire.DataError("Unknown flags in proof of ownership")
|
||||||
|
|
||||||
|
# Determine whether our ownership ID appears in the proof.
|
||||||
|
id_count = read_bitcoin_varint(r)
|
||||||
|
ownership_id = get_identifier(script_pubkey, keychain)
|
||||||
|
not_owned = True
|
||||||
|
for _ in range(id_count):
|
||||||
|
if utils.consteq(ownership_id, r.read(_OWNERSHIP_ID_LEN)):
|
||||||
|
not_owned = False
|
||||||
|
|
||||||
|
# Verify the BIP-322 SignatureProof.
|
||||||
|
|
||||||
|
proof_body = proof[: r.offset]
|
||||||
|
sighash = hashlib.sha256(proof_body)
|
||||||
|
sighash.update(script_pubkey)
|
||||||
|
sighash.update(commitment_data)
|
||||||
|
script_sig, witness = read_bip322_signature_proof(r)
|
||||||
|
|
||||||
|
# We don't call verifier.ensure_hash_type() to avoid possible compatibility
|
||||||
|
# issues between implementations, because the hash type doesn't influence
|
||||||
|
# the digest and the value to use is not defined in BIP-322.
|
||||||
|
verifier = SignatureVerifier(script_pubkey, script_sig, witness, coin)
|
||||||
|
verifier.verify(sighash.digest())
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
raise wire.DataError("Invalid proof of ownership")
|
||||||
|
|
||||||
|
return not_owned
|
||||||
|
|
||||||
|
|
||||||
|
def get_identifier(script_pubkey: bytes, keychain: seed.Keychain) -> bytes:
|
||||||
|
# k = Key(m/"SLIP-0019"/"Ownership identification key")
|
||||||
|
node = keychain.derive(_OWNERSHIP_ID_KEY_PATH)
|
||||||
|
|
||||||
|
# id = HMAC-SHA256(key = k, msg = scriptPubKey)
|
||||||
|
return hmac.Hmac(node.key(), script_pubkey, hashlib.sha256).digest()
|
@ -18,7 +18,7 @@ from apps.common import coininfo, seed
|
|||||||
from apps.common.writers import write_bitcoin_varint
|
from apps.common.writers import write_bitcoin_varint
|
||||||
|
|
||||||
from .. import addresses, common, multisig, scripts, writers
|
from .. import addresses, common, multisig, scripts, writers
|
||||||
from ..common import ecdsa_sign
|
from ..common import SIGHASH_ALL, ecdsa_sign
|
||||||
from ..verification import SignatureVerifier
|
from ..verification import SignatureVerifier
|
||||||
from . import helpers, progress, tx_weight
|
from . import helpers, progress, tx_weight
|
||||||
from .matchcheck import MultisigFingerprintChecker, WalletPathChecker
|
from .matchcheck import MultisigFingerprintChecker, WalletPathChecker
|
||||||
@ -27,9 +27,6 @@ if False:
|
|||||||
from typing import List, Optional, Set, Tuple, Union
|
from typing import List, Optional, Set, Tuple, Union
|
||||||
from trezor.crypto.bip32 import HDNode
|
from trezor.crypto.bip32 import HDNode
|
||||||
|
|
||||||
# Default signature hash type in Bitcoin which signs all inputs and all outputs of the transaction.
|
|
||||||
_SIGHASH_ALL = const(0x01)
|
|
||||||
|
|
||||||
# the chain id used for change
|
# the chain id used for change
|
||||||
_BIP32_CHANGE_CHAIN = const(1)
|
_BIP32_CHANGE_CHAIN = const(1)
|
||||||
|
|
||||||
@ -486,7 +483,7 @@ class Bitcoin:
|
|||||||
# ===
|
# ===
|
||||||
|
|
||||||
def get_sighash_type(self, txi: TxInputType) -> int:
|
def get_sighash_type(self, txi: TxInputType) -> int:
|
||||||
return _SIGHASH_ALL
|
return SIGHASH_ALL
|
||||||
|
|
||||||
def get_hash_type(self, txi: TxInputType) -> int:
|
def get_hash_type(self, txi: TxInputType) -> int:
|
||||||
""" Return the nHashType flags."""
|
""" Return the nHashType flags."""
|
||||||
|
Loading…
Reference in New Issue
Block a user