Replace ecdsa with cryptography in legacy bootloader, debug signing, and test helper files

Co-authored-by: obrusvit <14001709+obrusvit@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-18 15:20:59 +00:00
parent e2c63d123b
commit db5d801fe5
10 changed files with 193 additions and 89 deletions

View File

@@ -3,7 +3,13 @@ import argparse
import hashlib
import struct
import ecdsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
encode_dss_signature,
)
from cryptography.exceptions import InvalidSignature
SLOTS = 3
@@ -129,14 +135,16 @@ def check_signatures(data):
print(f"Slot #{x + 1}", "is empty")
else:
pk = pubkeys[indexes[x]]
verify = ecdsa.VerifyingKey.from_string(
bytes.fromhex(pk)[1:],
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
verify = ec.EllipticCurvePublicKey.from_encoded_point(
ec.SECP256K1(),
bytes.fromhex(pk),
)
try:
verify.verify(signature, to_sign, hashfunc=hashlib.sha256)
r = int.from_bytes(signature[:32], "big")
s = int.from_bytes(signature[32:], "big")
der_sig = encode_dss_signature(r, s)
verify.verify(der_sig, to_sign, ec.ECDSA(hashes.SHA256()))
if indexes[x] in used:
print(f"Slot #{x + 1} signature: DUPLICATE", signature.hex())
@@ -176,7 +184,7 @@ def sign(data, is_pem):
if pem_key.strip() == "":
# Blank key,let's remove existing signature from slot
return modify(data, slot, 0, b"\x00" * 64)
key = ecdsa.SigningKey.from_pem(pem_key)
key = serialization.load_pem_private_key(pem_key.encode(), password=None)
else:
print("Paste SECEXP (in hex) and press Enter:")
print("(blank private key removes the signature on given index)")
@@ -184,16 +192,15 @@ def sign(data, is_pem):
if secexp.strip() == "":
# Blank key,let's remove existing signature from slot
return modify(data, slot, 0, b"\x00" * 64)
key = ecdsa.SigningKey.from_secret_exponent(
secexp=int(secexp, 16),
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
key = ec.derive_private_key(int(secexp, 16), ec.SECP256K1())
to_sign = get_header(data, zero_signatures=True)
# Locate proper index of current signing key
pubkey = "04" + key.get_verifying_key().to_string().hex()
pubkey = key.public_key().public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
).hex()
index = None
for i, pk in pubkeys.items():
if pk == pubkey:
@@ -203,26 +210,41 @@ def sign(data, is_pem):
if index is None:
raise Exception("Unable to find private key index. Unknown private key?")
signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256)
der_sig = key.sign(to_sign, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der_sig)
signature = r.to_bytes(32, "big") + s.to_bytes(32, "big")
return modify(data, slot, index, signature)
def main(args):
if args.generate:
key = ecdsa.SigningKey.generate(
curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256
)
key = ec.generate_private_key(ec.SECP256K1())
print("PRIVATE KEY (SECEXP):")
print(key.to_string().hex())
print(
key.private_numbers().private_value.to_bytes(32, "big").hex()
)
print()
print("PRIVATE KEY (PEM):")
print(key.to_pem())
print(
key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption(),
).decode()
)
print("PUBLIC KEY:")
print("04" + key.get_verifying_key().to_string().hex())
print(
key.public_key()
.public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
)
.hex()
)
return
if not args.path:

View File

