From d41e778ca12408de476f4c75d79d6480728fbb8d Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Sun, 2 Feb 2020 16:14:30 +0000 Subject: BIP 340: Update reference code and test vectors as follows: - use evenness as tiebreaker - using different tags for nonce- and challenge hashing - add pubkey to nonce function. --- bip-0340/reference.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index f2a944f..9b5592e 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -51,7 +51,7 @@ def bytes_from_int(x): def bytes_from_point(P): return bytes_from_int(x(P)) -def point_from_bytes(b): +def lift_x_square_y(b): x = int_from_bytes(b) if x >= p: return None @@ -61,6 +61,13 @@ def point_from_bytes(b): return None return [x, y] +def lift_x_even_y(b): + P = lift_x_square_y(b) + if P is None: + return None + else: + return [x(P), y(P) if y(P) % 2 == 0 else p - y(P)] + def int_from_bytes(b): return int.from_bytes(b, byteorder="big") @@ -73,6 +80,9 @@ def is_square(x): def has_square_y(P): return not is_infinity(P) and is_square(y(P)) +def has_even_y(P): + return y(P) % 2 == 0 + def pubkey_gen(seckey): x = int_from_bytes(seckey) if not (1 <= x <= n - 1): @@ -87,13 +97,13 @@ def schnorr_sign(msg, seckey0): if not (1 <= seckey0 <= n - 1): raise ValueError('The secret key must be an integer in the range 1..n-1.') P = point_mul(G, seckey0) - seckey = seckey0 if has_square_y(P) else n - seckey0 - k0 = int_from_bytes(tagged_hash("BIPSchnorrDerive", bytes_from_int(seckey) + msg)) % n + seckey = seckey0 if has_even_y(P) else n - seckey0 + k0 = int_from_bytes(tagged_hash("BIP340/nonce", bytes_from_int(seckey) + bytes_from_point(P) + msg)) % n if k0 == 0: raise RuntimeError('Failure. This happens only with negligible probability.') R = point_mul(G, k0) k = n - k0 if not has_square_y(R) else k0 - e = int_from_bytes(tagged_hash("BIPSchnorr", bytes_from_point(R) + bytes_from_point(P) + msg)) % n + e = int_from_bytes(tagged_hash("BIP340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n return bytes_from_point(R) + bytes_from_int((k + e * seckey) % n) def schnorr_verify(msg, pubkey, sig): @@ -103,14 +113,14 @@ def schnorr_verify(msg, pubkey, sig): raise ValueError('The public key must be a 32-byte array.') if len(sig) != 64: raise ValueError('The signature must be a 64-byte array.') - P = point_from_bytes(pubkey) + P = lift_x_even_y(pubkey) if (P is None): return False r = int_from_bytes(sig[0:32]) s = int_from_bytes(sig[32:64]) if (r >= p or s >= n): return False - e = int_from_bytes(tagged_hash("BIPSchnorr", sig[0:32] + pubkey + msg)) % n + e = int_from_bytes(tagged_hash("BIP340/challenge", sig[0:32] + pubkey + msg)) % n R = point_add(point_mul(G, s), point_mul(P, n - e)) if R is None or not has_square_y(R) or x(R) != r: return False -- cgit v1.2.3 From b6b5f58e6e919a485604ce7037f650e1ae54969f Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 3 Feb 2020 21:56:03 +0000 Subject: BIP 340: Use synthetic nonces in reference code and test vectors --- bip-0340/reference.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index 9b5592e..1ada7f1 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -51,6 +51,9 @@ def bytes_from_int(x): def bytes_from_point(P): return bytes_from_int(x(P)) +def xor_bytes(b0, b1): + return bytes(x ^ y for (x, y) in zip(b0, b1)) + def lift_x_square_y(b): x = int_from_bytes(b) if x >= p: @@ -90,15 +93,18 @@ def pubkey_gen(seckey): P = point_mul(G, x) return bytes_from_point(P) -def schnorr_sign(msg, seckey0): +def schnorr_sign(msg, seckey0, aux_rand): if len(msg) != 32: raise ValueError('The message must be a 32-byte array.') seckey0 = int_from_bytes(seckey0) if not (1 <= seckey0 <= n - 1): raise ValueError('The secret key must be an integer in the range 1..n-1.') + if len(aux_rand) != 32: + raise ValueError('aux_rand must be 32 bytes instead of %i.' % len(aux_rand)) P = point_mul(G, seckey0) seckey = seckey0 if has_even_y(P) else n - seckey0 - k0 = int_from_bytes(tagged_hash("BIP340/nonce", bytes_from_int(seckey) + bytes_from_point(P) + msg)) % n + t = xor_bytes(bytes_from_int(seckey), tagged_hash("BIP340/aux", aux_rand)) + k0 = int_from_bytes(tagged_hash("BIP340/nonce", t + bytes_from_point(P) + msg)) % n if k0 == 0: raise RuntimeError('Failure. This happens only with negligible probability.') R = point_mul(G, k0) @@ -137,7 +143,7 @@ def test_vectors(): reader = csv.reader(csvfile) reader.__next__() for row in reader: - (index, seckey, pubkey, msg, sig, result, comment) = row + (index, seckey, pubkey, aux_rand, msg, sig, result, comment) = row pubkey = bytes.fromhex(pubkey) msg = bytes.fromhex(msg) sig = bytes.fromhex(sig) @@ -150,7 +156,8 @@ def test_vectors(): print(' * Failed key generation.') print(' Expected key:', pubkey.hex().upper()) print(' Actual key:', pubkey_actual.hex().upper()) - sig_actual = schnorr_sign(msg, seckey) + aux_rand = bytes.fromhex(aux_rand) + sig_actual = schnorr_sign(msg, seckey, aux_rand) if sig == sig_actual: print(' * Passed signing test.') else: -- cgit v1.2.3 From 9bfa53e9fb4af9f17d63806fe0710f18203c94c9 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Mon, 24 Feb 2020 17:01:19 +0000 Subject: BIP 340: Verify sig before returning it --- bip-0340/reference.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index 1ada7f1..79f9578 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -110,7 +110,10 @@ def schnorr_sign(msg, seckey0, aux_rand): R = point_mul(G, k0) k = n - k0 if not has_square_y(R) else k0 e = int_from_bytes(tagged_hash("BIP340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n - return bytes_from_point(R) + bytes_from_int((k + e * seckey) % n) + sig = bytes_from_point(R) + bytes_from_int((k + e * seckey) % n) + if not schnorr_verify(msg, bytes_from_point(P), sig): + raise RuntimeError('The signature does not pass verification.') + return sig def schnorr_verify(msg, pubkey, sig): if len(msg) != 32: -- cgit v1.2.3 From a6301c5af08d39121c1e1e7dc9ad1b9e9fe45942 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Wed, 4 Mar 2020 21:21:36 +0100 Subject: Optionally print intermediate values in reference code and make reference code and pseudocode more consistent with each other --- bip-0340/reference.py | 68 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 15 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index 79f9578..d6106fd 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -1,6 +1,15 @@ import hashlib import binascii +# Set DEBUG to True to get a detailed debug output including +# intermediate values during key generation, signing, and +# verification. This is implemented via calls to the +# debug_print_vars() function. +# +# If you want to print values on an individual basis, use +# the pretty() function, e.g., print(pretty(foo)). +DEBUG = False + p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 @@ -62,7 +71,7 @@ def lift_x_square_y(b): y = pow(y_sq, (p + 1) // 4, p) if pow(y, 2, p) != y_sq: return None - return [x, y] + return (x, y) def lift_x_even_y(b): P = lift_x_square_y(b) @@ -87,32 +96,37 @@ def has_even_y(P): return y(P) % 2 == 0 def pubkey_gen(seckey): - x = int_from_bytes(seckey) - if not (1 <= x <= n - 1): + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= n - 1): + debug_print_vars() raise ValueError('The secret key must be an integer in the range 1..n-1.') - P = point_mul(G, x) + P = point_mul(G, d0) return bytes_from_point(P) -def schnorr_sign(msg, seckey0, aux_rand): +def schnorr_sign(msg, seckey, aux_rand): if len(msg) != 32: + debug_print_vars() raise ValueError('The message must be a 32-byte array.') - seckey0 = int_from_bytes(seckey0) - if not (1 <= seckey0 <= n - 1): + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= n - 1): raise ValueError('The secret key must be an integer in the range 1..n-1.') if len(aux_rand) != 32: raise ValueError('aux_rand must be 32 bytes instead of %i.' % len(aux_rand)) - P = point_mul(G, seckey0) - seckey = seckey0 if has_even_y(P) else n - seckey0 - t = xor_bytes(bytes_from_int(seckey), tagged_hash("BIP340/aux", aux_rand)) + P = point_mul(G, d0) + d = d0 if has_even_y(P) else n - d0 + t = xor_bytes(bytes_from_int(d), tagged_hash("BIP340/aux", aux_rand)) k0 = int_from_bytes(tagged_hash("BIP340/nonce", t + bytes_from_point(P) + msg)) % n if k0 == 0: + debug_print_vars() raise RuntimeError('Failure. This happens only with negligible probability.') R = point_mul(G, k0) k = n - k0 if not has_square_y(R) else k0 e = int_from_bytes(tagged_hash("BIP340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n - sig = bytes_from_point(R) + bytes_from_int((k + e * seckey) % n) + sig = bytes_from_point(R) + bytes_from_int((k + e * d) % n) if not schnorr_verify(msg, bytes_from_point(P), sig): + debug_print_vars() raise RuntimeError('The signature does not pass verification.') + debug_print_vars() return sig def schnorr_verify(msg, pubkey, sig): @@ -123,26 +137,29 @@ def schnorr_verify(msg, pubkey, sig): if len(sig) != 64: raise ValueError('The signature must be a 64-byte array.') P = lift_x_even_y(pubkey) - if (P is None): - return False r = int_from_bytes(sig[0:32]) s = int_from_bytes(sig[32:64]) - if (r >= p or s >= n): + if (P is None) or (r >= p) or (s >= n): + debug_print_vars() return False e = int_from_bytes(tagged_hash("BIP340/challenge", sig[0:32] + pubkey + msg)) % n R = point_add(point_mul(G, s), point_mul(P, n - e)) if R is None or not has_square_y(R) or x(R) != r: + debug_print_vars() return False + debug_print_vars() return True # # The following code is only used to verify the test vectors. # import csv +import os +import sys def test_vectors(): all_passed = True - with open('test-vectors.csv', newline='') as csvfile: + with open(os.path.join(sys.path[0], 'test-vectors.csv'), newline='') as csvfile: reader = csv.reader(csvfile) reader.__next__() for row in reader: @@ -185,5 +202,26 @@ def test_vectors(): print('Some test vectors failed.') return all_passed +# +# The following code is only used for debugging +# +import inspect + +def pretty(v): + if isinstance(v, bytes): + return '0x' + v.hex() + if isinstance(v, int): + return pretty(bytes_from_int(v)) + if isinstance(v, tuple): + return tuple(map(pretty, v)) + return v + +def debug_print_vars(): + if DEBUG: + frame = inspect.currentframe().f_back + print(' Variables in function ', frame.f_code.co_name, ' at line ', frame.f_lineno, ':', sep='') + for var_name, var_val in frame.f_locals.items(): + print(' ' + var_name.rjust(11, ' '), '==', pretty(var_val)) + if __name__ == '__main__': test_vectors() -- cgit v1.2.3 From 8c5be9197540e1673187ff099b5fe40ce09c9216 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Thu, 12 Mar 2020 21:13:09 +0100 Subject: Make code and output a little bit more readable --- bip-0340/reference.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index d6106fd..346b639 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -33,13 +33,13 @@ def y(P): return P[1] def point_add(P1, P2): - if (P1 is None): + if P1 is None: return P2 - if (P2 is None): + if P2 is None: return P1 - if (x(P1) == x(P2) and y(P1) != y(P2)): + if (x(P1) == x(P2)) and (y(P1) != y(P2)): return None - if (P1 == P2): + if P1 == P2: lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p else: lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p @@ -49,7 +49,7 @@ def point_add(P1, P2): def point_mul(P, n): R = None for i in range(256): - if ((n >> i) & 1): + if (n >> i) & 1: R = point_add(R, P) P = point_add(P, P) return R @@ -90,7 +90,7 @@ def is_square(x): return pow(x, (p - 1) // 2, p) == 1 def has_square_y(P): - return not is_infinity(P) and is_square(y(P)) + return (not is_infinity(P)) and (is_square(y(P))) def has_even_y(P): return y(P) % 2 == 0 @@ -144,7 +144,7 @@ def schnorr_verify(msg, pubkey, sig): return False e = int_from_bytes(tagged_hash("BIP340/challenge", sig[0:32] + pubkey + msg)) % n R = point_add(point_mul(G, s), point_mul(P, n - e)) - if R is None or not has_square_y(R) or x(R) != r: + if (R is None) or (not has_square_y(R)) or (x(R) != r): debug_print_vars() return False debug_print_vars() @@ -168,7 +168,7 @@ def test_vectors(): msg = bytes.fromhex(msg) sig = bytes.fromhex(sig) result = result == 'TRUE' - print('\nTest vector #%-3i: ' % int(index)) + print('\nTest vector', ('#' + index).rjust(3, ' ') + ':') if seckey != '': seckey = bytes.fromhex(seckey) pubkey_actual = pubkey_gen(seckey) -- cgit v1.2.3 From 07d938a214475929e08df17e725b3904a3429dbf Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Tue, 17 Mar 2020 02:13:26 +0100 Subject: fixup! Optionally print intermediate values in reference code --- bip-0340/reference.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index 346b639..da1e689 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -78,7 +78,7 @@ def lift_x_even_y(b): if P is None: return None else: - return [x(P), y(P) if y(P) % 2 == 0 else p - y(P)] + return (x(P), y(P) if y(P) % 2 == 0 else p - y(P)) def int_from_bytes(b): return int.from_bytes(b, byteorder="big") @@ -90,7 +90,7 @@ def is_square(x): return pow(x, (p - 1) // 2, p) == 1 def has_square_y(P): - return (not is_infinity(P)) and (is_square(y(P))) + return (not is_infinity(P)) and is_square(y(P)) def has_even_y(P): return y(P) % 2 == 0 @@ -98,14 +98,12 @@ def has_even_y(P): def pubkey_gen(seckey): d0 = int_from_bytes(seckey) if not (1 <= d0 <= n - 1): - debug_print_vars() raise ValueError('The secret key must be an integer in the range 1..n-1.') P = point_mul(G, d0) return bytes_from_point(P) def schnorr_sign(msg, seckey, aux_rand): if len(msg) != 32: - debug_print_vars() raise ValueError('The message must be a 32-byte array.') d0 = int_from_bytes(seckey) if not (1 <= d0 <= n - 1): @@ -117,16 +115,14 @@ def schnorr_sign(msg, seckey, aux_rand): t = xor_bytes(bytes_from_int(d), tagged_hash("BIP340/aux", aux_rand)) k0 = int_from_bytes(tagged_hash("BIP340/nonce", t + bytes_from_point(P) + msg)) % n if k0 == 0: - debug_print_vars() raise RuntimeError('Failure. This happens only with negligible probability.') R = point_mul(G, k0) k = n - k0 if not has_square_y(R) else k0 e = int_from_bytes(tagged_hash("BIP340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n sig = bytes_from_point(R) + bytes_from_int((k + e * d) % n) + debug_print_vars() if not schnorr_verify(msg, bytes_from_point(P), sig): - debug_print_vars() raise RuntimeError('The signature does not pass verification.') - debug_print_vars() return sig def schnorr_verify(msg, pubkey, sig): -- cgit v1.2.3 From 72657270d8e4d6ef193878f1e743301edfae0e31 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Tue, 17 Mar 2020 02:30:39 +0100 Subject: When checking test vectors, handle RuntimeException in signing This is better for playing around with the code. Now these these exceptions can really be raised when the verification during signing fails. --- bip-0340/reference.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index da1e689..6b1645c 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -122,7 +122,7 @@ def schnorr_sign(msg, seckey, aux_rand): sig = bytes_from_point(R) + bytes_from_int((k + e * d) % n) debug_print_vars() if not schnorr_verify(msg, bytes_from_point(P), sig): - raise RuntimeError('The signature does not pass verification.') + raise RuntimeError('The created signature does not pass verification.') return sig def schnorr_verify(msg, pubkey, sig): @@ -173,13 +173,17 @@ def test_vectors(): print(' Expected key:', pubkey.hex().upper()) print(' Actual key:', pubkey_actual.hex().upper()) aux_rand = bytes.fromhex(aux_rand) - sig_actual = schnorr_sign(msg, seckey, aux_rand) - if sig == sig_actual: - print(' * Passed signing test.') - else: - print(' * Failed signing test.') - print(' Expected signature:', sig.hex().upper()) - print(' Actual signature:', sig_actual.hex().upper()) + try: + sig_actual = schnorr_sign(msg, seckey, aux_rand) + if sig == sig_actual: + print(' * Passed signing test.') + else: + print(' * Failed signing test.') + print(' Expected signature:', sig.hex().upper()) + print(' Actual signature:', sig_actual.hex().upper()) + all_passed = False + except RuntimeError as e: + print(' * Signing test raised exception:', e) all_passed = False result_actual = schnorr_verify(msg, pubkey, sig) if result == result_actual: -- cgit v1.2.3 From 756129cccfd539174b7a1b1a0203f1527d3c46c0 Mon Sep 17 00:00:00 2001 From: Janus Date: Wed, 18 Mar 2020 20:14:08 -0600 Subject: BIP-0340: Add typing annotations to reference.py Passes mypy's strict-mode with mypy 0.770. --- bip-0340/reference.py | 78 +++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 33 deletions(-) (limited to 'bip-0340/reference.py') diff --git a/bip-0340/reference.py b/bip-0340/reference.py index 6b1645c..f24963c 100644 --- a/bip-0340/reference.py +++ b/bip-0340/reference.py @@ -1,3 +1,4 @@ +from typing import Tuple, Optional, Any import hashlib import binascii @@ -17,22 +18,24 @@ n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # represented by the None keyword. G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) +Point = Tuple[int, int] + # This implementation can be sped up by storing the midstate after hashing # tag_hash instead of rehashing it all the time. -def tagged_hash(tag, msg): +def tagged_hash(tag: str, msg: bytes) -> bytes: tag_hash = hashlib.sha256(tag.encode()).digest() return hashlib.sha256(tag_hash + tag_hash + msg).digest() -def is_infinity(P): +def is_infinity(P: Optional[Point]) -> bool: return P is None -def x(P): +def x(P: Point) -> int: return P[0] -def y(P): +def y(P: Point) -> int: return P[1] -def point_add(P1, P2): +def point_add(P1: Optional[Point], P2: Optional[Point]) -> Optional[Point]: if P1 is None: return P2 if P2 is None: @@ -46,7 +49,7 @@ def point_add(P1, P2): x3 = (lam * lam - x(P1) - x(P2)) % p return (x3, (lam * (x(P1) - x3) - y(P1)) % p) -def point_mul(P, n): +def point_mul(P: Optional[Point], n: int) -> Optional[Point]: R = None for i in range(256): if (n >> i) & 1: @@ -54,16 +57,16 @@ def point_mul(P, n): P = point_add(P, P) return R -def bytes_from_int(x): +def bytes_from_int(x: int) -> bytes: return x.to_bytes(32, byteorder="big") -def bytes_from_point(P): +def bytes_from_point(P: Point) -> bytes: return bytes_from_int(x(P)) -def xor_bytes(b0, b1): +def xor_bytes(b0: bytes, b1: bytes) -> bytes: return bytes(x ^ y for (x, y) in zip(b0, b1)) -def lift_x_square_y(b): +def lift_x_square_y(b: bytes) -> Optional[Point]: x = int_from_bytes(b) if x >= p: return None @@ -73,36 +76,40 @@ def lift_x_square_y(b): return None return (x, y) -def lift_x_even_y(b): +def lift_x_even_y(b: bytes) -> Optional[Point]: P = lift_x_square_y(b) if P is None: return None else: return (x(P), y(P) if y(P) % 2 == 0 else p - y(P)) -def int_from_bytes(b): +def int_from_bytes(b: bytes) -> int: return int.from_bytes(b, byteorder="big") -def hash_sha256(b): +def hash_sha256(b: bytes) -> bytes: return hashlib.sha256(b).digest() -def is_square(x): - return pow(x, (p - 1) // 2, p) == 1 +def is_square(x: int) -> bool: + return int(pow(x, (p - 1) // 2, p)) == 1 -def has_square_y(P): - return (not is_infinity(P)) and is_square(y(P)) +def has_square_y(P: Optional[Point]) -> bool: + infinity = is_infinity(P) + if infinity: return False + assert P is not None + return is_square(y(P)) -def has_even_y(P): +def has_even_y(P: Point) -> bool: return y(P) % 2 == 0 -def pubkey_gen(seckey): +def pubkey_gen(seckey: bytes) -> bytes: d0 = int_from_bytes(seckey) if not (1 <= d0 <= n - 1): raise ValueError('The secret key must be an integer in the range 1..n-1.') P = point_mul(G, d0) + assert P is not None return bytes_from_point(P) -def schnorr_sign(msg, seckey, aux_rand): +def schnorr_sign(msg: bytes, seckey: bytes, aux_rand: bytes) -> bytes: if len(msg) != 32: raise ValueError('The message must be a 32-byte array.') d0 = int_from_bytes(seckey) @@ -111,12 +118,14 @@ def schnorr_sign(msg, seckey, aux_rand): if len(aux_rand) != 32: raise ValueError('aux_rand must be 32 bytes instead of %i.' % len(aux_rand)) P = point_mul(G, d0) + assert P is not None d = d0 if has_even_y(P) else n - d0 t = xor_bytes(bytes_from_int(d), tagged_hash("BIP340/aux", aux_rand)) k0 = int_from_bytes(tagged_hash("BIP340/nonce", t + bytes_from_point(P) + msg)) % n if k0 == 0: raise RuntimeError('Failure. This happens only with negligible probability.') R = point_mul(G, k0) + assert R is not None k = n - k0 if not has_square_y(R) else k0 e = int_from_bytes(tagged_hash("BIP340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n sig = bytes_from_point(R) + bytes_from_int((k + e * d) % n) @@ -125,7 +134,7 @@ def schnorr_sign(msg, seckey, aux_rand): raise RuntimeError('The created signature does not pass verification.') return sig -def schnorr_verify(msg, pubkey, sig): +def schnorr_verify(msg: bytes, pubkey: bytes, sig: bytes) -> bool: if len(msg) != 32: raise ValueError('The message must be a 32-byte array.') if len(pubkey) != 32: @@ -153,26 +162,26 @@ import csv import os import sys -def test_vectors(): +def test_vectors() -> bool: all_passed = True with open(os.path.join(sys.path[0], 'test-vectors.csv'), newline='') as csvfile: reader = csv.reader(csvfile) reader.__next__() for row in reader: - (index, seckey, pubkey, aux_rand, msg, sig, result, comment) = row - pubkey = bytes.fromhex(pubkey) - msg = bytes.fromhex(msg) - sig = bytes.fromhex(sig) - result = result == 'TRUE' + (index, seckey_hex, pubkey_hex, aux_rand_hex, msg_hex, sig_hex, result_str, comment) = row + pubkey = bytes.fromhex(pubkey_hex) + msg = bytes.fromhex(msg_hex) + sig = bytes.fromhex(sig_hex) + result = result_str == 'TRUE' print('\nTest vector', ('#' + index).rjust(3, ' ') + ':') - if seckey != '': - seckey = bytes.fromhex(seckey) + if seckey_hex != '': + seckey = bytes.fromhex(seckey_hex) pubkey_actual = pubkey_gen(seckey) if pubkey != pubkey_actual: print(' * Failed key generation.') print(' Expected key:', pubkey.hex().upper()) print(' Actual key:', pubkey_actual.hex().upper()) - aux_rand = bytes.fromhex(aux_rand) + aux_rand = bytes.fromhex(aux_rand_hex) try: sig_actual = schnorr_sign(msg, seckey, aux_rand) if sig == sig_actual: @@ -207,7 +216,7 @@ def test_vectors(): # import inspect -def pretty(v): +def pretty(v: Any) -> Any: if isinstance(v, bytes): return '0x' + v.hex() if isinstance(v, int): @@ -216,9 +225,12 @@ def pretty(v): return tuple(map(pretty, v)) return v -def debug_print_vars(): +def debug_print_vars() -> None: if DEBUG: - frame = inspect.currentframe().f_back + current_frame = inspect.currentframe() + assert current_frame is not None + frame = current_frame.f_back + assert frame is not None print(' Variables in function ', frame.f_code.co_name, ' at line ', frame.f_lineno, ':', sep='') for var_name, var_val in frame.f_locals.items(): print(' ' + var_name.rjust(11, ' '), '==', pretty(var_val)) -- cgit v1.2.3