mirror of
https://github.com/bitcoin/bips.git
synced 2026-11-09 19:47:16 +00:00
606 lines
22 KiB
Python
606 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate test vectors for BIP 459 DahLIAS fully aggregated signatures
|
|
for secp256k1.
|
|
|
|
Usage:
|
|
python gen_test_vectors.py
|
|
|
|
Outputs:
|
|
- vectors/test_vectors_nonce_gen.csv (NonceGen with a fixed rand')
|
|
- vectors/test_vectors_nonce_agg.csv (NonceAgg: success and failure cases)
|
|
- vectors/test_vectors_sign.csv (end-to-end signing with fixed nonces)
|
|
- vectors/test_vectors_sign_error.csv (sessions in which Sign must fail)
|
|
- vectors/test_vectors_verify.csv (Verify: success and failure cases)
|
|
- vectors/test_vectors_partial_sig_verify.csv (PartialSigVerify cases)
|
|
- vectors/test_vectors_tweak.csv (plain and X-only key tweaking)
|
|
"""
|
|
|
|
import csv
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
|
|
from secp256k1lab.secp256k1 import FE, G, GE, Scalar
|
|
from secp256k1lab.util import bytes_from_int, int_from_bytes, tagged_hash
|
|
|
|
from reference import (
|
|
NonceGenInternal,
|
|
NonceAgg,
|
|
Sign,
|
|
SigAgg,
|
|
Verify,
|
|
PartialSigVerify,
|
|
TweakSK,
|
|
TweakPK,
|
|
has_even_y,
|
|
n,
|
|
FULLAGG_TAG_SIG,
|
|
)
|
|
|
|
|
|
VECTORS_DIR = Path(__file__).resolve().parent / "vectors"
|
|
NONCE_GEN_VECTORS = VECTORS_DIR / "test_vectors_nonce_gen.csv"
|
|
NONCE_AGG_VECTORS = VECTORS_DIR / "test_vectors_nonce_agg.csv"
|
|
SIGN_VECTORS = VECTORS_DIR / "test_vectors_sign.csv"
|
|
SIGN_ERROR_VECTORS = VECTORS_DIR / "test_vectors_sign_error.csv"
|
|
VERIFY_VECTORS = VECTORS_DIR / "test_vectors_verify.csv"
|
|
PARTIAL_SIG_VERIFY_VECTORS = VECTORS_DIR / "test_vectors_partial_sig_verify.csv"
|
|
TWEAK_VECTORS = VECTORS_DIR / "test_vectors_tweak.csv"
|
|
|
|
|
|
def scalar_from_byte(b: int) -> Scalar:
|
|
"""Scalar whose 32-byte big-endian encoding is bytes([b]*32). Safe for 0x01..0xee."""
|
|
assert 1 <= b <= 0xee
|
|
return Scalar.from_bytes_checked(bytes([b] * 32))
|
|
|
|
|
|
def make_signer(i: int):
|
|
"""Deterministic (sk, pk, msg, secnonce, pubnonce). sk is normalized so pk has even y."""
|
|
sk = scalar_from_byte(i * 3 + 1)
|
|
pk = sk * G
|
|
if not has_even_y(pk):
|
|
sk = -sk
|
|
pk = sk * G
|
|
r1 = scalar_from_byte(i * 3 + 2)
|
|
r2 = scalar_from_byte(i * 3 + 3)
|
|
R1, R2 = r1 * G, r2 * G
|
|
msg = bytes([0x80 + i] * 32)
|
|
return sk, pk, msg, (r1, r2), (R1, R2)
|
|
|
|
|
|
def make_same_pk_signers():
|
|
"""Two signers sharing one key, signing different messages."""
|
|
sk = scalar_from_byte(1)
|
|
pk = sk * G
|
|
if not has_even_y(pk):
|
|
sk = -sk
|
|
pk = sk * G
|
|
msg_a = bytes([0xA0] * 32)
|
|
msg_b = bytes([0xA1] * 32)
|
|
r1_a, r2_a = scalar_from_byte(11), scalar_from_byte(12)
|
|
r1_b, r2_b = scalar_from_byte(13), scalar_from_byte(14)
|
|
return [
|
|
(sk, pk, msg_a, (r1_a, r2_a), (r1_a * G, r2_a * G)),
|
|
(sk, pk, msg_b, (r1_b, r2_b), (r1_b * G, r2_b * G)),
|
|
]
|
|
|
|
|
|
def make_tweaked_signer(i: int, tweak_byte: int, is_xonly: bool):
|
|
"""Signer with a tweaked key pair, using an odd-y base for X-only tweaks to exercise normalization."""
|
|
sk, pk, msg, secnonce, pubnonce = make_signer(i)
|
|
if is_xonly:
|
|
sk, pk = -sk, -pk
|
|
t = scalar_from_byte(tweak_byte)
|
|
return TweakSK(sk, t, is_xonly), TweakPK(pk, t, is_xonly), msg, secnonce, pubnonce
|
|
|
|
|
|
def make_duplicate_pm_signers():
|
|
"""Two signers with identical (pk, m) but distinct nonces."""
|
|
sk = scalar_from_byte(1)
|
|
pk = sk * G
|
|
if not has_even_y(pk):
|
|
sk = -sk
|
|
pk = sk * G
|
|
msg = bytes([0xC0] * 32)
|
|
r1_a, r2_a = scalar_from_byte(20), scalar_from_byte(21)
|
|
r1_b, r2_b = scalar_from_byte(22), scalar_from_byte(23)
|
|
return [
|
|
(sk, pk, msg, (r1_a, r2_a), (r1_a * G, r2_a * G)),
|
|
(sk, pk, msg, (r1_b, r2_b), (r1_b * G, r2_b * G)),
|
|
]
|
|
|
|
|
|
def serialize_pubnonce(pubnonce) -> bytes:
|
|
R1, R2 = pubnonce
|
|
return R1.to_bytes_compressed() + R2.to_bytes_compressed()
|
|
|
|
|
|
def serialize_secnonce(secnonce) -> bytes:
|
|
r1, r2 = secnonce
|
|
return r1.to_bytes() + r2.to_bytes()
|
|
|
|
|
|
def serialize_sig(sig) -> bytes:
|
|
R, s = sig
|
|
return R.to_bytes_xonly() + s.to_bytes()
|
|
|
|
|
|
def serialize_pk(pk: GE) -> bytes:
|
|
return pk.to_bytes_xonly()
|
|
|
|
|
|
def format_list(items: List[bytes], sep: str = ";") -> str:
|
|
if not items:
|
|
return ""
|
|
return sep.join(item.hex().upper() for item in items)
|
|
|
|
|
|
def sign_session(signers):
|
|
"""Run NonceAgg -> Sign (per signer) -> SigAgg with deterministic inputs."""
|
|
sks = [s[0] for s in signers]
|
|
pks = [GE.from_bytes_xonly(s[1].to_bytes_xonly()) for s in signers]
|
|
msgs = [s[2] for s in signers]
|
|
secnonces = [s[3] for s in signers]
|
|
pubnonces = [s[4] for s in signers]
|
|
aggnonce = NonceAgg(pubnonces)
|
|
psigs = [
|
|
Sign(secnonces[i], sks[i], msgs[i], aggnonce, pks, msgs, pubnonces)
|
|
for i in range(len(sks))
|
|
]
|
|
sig = SigAgg(aggnonce, pks, msgs, pubnonces, psigs)
|
|
return aggnonce, psigs, sig
|
|
|
|
|
|
def sign_unchecked(signers, msgs, r: Scalar):
|
|
"""Aggregate signature from a party holding all secret keys, skipping the message length check."""
|
|
R = r * G
|
|
if not has_even_y(R):
|
|
r = -r
|
|
R = r * G
|
|
L = b"".join(serialize_pk(s[1]) + m for s, m in zip(signers, msgs))
|
|
s = r
|
|
for (sk, pk, *_), m in zip(signers, msgs):
|
|
d = sk if has_even_y(pk) else -sk
|
|
c = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_SIG, L + R.to_bytes_xonly() + serialize_pk(pk) + m))
|
|
s = s + c * d
|
|
return R, s
|
|
|
|
|
|
def gen_nonce_gen_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index", "rand_", "secret_key", "extra_in",
|
|
"expected_secnonce", "expected_pubnonce", "comment",
|
|
))
|
|
|
|
rand_prime = bytes([0x0F] * 32)
|
|
|
|
def row(idx, sk, extra_in, comment):
|
|
secnonce, pubnonce = NonceGenInternal(rand_prime, sk, extra_in)
|
|
writer.writerow((
|
|
idx, rand_prime.hex().upper(),
|
|
sk.to_bytes().hex().upper() if sk is not None else "",
|
|
extra_in.hex().upper(),
|
|
serialize_secnonce(secnonce).hex().upper(),
|
|
serialize_pubnonce(pubnonce).hex().upper(),
|
|
comment,
|
|
))
|
|
|
|
sk = scalar_from_byte(0x02)
|
|
extra_in = bytes([0x08] * 32)
|
|
row(0, sk, extra_in, "Secret key and extra_in")
|
|
row(1, sk, b"", "Secret key and no extra_in")
|
|
row(2, None, extra_in, "No secret key and extra_in")
|
|
row(3, None, b"", "No secret key and no extra_in")
|
|
|
|
|
|
def gen_nonce_agg_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index", "pubnonces", "expected_result", "expected_aggnonce", "comment",
|
|
))
|
|
|
|
def nonce_agg_bytes(pubnonces):
|
|
return NonceAgg([(GE.from_bytes_compressed(p[:33]), GE.from_bytes_compressed(p[33:]))
|
|
for p in pubnonces])
|
|
|
|
def row(idx, pubnonces, comment):
|
|
aggnonce = serialize_pubnonce(nonce_agg_bytes(pubnonces))
|
|
writer.writerow((idx, format_list(pubnonces), "TRUE", aggnonce.hex().upper(), comment))
|
|
|
|
def error_row(idx, pubnonces, comment):
|
|
try:
|
|
nonce_agg_bytes(pubnonces)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
raise RuntimeError(f"NonceAgg unexpectedly succeeded: {comment}")
|
|
writer.writerow((idx, format_list(pubnonces), "FALSE", "", comment))
|
|
|
|
signers = [make_signer(i) for i in range(3)]
|
|
pubnonces = [serialize_pubnonce(s[4]) for s in signers]
|
|
(R1_0, R2_0), (R1_1, R2_1) = signers[0][4], signers[1][4]
|
|
row(0, pubnonces[:2], "Two signers")
|
|
row(1, pubnonces, "Three signers")
|
|
error_row(2, [pubnonces[0], b"\x04" + pubnonces[1][1:]],
|
|
"Signer 1's pubnonce first half has prefix 0x04")
|
|
error_row(3, [pubnonces[0][:33] + b"\x02" + bytes(32), pubnonces[1]],
|
|
"Signer 0's pubnonce second half is not on the curve")
|
|
error_row(4, [pubnonces[0][:33] + b"\x02" + bytes_from_int(FE.SIZE + 1), pubnonces[1]],
|
|
"Signer 0's pubnonce second half exceeds field size")
|
|
error_row(5, [pubnonces[0], serialize_pubnonce((-R1_0, R2_1))],
|
|
"Aggregate nonce R1 is infinity")
|
|
error_row(6, [pubnonces[0], serialize_pubnonce((R1_1, -R2_0))],
|
|
"Aggregate nonce R2 is infinity")
|
|
|
|
|
|
def gen_sign_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index",
|
|
"secret_keys",
|
|
"messages",
|
|
"secnonces",
|
|
"expected_aggnonce",
|
|
"expected_partial_sigs",
|
|
"expected_sig",
|
|
"comment",
|
|
))
|
|
|
|
def write_row(idx, signers, comment):
|
|
aggnonce, psigs, sig = sign_session(signers)
|
|
writer.writerow((
|
|
idx,
|
|
format_list([s[0].to_bytes() for s in signers]),
|
|
format_list([s[2] for s in signers]),
|
|
format_list([serialize_secnonce(s[3]) for s in signers]),
|
|
serialize_pubnonce(aggnonce).hex().upper(),
|
|
format_list([p.to_bytes() for p in psigs]),
|
|
serialize_sig(sig).hex().upper(),
|
|
comment,
|
|
))
|
|
pks = [s[1] for s in signers]
|
|
msgs = [s[2] for s in signers]
|
|
pubnonces = [s[4] for s in signers]
|
|
for i in range(len(signers)):
|
|
assert PartialSigVerify(psigs[i], pks, msgs, pubnonces, i), \
|
|
f"generated psig failed self-verification: {comment}"
|
|
assert Verify(pks, msgs, sig), \
|
|
f"generated sig failed self-verification: {comment}"
|
|
|
|
write_row(0, [make_signer(0)], "Single signer")
|
|
write_row(1, [make_signer(i) for i in range(2)], "Two signers")
|
|
write_row(2, [make_signer(i) for i in range(3)], "Three signers")
|
|
write_row(3, [make_signer(i) for i in range(5)], "Five signers")
|
|
write_row(4, make_same_pk_signers(), "Same pubkey signs two different messages")
|
|
write_row(5, [make_tweaked_signer(0, 0x21, False), make_tweaked_signer(1, 0x22, True)],
|
|
"Signers with tweaked keys")
|
|
write_row(6, make_duplicate_pm_signers(), "Exact duplicate (pk, m) entries")
|
|
|
|
|
|
def gen_sign_error_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index", "secret_key", "message", "secnonce", "aggnonce",
|
|
"pubkeys", "messages", "pubnonces",
|
|
"comment",
|
|
))
|
|
|
|
def write_row(idx, signer, pks, msgs, pubnonces, comment, aggnonce=None):
|
|
sk, _, msg, secnonce, _ = signer
|
|
if aggnonce is None:
|
|
aggnonce = serialize_pubnonce(NonceAgg(pubnonces))
|
|
try:
|
|
R1, R2 = GE.from_bytes_compressed(aggnonce[:33]), GE.from_bytes_compressed(aggnonce[33:])
|
|
Sign(secnonce, sk, msg, (R1, R2), pks, msgs, pubnonces)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
raise RuntimeError(f"Sign unexpectedly succeeded: {comment}")
|
|
writer.writerow((
|
|
idx,
|
|
sk.to_bytes().hex().upper(),
|
|
msg.hex().upper(),
|
|
serialize_secnonce(secnonce).hex().upper(),
|
|
aggnonce.hex().upper(),
|
|
format_list([serialize_pk(p) for p in pks]),
|
|
format_list(msgs),
|
|
format_list([serialize_pubnonce(p) for p in pubnonces]),
|
|
comment,
|
|
))
|
|
|
|
signers = [make_signer(i) for i in range(3)]
|
|
pks = [GE.from_bytes_xonly(s[1].to_bytes_xonly()) for s in signers]
|
|
msgs = [s[2] for s in signers]
|
|
pubnonces = [s[4] for s in signers]
|
|
|
|
dup_pubnonces = list(pubnonces)
|
|
dup_pubnonces[2] = (pubnonces[2][0], pubnonces[0][1])
|
|
write_row(0, signers[0], pks, msgs, dup_pubnonces,
|
|
"Signer's R2 appears at two indices")
|
|
|
|
missing_pubnonces = list(pubnonces)
|
|
missing_pubnonces[0] = (pubnonces[0][0], scalar_from_byte(0x63) * G)
|
|
write_row(1, signers[0], pks, msgs, missing_pubnonces,
|
|
"Signer's R2 appears at no index")
|
|
|
|
wrong_msgs = list(msgs)
|
|
wrong_msgs[0] = bytes([0xEE] * 32)
|
|
write_row(2, signers[0], pks, wrong_msgs, pubnonces,
|
|
"Message at signer's index was substituted")
|
|
|
|
wrong_pks = list(pks)
|
|
wrong_pks[0] = GE.from_bytes_xonly((scalar_from_byte(0x55) * G).to_bytes_xonly())
|
|
write_row(3, signers[0], wrong_pks, msgs, pubnonces,
|
|
"Public key at signer's index was substituted")
|
|
|
|
sk, pk, msg, secnonce, pubnonce = signers[0]
|
|
write_row(4, (sk, pk, msg, (Scalar(0), Scalar(0)), pubnonce), pks, msgs, pubnonces,
|
|
"Secnonce is all zeros")
|
|
write_row(5, (Scalar(0), pk, msg, secnonce, pubnonce), pks, msgs, pubnonces,
|
|
"Secret key is zero")
|
|
|
|
aggnonce = serialize_pubnonce(NonceAgg(pubnonces))
|
|
write_row(6, signers[0], pks, msgs, pubnonces,
|
|
"Aggregate nonce first half is all zeros", bytes(33) + aggnonce[33:])
|
|
write_row(7, signers[0], pks, msgs, pubnonces,
|
|
"Aggregate nonce second half is not on the curve", aggnonce[:33] + b"\x02" + bytes(32))
|
|
write_row(8, signers[0], pks, msgs, pubnonces,
|
|
"Aggregate nonce second half exceeds field size",
|
|
aggnonce[:33] + b"\x02" + bytes_from_int(FE.SIZE + 1))
|
|
|
|
|
|
def gen_verify_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index", "pubkeys", "messages", "sig", "expected_result", "comment",
|
|
))
|
|
|
|
idx = 0
|
|
|
|
signers = [make_signer(i) for i in range(2)]
|
|
_, _, sig = sign_session(signers)
|
|
pks = [serialize_pk(s[1]) for s in signers]
|
|
msgs = [s[2] for s in signers]
|
|
sig_bytes = serialize_sig(sig)
|
|
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
sig_bytes.hex().upper(), "TRUE",
|
|
"Two-signer valid aggregate"))
|
|
idx += 1
|
|
|
|
signers3 = [make_signer(i) for i in range(3)]
|
|
_, _, sig3 = sign_session(signers3)
|
|
pks3 = [serialize_pk(s[1]) for s in signers3]
|
|
msgs3 = [s[2] for s in signers3]
|
|
sig3_bytes = serialize_sig(sig3)
|
|
writer.writerow((idx, format_list(pks3), format_list(msgs3),
|
|
sig3_bytes.hex().upper(), "TRUE",
|
|
"Three-signer valid aggregate"))
|
|
idx += 1
|
|
|
|
wrong_msgs = [msgs[0], bytes([0xff] * 32)]
|
|
writer.writerow((idx, format_list(pks), format_list(wrong_msgs),
|
|
sig_bytes.hex().upper(), "FALSE",
|
|
"Wrong message for second signer"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, format_list([pks[1], pks[0]]), format_list(msgs),
|
|
sig_bytes.hex().upper(), "FALSE",
|
|
"Pubkey order swapped"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, format_list([pks[0]]), format_list([msgs[0]]),
|
|
sig_bytes.hex().upper(), "FALSE",
|
|
"Dropped second signer"))
|
|
idx += 1
|
|
|
|
s_int = int_from_bytes(sig_bytes[32:64])
|
|
tampered = sig_bytes[:32] + bytes_from_int(s_int ^ 1)
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
tampered.hex().upper(), "FALSE",
|
|
"Tampered s (bit flip)"))
|
|
idx += 1
|
|
|
|
s_zero = sig_bytes[:32] + bytes(32)
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
s_zero.hex().upper(), "FALSE",
|
|
"s = 0 (in range, wrong)"))
|
|
idx += 1
|
|
|
|
s_eq_n = sig_bytes[:32] + bytes_from_int(n)
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
s_eq_n.hex().upper(), "FALSE",
|
|
"s = n (out of range)"))
|
|
idx += 1
|
|
|
|
s_max = sig_bytes[:32] + bytes([0xff] * 32)
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
s_max.hex().upper(), "FALSE",
|
|
"s = 2^256 - 1"))
|
|
idx += 1
|
|
|
|
def find_off_curve_x() -> bytes:
|
|
for i in range(1, 256):
|
|
cand = bytes([i] * 32)
|
|
try:
|
|
GE.from_bytes_xonly(cand)
|
|
except Exception:
|
|
return cand
|
|
raise RuntimeError("could not find an off-curve x")
|
|
|
|
off_curve = find_off_curve_x()
|
|
|
|
bad_R_sig = off_curve + sig_bytes[32:64]
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
bad_R_sig.hex().upper(), "FALSE",
|
|
"R x-coord not on curve"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, format_list([off_curve, pks[1]]), format_list(msgs),
|
|
sig_bytes.hex().upper(), "FALSE",
|
|
"Public key not on curve"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
sig_bytes[:-1].hex().upper(), "FALSE",
|
|
"Signature too short"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, format_list(pks), format_list(msgs),
|
|
(sig_bytes + b"\x00").hex().upper(), "FALSE",
|
|
"Signature too long"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, format_list(pks), format_list([msgs[0]]),
|
|
sig_bytes.hex().upper(), "FALSE",
|
|
"Pubkey/message count mismatch"))
|
|
idx += 1
|
|
|
|
writer.writerow((idx, "", "",
|
|
sig_bytes.hex().upper(), "FALSE",
|
|
"Empty signer list"))
|
|
idx += 1
|
|
|
|
for bad_m0, comment in ((msgs[0][:31], "Message too short"), (msgs[0] + b"\x00", "Message too long")):
|
|
bad_msgs = [bad_m0, msgs[1]]
|
|
bad_sig = serialize_sig(sign_unchecked(signers, bad_msgs, scalar_from_byte(0x70)))
|
|
writer.writerow((idx, format_list(pks), format_list(bad_msgs),
|
|
bad_sig.hex().upper(), "FALSE", comment))
|
|
idx += 1
|
|
|
|
|
|
def gen_partial_sig_verify_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index", "signer_index", "psig",
|
|
"pubkeys", "messages", "pubnonces",
|
|
"expected_result", "comment",
|
|
))
|
|
|
|
signers = [make_signer(i) for i in range(2)]
|
|
aggnonce, psigs, _ = sign_session(signers)
|
|
pks_hex = format_list([serialize_pk(s[1]) for s in signers])
|
|
msgs_hex = format_list([s[2] for s in signers])
|
|
pubnonces_hex = format_list([serialize_pubnonce(s[4]) for s in signers])
|
|
|
|
def write_row(idx, signer_index, psig_bytes, expected, comment):
|
|
writer.writerow((
|
|
idx, signer_index, psig_bytes.hex().upper(),
|
|
pks_hex, msgs_hex, pubnonces_hex,
|
|
expected, comment,
|
|
))
|
|
|
|
idx = 0
|
|
write_row(idx, 0, psigs[0].to_bytes(), "TRUE", "Honest partial sig (signer 0)")
|
|
idx += 1
|
|
write_row(idx, 1, psigs[1].to_bytes(), "TRUE", "Honest partial sig (signer 1)")
|
|
idx += 1
|
|
write_row(idx, 1, psigs[0].to_bytes(), "FALSE",
|
|
"Honest psig verified at the wrong signer_index")
|
|
idx += 1
|
|
write_row(idx, 0, bytes_from_int(int(psigs[0]) ^ 1), "FALSE",
|
|
"Tampered partial sig (bit flip)")
|
|
idx += 1
|
|
write_row(idx, 0, bytes(32), "FALSE", "psig = 0 (in range, wrong)")
|
|
idx += 1
|
|
write_row(idx, 0, bytes_from_int(n), "FALSE", "psig = n (out of range)")
|
|
idx += 1
|
|
write_row(idx, 2, psigs[0].to_bytes(), "FALSE", "signer_index out of range")
|
|
idx += 1
|
|
fake_aggnonce = (aggnonce[0] + G, aggnonce[1])
|
|
psig_wrong = Sign(signers[0][3], signers[0][0], signers[0][2], fake_aggnonce,
|
|
[s[1] for s in signers], [s[2] for s in signers],
|
|
[s[4] for s in signers])
|
|
write_row(idx, 0, psig_wrong.to_bytes(), "FALSE", "psig for a different aggregate nonce")
|
|
idx += 1
|
|
R1_0, R2_0 = signers[0][4]
|
|
_, R2_1 = signers[1][4]
|
|
inf_pubnonces = format_list([serialize_pubnonce((R1_0, R2_0)),
|
|
serialize_pubnonce((-R1_0, R2_1))])
|
|
writer.writerow((idx, 0, psigs[0].to_bytes().hex().upper(),
|
|
pks_hex, msgs_hex, inf_pubnonces,
|
|
"FALSE", "Aggregate nonce R1 is infinity"))
|
|
|
|
|
|
def gen_tweak_vectors(f):
|
|
writer = csv.writer(f)
|
|
writer.writerow((
|
|
"index", "internal_secret_key", "tweak", "is_xonly", "expected_result",
|
|
"expected_secret_key", "expected_pubkey", "comment",
|
|
))
|
|
|
|
def row(idx, sk, t, is_xonly, comment):
|
|
sk_out = TweakSK(sk, t, is_xonly)
|
|
pk_out = TweakPK(sk * G, t, is_xonly)
|
|
assert sk_out * G == pk_out
|
|
writer.writerow((
|
|
idx, sk.to_bytes().hex().upper(), t.to_bytes().hex().upper(),
|
|
"TRUE" if is_xonly else "FALSE", "TRUE",
|
|
sk_out.to_bytes().hex().upper(), pk_out.to_bytes_compressed().hex().upper(),
|
|
comment,
|
|
))
|
|
|
|
def error_row(idx, sk, t, is_xonly, comment):
|
|
for tweak_fn, key in ((TweakSK, sk), (TweakPK, sk * G)):
|
|
try:
|
|
tweak_fn(key, t, is_xonly)
|
|
except ValueError:
|
|
continue
|
|
raise RuntimeError(f"{tweak_fn.__name__} unexpectedly succeeded: {comment}")
|
|
writer.writerow((
|
|
idx, sk.to_bytes().hex().upper(), t.to_bytes().hex().upper(),
|
|
"TRUE" if is_xonly else "FALSE", "FALSE", "", "",
|
|
comment,
|
|
))
|
|
|
|
sk1 = scalar_from_byte(1)
|
|
even = sk1 if has_even_y(sk1 * G) else -sk1
|
|
odd = -even
|
|
t = scalar_from_byte(0x02)
|
|
row(0, even, t, False, "Plain tweak of an even-y key")
|
|
row(1, odd, t, False, "Plain tweak of an odd-y key")
|
|
row(2, even, t, True, "X-only tweak of an even-y key")
|
|
row(3, odd, t, True, "X-only tweak of an odd-y key")
|
|
error_row(4, odd, -odd, False, "Plain tweak resulting in the point at infinity")
|
|
error_row(5, odd, odd, True, "X-only tweak resulting in the point at infinity")
|
|
writer.writerow((6, even.to_bytes().hex().upper(), bytes_from_int(n).hex().upper(),
|
|
"FALSE", "FALSE", "", "", "Tweak = n (out of range)"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
os.makedirs(VECTORS_DIR, exist_ok=True)
|
|
|
|
print(f"Generating {NONCE_GEN_VECTORS}")
|
|
with open(NONCE_GEN_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_nonce_gen_vectors(f)
|
|
|
|
print(f"Generating {NONCE_AGG_VECTORS}")
|
|
with open(NONCE_AGG_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_nonce_agg_vectors(f)
|
|
|
|
print(f"Generating {SIGN_VECTORS}")
|
|
with open(SIGN_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_sign_vectors(f)
|
|
|
|
print(f"Generating {SIGN_ERROR_VECTORS}")
|
|
with open(SIGN_ERROR_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_sign_error_vectors(f)
|
|
|
|
print(f"Generating {VERIFY_VECTORS}")
|
|
with open(VERIFY_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_verify_vectors(f)
|
|
|
|
print(f"Generating {PARTIAL_SIG_VERIFY_VECTORS}")
|
|
with open(PARTIAL_SIG_VERIFY_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_partial_sig_verify_vectors(f)
|
|
|
|
print(f"Generating {TWEAK_VECTORS}")
|
|
with open(TWEAK_VECTORS, "w", newline="", encoding="utf-8") as f:
|
|
gen_tweak_vectors(f)
|
|
|
|
print("Done.")
|