From 5bbeeae962f59768b92fc8905e5b0284366832bd Mon Sep 17 00:00:00 2001 From: Liam Gilligan <104160230+liamgilligan@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:23:55 +0000 Subject: [PATCH] BIP461: add test vectors and reference implementation (#2328) All vectors were tested against libsecp256k1, and all verification vectors were checked against OpenSSL. A check was also added to the `verify` specification to reject pubkeys with x or y coordinates greater than or equal to `p`. --- bip-0461.md | 9 ++ bip-0461/LICENSE | 6 + bip-0461/gen_test_vectors.py | 194 +++++++++++++++++++++++++++ bip-0461/reference.py | 220 +++++++++++++++++++++++++++++++ bip-0461/run_test_vectors.py | 59 +++++++++ bip-0461/sign_test_vectors.csv | 7 + bip-0461/verify_test_vectors.csv | 17 +++ 7 files changed, 512 insertions(+) create mode 100644 bip-0461/LICENSE create mode 100644 bip-0461/gen_test_vectors.py create mode 100644 bip-0461/reference.py create mode 100644 bip-0461/run_test_vectors.py create mode 100644 bip-0461/sign_test_vectors.csv create mode 100644 bip-0461/verify_test_vectors.csv diff --git a/bip-0461.md b/bip-0461.md index 608e0939..f12ec723 100644 --- a/bip-0461.md +++ b/bip-0461.md @@ -131,6 +131,8 @@ The algorithm *verify(sig, P, msghash)* is defined as: - If *is_infinite(P)*: - Return *false* +- If *x(P) >= p* or *y(P) >= p*: + - Return *false* - If *y(P)2 ≠ x(P)3 + 7 mod p*: - Return *false* - Let *(r, s) = sig* @@ -200,6 +202,13 @@ Grinding requires a nonce derivation that is deterministic in the secret key and Signatures generated under this specification are normal valid signatures and will verify under existing consensus rules, and thus are fully backward compatible. +## Reference Implementation + +For development and testing purposes, we provide a [collection of test vectors in CSV format](bip-0461/), +a naive, highly inefficient, and non-constant time [pure Python 3.8 reference implementation of the signing and verification algorithm](bip-0461/reference.py) +as well as the [script used to generate the test vectors](bip-0461/gen_test_vectors.py). +The reference implementation is for demonstration purposes only and not to be used in production environments. + ## Copyright This BIP is licensed under the CC0-1.0 license. diff --git a/bip-0461/LICENSE b/bip-0461/LICENSE new file mode 100644 index 00000000..55b4d336 --- /dev/null +++ b/bip-0461/LICENSE @@ -0,0 +1,6 @@ +CC0-1.0 + +The contents of this directory are provided under the terms of CC0 1.0: + + - CC0 1.0 + https://creativecommons.org/publicdomain/zero/1.0/ diff --git a/bip-0461/gen_test_vectors.py b/bip-0461/gen_test_vectors.py new file mode 100644 index 00000000..2b641f8c --- /dev/null +++ b/bip-0461/gen_test_vectors.py @@ -0,0 +1,194 @@ +"""Generate the low-R grinding test vectors.""" + +import csv +import os +import sys +from reference import ( + bytes_from_int, + G, + deterministic_sign, + grind_extra, + is_low_r, + n, + normalize_low_s, + p, + point_add, + point_mul, + pubkey_gen, + sign, + verify, +) + +FILENAME_SIGN_TEST = os.path.join(sys.path[0], 'sign_test_vectors.csv') +FILENAME_VERIFY_TEST = os.path.join(sys.path[0], 'verify_test_vectors.csv') + +# (secret key, message hash, comment) +SIGN_CASES = [ + (0x12bef4612f311c38408e662304f22ef64792e80561b0a8adecd2074687e12bf1, 0xec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff, "grinding stops at counter 7"), + (0x4889e538d453c1b6834f66d70274c770e689210505c34fc5caad70fa29c8ad99, 2**256 - 1, "msghash%n=2^256-1-n"), + (0x4889e538d453c1b6834f66d70274c770e689210505c34fc5caad70fa29c8ad99, n, "msghash%n=0"), +] + +# Secret keys outside 1..n-1, for which signing must fail. +INVALID_SECKEY_CASES = [ + (0, "sk=0"), + (n, "sk=n"), + (p, "sk=p, which is above n"), +] + +def pubkey_hex(P): + """Serialize a public key in SEC 1 format: uncompressed, or the single byte 00 + for the point at infinity.""" + if P is None: + return "00" + return "04" + f"{P[0]:064x}" + f"{P[1]:064x}" + +def sig_hex(sig): + return (bytes_from_int(sig[0]) + bytes_from_int(sig[1])).hex() + +def signed_cases(): + """Record the signature and grind counter of each case.""" + cases = [] + for sk, msg, comment in SIGN_CASES: + seckey, msghash = bytes_from_int(sk), bytes_from_int(msg) + for count in range(2**32): + sig = sign(seckey, msghash, grind_extra(count)) + if is_low_r(sig): + break + # `sign` returns s unnormalized; normalization happens once after grinding + sig = normalize_low_s(sig) + assert deterministic_sign(seckey, msghash) == sig + cases.append((sk, msg, comment, sig, count)) + return cases + +def sign_vectors(cases): + """Generate the valid and invalid signing vectors.""" + for sk, msg, comment, sig, _ in cases: + assert is_low_r(sig) + yield { + "seckey": f"{sk:064x}", + "msghash": f"{msg:064x}", + "sig": sig_hex(sig), + "comment": comment, + } + msg = cases[0][1] # nonzero, so a missing key check can't loop forever on s = 0 + for sk, comment in INVALID_SECKEY_CASES: + raised = False + try: + deterministic_sign(bytes_from_int(sk), bytes_from_int(msg)) + except ValueError: + raised = True + assert raised + # An empty signature means signing must fail for this secret key. + yield { + "seckey": f"{sk:064x}", + "msghash": f"{msg:064x}", + "sig": "", + "comment": comment, + } + +def verify_row(P, msg, sig, result, comment): + assert verify(sig, P, bytes_from_int(msg)) is result + return { + "pubkey": pubkey_hex(P), + "msghash": f"{msg:064x}", + "sig": sig_hex(sig), + "result": "TRUE" if result else "FALSE", + "comment": comment, + } + +def verify_vectors(cases): + """Generate the valid and invalid verification vectors.""" + for sk, msg, _, sig, _ in cases: + yield verify_row(pubkey_gen(bytes_from_int(sk)), msg, sig, True, "") + sk, msg = cases[0][:2] + yield verify_row(pubkey_gen(bytes_from_int(sk)), msg, cases[1][3], False, + "signature made with a different key and message") + + sk, msg, _, sig, count = cases[0] + P = pubkey_gen(bytes_from_int(sk)) + r, s = sig + + # This case required grinding for a low-r, so we can inject an earlier + # counter to get a valid high-r signature, which should verify. + assert count > 0 + high_r = normalize_low_s(sign(bytes_from_int(sk), bytes_from_int(msg), grind_extra(0))) + assert not is_low_r(high_r) + yield verify_row(P, msg, high_r, True, + "high-r signature: low-r is a signing policy, not a validity rule") + + # s outside 1..n-1, and r = s = 0. + yield verify_row(P, msg, (r, 0), False, "s=0") + yield verify_row(P, msg, (r, n), False, "s=n") + yield verify_row(P, msg, (0, 0), False, + "r = s = 0; a verifier missing the zero checks can accept this for " + "any key and message, as in CVE-2022-21449") + + # Pubkey which will verify under the given `msghash`/`sig` pair if it receives + # no explicit on-curve check. + Q = (2, 3) + assert Q[1] ** 2 % p != (Q[0] ** 3 + 7) % p + assert point_mul(Q, 6) is None + u2 = 1 + forged_R = point_mul(Q, u2) + assert forged_R is not None + forged_r = forged_R[0] % n + forged_s = (forged_r * pow(u2, -1, n)) % n + assert 1 <= forged_r <= n - 1 and 1 <= forged_s <= n - 1 + yield verify_row(Q, 0, (forged_r, forged_s), False, + "public key not on the curve, with a signature that verifies " + "if the on-curve check is skipped") + + x_one = (1, pow(1 + 7, (p + 1) // 4, p)) + y_one = (pow((1 - 7) % p, (p + 2) // 9, p), 1) + for key, wide_key, comment in [ + (x_one, (x_one[0] + p, x_one[1]), + "public key x = p+1; accepted if coordinates are reduced mod p instead of rejected"), + (y_one, (y_one[0], y_one[1] + p), + "public key y = p+1; accepted if coordinates are reduced mod p instead of rejected"), + ]: + assert key[1] ** 2 % p == (key[0] ** 3 + 7) % p and max(wide_key) < 2**256 + key_R = point_add(G, key) + assert key_R is not None + key_r = key_R[0] % n + assert verify((key_r, key_r), key, bytes_from_int(key_r)) + yield verify_row(wide_key, key_r, (key_r, key_r), False, comment) + + yield verify_row(None, msg, sig, False, "public key is the point at infinity") + + # Vector that produces R as the point at infinity. + infinite_r_msg = (-r * sk) % n + assert (infinite_r_msg + r * sk) % n == 0 and infinite_r_msg != 0 + yield verify_row(P, infinite_r_msg, sig, False, "u1*G + u2*P is the point at infinity") + + # Vector with x(R) greater than n, unreachable in practice (n + 2, since + # x = n gives r = 0). + big_x = n + 2 + big_y = pow((big_x ** 3 + 7) % p, (p + 1) // 4, p) + assert big_y ** 2 % p == (big_x ** 3 + 7) % p + big_R = (big_x, big_y) + big_P = point_add(big_R, (G[0], p - G[1])) + assert big_P is not None and big_x % n == 2 + yield verify_row(big_P, 2, (2, 2), True, + "x(R) = n+2; fails if x(R) is compared to r without reducing mod n") + # The same signature with r or s moved out of range. + yield verify_row(big_P, 2, (2 + n, 2), False, + "r = n+2; accepted if r is reduced mod n instead of rejected") + yield verify_row(big_P, 2, (2, 2 + n), False, + "s = n+2; accepted if s is reduced mod n instead of rejected") + +def gen_all(fil, fields, vectors): + writer = csv.DictWriter(fil, fields) + writer.writeheader() + for row in vectors: + writer.writerow(row) + +if __name__ == "__main__": + cases = signed_cases() + print(f"Generating {FILENAME_SIGN_TEST}...") + with open(FILENAME_SIGN_TEST, "w", newline='', encoding="utf-8") as fil_sign: + gen_all(fil_sign, ["seckey", "msghash", "sig", "comment"], + sign_vectors(cases)) + print(f"Generating {FILENAME_VERIFY_TEST}...") + with open(FILENAME_VERIFY_TEST, "w", newline='', encoding="utf-8") as fil_verify: + gen_all(fil_verify, ["pubkey", "msghash", "sig", "result", "comment"], verify_vectors(cases)) diff --git a/bip-0461/reference.py b/bip-0461/reference.py new file mode 100644 index 00000000..093f972a --- /dev/null +++ b/bip-0461/reference.py @@ -0,0 +1,220 @@ +from typing import Tuple, Optional +import hashlib +import hmac + +p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F +n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +half_order = (n - 1) // 2 + +# Points are tuples of X and Y coordinates and the point at infinity is +# represented by the None keyword. +G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) + +Point = Tuple[int, int] + +# A signature is the integer pair (r, s) +Signature = Tuple[int, int] + +def is_infinite(P: Optional[Point]) -> bool: + return P is None + +def x(P: Point) -> int: + assert not is_infinite(P) + return P[0] + +def y(P: Point) -> int: + assert not is_infinite(P) + return P[1] + +def int_from_bytes(b: bytes) -> int: + return int.from_bytes(b, byteorder="big") + +def point_add(P1: Optional[Point], P2: Optional[Point]) -> Optional[Point]: + if P1 is None: + return P2 + if P2 is None: + return P1 + if (x(P1) == x(P2)) and (y(P1) != y(P2)): + return None + 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 + x3 = (lam * lam - x(P1) - x(P2)) % p + return (x3, (lam * (x(P1) - x3) - y(P1)) % p) + +def point_mul(P: Optional[Point], n: int) -> Optional[Point]: + R = None + for i in range(256): + if (n >> i) & 1: + R = point_add(R, P) + P = point_add(P, P) + return R + +def bytes_from_int(x: int) -> bytes: + return x.to_bytes(32, byteorder="big") + +def bytes_le(l: int, x: int) -> bytes: + return x.to_bytes(l, byteorder="little") + +def fill_bytes(l: int, x: int) -> bytes: + return bytes([x]) * l + +def pubkey_gen(seckey: bytes) -> Point: + if len(seckey) != 32: + raise ValueError('The secret key must be a 32-byte array.') + 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 P + +def hmac_sha256(key: bytes, data: bytes) -> bytes: + return hmac.new(key, data, hashlib.sha256).digest() + + +def nonce_rfc6979(seckey: bytes, msghash: bytes, extra: bytes, count: int) -> bytes: + """ + There are two auxiliary inputs here which are both effectively counters, but + serve different purposes. + + `extra`: in the event that a high-r signature is produced, we inject `extra` to produce + a different nonce that may produce a low-r signature. It is the grind counter's + encoding, see `grind_extra`. + + `count`: in the event that the nonce is either invalid *OR* produces an invalid signature + (both are cryptographically unreachable events), `sign` increments `count` to produce a + different nonce which may (will almost certainly) be valid and produce a valid signature. + + `K` and `V` are the internal state variables of the RFC 6979 generator and carry its + names. Both are 32-byte arrays rather than curve points, and `K` is unrelated to the + nonce `k`. + """ + if len(seckey) != 32: + raise ValueError('The secret key must be a 32-byte array.') + if len(msghash) != 32: + raise ValueError('The message hash must be a 32-byte array.') + + # RFC 6979 section 3.2d feeds bits2octets(h1), i.e. the message hash reduced + # modulo n, not the raw hash. libsecp256k1 does the same (`msgmod32` in + # `nonce_function_rfc6979`). The two differ only when int(msghash) >= n. + h = bytes_from_int(int_from_bytes(msghash) % n) + + V = fill_bytes(32, 0x01) + K = fill_bytes(32, 0x00) + + K = hmac_sha256(K, V + b'\x00' + seckey + h + extra) + V = hmac_sha256(K, V) + K = hmac_sha256(K, V + b'\x01' + seckey + h + extra) + V = hmac_sha256(K, V) + + V = hmac_sha256(K, V) + + for _ in range(count): + K = hmac_sha256(K, V + b'\x00') + V = hmac_sha256(K, V) + V = hmac_sha256(K, V) + + return V + + +def sign(seckey: bytes, msghash: bytes, extra: bytes) -> Signature: + """See `nonce_rfc6979` for the difference between `extra` and `count`.""" + if len(seckey) != 32: + raise ValueError('The secret key must be a 32-byte array.') + if len(msghash) != 32: + raise ValueError('The message hash must be a 32-byte array.') + + d = int_from_bytes(seckey) + if not (1 <= d <= n - 1): + raise ValueError('The secret key must be an integer in the range 1..n-1.') + + for count in range(2**32): + nonce = nonce_rfc6979(seckey, msghash, extra, count) + k = int_from_bytes(nonce) + if not (1 <= k <= n - 1): + continue + R = point_mul(G, k) + assert R is not None + r = x(R) % n + if r == 0: + continue + s = (pow(k, -1, n) * (d * r + int_from_bytes(msghash))) % n + if s == 0: + continue + return (r, s) + raise RuntimeError('No valid signature found below the attempt counter limit.') + +def verify(sig: Signature, pubkey: Optional[Point], msghash: bytes) -> bool: + if len(msghash) != 32: + raise ValueError('The message hash must be a 32-byte array.') + + if is_infinite(pubkey): + return False + assert pubkey is not None + + if x(pubkey) >= p or y(pubkey) >= p: + return False + + if (y(pubkey) ** 2) % p != (x(pubkey) ** 3 + 7) % p: + return False + + (r, s) = sig + + if r <= 0 or r >= n: + return False + + if s <= 0 or s >= n: + return False + + w = pow(s, -1, n) + + u1 = (w * int_from_bytes(msghash)) % n + u2 = (w * r) % n + R = point_add(point_mul(G, u1), point_mul(pubkey, u2)) + + if is_infinite(R): + return False + assert R is not None + + if x(R) % n != r: + return False + + return True + +def grind_extra(i: int) -> bytes: + """The additional data corresponding to the grind counter.""" + if not (0 <= i <= 2**32 - 1): + raise ValueError('The grind counter must be an integer in the range 0..2^32-1.') + return b'' if i == 0 else bytes_le(4, i) + fill_bytes(28, 0x00) + +def is_low_r(sig: Signature) -> bool: + """Can also be written as bytes_from_int(r)[0] < 0x80""" + (r, _) = sig + return r < 2**255 + +def normalize_low_s(sig: Signature) -> Signature: + (r, s) = sig + return (r, n - s) if s > half_order else (r, s) + +def deterministic_sign(seckey: bytes, msghash: bytes) -> Signature: + if len(seckey) != 32: + raise ValueError('The secret key must be a 32-byte array.') + if not (1 <= int_from_bytes(seckey) <= n - 1): + raise ValueError('The secret key must be an integer in the range 1..n-1.') + + (r, s) = sign(seckey, msghash, grind_extra(0)) + i = 1 + while r >= 2**255: + if i >= 2**32: + raise RuntimeError('No low-r signature found below the grind counter limit.') + (r, s) = sign(seckey, msghash, grind_extra(i)) + i += 1 + + (r, s) = normalize_low_s((r, s)) + + P = pubkey_gen(seckey) + if not verify((r, s), P, msghash): + raise RuntimeError('The created signature is invalid.') + return (r, s) diff --git a/bip-0461/run_test_vectors.py b/bip-0461/run_test_vectors.py new file mode 100644 index 00000000..f8f79b99 --- /dev/null +++ b/bip-0461/run_test_vectors.py @@ -0,0 +1,59 @@ +"""Run the low-R grinding test vectors.""" + +import csv +import os +import sys + +import reference + +FILENAME_SIGN_TEST = os.path.join(sys.path[0], 'sign_test_vectors.csv') +FILENAME_VERIFY_TEST = os.path.join(sys.path[0], 'verify_test_vectors.csv') + +def sig_from_hex(s): + """Parse a compact signature into the integer pair (r, s).""" + b = bytes.fromhex(s) + assert len(b) == 64 + return (int.from_bytes(b[:32], 'big'), int.from_bytes(b[32:], 'big')) + +def sig_hex(sig): + return (reference.bytes_from_int(sig[0]) + reference.bytes_from_int(sig[1])).hex() + +def pubkey_from_hex(s): + """Parse an uncompressed SEC 1 public key into a point.""" + b = bytes.fromhex(s) + if b == b'\x00': + return None + assert len(b) == 65 and b[0] == 0x04 + return (int.from_bytes(b[1:33], 'big'), int.from_bytes(b[33:], 'big')) + +def result_from_str(s): + assert s in ('TRUE', 'FALSE') + return s == 'TRUE' + +with open(FILENAME_SIGN_TEST, newline='', encoding='utf-8') as csvfile: + print(f"Running {FILENAME_SIGN_TEST} tests...") + reader = csv.DictReader(csvfile) + for row in reader: + seckey = bytes.fromhex(row['seckey']) + msghash = bytes.fromhex(row['msghash']) + if row['sig'] == '': + # An empty signature means signing must fail for this secret key. + raised = False + try: + reference.deterministic_sign(seckey, msghash) + except ValueError: + raised = True + assert raised + continue + sig = reference.deterministic_sign(seckey, msghash) + assert reference.is_low_r(sig) + assert row['sig'] == sig_hex(sig) + +with open(FILENAME_VERIFY_TEST, newline='', encoding='utf-8') as csvfile: + print(f"Running {FILENAME_VERIFY_TEST} tests...") + reader = csv.DictReader(csvfile) + for row in reader: + pubkey = pubkey_from_hex(row['pubkey']) + msghash = bytes.fromhex(row['msghash']) + sig = sig_from_hex(row['sig']) + assert reference.verify(sig, pubkey, msghash) == result_from_str(row['result']) diff --git a/bip-0461/sign_test_vectors.csv b/bip-0461/sign_test_vectors.csv new file mode 100644 index 00000000..c9787b7e --- /dev/null +++ b/bip-0461/sign_test_vectors.csv @@ -0,0 +1,7 @@ +seckey,msghash,sig,comment +12bef4612f311c38408e662304f22ef64792e80561b0a8adecd2074687e12bf1,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc,grinding stops at counter 7 +4889e538d453c1b6834f66d70274c770e689210505c34fc5caad70fa29c8ad99,ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,60a5d1904124dc3925c4887192306e43bf1d0989f89af7adecf06c8ab03bfdf94c2124bfa724c3dce24be6dab661eb1b38cdc42cfa250b61458b6ef2fc46396c,msghash%n=2^256-1-n +4889e538d453c1b6834f66d70274c770e689210505c34fc5caad70fa29c8ad99,fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,2e2b06d3334157fa44689912d33be79bafe30ec4b88e4a7d709f33615a1bb2066611e2fa8f82a2cf7802acf64988a5a002c914cc63e553be801db3997d0924c6,msghash%n=0 +0000000000000000000000000000000000000000000000000000000000000000,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,,sk=0 +fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,,sk=n +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,,"sk=p, which is above n" diff --git a/bip-0461/verify_test_vectors.csv b/bip-0461/verify_test_vectors.csv new file mode 100644 index 00000000..489d9ce3 --- /dev/null +++ b/bip-0461/verify_test_vectors.csv @@ -0,0 +1,17 @@ +pubkey,msghash,sig,result,comment +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc,TRUE, +04a0429363c22cd4b73db46b180fd05d9c3555c0ab31e01363717e23e26886bd5de3ae91e83e58d166955bf64c826bac4f46bf2b018b730ffd77c6a248e0f3f8cd,ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,60a5d1904124dc3925c4887192306e43bf1d0989f89af7adecf06c8ab03bfdf94c2124bfa724c3dce24be6dab661eb1b38cdc42cfa250b61458b6ef2fc46396c,TRUE, +04a0429363c22cd4b73db46b180fd05d9c3555c0ab31e01363717e23e26886bd5de3ae91e83e58d166955bf64c826bac4f46bf2b018b730ffd77c6a248e0f3f8cd,fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,2e2b06d3334157fa44689912d33be79bafe30ec4b88e4a7d709f33615a1bb2066611e2fa8f82a2cf7802acf64988a5a002c914cc63e553be801db3997d0924c6,TRUE, +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,60a5d1904124dc3925c4887192306e43bf1d0989f89af7adecf06c8ab03bfdf94c2124bfa724c3dce24be6dab661eb1b38cdc42cfa250b61458b6ef2fc46396c,FALSE,signature made with a different key and message +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,df12252ec1f17a66cc6b438a79f5e63b0b91eefd90c57c636ce2b8e3401075fe37d01c81ba1f9cfa590885ffce18d8a289363f2c756ae8964c608f991a4b883a,TRUE,"high-r signature: low-r is a signing policy, not a validity rule" +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f70000000000000000000000000000000000000000000000000000000000000000,FALSE,s=0 +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f7fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,FALSE,s=n +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,FALSE,"r = s = 0; a verifier missing the zero checks can accept this for any key and message, as in CVE-2022-21449" +0400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003,0000000000000000000000000000000000000000000000000000000000000000,00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002,FALSE,"public key not on the curve, with a signature that verifies if the on-curve check is skipped" +04fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc304218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee,57d783579d03d9ab67a8aa7ad9b75a66ebca4ebce1b5be71442db1307f9146a8,57d783579d03d9ab67a8aa7ad9b75a66ebca4ebce1b5be71442db1307f9146a857d783579d03d9ab67a8aa7ad9b75a66ebca4ebce1b5be71442db1307f9146a8,FALSE,public key x = p+1; accepted if coordinates are reduced mod p instead of rejected +041fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30,663c42aaae1ba20a1f06879b77b9ef4de8f7f9a1b9b34a3e0fb2fd7343dc4229,663c42aaae1ba20a1f06879b77b9ef4de8f7f9a1b9b34a3e0fb2fd7343dc4229663c42aaae1ba20a1f06879b77b9ef4de8f7f9a1b9b34a3e0fb2fd7343dc4229,FALSE,public key y = p+1; accepted if coordinates are reduced mod p instead of rejected +00,ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff,5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc,FALSE,public key is the point at infinity +046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f,48f414d53b080c53fbe5a11e5fa5cb30b45d1fbb7d31f8747007ceea633246af,5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc,FALSE,u1*G + u2*P is the point at infinity +04fa080d6569edae14025c385d49d06a25b116121767026fe837249ff0a5bcbac771aeed5573db3682d4f047d920f14c61b39cfd546ef2de3f4a8cf17c2e7a964d,0000000000000000000000000000000000000000000000000000000000000002,00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002,TRUE,x(R) = n+2; fails if x(R) is compared to r without reducing mod n +04fa080d6569edae14025c385d49d06a25b116121767026fe837249ff0a5bcbac771aeed5573db3682d4f047d920f14c61b39cfd546ef2de3f4a8cf17c2e7a964d,0000000000000000000000000000000000000000000000000000000000000002,fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641430000000000000000000000000000000000000000000000000000000000000002,FALSE,r = n+2; accepted if r is reduced mod n instead of rejected +04fa080d6569edae14025c385d49d06a25b116121767026fe837249ff0a5bcbac771aeed5573db3682d4f047d920f14c61b39cfd546ef2de3f4a8cf17c2e7a964d,0000000000000000000000000000000000000000000000000000000000000002,0000000000000000000000000000000000000000000000000000000000000002fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364143,FALSE,s = n+2; accepted if s is reduced mod n instead of rejected