mirror of
https://github.com/bitcoin/bips.git
synced 2026-11-02 19:41:29 +00:00
198 lines
5.7 KiB
Python
198 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Schnorr signature half-aggregation reference implementation
|
|
|
|
WARNING: This implementation is for demonstration purposes only and _not_ to
|
|
be used in production environments.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
|
|
from secp256k1lab.secp256k1 import (
|
|
G,
|
|
GE,
|
|
Scalar,
|
|
)
|
|
from secp256k1lab.util import (
|
|
tagged_hash,
|
|
)
|
|
|
|
|
|
def Aggregate(pms):
|
|
"""
|
|
Aggregates an array of triples (public key, message, signature) into a single aggregate signature.
|
|
|
|
:param pms: An array of triples (public key, message, signature).
|
|
:return: The aggregate signature.
|
|
"""
|
|
|
|
# Let aggsig = bytes(0)
|
|
aggsig = bytes([0] * 32)
|
|
|
|
pm_aggd = []
|
|
|
|
# Return IncAggregate(aggsig, pms0..u-1); fail if that fails.
|
|
return IncAggregate(aggsig, pm_aggd, pms)
|
|
|
|
|
|
# IncAggregate(aggsig, pm_aggd0..v-1, pms_to_agg0..u-1)
|
|
def IncAggregate(aggsig, pm_aggd, pms_to_agg):
|
|
"""
|
|
Incrementally aggregates an additional array of triples (public key, message, signature)
|
|
into an existing aggregate signature.
|
|
|
|
:param aggsig: A byte array representing the aggregate signature.
|
|
:param pm_aggd: An array of tuples (public key, message).
|
|
:param pms_to_agg: An array of triples (public key, message, signature).
|
|
:return: The new aggregate signature.
|
|
"""
|
|
|
|
v = len(pm_aggd)
|
|
u = len(pms_to_agg)
|
|
|
|
# Fail if len(aggsig) ≠ 32 * (v + 1)
|
|
if len(aggsig) != 32 * (v + 1):
|
|
raise ValueError("Length of aggsig must be 32 * (v + 1)")
|
|
|
|
r_values = []
|
|
pmr_to_agg = []
|
|
|
|
# For i = 0 .. v-1:
|
|
for i in range(v):
|
|
# Let (pki, mi) = pm_aggdi
|
|
(pki, mi) = pm_aggd[i]
|
|
if len(pki) != 32 or len(mi) != 32:
|
|
raise ValueError("Public key and message must be 32 bytes")
|
|
|
|
# Let ri = aggsig[i⋅32:(i+1)⋅32]
|
|
ri = aggsig[i * 32:(i + 1) * 32]
|
|
r_values.append(ri)
|
|
|
|
pmr_to_agg.append((pki, mi, ri))
|
|
|
|
z_values = []
|
|
s_values = []
|
|
|
|
# For i = v .. v+u-1:
|
|
for i in range(v, v + u):
|
|
# Let (pki, mi, sigi) = pms_to_aggi-v
|
|
(pki, mi, sigi) = pms_to_agg[i - v]
|
|
if len(pki) != 32 or len(mi) != 32 or len(sigi) != 64:
|
|
raise ValueError("Public key and message must be 32 bytes and signature 64 bytes")
|
|
|
|
# Let ri = sigi[0:32]
|
|
ri = sigi[0:32]
|
|
r_values.append(ri)
|
|
|
|
# Let si = int(sigi[32:64]); fail if si ≥ n
|
|
si = Scalar.from_bytes_checked(sigi[32:64])
|
|
s_values.append(si)
|
|
|
|
# If i = 0:
|
|
# Let zi = 1
|
|
# Else:
|
|
# Let zi = int(hashBIP0458/randomizer(r0 || pk0 || m0 || ... || ri || pki || mi)) mod n
|
|
pmr_to_agg.append((pki, mi, ri))
|
|
z_values.append(hashHalfAgg_randomizer(pmr_to_agg))
|
|
|
|
# Let sagg = int(aggsig[v⋅32:(v+1)⋅32]); fail if sagg ≥ n
|
|
s = Scalar.from_bytes_checked(aggsig[v * 32:(v + 1) * 32])
|
|
|
|
# Let s = sagg + zv⋅sv + ... + zv+u-1⋅sv+u-1 mod n
|
|
for i in range(u):
|
|
s = s + z_values[i] * s_values[i]
|
|
|
|
# Return r0 || ... || rv+u-1 || bytes(s)
|
|
return b''.join(r_values) + s.to_bytes()
|
|
|
|
|
|
# VerifyAggregate(aggsig, pm_aggd0..u-1)
|
|
def VerifyAggregate(aggsig, pm_aggd):
|
|
"""
|
|
Verifies an aggregate signature against an array of public key and message tuples.
|
|
|
|
:param aggsig: A byte array representing the aggregate signature.
|
|
:param pm_aggd: An array of tuples (public key, message).
|
|
:return: Boolean indicating whether the verification is successful.
|
|
"""
|
|
|
|
u = len(pm_aggd)
|
|
|
|
# Fail if len(aggsig) ≠ 32 * (u + 1)
|
|
if len(aggsig) != 32 * (u + 1):
|
|
raise ValueError("Length of aggsig must be 32 * (u + 1)")
|
|
|
|
z_values = []
|
|
R_values = []
|
|
P_values = []
|
|
e_values = []
|
|
r_values = []
|
|
|
|
# For i = 0 .. u-1:
|
|
for i in range(u):
|
|
# Let (pki, mi) = pm_aggdi
|
|
(pki, mi) = pm_aggd[i]
|
|
if len(pki) != 32 or len(mi) != 32:
|
|
raise ValueError("Public key and message must be 32 bytes")
|
|
|
|
# Let Pi = lift_x(int(pki)); fail if that fails
|
|
try:
|
|
Pi = GE.from_bytes_xonly(pki)
|
|
except ValueError:
|
|
return False
|
|
P_values.append(Pi)
|
|
|
|
# Let ri = aggsig[i⋅32:(i+1)⋅32]
|
|
ri = aggsig[i * 32:(i + 1) * 32]
|
|
# Let Ri = lift_x(int(ri)); fail if that fails
|
|
try:
|
|
Ri = GE.from_bytes_xonly(ri)
|
|
except ValueError:
|
|
return False
|
|
R_values.append(Ri)
|
|
r_values.append(ri)
|
|
|
|
# Let ei = int(hashBIP0340/challenge(ri || pki || mi)) mod n
|
|
ei = Scalar.from_bytes_wrapping(hashBIP0340_challenge(ri, pki, mi))
|
|
e_values.append(ei)
|
|
|
|
# If i = 0:
|
|
# Let zi = 1
|
|
# Else:
|
|
# Let zi = int(hashBIP0458/randomizer(r0 || pk0 || m0 || ... || ri || pki || mi)) mod n
|
|
pmr = [(pki, mi, ri) for (pki, mi), ri in zip(pm_aggd, r_values)]
|
|
z_values.append(hashHalfAgg_randomizer(pmr))
|
|
|
|
# Let s = int(aggsig[u⋅32:(u+1)⋅32]); fail if s ≥ n
|
|
try:
|
|
s = Scalar.from_bytes_checked(aggsig[u * 32:(u + 1) * 32])
|
|
except ValueError:
|
|
return False
|
|
|
|
# Fail if s⋅G ≠ z0⋅(R0 + e0⋅P0) + ... + zu-1⋅(Ru-1 + eu-1⋅Pu-1)
|
|
lhs = s * G
|
|
rhs = GE()
|
|
for i in range(u):
|
|
rhs += z_values[i] * (R_values[i] + e_values[i] * P_values[i])
|
|
|
|
return lhs == rhs
|
|
|
|
|
|
def hashBIP0340_challenge(sig, pubkey, msg):
|
|
return tagged_hash("BIP0340/challenge", sig + pubkey + msg)
|
|
|
|
|
|
def hashHalfAgg_randomizer(pmr):
|
|
if len(pmr) == 1:
|
|
return Scalar(1)
|
|
|
|
random_input = bytes()
|
|
for (pki, mi, ri) in pmr:
|
|
random_input += ri
|
|
random_input += pki
|
|
random_input += mi
|
|
|
|
return Scalar.from_bytes_wrapping(tagged_hash("BIP0458/randomizer", random_input))
|