from hashlib import sha256 # Implementation of Sigma protocol & OR proofs def hash_two_points(a, b): h = sha256((str(a)+str(b)).encode('utf-8')) return int(h.hexdigest(), 16) def generic_verify(g, X, A, c, z): return g * int(z) == X * int(c) + A ### # Sigma protocol interactive ### class Prover_interactive: def __init__(self, F, g): self.F = F # Z_q self.g = g # elliptic curve generator def new_key(self): self.w = self.F.random_element() X = self.g * int(self.w) return X def new_commitment(self): self.a = self.F.random_element() A = self.g * int(self.a) return A def gen_proof(self, c): return int(self.a) + int(c) * int(self.w) class Verifier_interactive: def __init__(self, F, g): self.F = F self.g = g def new_challenge(self, A): self.A = A self.c = self.F.random_element() return self.c def verify(self, X, z): return self.g * int(z) == X * int(self.c) + self.A ### # Sigma protocol non-interactive ### class Prover: def __init__(self, F, g): self.F = F # Z_p self.g = g # elliptic curve generator def new_key(self): self.w = self.F.random_element() X = self.g * int(self.w) return X def gen_proof(self, X): a = self.F.random_element() A = self.g * int(a) c = hash_two_points(A, X) z = int(a) + c * int(self.w) return A, z class Verifier: def __init__(self, F, g): self.F = F self.g = g def verify(self, X, A, z): c = hash_two_points(A, X) return self.g * int(z) == X * c + A class Simulator: def __init__(self, F, g): self.F = F self.g = g def simulate(self, X): c = self.F.random_element() z = self.F.random_element() # A = g * int(z) + X*(-int(c)) A = g * int(z) - X * int(c) return A, c, z ### # OR proof (with 2 parties) ### class ORProver_2parties: def __init__(self, F, g): self.F = F # Z_p self.g = g # elliptic curve generator def new_key(self): self.w = self.F.random_element() X = self.g * int(self.w) return X def gen_commitments(self, xs): # gen commitment A self.a = self.F.random_element() A = self.g * int(self.a) # run the simulator for 1-b sim = Simulator(self.F, self.g) A_1, c_1, z_1 = sim.simulate(xs[1]) self.A_1 = A_1 self.c_1 = c_1 self.z_1 = z_1 return [A, A_1] def gen_proof(self, s): # split the challenge s = c xor c_1 c = int(s) ^^ int(self.c_1) # compute z z = int(self.a) + int(c) * int(self.w) # note, here the order of the returned elements is always the same, in # a real-world implementation would be shuffled return [c, self.c_1], [z, self.z_1] class ORVerifier_2parties: def __init__(self, F, g): self.F = F self.g = g def new_challenge(self, As): self.As = As self.s = self.F.random_element() return self.s def verify(self, Xs, cs, zs): assert self.s == int(cs[0]) ^^ int(cs[1]) assert self.g * int(zs[0]) == Xs[0] * int(cs[0]) + self.As[0] assert self.g * int(zs[1]) == Xs[1] * int(cs[1]) + self.As[1] ### # OR proof (with n parties) ### class ORProver: def __init__(self, F, g): self.F = F # Z_p self.g = g # elliptic curve generator def new_key(self): self.w = self.F.random_element() X = self.g * int(self.w) return X def gen_commitments(self, xs): # gen commitment A self.a = self.F.random_element() A = self.g * int(self.a) self.As = [A] # run the simulator for the rest of Xs sim = Simulator(self.F, self.g) self.cs = [] self.zs = [] for i in range(1, len(xs)): A_1, c_1, z_1 = sim.simulate(xs[i]) self.As.append(A_1) self.cs.append(c_1) self.zs.append(z_1) return self.As def gen_proof(self, s): # split the challenge s = c xor c_1 xor c_2 xor ... xor c_n c = int(s) for i in range(len(self.cs)): c = c ^^ int(self.cs[i]) self.cs.insert(0, c) # add c at the beginning of cs array # compute z z = int(self.a) + int(c) * int(self.w) self.zs.insert(0, z) # add z at the beginning of zs array # note, here the order of the returned elements is always the same, in # a real-world implementation would be shuffled return self.cs, self.zs class ORVerifier: def __init__(self, F, g): self.F = F self.g = g def new_challenge(self, As): self.As = As self.s = self.F.random_element() return self.s def verify(self, Xs, cs, zs): # check s == c_0 xor c_1 xor c_2 xor ... xor c_n computed_s = int(cs[0]) for i in range(1, len(cs)): computed_s = computed_s ^^ int(cs[i]) assert self.s == computed_s # check g*z == X*c + A (in multiplicative notation would g^z ==X^c * A) for i in range(len(Xs)): assert self.g * int(zs[i]) == Xs[i] * int(cs[i]) + self.As[i] # Tests import unittest, operator # ethereum elliptic curve p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F a = 0 b = 7 F = GF(p) E = EllipticCurve(F, [a,b]) GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 g = E(GX,GY) n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 h = 1 q = g.order() assert is_prime(p) assert is_prime(q) class TestSigmaProtocol(unittest.TestCase): def test_interactive(self): alice = Prover_interactive(F, g) # Alice generates witness w & statement X X = alice.new_key() assert X == alice.g * int(alice.w) # Alice generates the commitment A A = alice.new_commitment() assert A == alice.g * int(alice.a) # Bob generates the challenge (and stores A) bob = Verifier_interactive(F, g) c = bob.new_challenge(A) # Alice generates the proof z = alice.gen_proof(c) # Bob verifies the proof assert bob.verify(X, z) # check with the generic_verify function assert generic_verify(g, X, A, c, z) def test_non_interactive(self): alice = Prover(F, g) # Alice generates witness w & statement X X = alice.new_key() assert X == alice.g * int(alice.w) # Alice generates the proof A, z = alice.gen_proof(X) # Bob generates the challenge bob = Verifier(F, g) # Bob verifies the proof assert bob.verify(X, A, z) # check with the generic_verify function c = hash_two_points(A, X) assert generic_verify(g, X, A, c, z) def test_simulator(self): sim = Simulator(F, g) # set a public key X, for which we don't know w unknown_w = 3 X = g * unknown_w # simulate for X A, c, z = sim.simulate(X) # verify the simulated proof assert generic_verify(g, X, A, c, z) class TestORProof(unittest.TestCase): def test_2_parties(self): # set a public key X, for which we don't know w unknown_w = 3 X_1 = g * unknown_w alice = ORProver_2parties(F, g) # Alice generates key pair X = alice.new_key() Xs = [X, X_1] # Alice generates commitments (internally running the simulator) As = alice.gen_commitments(Xs) # Bob generates the challenge (and stores As) bob = ORVerifier_2parties(F, g) s = bob.new_challenge(As) # Alice generates the ORproof cs, zs = alice.gen_proof(s) # Bob verifies the proofs bob.verify(Xs, cs, zs) def test_n_parties(self): # set n public keys X, for which we don't know w Xs = [] for i in range(10): X_i = g * i Xs.append(X_i) alice = ORProver(F, g) # Alice generates key pair X = alice.new_key() Xs.insert(0, X) # add X at the beginning of Xs array # Alice generates commitments (internally running the simulator) As = alice.gen_commitments(Xs) # Bob generates the challenge (and stores As) bob = ORVerifier(F, g) s = bob.new_challenge(As) # Alice generates the ORproof cs, zs = alice.gen_proof(s) # Bob verifies the proofs bob.verify(Xs, cs, zs) if __name__ == '__main__': unittest.main()