Merge pull request #9070 from SomberNight/202405_ecc_ecdsa_low_s
ecc: ecdsa_verify to enforce low-S rule
This commit is contained in:
@@ -342,7 +342,13 @@ class ECPubkey(object):
|
|||||||
# check message
|
# check message
|
||||||
return self.ecdsa_verify(sig65[1:], msg32)
|
return self.ecdsa_verify(sig65[1:], msg32)
|
||||||
|
|
||||||
def ecdsa_verify(self, sig64: bytes, msg32: bytes) -> bool:
|
def ecdsa_verify(
|
||||||
|
self,
|
||||||
|
sig64: bytes,
|
||||||
|
msg32: bytes,
|
||||||
|
*,
|
||||||
|
enforce_low_s: bool = True, # policy/standardness rule
|
||||||
|
) -> bool:
|
||||||
assert_bytes(sig64)
|
assert_bytes(sig64)
|
||||||
if len(sig64) != 64:
|
if len(sig64) != 64:
|
||||||
return False
|
return False
|
||||||
@@ -353,7 +359,8 @@ class ECPubkey(object):
|
|||||||
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64)
|
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64)
|
||||||
if 1 != ret:
|
if 1 != ret:
|
||||||
return False
|
return False
|
||||||
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
|
if not enforce_low_s:
|
||||||
|
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
|
||||||
|
|
||||||
pubkey = self._to_libsecp256k1_pubkey_ptr()
|
pubkey = self._to_libsecp256k1_pubkey_ptr()
|
||||||
if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg32, pubkey):
|
if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg32, pubkey):
|
||||||
@@ -439,7 +446,8 @@ def verify_usermessage_with_address(address: str, sig65: bytes, message: bytes,
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
# check message
|
# check message
|
||||||
return public_key.ecdsa_verify(sig65[1:], h)
|
# note: `$ bitcoin-cli verifymessage` does NOT enforce the low-S rule for ecdsa sigs
|
||||||
|
return public_key.ecdsa_verify(sig65[1:], h, enforce_low_s=False)
|
||||||
|
|
||||||
|
|
||||||
def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
|
def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
|
||||||
@@ -566,6 +574,8 @@ class ECPrivkey(ECPubkey):
|
|||||||
return sig64
|
return sig64
|
||||||
|
|
||||||
def ecdsa_sign_recoverable(self, msg32: bytes, *, is_compressed: bool) -> bytes:
|
def ecdsa_sign_recoverable(self, msg32: bytes, *, is_compressed: bool) -> bytes:
|
||||||
|
assert len(msg32) == 32, len(msg32)
|
||||||
|
|
||||||
def bruteforce_recid(sig64: bytes):
|
def bruteforce_recid(sig64: bytes):
|
||||||
for recid in range(4):
|
for recid in range(4):
|
||||||
sig65 = construct_ecdsa_sig65(sig64, recid, is_compressed=is_compressed)
|
sig65 = construct_ecdsa_sig65(sig64, recid, is_compressed=is_compressed)
|
||||||
|
|||||||
@@ -246,6 +246,15 @@ class Test_bitcoin(ElectrumTestCase):
|
|||||||
self.assertFalse(ecc.verify_usermessage_with_address(addr1, b'wrong', msg1))
|
self.assertFalse(ecc.verify_usermessage_with_address(addr1, b'wrong', msg1))
|
||||||
self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig2, msg1))
|
self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig2, msg1))
|
||||||
|
|
||||||
|
def test_signmessage_low_s(self):
|
||||||
|
"""`$ bitcoin-cli verifymessage` does NOT enforce the low-S rule for ecdsa sigs. This tests we do the same."""
|
||||||
|
addr = "15hETetDmcXm1mM4sEf7U2KXC9hDHFMSzz"
|
||||||
|
sig_low_s = b'Hzsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5aKfmX4Db7ExLGa7FGn0m6Mf43KsbEOWpvUUUBTM3Uusw='
|
||||||
|
sig_high_s = b'IDsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5a1gZoH8kE7O05lE65YLZFzLx3sh/rDzXMbo1dQAJhhnU='
|
||||||
|
msg = b'Chancellor on brink of second bailout for banks'
|
||||||
|
self.assertTrue(ecc.verify_usermessage_with_address(address=addr, sig65=base64.b64decode(sig_low_s), message=msg))
|
||||||
|
self.assertTrue(ecc.verify_usermessage_with_address(address=addr, sig65=base64.b64decode(sig_high_s), message=msg))
|
||||||
|
|
||||||
def test_signmessage_segwit_witness_v0_address(self):
|
def test_signmessage_segwit_witness_v0_address(self):
|
||||||
msg = b'Electrum'
|
msg = b'Electrum'
|
||||||
# p2wpkh-p2sh
|
# p2wpkh-p2sh
|
||||||
|
|||||||
@@ -116,3 +116,27 @@ class TestSchnorr(ElectrumTestCase):
|
|||||||
for tag, msg in data:
|
for tag, msg in data:
|
||||||
self.assertEqual(bip340_tagged_hash__from_libsecp(tag, msg),
|
self.assertEqual(bip340_tagged_hash__from_libsecp(tag, msg),
|
||||||
ecc.bip340_tagged_hash(tag, msg))
|
ecc.bip340_tagged_hash(tag, msg))
|
||||||
|
|
||||||
|
|
||||||
|
class TestEcdsa(ElectrumTestCase):
|
||||||
|
|
||||||
|
def test_verify_enforces_low_s(self):
|
||||||
|
# privkey = ecc.ECPrivkey(bytes.fromhex("d473e2ec218dca8e3508798f01cdfde0135fc79d95526b12e3537fe57e479ac1"))
|
||||||
|
# r, low_s = privkey.ecdsa_sign(msg32, sigencode=lambda x, y: (x,y))
|
||||||
|
# pubkey = ecc.ECPubkey(privkey.get_public_key_bytes())
|
||||||
|
pubkey = ecc.ECPubkey(bytes.fromhex("03befe4f7c92eaed73fb8eddac28c6191c87c6a3546bf8dc09643e1e10bc6f5ab0"))
|
||||||
|
msg32 = sha256("hello there")
|
||||||
|
r = 29658118546717807188148256874354333643324863178937517286987684851194094232509
|
||||||
|
# low-S
|
||||||
|
low_s = 9695211969150896589566136599751503273246834163278279637071703776634378000266
|
||||||
|
sig64_low_s = (
|
||||||
|
int.to_bytes(r, length=32, byteorder="big") +
|
||||||
|
int.to_bytes(low_s, length=32, byteorder="big"))
|
||||||
|
self.assertTrue(pubkey.ecdsa_verify(sig64_low_s, msg32))
|
||||||
|
# high-S
|
||||||
|
high_s = ecc.CURVE_ORDER - low_s
|
||||||
|
sig64_high_s = (
|
||||||
|
int.to_bytes(r, length=32, byteorder="big") +
|
||||||
|
int.to_bytes(high_s, length=32, byteorder="big"))
|
||||||
|
self.assertFalse(pubkey.ecdsa_verify(sig64_high_s, msg32))
|
||||||
|
self.assertTrue(pubkey.ecdsa_verify(sig64_high_s, msg32, enforce_low_s=False))
|
||||||
|
|||||||
Reference in New Issue
Block a user