diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 33bdc38243..c5c6c1b9ab 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -91,6 +91,8 @@ trezor.crypto.rlp import trezor.crypto.rlp trezor.crypto.scripts import trezor.crypto.scripts +trezor.crypto.signature +import trezor.crypto.signature trezor.crypto.slip39 import trezor.crypto.slip39 trezor.enums.AmountUnit diff --git a/core/src/apps/binance/sign_tx.py b/core/src/apps/binance/sign_tx.py index c0062967d6..7bf46395ab 100644 --- a/core/src/apps/binance/sign_tx.py +++ b/core/src/apps/binance/sign_tx.py @@ -13,6 +13,7 @@ async def sign_tx(envelope: BinanceSignTx, keychain: Keychain) -> BinanceSignedT from trezor import wire from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha256 + from trezor.crypto.signature import encode_raw_signature from trezor.enums import MessageType from trezor.messages import ( BinanceCancelMsg, @@ -59,6 +60,8 @@ async def sign_tx(envelope: BinanceSignTx, keychain: Keychain) -> BinanceSignedT # generate_content_signature msghash = sha256(msg_json.encode()).digest() - signature_bytes = secp256k1.sign_recoverable(node.private_key(), msghash)[1:65] + signature_bytes = encode_raw_signature( + secp256k1.sign_recoverable(node.private_key(), msghash) + ) return BinanceSignedTx(signature=signature_bytes, public_key=node.public_key()) diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 1c946891a0..6d5db6cf5a 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -105,11 +105,11 @@ NONSEGWIT_INPUT_SCRIPT_TYPES = ( def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes: - from trezor.crypto import der from trezor.crypto.curve import secp256k1 + from trezor.crypto.signature import encode_der_signature sig = secp256k1.sign_recoverable(node.private_key(), digest) - sigder = der.encode_seq((sig[1:33], sig[33:65])) + sigder = encode_der_signature(sig) return sigder diff --git a/core/src/apps/bitcoin/sign_message.py b/core/src/apps/bitcoin/sign_message.py index 9ffae0ca70..8d2a4c9882 100644 --- a/core/src/apps/bitcoin/sign_message.py +++ b/core/src/apps/bitcoin/sign_message.py @@ -8,12 +8,13 @@ if TYPE_CHECKING: from apps.common.coininfo import CoinInfo from apps.common.keychain import Keychain +from trezor.crypto.signature import encode_bip137_signature + @with_keychain async def sign_message( msg: SignMessage, keychain: Keychain, coin: CoinInfo ) -> MessageSignature: - from trezor import wire from trezor.crypto.curve import secp256k1 from trezor.enums import InputScriptType from trezor.messages import MessageSignature @@ -52,19 +53,9 @@ async def sign_message( seckey = node.private_key() digest = message_digest(coin, message) - signature = secp256k1.sign_recoverable(seckey, digest) - - if script_type == InputScriptType.SPENDADDRESS: - script_type_info = 0 - elif script_type == InputScriptType.SPENDP2SHWITNESS: - script_type_info = 4 - elif script_type == InputScriptType.SPENDWITNESS: - script_type_info = 8 - else: - raise wire.ProcessError("Unsupported script type") - - # Add script type information to the recovery byte. - if script_type_info != 0 and not msg.no_script_type: - signature = bytes([signature[0] + script_type_info]) + signature[1:] + signature = encode_bip137_signature( + secp256k1.sign_recoverable(seckey, digest, False), + script_type if not msg.no_script_type else InputScriptType.SPENDADDRESS, + ) return MessageSignature(address=address, signature=signature) diff --git a/core/src/apps/bitcoin/verify_message.py b/core/src/apps/bitcoin/verify_message.py index 2c301db426..22b494a115 100644 --- a/core/src/apps/bitcoin/verify_message.py +++ b/core/src/apps/bitcoin/verify_message.py @@ -53,6 +53,7 @@ def _address_to_script_type(address: str, coin: CoinInfo) -> InputScriptType: async def verify_message(msg: VerifyMessage) -> Success: from trezor import TR, utils from trezor.crypto.curve import secp256k1 + from trezor.crypto.signature import decode_bip137_signature from trezor.enums import InputScriptType from trezor.messages import Success from trezor.ui.layouts import confirm_signverify, show_success @@ -77,32 +78,40 @@ async def verify_message(msg: VerifyMessage) -> Success: digest = message_digest(coin, message) - script_type = _address_to_script_type(address, coin) - recid = signature[0] - if 27 <= recid <= 34: - # p2pkh or no script type provided - pass # use the script type from the address - elif 35 <= recid <= 38 and script_type == InputScriptType.SPENDP2SHWITNESS: - # segwit-in-p2sh - signature = bytes([signature[0] - 4]) + signature[1:] - elif 39 <= recid <= 42 and script_type == InputScriptType.SPENDWITNESS: - # native segwit - signature = bytes([signature[0] - 8]) + signature[1:] - else: - raise ProcessError("Invalid signature") + address_script_type = _address_to_script_type(address, coin) + signature_script_type, recoverable_signature = decode_bip137_signature(signature) - pubkey = secp256k1.verify_recover(signature, digest) + if signature_script_type not in ( + InputScriptType.SPENDADDRESS, + InputScriptType.SPENDADDRESS_UNCOMPRESSED, + ): + if signature_script_type != address_script_type: + raise ProcessError("Invalid signature") + + if signature_script_type == InputScriptType.SPENDP2SHWITNESS: + recoverable_signature = ( + bytes([recoverable_signature[0] - 4]) + recoverable_signature[1:] + ) + if signature_script_type == InputScriptType.SPENDWITNESS: + recoverable_signature = ( + bytes([recoverable_signature[0] - 8]) + recoverable_signature[1:] + ) + + pubkey = secp256k1.verify_recover( + recoverable_signature, + digest, + ) if not pubkey: raise ProcessError("Invalid signature") - if script_type == InputScriptType.SPENDADDRESS: + if address_script_type == InputScriptType.SPENDADDRESS: addr = address_pkh(pubkey, coin) if not utils.BITCOIN_ONLY and coin.cashaddr_prefix is not None: addr = address_to_cashaddr(addr, coin) - elif script_type == InputScriptType.SPENDP2SHWITNESS: + elif address_script_type == InputScriptType.SPENDP2SHWITNESS: addr = address_p2wpkh_in_p2sh(pubkey, coin) - elif script_type == InputScriptType.SPENDWITNESS: + elif address_script_type == InputScriptType.SPENDWITNESS: addr = address_p2wpkh(pubkey, coin) else: raise ProcessError("Invalid signature") diff --git a/core/src/apps/eos/helpers.py b/core/src/apps/eos/helpers.py index 08f103921b..1caaf7db6d 100644 --- a/core/src/apps/eos/helpers.py +++ b/core/src/apps/eos/helpers.py @@ -14,6 +14,17 @@ def base58_encode(prefix: str, sig_prefix: str, data: bytes) -> str: return prefix + b58 +def encode_signature(recoverable_signature: bytes) -> str: + from trezor.crypto.signature import encode_bip137_signature + from trezor.enums import InputScriptType + + return base58_encode( + "SIG_", + "K1", + encode_bip137_signature(recoverable_signature, InputScriptType.SPENDADDRESS), + ) + + def eos_name_to_string(value: int) -> str: charmap = ".12345abcdefghijklmnopqrstuvwxyz" tmp = value diff --git a/core/src/apps/eos/sign_tx.py b/core/src/apps/eos/sign_tx.py index 2a39fd84cb..25b1b75911 100644 --- a/core/src/apps/eos/sign_tx.py +++ b/core/src/apps/eos/sign_tx.py @@ -20,7 +20,7 @@ async def sign_tx(msg: EosSignTx, keychain: Keychain) -> EosSignedTx: from apps.common import paths from .actions import process_action - from .helpers import base58_encode + from .helpers import encode_signature from .layout import require_sign_tx from .writers import write_bytes_fixed, write_header, write_uvarint @@ -52,7 +52,7 @@ async def sign_tx(msg: EosSignTx, keychain: Keychain) -> EosSignedTx: digest = sha.get_digest() signature = secp256k1.sign_recoverable( - node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS + node.private_key(), digest, False, secp256k1.CANONICAL_SIG_EOS ) - return EosSignedTx(signature=base58_encode("SIG_", "K1", signature)) + return EosSignedTx(signature=encode_signature(signature)) diff --git a/core/src/apps/ethereum/helpers.py b/core/src/apps/ethereum/helpers.py index 09a9343691..89a774a238 100644 --- a/core/src/apps/ethereum/helpers.py +++ b/core/src/apps/ethereum/helpers.py @@ -221,3 +221,25 @@ def _from_bytes_bigendian_signed(b: bytes) -> int: return -result - 1 else: return int.from_bytes(b, "big") + + +def encode_signature(recoverable_signature: bytes) -> bytes: + from trezor.crypto.signature import encode_bip137_signature + from trezor.enums import InputScriptType + + signature = encode_bip137_signature( + recoverable_signature, InputScriptType.SPENDADDRESS_UNCOMPRESSED + ) + return signature[1:] + signature[0:1] + + +def decode_signature(signature: bytes) -> bytes: + from trezor.crypto.signature import decode_bip137_signature + from trezor.enums import InputScriptType + + script_type, recoverable_signature = decode_bip137_signature( + signature[-1:] + signature[:-1] + ) + if script_type != InputScriptType.SPENDADDRESS_UNCOMPRESSED: + raise ValueError("Unsupported script type") + return recoverable_signature diff --git a/core/src/apps/ethereum/sign_message.py b/core/src/apps/ethereum/sign_message.py index f544bf4f03..dc7acd16d4 100644 --- a/core/src/apps/ethereum/sign_message.py +++ b/core/src/apps/ethereum/sign_message.py @@ -35,7 +35,7 @@ async def sign_message( from apps.common import paths from apps.common.signverify import decode_message - from .helpers import address_from_bytes + from .helpers import address_from_bytes, encode_signature await paths.validate_path(keychain, msg.address_n) @@ -55,5 +55,5 @@ async def sign_message( return EthereumMessageSignature( address=address, - signature=signature[1:] + bytearray([signature[0]]), + signature=encode_signature(signature), ) diff --git a/core/src/apps/ethereum/sign_typed_data.py b/core/src/apps/ethereum/sign_typed_data.py index bba61eb22d..b9a8424105 100644 --- a/core/src/apps/ethereum/sign_typed_data.py +++ b/core/src/apps/ethereum/sign_typed_data.py @@ -33,7 +33,7 @@ async def sign_typed_data( from apps.common import paths - from .helpers import address_from_bytes + from .helpers import address_from_bytes, encode_signature from .layout import require_confirm_address await paths.validate_path(keychain, msg.address_n) @@ -54,7 +54,7 @@ async def sign_typed_data( return EthereumTypedDataSignature( address=address_from_bytes(address_bytes, defs.network), - signature=signature[1:] + signature[0:1], + signature=encode_signature(signature), ) diff --git a/core/src/apps/ethereum/verify_message.py b/core/src/apps/ethereum/verify_message.py index 227e45bf91..ffccf049e6 100644 --- a/core/src/apps/ethereum/verify_message.py +++ b/core/src/apps/ethereum/verify_message.py @@ -14,13 +14,13 @@ async def verify_message(msg: EthereumVerifyMessage) -> Success: from apps.common.signverify import decode_message - from .helpers import address_from_bytes, bytes_from_address + from .helpers import address_from_bytes, bytes_from_address, decode_signature from .sign_message import message_digest digest = message_digest(msg.message) if len(msg.signature) != 65: raise DataError("Invalid signature") - sig = bytearray([msg.signature[64]]) + msg.signature[:64] + sig = decode_signature(msg.signature) pubkey = secp256k1.verify_recover(sig, digest) diff --git a/core/src/apps/ripple/sign_tx.py b/core/src/apps/ripple/sign_tx.py index 28de37237e..02f15636c5 100644 --- a/core/src/apps/ripple/sign_tx.py +++ b/core/src/apps/ripple/sign_tx.py @@ -11,9 +11,9 @@ if TYPE_CHECKING: # NOTE: it is one big function because that way it is the most flash-space-efficient @auto_keychain(__name__) async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx: - from trezor.crypto import der from trezor.crypto.curve import secp256k1 from trezor.crypto.hashlib import sha512 + from trezor.crypto.signature import encode_der_signature from trezor.messages import RippleSignedTx from trezor.wire import ProcessError @@ -55,7 +55,7 @@ async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx: # Signs and encodes signature into DER format first_half_of_sha512 = sha512(to_sign).digest()[:32] sig = secp256k1.sign_recoverable(node.private_key(), first_half_of_sha512) - sig_encoded = der.encode_seq((sig[1:33], sig[33:65])) + sig_encoded = encode_der_signature(sig) tx = serialize(msg, source_address, node.public_key(), sig_encoded) return RippleSignedTx(signature=sig_encoded, serialized_tx=tx) diff --git a/core/src/apps/webauthn/credential.py b/core/src/apps/webauthn/credential.py index c412f06623..315656feba 100644 --- a/core/src/apps/webauthn/credential.py +++ b/core/src/apps/webauthn/credential.py @@ -7,6 +7,7 @@ import storage.device as storage_device from trezor import utils from trezor.crypto import chacha20poly1305, der, hashlib, hmac, random from trezor.crypto.curve import ed25519, nist256p1 +from trezor.crypto.signature import encode_der_signature from apps.common import cbor, seed from apps.common.paths import HARDENED @@ -95,7 +96,7 @@ class Credential: for segment in data: dig.update(segment) sig = nist256p1.sign_recoverable(self._private_key(), dig.digest(), False) - return der.encode_seq((sig[1:33], sig[33:])) + return encode_der_signature(sig) def bogus_signature(self) -> bytes: raise NotImplementedError diff --git a/core/src/apps/webauthn/fido2.py b/core/src/apps/webauthn/fido2.py index d4ffd2b70e..82663099b8 100644 --- a/core/src/apps/webauthn/fido2.py +++ b/core/src/apps/webauthn/fido2.py @@ -1304,13 +1304,13 @@ def _msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd: def basic_attestation_sign(data: Iterable[bytes]) -> bytes: - from trezor.crypto import der + from trezor.crypto.signature import encode_der_signature dig = hashlib.sha256() for segment in data: dig.update(segment) sig = nist256p1.sign_recoverable(_FIDO_ATT_PRIV_KEY, dig.digest(), False) - return der.encode_seq((sig[1:33], sig[33:])) + return encode_der_signature(sig) def _msg_register_sign(challenge: bytes, cred: U2fCredential) -> bytes: diff --git a/core/src/trezor/crypto/signature.py b/core/src/trezor/crypto/signature.py new file mode 100644 index 0000000000..9806dc42d4 --- /dev/null +++ b/core/src/trezor/crypto/signature.py @@ -0,0 +1,50 @@ +from trezor.crypto.der import encode_seq +from trezor.enums import InputScriptType + + +def encode_der_signature(recoverable_signature: bytes) -> bytes: + assert len(recoverable_signature) == 65 + return encode_seq((recoverable_signature[1:33], recoverable_signature[33:65])) + + +def encode_raw_signature(recoverable_signature: bytes) -> bytes: + assert len(recoverable_signature) == 65 + return recoverable_signature[1:65] + + +def encode_bip137_signature( + recoverable_signature: bytes, script_type: InputScriptType +) -> bytes: + def get_script_type_number(script_type: InputScriptType) -> int: + if script_type == InputScriptType.SPENDADDRESS_UNCOMPRESSED: + return 0 + if script_type == InputScriptType.SPENDADDRESS: + return 4 + elif script_type == InputScriptType.SPENDP2SHWITNESS: + return 8 + elif script_type == InputScriptType.SPENDWITNESS: + return 12 + else: + raise ValueError("Unsupported script type") + + assert len(recoverable_signature) == 65 + header = get_script_type_number(script_type) + recoverable_signature[0] + return bytes([header]) + recoverable_signature[1:] + + +def decode_bip137_signature(signature: bytes) -> tuple[InputScriptType, bytes]: + def get_script_type(header: int) -> InputScriptType: + if 27 <= header <= 30: + return InputScriptType.SPENDADDRESS_UNCOMPRESSED + elif 31 <= header <= 34: + return InputScriptType.SPENDADDRESS + elif 35 <= header <= 38: + return InputScriptType.SPENDP2SHWITNESS + elif 39 <= header <= 42: + return InputScriptType.SPENDWITNESS + else: + raise ValueError("Unsupported script type") + + assert len(signature) == 65 + header = signature[0] + return get_script_type(header), bytes([header]) + signature[1:]