2023-12-12 10:19:53 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
"""
|
2025-03-17 18:29:11 +03:00
|
|
|
test.modtest_d.ecc: elliptic curve unit test for the MMGen suite
|
2023-12-12 10:19:53 +00:00
|
|
|
"""
|
|
|
|
|
|
2025-06-29 14:04:46 +00:00
|
|
|
from mmgen.proto.secp256k1.secp256k1 import (
|
|
|
|
|
pubkey_gen,
|
|
|
|
|
pubkey_tweak_add,
|
|
|
|
|
pubkey_check,
|
|
|
|
|
sign_msghash,
|
|
|
|
|
pubkey_recover,
|
|
|
|
|
verify_sig)
|
2023-12-12 10:19:53 +00:00
|
|
|
|
2024-09-20 09:36:06 +00:00
|
|
|
from ..include.common import vmsg
|
2025-07-19 10:21:06 +00:00
|
|
|
from ..include.ecc import pubkey_tweak_add_pyecdsa, sign_msghash_pyecdsa, verify_sig_pyecdsa
|
2023-12-12 10:19:53 +00:00
|
|
|
from mmgen.protocol import CoinProtocol
|
|
|
|
|
|
|
|
|
|
secp256k1_group_order = CoinProtocol.Secp256k1.secp256k1_group_order
|
|
|
|
|
|
|
|
|
|
class unit_tests:
|
|
|
|
|
|
2025-06-29 14:04:46 +00:00
|
|
|
def sig_ops(self, name, ut):
|
|
|
|
|
vmsg(' Creating and verifying signatures and recovering public keys:')
|
|
|
|
|
for mh, pk in (
|
|
|
|
|
(1, 1),
|
|
|
|
|
(123456789 * 2**222, 12345),
|
|
|
|
|
(123456789 * 2**222, 2**256 - 2**129 - 987654321),
|
|
|
|
|
(9999999, 2**233),
|
|
|
|
|
(12345, 1234 * 2**240),
|
|
|
|
|
):
|
|
|
|
|
msghash = mh.to_bytes(32, 'big')
|
|
|
|
|
privkey = pk.to_bytes(32, 'big')
|
|
|
|
|
vmsg(f'\n msg: {msghash.hex()}')
|
|
|
|
|
vmsg(f' privkey: {privkey.hex()}')
|
|
|
|
|
pubkey = pubkey_gen(privkey, 1)
|
|
|
|
|
sig, recid = sign_msghash(msghash, privkey)
|
2025-07-19 10:21:06 +00:00
|
|
|
sig_chk = sign_msghash_pyecdsa(msghash, privkey)
|
2025-06-29 14:04:46 +00:00
|
|
|
if sig != sig_chk:
|
|
|
|
|
import time
|
|
|
|
|
from mmgen.util import ymsg
|
2025-07-19 10:21:06 +00:00
|
|
|
ymsg(f'Warning: signature ({sig.hex()}) doesn’t match reference value ({sig_chk.hex()})!')
|
2025-06-29 14:04:46 +00:00
|
|
|
time.sleep(1)
|
|
|
|
|
vmsg(f' recid: {recid}')
|
|
|
|
|
assert recid in (0, 1)
|
|
|
|
|
assert verify_sig(sig, msghash, pubkey) == 1, 'signature verification failed (secp256k1)'
|
2025-07-19 10:21:06 +00:00
|
|
|
assert verify_sig_pyecdsa(sig, msghash, pubkey) == 1, 'signature verification failed (ecdsa)'
|
2025-06-29 14:04:46 +00:00
|
|
|
pubkey_rec = pubkey_recover(msghash, sig, recid, True)
|
|
|
|
|
assert pubkey == pubkey_rec, f'{pubkey.hex()} != {pubkey_rec.hex()}'
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def sig_errors(self, name, ut):
|
|
|
|
|
vmsg(' Testing error handling for signature ops')
|
|
|
|
|
|
|
|
|
|
msghash = bytes.fromhex('deadbeef' * 8)
|
|
|
|
|
privkey = bytes.fromhex('beadcafe' * 8)
|
|
|
|
|
pubkey = pubkey_gen(privkey, 1)
|
|
|
|
|
sig, recid = sign_msghash(msghash, privkey)
|
|
|
|
|
|
|
|
|
|
def sign1(): sign_msghash(1, bytes(32))
|
|
|
|
|
def sign2(): sign_msghash(b'\xff' + bytes(32), bytes(32))
|
|
|
|
|
def sign3(): sign_msghash(bytes(32), bytes(32))
|
|
|
|
|
|
|
|
|
|
def verify1(): verify_sig(1, 2, 3)
|
|
|
|
|
def verify2(): verify_sig(bytes(64), bytes(32), bytes(33))
|
|
|
|
|
def verify3(): assert verify_sig(bytes([99]) + sig[1:], msghash, pubkey) == 1, 'bad signature'
|
|
|
|
|
def verify4(): assert verify_sig(sig, msghash, pubkey) == 0, 'good signature'
|
|
|
|
|
def verify5(): verify_sig(sig, msghash + bytes([201]), pubkey)
|
|
|
|
|
def verify6(): verify_sig(sig + bytes([66]), msghash, pubkey)
|
|
|
|
|
|
|
|
|
|
def recov1(): pubkey_recover(1, 2, 3)
|
|
|
|
|
def recov2(): pubkey_recover(msghash, sig, 8, True)
|
|
|
|
|
def recov3(): pubkey_recover(msghash, sig, -3, 1)
|
|
|
|
|
def recov4(): pubkey_recover(msghash, bytes([77]) + sig, recid, 1)
|
|
|
|
|
def recov5(): pubkey_recover(msghash + bytes([33]), sig, recid, 1)
|
|
|
|
|
def recov6():
|
|
|
|
|
assert pubkey_recover(msghash[:-1] + bytes([44]), sig, recid, 1) == pubkey, 'bad pubkey'
|
|
|
|
|
def recov7():
|
|
|
|
|
assert pubkey_recover(msghash, sig, recid, True) != pubkey, 'good pubkey'
|
|
|
|
|
|
|
|
|
|
bad_data = (
|
|
|
|
|
('sign: bad args', 'ValueError', 'Unable to parse', sign1),
|
|
|
|
|
('sign: bad msghash len', 'RuntimeError', 'hash length', sign2),
|
|
|
|
|
('sign: privkey=0', 'ValueError', 'Private key not in allowable', sign3),
|
|
|
|
|
('verify: bad args', 'ValueError', 'Unable to parse', verify1),
|
|
|
|
|
('verify: bad pubkey', 'RuntimeError', 'Failed to parse', verify2),
|
|
|
|
|
('verify: bad sig', 'AssertionError', 'bad signature', verify3),
|
|
|
|
|
('verify: good sig', 'AssertionError', 'good signature', verify4),
|
|
|
|
|
('verify: bad msghash len', 'RuntimeError', 'message hash length', verify5),
|
|
|
|
|
('verify: bad sig len', 'RuntimeError', 'Invalid signature length', verify6),
|
|
|
|
|
('recover: bad args', 'ValueError', 'Unable to parse', recov1),
|
|
|
|
|
('recover: bad recid', 'RuntimeError', 'Invalid recovery ID', recov2),
|
|
|
|
|
('recover: bad recid', 'RuntimeError', 'Invalid recovery ID', recov3),
|
|
|
|
|
('recover: bad sig len', 'RuntimeError', 'Invalid signature length', recov4),
|
|
|
|
|
('recover: bad msghash len', 'RuntimeError', 'message hash length', recov5),
|
|
|
|
|
('recover: bad pubkey', 'AssertionError', 'bad pubkey', recov6),
|
|
|
|
|
('recover: bad pubkey', 'AssertionError', 'good pubkey', recov7),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
ut.process_bad_data(bad_data, pfx='')
|
|
|
|
|
return True
|
|
|
|
|
|
2024-10-18 10:32:14 +00:00
|
|
|
def pubkey_ops(self, name, ut):
|
2024-09-20 09:36:06 +00:00
|
|
|
vmsg(' Generating pubkey, adding scalar 123456789 to pubkey:')
|
2024-10-18 10:32:14 +00:00
|
|
|
pk_addend_bytes = int.to_bytes(123456789, length=32, byteorder='big')
|
2023-12-12 10:19:53 +00:00
|
|
|
|
|
|
|
|
for privkey in (
|
|
|
|
|
'beadcafe' * 8,
|
|
|
|
|
f'{1:064x}',
|
|
|
|
|
f'{secp256k1_group_order-1:x}',
|
|
|
|
|
):
|
|
|
|
|
vmsg(f' privkey = 0x{privkey}')
|
2024-10-18 10:32:14 +00:00
|
|
|
for compressed, length in ((False, 65), (True, 33)):
|
2023-12-12 10:19:53 +00:00
|
|
|
vmsg(f' {compressed=}')
|
2024-10-18 10:32:14 +00:00
|
|
|
pubkey_bytes = pubkey_gen(bytes.fromhex(privkey), int(compressed))
|
2023-12-12 10:19:53 +00:00
|
|
|
pubkey_check(pubkey_bytes)
|
|
|
|
|
vmsg(f' pubkey: {pubkey_bytes.hex()}')
|
|
|
|
|
|
|
|
|
|
res1 = pubkey_tweak_add(pubkey_bytes, pk_addend_bytes)
|
|
|
|
|
pubkey_check(res1)
|
|
|
|
|
vmsg(f' tweaked: {res1.hex()}')
|
|
|
|
|
|
2024-10-18 10:32:14 +00:00
|
|
|
res2 = pubkey_tweak_add_pyecdsa(pubkey_bytes, pk_addend_bytes)
|
2023-12-12 10:19:53 +00:00
|
|
|
pubkey_check(res2)
|
|
|
|
|
|
|
|
|
|
assert len(res1) == length
|
|
|
|
|
assert res1 == res2
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
2024-10-18 10:32:14 +00:00
|
|
|
def pubkey_errors(self, name, ut):
|
2025-06-29 14:04:46 +00:00
|
|
|
vmsg(' Testing error handling for public key ops')
|
2023-12-12 10:19:53 +00:00
|
|
|
|
2024-10-18 10:32:14 +00:00
|
|
|
def gen1(): pubkey_gen(bytes(32), 1)
|
|
|
|
|
def gen2(): pubkey_gen(secp256k1_group_order.to_bytes(length=32, byteorder='big'), 1)
|
|
|
|
|
def gen3(): pubkey_gen((secp256k1_group_order+1).to_bytes(length=32, byteorder='big'), 1)
|
|
|
|
|
def gen4(): pubkey_gen(bytes.fromhex('ff'*32), 1)
|
|
|
|
|
def gen5(): pubkey_gen(bytes.fromhex('ab'*31), 1)
|
|
|
|
|
def gen6(): pubkey_gen(bytes.fromhex('ab'*33), 1)
|
2023-12-12 10:19:53 +00:00
|
|
|
|
|
|
|
|
pubkey_bytes = pubkey_gen(bytes.fromhex('beadcafe'*8), 1)
|
2024-10-18 10:32:14 +00:00
|
|
|
def tweak1(): pubkey_tweak_add(pubkey_bytes, bytes(32))
|
|
|
|
|
def tweak2(): pubkey_tweak_add(bytes.fromhex('03'*64), int.to_bytes(1, length=32, byteorder='big'))
|
2023-12-12 10:19:53 +00:00
|
|
|
|
|
|
|
|
def check1(): pubkey_check(bytes.fromhex('04'*33))
|
|
|
|
|
def check2(): pubkey_check(bytes.fromhex('03'*65))
|
|
|
|
|
def check3(): pubkey_check(bytes.fromhex('02'*65))
|
|
|
|
|
def check4(): pubkey_check(bytes.fromhex('03'*64))
|
|
|
|
|
def check5(): pubkey_check(b'')
|
|
|
|
|
|
|
|
|
|
bad_data = (
|
|
|
|
|
('privkey == 0', 'ValueError', 'Private key not in allowable range', gen1),
|
|
|
|
|
('privkey == group order', 'ValueError', 'Private key not in allowable range', gen2),
|
|
|
|
|
('privkey == group order+1', 'ValueError', 'Private key not in allowable range', gen3),
|
|
|
|
|
('privkey == 2^256-1', 'ValueError', 'Private key not in allowable range', gen4),
|
|
|
|
|
('len(privkey) == 31', 'ValueError', 'Private key length not 32 bytes', gen5),
|
|
|
|
|
('len(privkey) == 33', 'ValueError', 'Private key length not 32 bytes', gen6),
|
|
|
|
|
|
|
|
|
|
('tweak == 0', 'ValueError', 'Tweak not in allowable range', tweak1),
|
|
|
|
|
('pubkey length == 64', 'ValueError', 'Serialized public key length not', tweak2),
|
|
|
|
|
|
|
|
|
|
('invalid pubkey (33 bytes)', 'ValueError', 'Invalid first byte', check1),
|
|
|
|
|
('invalid pubkey (65 bytes)', 'ValueError', 'Invalid first byte', check2),
|
|
|
|
|
('invalid pubkey (65 bytes)', 'ValueError', 'Invalid first byte', check3),
|
|
|
|
|
('pubkey length == 64', 'ValueError', 'Serialized public key length not', check4),
|
|
|
|
|
('pubkey length == 0', 'ValueError', 'Serialized public key length not', check5),
|
|
|
|
|
)
|
|
|
|
|
|
2024-10-18 10:32:14 +00:00
|
|
|
ut.process_bad_data(bad_data, pfx='')
|
2023-12-12 10:19:53 +00:00
|
|
|
return True
|