from Crypto.Util.number import getRandomRange
from Crypto.Hash import SHA256
class ORProofZKP:
"""OR-proof (Cramer-Damgard-Schoenmakers 1994) : prouver qu'on connait
le log discret de y1 OU celui de y2, SANS reveler lequel des deux.
Reutilise les parametres du groupe de Schnorr (p=2q+1 premier sur, g d'ordre q)."""
def __init__(self, p, q, g):
self.p, self.q, self.g = p, q, g
def _challenge(self, y1, y2, R1, R2):
"""Challenge global via Fiat-Shamir (hash de tous les engagements)."""
h = SHA256.new()
for val in (self.g, y1, y2, R1, R2):
h.update(val.to_bytes(256, 'big'))
return int.from_bytes(h.digest(), 'big') % self.q
def prove(self, known_idx, x_known, y1, y2):
"""known_idx in {1, 2} : le prouveur connait x_known = log_g(y_{known_idx}).
L'autre branche est SIMULEE (le prouveur ne la connait pas), la branche
connue est honnete. (R1, R2) sont places en position fixe pour le verificateur."""
y_other = y2 if known_idx == 1 else y1
# 1. Simuler la branche 'other' : choisir c_other, s_other, en deduire R_other
c_other = getRandomRange(1, self.q)
s_other = getRandomRange(1, self.q)
R_other = (pow(self.g, s_other, self.p) * pow(y_other, self.q - c_other, self.p)) % self.p
# 2. Branche honnete : engagement R_known = g^r
r_known = getRandomRange(1, self.q)
R_known = pow(self.g, r_known, self.p)
# 3. Assembler (R1, R2) en position fixe + challenge global
if known_idx == 1:
R1, R2 = R_known, R_other
else:
R1, R2 = R_other, R_known
c = self._challenge(y1, y2, R1, R2)
c_known = (c - c_other) % self.q # la somme c1+c2 doit egaler c
s_known = (r_known + c_known * x_known) % self.q
# 4. Renvoyer les champs en position fixe (y1->slot1, y2->slot2)
if known_idx == 1:
return {"R1": R1, "R2": R2, "c1": c_known, "c2": c_other,
"s1": s_known, "s2": s_other}
return {"R1": R1, "R2": R2, "c1": c_other, "c2": c_known,
"s1": s_other, "s2": s_known}
def verify(self, y1, y2, proof):
"""Le verificateur reconstruit les engagements et verifie c1 + c2 == c."""
R1, R2 = proof["R1"], proof["R2"]
c1, c2, s1, s2 = proof["c1"], proof["c2"], proof["s1"], proof["s2"]
R1p = (pow(self.g, s1, self.p) * pow(y1, self.q - c1, self.p)) % self.p
R2p = (pow(self.g, s2, self.p) * pow(y2, self.q - c2, self.p)) % self.p
c_prime = self._challenge(y1, y2, R1p, R2p)
return (c1 + c2) % self.q == c_prime
# Reutiliser le groupe de Schnorr (p=2q+1, g d'ordre q)
or_pf = ORProofZKP(schnorr.p, schnorr.q, schnorr.g)
# Deux cles publiques : le prouveur connait le secret de la 1ere, pas de la 2eme
or_x1 = getRandomRange(1, schnorr.q); or_y1 = pow(schnorr.g, or_x1, schnorr.p)
or_x2 = getRandomRange(1, schnorr.q); or_y2 = pow(schnorr.g, or_x2, schnorr.p)
print("OR-PROOF (CRAMER-DAMGARD-SCHOENMAKERS 1994)")
print("=" * 60)
print("Statement public : je connais log_g(y1) OU log_g(y2)")
print(f" or_y1 = g^or_x1 (le prouveur connait or_x1)")
print(f" or_y2 = g^or_x2 (le prouveur NE connait PAS or_x2)")
print()
# Cas 1 : le prouveur connait or_x1 (branche 1 honnete, branche 2 simulee)
proof1 = or_pf.prove(1, or_x1, or_y1, or_y2)
v1 = or_pf.verify(or_y1, or_y2, proof1)
print(f"Cas 1 - prouveur connait or_x1 (branche 1) : {'VALIDE' if v1 else 'INVALIDE'}")
print(" -> Le verificateur accepte, SANS savoir si c'etait or_x1 ou or_x2")
# Cas 2 : symetrique, le prouveur connait or_x2 (branche 2 honnete)
proof2 = or_pf.prove(2, or_x2, or_y1, or_y2)
v2 = or_pf.verify(or_y1, or_y2, proof2)
print(f"Cas 2 - prouveur connait or_x2 (branche 2) : {'VALIDE' if v2 else 'INVALIDE'}")
print(" -> Symetrique : la preuve est aussi valable depuis l'autre branche")
# Cas 3 : preuve truquee (on falsifie s1)
proof_fake = dict(proof1)
proof_fake["s1"] = (proof1["s1"] + 1) % schnorr.q
v3 = or_pf.verify(or_y1, or_y2, proof_fake)
print(f"Cas 3 - preuve truquee (s1 falsifie) : {'VALIDE' if v3 else 'INVALIDE'}")
print(" -> Modifier s1 casse l'equation de Schnorr -> rejet")
print()
# Indistinguabilite : c1 et c2 sont uniformes, le verificateur ne sait pas
print("Indistinguabilite :")
print(f" c1 = {proof1['c1']}")
print(f" c2 = {proof1['c2']}")
print(" (c1, c2) sont uniformement aleatoires ; le verificateur ne peut pas")
print(" determiner quelle branche etait honnete et laquelle etait simulee.")
print()
print("Applications : vote anonyme (prouver 'mon bulletin est 0 OU 1' sans")
print(" dire lequel), signatures en anneau (ring signatures, Monero).")