@@ -3,8 +3,13 @@ import argparse
import hashlib
import struct
import ecdsa
from ecdsa import BadSignatureError
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
encode_dss_signature,
)
from cryptography.exceptions import InvalidSignature
SLOTS = 3
@@ -121,14 +126,16 @@ def check_signatures(data):
else:
pubkeys = pubkeys_dev
pk = pubkeys[indexes[x]]
verify = ecdsa.VerifyingKey.from_string(
bytes.fromhex(pk)[1:],
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
verify = ec.EllipticCurvePublicKey.from_encoded_point(
ec.SECP256K1(),
bytes.fromhex(pk),
)
try:
verify.verify(signature, to_sign, hashfunc=hashlib.sha256)
r = int.from_bytes(signature[:32], "big")
s = int.from_bytes(signature[32:], "big")
der_sig = encode_dss_signature(r, s)
verify.verify(der_sig, to_sign, ec.ECDSA(hashes.SHA256()))
if indexes[x] in used:
print(f"Slot #{x + 1} signature: DUPLICATE", signature.hex())
@@ -150,16 +157,15 @@ def modify(data, slot, index, signature):
def sign(data, slot, secexp):
key = ecdsa.SigningKey.from_secret_exponent(
secexp=int(secexp, 16),
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
key = ec.derive_private_key(int(secexp, 16), ec.SECP256K1())
to_sign = get_header(data, zero_signatures=True)
# Locate proper index of current signing key
pubkey = "04" + key.get_verifying_key().to_string().hex()
pubkey = key.public_key().public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
).hex()
index = None
pubkeys = pubkeys_dev
@@ -171,7 +177,9 @@ def sign(data, slot, secexp):
if index is None:
raise Exception("Unable to find private key index. Unknown private key?")
signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256)
der_sig = key.sign(to_sign, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der_sig)
signature = r.to_bytes(32, "big") + s.to_bytes(32, "big")
return modify(data, slot, index, signature)
@@ -223,14 +231,16 @@ def check_signatures_old(data):
else:
pubkeys = pubkeys_dev
pk = pubkeys[indexes[x]]
verify = ecdsa.VerifyingKey.from_string(
bytes.fromhex(pk)[1:],
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
verify = ec.EllipticCurvePublicKey.from_encoded_point(
ec.SECP256K1(),
bytes.fromhex(pk),
)
try:
verify.verify(signature, to_sign, hashfunc=hashlib.sha256)
r = int.from_bytes(signature[:32], "big")
s = int.from_bytes(signature[32:], "big")
der_sig = encode_dss_signature(r, s)
verify.verify(der_sig, to_sign, ec.ECDSA(hashes.SHA256()))
if indexes[x] in used:
print("Slot #%d signature: DUPLICATE" % (x + 1), signature.hex())
@@ -238,7 +248,7 @@ def check_signatures_old(data):
used.append(indexes[x])
print("Slot #%d signature: VALID" % (x + 1), signature.hex())
except BadSignatureError:
except InvalidSignature:
print("Slot #%d signature: INVALID" % (x + 1), signature.hex())
@@ -261,16 +271,15 @@ def modify_old(data, slot, index, signature):
def sign_old(data, slot, secexp):
key = ecdsa.SigningKey.from_secret_exponent(
secexp=int(secexp, 16),
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
key = ec.derive_private_key(int(secexp, 16), ec.SECP256K1())
to_sign = prepare_old(data)[256:] # without meta
# Locate proper index of current signing key
pubkey = "04" + key.get_verifying_key().to_string().hex()
pubkey = key.public_key().public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
).hex()
index = None
pubkeys = pubkeys_dev
@@ -283,7 +292,9 @@ def sign_old(data, slot, secexp):
if index is None:
raise Exception("Unable to find private key index. Unknown private key?")
signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256)
der_sig = key.sign(to_sign, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der_sig)
signature = r.to_bytes(32, "big") + s.to_bytes(32, "big")
return modify_old(data, slot, index, signature)

View File

@@ -4,7 +4,9 @@ import os
import subprocess
from binascii import hexlify, unhexlify
import ecdsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
print("master secret:", end="")
h = input()
@@ -20,16 +22,25 @@ print()
for i in range(1, 6):
se = hashlib.sha256(h + chr(i).encode("ascii")).hexdigest()
print("seckey", i, ":", se)
sk = ecdsa.SigningKey.from_secret_exponent(
secexp=int(se, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256
sk = ec.derive_private_key(int(se, 16), ec.SECP256K1())
pk = sk.public_key()
pk_bytes = pk.public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
)
print(
"pubkey",
i,
":",
(b"04" + hexlify(sk.get_verifying_key().to_string())).decode("ascii"),
hexlify(pk_bytes).decode("ascii"),
)
print(
sk.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption(),
).decode("ascii")
)
print(sk.to_pem().decode("ascii"))
p = subprocess.Popen("ssss-split -t 3 -n 5 -x".split(" "), stdin=subprocess.PIPE)
p.communicate(input=hexlify(h) + "\n")

View File

@@ -2,7 +2,9 @@
import sys
from hashlib import sha256
import ecdsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, encode_dss_signature
# arg 1 - hex digest of firmware header with zeroed sigslots
# arg 2 - public key (compressed or uncompressed)
@@ -17,6 +19,10 @@ prefix = b"\x18Bitcoin Signed Message:\n\x20"
message_predigest = prefix + digest
message = sha256(message_predigest).digest()
vk = ecdsa.VerifyingKey.from_string(public_key, curve=ecdsa.SECP256k1, hashfunc=sha256)
result = vk.verify(sig, message)
vk = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key)
r = int.from_bytes(sig[:32], "big")
s = int.from_bytes(sig[32:], "big")
der_sig = encode_dss_signature(r, s)
vk.verify(der_sig, message, ec.ECDSA(Prehashed(hashes.SHA256())))
result = True
print("Signature verification result", result)

View File

@@ -5,7 +5,14 @@ import pprint
import sys
from hashlib import sha1, sha256
import ecdsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
encode_dss_signature,
)
from cryptography.exceptions import InvalidSignature
from fill_t1_fw_signatures import Signatures
secret_keys_hex = [
@@ -17,13 +24,19 @@ secret_keys_hex = [
]
secret_keys = [
ecdsa.SigningKey.from_string(
bytes.fromhex(sk), curve=ecdsa.SECP256k1, hashfunc=sha256
ec.derive_private_key(
int.from_bytes(bytes.fromhex(sk), "big"), ec.SECP256K1()
)
for sk in secret_keys_hex
]
public_keys = [sk.get_verifying_key() for sk in secret_keys]
public_keys_hex = [pk.to_string("compressed").hex() for pk in public_keys]
public_keys = [sk.public_key() for sk in secret_keys]
public_keys_hex = [
pk.public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.CompressedPoint,
).hex()
for pk in public_keys
]
# arg1 is input trezor.bin filename to be signed
# arg2 is output filename, if omitted, will use input file + ".signed"
@@ -54,21 +67,21 @@ assert public_keys_hex == [
print("Sanity check")
for sk, pk_hex in zip(secret_keys, public_keys_hex):
pk = ecdsa.VerifyingKey.from_string(
bytes.fromhex(pk_hex), curve=ecdsa.SECP256k1, hashfunc=sha256
pk = ec.EllipticCurvePublicKey.from_encoded_point(
ec.SECP256K1(), bytes.fromhex(pk_hex)
)
message = bytes(os.urandom(64))
# These should work
sig = sk.sign_deterministic(message, hashfunc=sha256)
pk.verify(sig, message, hashfunc=sha256) # throws exception if wrong
der_sig = sk.sign(message, ec.ECDSA(hashes.SHA256()))
pk.verify(der_sig, message, ec.ECDSA(hashes.SHA256())) # throws exception if wrong
# These should fail
try:
sig = sk.sign_deterministic(message, hashfunc=sha1)
pk.verify(sig, message, hashfunc=sha256) # should throw
der_sig = sk.sign(message, ec.ECDSA(hashes.SHA1()))
pk.verify(der_sig, message, ec.ECDSA(hashes.SHA256())) # should throw
raise RuntimeError("These should not have matched!")
except ecdsa.keys.BadSignatureError:
except InvalidSignature:
# print("Bad sig check fail test ok")
pass # fine, should have failed
@@ -87,13 +100,16 @@ for i in sig_indices:
index = i - 1 # in FW indices are indexed from 1, 0 means none
print(f"--- Key {index}, sigindex {i}")
sk = secret_keys[index]
sig_64bytes = sk.sign_deterministic(header, hashfunc=sha256)
der_sig = sk.sign(header, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der_sig)
sig_64bytes = r.to_bytes(32, "big") + s.to_bytes(32, "big")
assert len(sig_64bytes) == 64
print("Signature:", sig_64bytes.hex())
pk = ecdsa.VerifyingKey.from_string(
bytes.fromhex(public_keys_hex[index]), curve=ecdsa.SECP256k1, hashfunc=sha256
pk = ec.EllipticCurvePublicKey.from_encoded_point(
ec.SECP256K1(), bytes.fromhex(public_keys_hex[index])
)
pk.verify(sig_64bytes, header, hashfunc=sha256) # throws exception if wrong
der_sig_verify = encode_dss_signature(r, s)
pk.verify(der_sig_verify, header, ec.ECDSA(hashes.SHA256())) # throws exception if wrong
print(f"Public key {public_keys_hex[index]}")
print("Verified created sig with public key")
print("=================================")

View File

@@ -5,13 +5,20 @@ from hashlib import sha256
from pathlib import Path
import click
import ecdsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
Prehashed,
decode_dss_signature,
)
from trezorlib.firmware.legacy import LegacyV2Firmware
from trezorlib.firmware.models import LEGACY_V3_DEV
SECRET_KEYS = [
ecdsa.SigningKey.from_string(bytes.fromhex(sk), curve=ecdsa.SECP256k1)
ec.derive_private_key(
int.from_bytes(bytes.fromhex(sk), "big"), ec.SECP256K1()
)
for sk in (
"ca8de06e1e93d101136fa6fbc41432c52b6530299dfe32808030ee8e679702f1",
"dde47dd393f7d76f9b522bfa9760bc4543d2c3654491393774f54e066461fccb",
@@ -19,17 +26,25 @@ SECRET_KEYS = [
)
]
PUBLIC_KEYS: list[ecdsa.VerifyingKey] = [sk.get_verifying_key() for sk in SECRET_KEYS]
PUBLIC_KEYS = [sk.public_key() for sk in SECRET_KEYS]
# Should be these public keys
assert [pk.to_string("compressed") for pk in PUBLIC_KEYS] == LEGACY_V3_DEV.firmware_keys
assert [
pk.public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.CompressedPoint,
)
for pk in PUBLIC_KEYS
] == LEGACY_V3_DEV.firmware_keys
def signmessage(digest: bytes, key: ecdsa.SigningKey) -> bytes:
def signmessage(digest: bytes, key: ec.EllipticCurvePrivateKey) -> bytes:
"""Sign via SignMessage"""
btc_digest = b"\x18Bitcoin Signed Message:\n\x20" + digest
final_digest = sha256(sha256(btc_digest).digest()).digest()
return key.sign_digest_deterministic(final_digest, hashfunc=sha256)
der_sig = key.sign(final_digest, ec.ECDSA(Prehashed(hashes.SHA256())))
r, s = decode_dss_signature(der_sig)
return r.to_bytes(32, "big") + s.to_bytes(32, "big")
@click.command()

View File

@@ -39,7 +39,7 @@ dependencies = [
"requests~=2.32",
"termcolor",
"Pillow>=11",
"ecdsa>=0.16,<0.17",
"pyasn1",
"noiseprotocol>=0.3.1,<0.4",
"west>=1.4.0,<2",

View File

@@ -2,7 +2,9 @@ import os
from hashlib import sha256
from typing import List
from ecdsa import NIST256p, SigningKey
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, decode_dss_signature
from trezorlib import evolu
from trezorlib.debuglink import DebugSession as Session
@@ -19,7 +21,9 @@ TEST_host_static_public_key = curve25519.get_public_key(TEST_host_static_private
def get_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes:
private_key = get_delegated_identity_key(client)
signing_key = SigningKey.from_string(private_key, curve=NIST256p)
signing_key = ec.derive_private_key(
int.from_bytes(private_key, "big"), ec.SECP256R1()
)
ctx = sha256()
ctx.update(compact_size(len(header)))
@@ -27,7 +31,9 @@ def get_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes:
for arg in arguments:
ctx.update(compact_size(len(arg)))
ctx.update(arg)
return signing_key.sign_digest(ctx.digest())
der_sig = signing_key.sign(ctx.digest(), ec.ECDSA(Prehashed(hashes.SHA256())))
r, s = decode_dss_signature(der_sig)
return r.to_bytes(32, "big") + s.to_bytes(32, "big")
def get_invalid_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes:

View File

@@ -1,5 +1,6 @@
import pytest
from ecdsa import NIST256p, SigningKey, VerifyingKey
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from trezorlib import evolu
from trezorlib.debuglink import TrezorTestContext as Client
@@ -13,10 +14,14 @@ pytestmark = pytest.mark.models("core")
def signing_buffer(private_key: bytes, challenge: bytes, size: int) -> bytes:
public_key: VerifyingKey = SigningKey.from_string(private_key, curve=NIST256p).get_verifying_key() # type: ignore
sk = ec.derive_private_key(int.from_bytes(private_key, "big"), ec.SECP256R1())
public_key_bytes = sk.public_key().public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
)
components = [
b"EvoluSignRegistrationRequestV1:",
public_key.to_string("uncompressed"),
public_key_bytes,
challenge,
size.to_bytes(4, "big"),
]

View File

@@ -1,7 +1,9 @@
from dataclasses import dataclass
from hashlib import sha256
from ecdsa import NIST256p, SigningKey
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, decode_dss_signature
from cryptography.hazmat.primitives import hashes
from trezorlib import messages
from trezorlib.client import Session
@@ -37,9 +39,12 @@ class CoinPurchaseMemo:
address_resp: messages.Address | messages.EthereumAddress | None = None
payment_req_signer = SigningKey.from_string(
b"\x05\x62\x35\xb0\x47\x6f\x05\x7f\x27\x65\x21\x97\x24\xf7\xf1\x80\x7d\x58\x80\x2b\x55\x0e\xd5\xbf\x6f\x73\x05\x0a\xf5\x45\x63\x00",
curve=NIST256p,
payment_req_signer = ec.derive_private_key(
int.from_bytes(
b"\x05\x62\x35\xb0\x47\x6f\x05\x7f\x27\x65\x21\x97\x24\xf7\xf1\x80\x7d\x58\x80\x2b\x55\x0e\xd5\xbf\x6f\x73\x05\x0a\xf5\x45\x63\x00",
"big",
),
ec.SECP256R1(),
)
@@ -135,6 +140,13 @@ def make_payment_request(
else None
)
der_sig = payment_req_signer.sign(
h_pr.digest(),
ec.ECDSA(Prehashed(hashes.SHA256())),
)
r, s = decode_dss_signature(der_sig)
raw_sig = r.to_bytes(32, "big") + s.to_bytes(32, "big")
return messages.PaymentRequest(
recipient_name=recipient_name,
amount=(
@@ -142,5 +154,5 @@ def make_payment_request(
),
memos=msg_memos,
nonce=nonce,
signature=payment_req_signer.sign_digest_deterministic(h_pr.digest()),
signature=raw_sig,
)