Skip to content

Instantly share code, notes, and snippets.

@jaonoctus
Last active July 14, 2025 21:43
Show Gist options
  • Save jaonoctus/ec40e7b8ecdf77918281f07ef147ac28 to your computer and use it in GitHub Desktop.
Save jaonoctus/ec40e7b8ecdf77918281f07ef147ac28 to your computer and use it in GitHub Desktop.
# --- Setup secp256k1 ---
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
a_curve, b_curve = 0, 7
E = EllipticCurve(GF(p), [a_curve, b_curve])
G = E(
55066263022277343669578718895168534326250603453777594175500187360389116729240,
32670510020758816978083085130507043184471273380659243275938904335757337482424,
)
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
import hashlib
from random import randint
import os
# --- Constants ---
DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_Cashu_"
# --- Hash-to-curve implementation ---
def hash_to_curve(x_hex: str):
"""Implements Y = h'(x)"""
x_bytes = bytes.fromhex(x_hex)
counter = 0
while True:
msg_hash = hashlib.sha256(DOMAIN_SEPARATOR + x_bytes).digest()
digest = hashlib.sha256(msg_hash + counter.to_bytes(4, 'little')).digest()
x_candidate = int.from_bytes(digest, 'big') % p
try:
point = E.lift_x(x_candidate)
return point
except (ValueError, ArithmeticError):
counter += 1
# --- Fiat–Shamir challenge hash ---
def challenge_hash(*points):
h = hashlib.sha256()
for P in points:
x, y = P.xy()
h.update(int(x).to_bytes(32, 'big'))
h.update(int(y).to_bytes(32, 'big'))
return int.from_bytes(h.digest(), 'big') % n
# --- Alice (user) setup ---
x_bytes = os.urandom(32)
x_hex = x_bytes.hex()
r = randint(1, n - 1)
R = r * G # R = rG
Y = hash_to_curve(x_hex) # Y = h'(x)
T = Y + R # T = Y + R
# --- Bob (mint) setup ---
a = randint(1, n - 1)
A = a * G # A = aG
Q = a * T # Q = aT
# --- DLEQ proof generation ---
j = randint(1, n - 1)
J1 = j * G # J1 = jG
J2 = j * T # J2 = jT
e = challenge_hash(J1, J2, A, Q) # e = h(J1 | J2 | A | Q)
s = (j + e * a) % n # s = j + ea
# --- Alice unblinds the signature ---
Z = Q - r * A # Z = Q - rA = aY
# --- DLEQ verification ---
J1_check = s * G - e * A # J1' = sG - eA
J2_check = s * T - e * Q # J2' = sT - eQ
e_check = challenge_hash(J1_check, J2_check, A, Q) # DLEQ uses Q
dleq_valid = (e_check == e)
# --- Token verification (unblinded signature check) ---
Y_check = hash_to_curve(x_hex)
valid = a * Y_check == Z # Verify Z = aY
# --- Output ---
print("\n--- Alice (user) setup ---")
print("x (secret):", x_hex)
print("Y = h'(x) =", Y.xy())
print("R = rG =", R.xy())
print("T = Y + R =", T.xy())
print("\n--- Bob (mint) setup ---")
print("A = aG =", A.xy())
print("Q = aT =", Q.xy())
print("\n--- DLEQ proof generation ---")
print("J1 = jG =", J1.xy())
print("J2 = jT =", J2.xy())
print("e = h(J1 | J2 | A | Q) =", e)
print("s = j + ea =", s)
print("\n--- Alice unblinds the signature ---")
print("Z = Q - rA = aY =", Z.xy())
print("\n--- DLEQ verification ---")
print("J1' = sG - eA =", J1_check.xy())
print("J2' = sT - eQ =", J2_check.xy())
print("DLEQ valid (e_check == e):", dleq_valid)
print("\n--- Token verification (unblinded signature check) ---")
print("Z == aY (valid token):", valid)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment