From dedd181ae8d9efd41eb550a4411c2307487756e0 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Sun, 13 Mar 2022 11:36:25 +0100 Subject: [PATCH] Add sage impl of Sigma protocol & OR proofs --- crypto.sage | 210 +++++++++++++++++++++++++++++++++++++++++++++++ crypto_test.sage | 129 +++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 crypto.sage create mode 100644 crypto_test.sage diff --git a/crypto.sage b/crypto.sage new file mode 100644 index 0000000..473320d --- /dev/null +++ b/crypto.sage @@ -0,0 +1,210 @@ +from hashlib import sha256 + +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(object): + 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 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(object): + 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(object): + 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(object): + 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(object): + 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(object): + 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(object): + 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(object): + 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(object): + 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] diff --git a/crypto_test.sage b/crypto_test.sage new file mode 100644 index 0000000..082e6e2 --- /dev/null +++ b/crypto_test.sage @@ -0,0 +1,129 @@ +import unittest, operator +load("crypto.sage") + + +# 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 begining 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()