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`.
This commit is contained in:
Liam Gilligan authored and GitHub committed 2026-11-03 14:23:55 -08:00
1 parent 21bfd24d6e
commit 5bbeeae962
7 files changed
+512

No files matched your search

+6
View File
@@ -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/
+194
View File
@@ -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))
+220
View File
@@ -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)
+59
View File
@@ -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'])
+7
View File
@@ -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"
1 seckey msghash sig comment
2 12bef4612f311c38408e662304f22ef64792e80561b0a8adecd2074687e12bf1 ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff 5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc grinding stops at counter 7
3 4889e538d453c1b6834f66d70274c770e689210505c34fc5caad70fa29c8ad99 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 60a5d1904124dc3925c4887192306e43bf1d0989f89af7adecf06c8ab03bfdf94c2124bfa724c3dce24be6dab661eb1b38cdc42cfa250b61458b6ef2fc46396c msghash%n=2^256-1-n
4 4889e538d453c1b6834f66d70274c770e689210505c34fc5caad70fa29c8ad99 fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 2e2b06d3334157fa44689912d33be79bafe30ec4b88e4a7d709f33615a1bb2066611e2fa8f82a2cf7802acf64988a5a002c914cc63e553be801db3997d0924c6 msghash%n=0
5 0000000000000000000000000000000000000000000000000000000000000000 ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff sk=0
6 fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff sk=n
7 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff sk=p, which is above n
+17
View File
@@ -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
1 pubkey msghash sig result comment
2 046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff 5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc TRUE
3 04a0429363c22cd4b73db46b180fd05d9c3555c0ab31e01363717e23e26886bd5de3ae91e83e58d166955bf64c826bac4f46bf2b018b730ffd77c6a248e0f3f8cd ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 60a5d1904124dc3925c4887192306e43bf1d0989f89af7adecf06c8ab03bfdf94c2124bfa724c3dce24be6dab661eb1b38cdc42cfa250b61458b6ef2fc46396c TRUE
4 04a0429363c22cd4b73db46b180fd05d9c3555c0ab31e01363717e23e26886bd5de3ae91e83e58d166955bf64c826bac4f46bf2b018b730ffd77c6a248e0f3f8cd fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 2e2b06d3334157fa44689912d33be79bafe30ec4b88e4a7d709f33615a1bb2066611e2fa8f82a2cf7802acf64988a5a002c914cc63e553be801db3997d0924c6 TRUE
5 046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff 60a5d1904124dc3925c4887192306e43bf1d0989f89af7adecf06c8ab03bfdf94c2124bfa724c3dce24be6dab661eb1b38cdc42cfa250b61458b6ef2fc46396c FALSE signature made with a different key and message
6 046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff df12252ec1f17a66cc6b438a79f5e63b0b91eefd90c57c636ce2b8e3401075fe37d01c81ba1f9cfa590885ffce18d8a289363f2c756ae8964c608f991a4b883a TRUE high-r signature: low-r is a signing policy, not a validity rule
7 046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff 5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f70000000000000000000000000000000000000000000000000000000000000000 FALSE s=0
8 046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff 5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f7fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 FALSE s=n
9 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
10 0400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003 0000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002 FALSE public key not on the curve, with a signature that verifies if the on-curve check is skipped
11 04fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc304218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee 57d783579d03d9ab67a8aa7ad9b75a66ebca4ebce1b5be71442db1307f9146a8 57d783579d03d9ab67a8aa7ad9b75a66ebca4ebce1b5be71442db1307f9146a857d783579d03d9ab67a8aa7ad9b75a66ebca4ebce1b5be71442db1307f9146a8 FALSE public key x = p+1; accepted if coordinates are reduced mod p instead of rejected
12 041fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30 663c42aaae1ba20a1f06879b77b9ef4de8f7f9a1b9b34a3e0fb2fd7343dc4229 663c42aaae1ba20a1f06879b77b9ef4de8f7f9a1b9b34a3e0fb2fd7343dc4229663c42aaae1ba20a1f06879b77b9ef4de8f7f9a1b9b34a3e0fb2fd7343dc4229 FALSE public key y = p+1; accepted if coordinates are reduced mod p instead of rejected
13 00 ec888dbfce53624100e889809bce7d3cbe677b56a15968c5964d8d3690f57bff 5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc FALSE public key is the point at infinity
14 046cb5d4539027cde94700dbc595a8743b20188e45096ec83e6af0cf8e3c7baa428c0f0d7b20be55b9b2d9b888a8be73c2e57c4527a2d76568679b42db199be57f 48f414d53b080c53fbe5a11e5fa5cb30b45d1fbb7d31f8747007ceea633246af 5454d43ce9b30ae6790ef0b3fe49363d052f130472586d12ac80e9f1d4fae1f743678a8cad1215ffec68092a993909082998c504c5de982aef28717e57338bbc FALSE u1*G + u2*P is the point at infinity
15 04fa080d6569edae14025c385d49d06a25b116121767026fe837249ff0a5bcbac771aeed5573db3682d4f047d920f14c61b39cfd546ef2de3f4a8cf17c2e7a964d 0000000000000000000000000000000000000000000000000000000000000002 00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002 TRUE x(R) = n+2; fails if x(R) is compared to r without reducing mod n
16 04fa080d6569edae14025c385d49d06a25b116121767026fe837249ff0a5bcbac771aeed5573db3682d4f047d920f14c61b39cfd546ef2de3f4a8cf17c2e7a964d 0000000000000000000000000000000000000000000000000000000000000002 fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641430000000000000000000000000000000000000000000000000000000000000002 FALSE r = n+2; accepted if r is reduced mod n instead of rejected
17 04fa080d6569edae14025c385d49d06a25b116121767026fe837249ff0a5bcbac771aeed5573db3682d4f047d920f14c61b39cfd546ef2de3f4a8cf17c2e7a964d 0000000000000000000000000000000000000000000000000000000000000002 0000000000000000000000000000000000000000000000000000000000000002fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364143 FALSE s = n+2; accepted if s is reduced mod n instead of rejected