tecc.py: properly handle point at infinity - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 59c1d03f018026ac301c4e74facfc64da8ae4708 DIR parent 1a8e8bc047bc6cd124b338e9c69c3bbb561db54a HTML Author: SomberNight <somber.night@protonmail.com> Date: Sat, 16 Jun 2018 06:34:03 +0200 ecc.py: properly handle point at infinity Diffstat: M lib/ecc.py | 33 +++++++++++++++++++++++++------ M lib/tests/test_bitcoin.py | 26 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) --- DIR diff --git a/lib/ecc.py b/lib/ecc.py t@@ -49,6 +49,10 @@ def generator(): return ECPubkey.from_point(generator_secp256k1) +def point_at_infinity(): + return ECPubkey(None) + + def sig_string_from_der_sig(der_sig, order=CURVE_ORDER): r, s = ecdsa.util.sigdecode_der(der_sig, order) return ecdsa.util.sigencode_string(r, s, order) t@@ -83,6 +87,8 @@ def point_to_ser(P, compressed=True) -> bytes: x, y = P else: x, y = P.x(), P.y() + if x is None or y is None: # infinity + return None if compressed: return bfh(('%02x' % (2+(y&1))) + ('%064x' % x)) return bfh('04'+('%064x' % x)+('%064x' % y)) t@@ -115,7 +121,10 @@ def ser_to_point(ser: bytes) -> (int, int): def _ser_to_python_ecdsa_point(ser: bytes) -> ecdsa.ellipticcurve.Point: x, y = ser_to_point(ser) - return Point(curve_secp256k1, x, y, CURVE_ORDER) + try: + return Point(curve_secp256k1, x, y, CURVE_ORDER) + except: + raise InvalidECPointException() class InvalidECPointException(Exception): t@@ -166,12 +175,19 @@ class _MySigningKey(ecdsa.SigningKey): return r, s +class _PubkeyForPointAtInfinity: + point = ecdsa.ellipticcurve.INFINITY + + class ECPubkey(object): def __init__(self, b: bytes): - assert_bytes(b) - point = _ser_to_python_ecdsa_point(b) - self._pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, point) + if b is not None: + assert_bytes(b) + point = _ser_to_python_ecdsa_point(b) + self._pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, point) + else: + self._pubkey = _PubkeyForPointAtInfinity() @classmethod def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes): t@@ -205,6 +221,7 @@ class ECPubkey(object): return ECPubkey(_bytes) def get_public_key_bytes(self, compressed=True): + if self.is_at_infinity(): raise Exception('point is at infinity') return point_to_ser(self.point(), compressed) def get_public_key_hex(self, compressed=True): t@@ -229,7 +246,8 @@ class ECPubkey(object): return self.from_point(ecdsa_point) def __eq__(self, other): - return self.get_public_key_bytes() == other.get_public_key_bytes() + return self._pubkey.point.x() == other._pubkey.point.x() \ + and self._pubkey.point.y() == other._pubkey.point.y() def __ne__(self, other): return not (self == other) t@@ -275,6 +293,9 @@ class ECPubkey(object): def order(cls): return CURVE_ORDER + def is_at_infinity(self): + return self == point_at_infinity() + def msg_magic(message: bytes) -> bytes: from .bitcoin import var_int t@@ -318,7 +339,7 @@ class ECPrivkey(ECPubkey): raise Exception('unexpected size for secret. should be 32 bytes, not {}'.format(len(privkey_bytes))) secret = string_to_number(privkey_bytes) if not is_secret_within_curve_range(secret): - raise Exception('Invalid secret scalar (not within curve order)') + raise InvalidECPointException('Invalid secret scalar (not within curve order)') self.secret_scalar = secret point = generator_secp256k1 * secret DIR diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py t@@ -126,6 +126,32 @@ class Test_bitcoin(SequentialTestCase): eck.verify_message_for_address(signature, message) @needs_test_with_all_ecc_implementations + def test_ecc_sanity(self): + G = ecc.generator() + n = G.order() + self.assertEqual(ecc.CURVE_ORDER, n) + inf = n * G + self.assertEqual(ecc.point_at_infinity(), inf) + self.assertTrue(inf.is_at_infinity()) + self.assertFalse(G.is_at_infinity()) + self.assertEqual(11 * G, 7 * G + 4 * G) + self.assertEqual((n + 2) * G, 2 * G) + self.assertEqual((n - 2) * G, -2 * G) + A = (n - 2) * G + B = (n - 1) * G + C = n * G + D = (n + 1) * G + self.assertFalse(A.is_at_infinity()) + self.assertFalse(B.is_at_infinity()) + self.assertTrue(C.is_at_infinity()) + self.assertTrue((C * 5).is_at_infinity()) + self.assertFalse(D.is_at_infinity()) + self.assertEqual(inf, C) + self.assertEqual(inf, A + 2 * G) + self.assertEqual(inf, D + (-1) * G) + self.assertNotEqual(A, B) + + @needs_test_with_all_ecc_implementations def test_msg_signing(self): msg1 = b'Chancellor on brink of second bailout for banks' msg2 = b'Electrum'