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

+9
View File
@@ -131,6 +131,8 @@ The algorithm *verify(sig, P, msghash)* is defined as:
- If *is_infinite(P)*: - If *is_infinite(P)*:
- Return *false* - Return *false*
- If *x(P) >= p* or *y(P) >= p*:
- Return *false*
- If *y(P)<sup>2</sup> ≠ x(P)<sup>3</sup> + 7 mod p*: - If *y(P)<sup>2</sup> ≠ x(P)<sup>3</sup> + 7 mod p*:
- Return *false* - Return *false*
- Let *(r, s) = sig* - 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. 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 ## Copyright
This BIP is licensed under the CC0-1.0 license. This BIP is licensed under the CC0-1.0 license.
+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