From 7d949bac885f6fa2727c5af9da6ef2982a1d8e88 Mon Sep 17 00:00:00 2001 From: narodnik Date: Tue, 27 Oct 2020 20:26:58 +0100 Subject: [PATCH 01/15] proof of encrypted polynomial --- scripts/zk/3-encrypted-polynomial.py | 99 ++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 scripts/zk/3-encrypted-polynomial.py diff --git a/scripts/zk/3-encrypted-polynomial.py b/scripts/zk/3-encrypted-polynomial.py new file mode 100644 index 000000000..42192f800 --- /dev/null +++ b/scripts/zk/3-encrypted-polynomial.py @@ -0,0 +1,99 @@ +from bls_py import bls12381 +from bls_py import pairing +from bls_py import ec +from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q +import random +import numpy as np + +# Section 3.3.4 from "Why and How zk-SNARK Works" + +def rand_scalar(): + return random.randrange(1, bls12381.q) + +#x = rand_scalar() +#y = ec.y_for_x(x) + +g1 = ec.generator_Fq(bls12381) +g2 = ec.generator_Fq2(bls12381) + +null = ec.AffinePoint(Fq(Q, 0), Fq(Q, 1), True, bls12381) +assert g1 + null == g1 + +################################# +# Verifier +################################# + +# samples a random value (a secret) +s = rand_scalar() + +# calculates encryptions of s for all powers i in 0 to d +# E(s^i) = g^s^i +d = 10 +encrypted_powers = [ + g1 * (s**i) for i in range(d) +] + +# evaluates unencrypted target polynomial with s: t(s) +target = (s - 1) * (s - 2) + +# encrypted values of s provided to the prover + +################################# +# Prover +################################# + +# E(p(s)) = p(s)G +# = c_d s^d G + ... + c_1 s^1 G + c_0 s^0 G +# = s^3 G - 3 s^2 G + 2 s G +# E(h(s)) = sG +# t(s) = s^2 - 3s + 2 +# E(h(s)) t(s) = s^3 G - 3 s^2 G + 2 s G + +# Lets test these manually: + +e_s = encrypted_powers +e_p_s = e_s[3] - 3 * e_s[2] + 2 * e_s[1] +e_h_s = e_s[1] +t_s = s**2 - 3*s + 2 +assert t_s == target +assert e_p_s == e_h_s * t_s + +############################# + +# x^3 - 3x^2 + 2x +main_poly = np.poly1d([1, -3, 2, 0]) +# (x - 1)(x - 2) +target_poly = np.poly1d([1, -1]) * np.poly1d([1, -2]) + +# Calculates polynomial h(x) = p(x) / t(x) +cofactor, remainder = main_poly / target_poly +assert remainder == np.poly1d([0]) + +# Using encrypted powers and coefficients, evaluates +# E(p(s)) and E(h(s)) +def evaluate(poly, encrypted_powers): + coeffs = list(poly.coef)[::-1] + result = null + for power, coeff in zip(encrypted_powers, coeffs): + #print(coeff, power) + coeff = int(coeff) + if coeff < 0: + result -= power * (-coeff) + else: + result += power * coeff + return result + +encrypted_poly = evaluate(main_poly, encrypted_powers) +assert encrypted_poly == e_p_s +encrypted_cofactor = evaluate(cofactor, encrypted_powers) + +# resulting g^p and g^h are provided to the verifier + +################################# +# Verifier +################################# + +# Last check that p = t(s) h + +assert encrypted_poly == encrypted_cofactor * target + From 250c67b168c6142ef1c0166afd619d31feba3317 Mon Sep 17 00:00:00 2001 From: narodnik Date: Tue, 27 Oct 2020 20:32:55 +0100 Subject: [PATCH 02/15] add comment --- scripts/zk/3-encrypted-polynomial.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/zk/3-encrypted-polynomial.py b/scripts/zk/3-encrypted-polynomial.py index 42192f800..76973923b 100644 --- a/scripts/zk/3-encrypted-polynomial.py +++ b/scripts/zk/3-encrypted-polynomial.py @@ -77,6 +77,9 @@ def evaluate(poly, encrypted_powers): for power, coeff in zip(encrypted_powers, coeffs): #print(coeff, power) coeff = int(coeff) + # I have to do this for some strange reason + # Because if coeff is negative and I do += power * coeff + # then it gives me a different result than what I expect if coeff < 0: result -= power * (-coeff) else: From 573d38b7ba6264a75d16fa11d921c29656482106 Mon Sep 17 00:00:00 2001 From: narodnik Date: Tue, 27 Oct 2020 20:33:21 +0100 Subject: [PATCH 03/15] move qap.py to scripts/zk/ subdir --- scripts/{ => zk}/qap.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{ => zk}/qap.py (100%) diff --git a/scripts/qap.py b/scripts/zk/qap.py similarity index 100% rename from scripts/qap.py rename to scripts/zk/qap.py From 716209a33551c563c95be52e9c8009aa214c52bd Mon Sep 17 00:00:00 2001 From: narodnik Date: Tue, 27 Oct 2020 20:34:22 +0100 Subject: [PATCH 04/15] mark trusted setup section in 3-encrypted-polynomial.py --- scripts/zk/3-encrypted-polynomial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/zk/3-encrypted-polynomial.py b/scripts/zk/3-encrypted-polynomial.py index 76973923b..ee54a623d 100644 --- a/scripts/zk/3-encrypted-polynomial.py +++ b/scripts/zk/3-encrypted-polynomial.py @@ -20,7 +20,7 @@ null = ec.AffinePoint(Fq(Q, 0), Fq(Q, 1), True, bls12381) assert g1 + null == g1 ################################# -# Verifier +# Verifier (trusted setup) ################################# # samples a random value (a secret) From 0bf213e949d4455f59cf1f56dd71dea1a6b8ffaf Mon Sep 17 00:00:00 2001 From: narodnik Date: Tue, 27 Oct 2020 20:35:41 +0100 Subject: [PATCH 05/15] mark s as toxic waste in encrypted poly script --- scripts/zk/3-encrypted-polynomial.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/zk/3-encrypted-polynomial.py b/scripts/zk/3-encrypted-polynomial.py index ee54a623d..af98250de 100644 --- a/scripts/zk/3-encrypted-polynomial.py +++ b/scripts/zk/3-encrypted-polynomial.py @@ -37,6 +37,7 @@ encrypted_powers = [ target = (s - 1) * (s - 2) # encrypted values of s provided to the prover +# Actual values of s are toxic waste and discarded ################################# # Prover From 7f63fbe98cb97a9f6b021079d534f7dc45a12e1b Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 17:30:54 +0100 Subject: [PATCH 06/15] more zk scripts --- scripts/zk/3.4-restricted-polynomial.py | 119 +++++++++++++++++++ scripts/zk/3.5-zero-knowledge.py | 129 +++++++++++++++++++++ scripts/zk/3.6-trusted-setup.py | 145 ++++++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100644 scripts/zk/3.4-restricted-polynomial.py create mode 100644 scripts/zk/3.5-zero-knowledge.py create mode 100644 scripts/zk/3.6-trusted-setup.py diff --git a/scripts/zk/3.4-restricted-polynomial.py b/scripts/zk/3.4-restricted-polynomial.py new file mode 100644 index 000000000..dd75be50f --- /dev/null +++ b/scripts/zk/3.4-restricted-polynomial.py @@ -0,0 +1,119 @@ +from bls_py import bls12381 +from bls_py import pairing +from bls_py import ec +from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q +import random +import numpy as np + +# Section 3.3.4 from "Why and How zk-SNARK Works" + +def rand_scalar(): + return random.randrange(1, bls12381.q) + +#x = rand_scalar() +#y = ec.y_for_x(x) + +g1 = ec.generator_Fq(bls12381) +g2 = ec.generator_Fq2(bls12381) + +null = ec.AffinePoint(Fq(Q, 0), Fq(Q, 1), True, bls12381) +assert g1 + null == g1 + +################################# +# Verifier (trusted setup) +################################# + +# samples a random value (a secret) +s = rand_scalar() + +# calculate the shift +a = rand_scalar() + +# calculates encryptions of s for all powers i in 0 to d +# E(s^i) = g^s^i +d = 10 +encrypted_powers = [ + g1 * (s**i) for i in range(d) +] +encrypted_shifted_powers = [ + g1 * (a * s**i) for i in range(d) +] + +# evaluates unencrypted target polynomial with s: t(s) +target = (s - 1) * (s - 2) + +# encrypted values of s provided to the prover +# Actual values of s are toxic waste and discarded + +################################# +# Prover +################################# + +# E(p(s)) = p(s)G +# = c_d s^d G + ... + c_1 s^1 G + c_0 s^0 G +# = s^3 G - 3 s^2 G + 2 s G +# E(h(s)) = sG +# t(s) = s^2 - 3s + 2 +# E(h(s)) t(s) = s^3 G - 3 s^2 G + 2 s G + +# Lets test these manually: + +e_s = encrypted_powers +e_p_s = e_s[3] - 3 * e_s[2] + 2 * e_s[1] +e_h_s = e_s[1] +t_s = s**2 - 3*s + 2 +assert t_s == target +assert e_p_s == e_h_s * t_s + +e_as = encrypted_shifted_powers +e_p_as = e_as[3] - 3 * e_as[2] + 2 * e_as[1] +assert e_p_s * a == e_p_as + +############################# + +# x^3 - 3x^2 + 2x +main_poly = np.poly1d([1, -3, 2, 0]) +# (x - 1)(x - 2) +target_poly = np.poly1d([1, -1]) * np.poly1d([1, -2]) + +# Calculates polynomial h(x) = p(x) / t(x) +cofactor, remainder = main_poly / target_poly +assert remainder == np.poly1d([0]) + +# Using encrypted powers and coefficients, evaluates +# E(p(s)) and E(h(s)) +def evaluate(poly, encrypted_powers): + coeffs = list(poly.coef)[::-1] + result = null + for power, coeff in zip(encrypted_powers, coeffs): + #print(coeff, power) + coeff = int(coeff) + # I have to do this for some strange reason + # Because if coeff is negative and I do += power * coeff + # then it gives me a different result than what I expect + if coeff < 0: + result -= power * (-coeff) + else: + result += power * coeff + return result + +encrypted_poly = evaluate(main_poly, encrypted_powers) +assert encrypted_poly == e_p_s +encrypted_cofactor = evaluate(cofactor, encrypted_powers) + +# Alpha shifted powers +encrypted_shift_poly = evaluate(main_poly, encrypted_shifted_powers) + +# resulting g^p and g^h are provided to the verifier + +################################# +# Verifier +################################# + +# Last check that p = t(s) h + +assert encrypted_poly == encrypted_cofactor * target + +# Verify (g^p)^a == g^p' + +assert encrypted_poly * a == encrypted_shift_poly diff --git a/scripts/zk/3.5-zero-knowledge.py b/scripts/zk/3.5-zero-knowledge.py new file mode 100644 index 000000000..d8ae1b6b6 --- /dev/null +++ b/scripts/zk/3.5-zero-knowledge.py @@ -0,0 +1,129 @@ +from bls_py import bls12381 +from bls_py import pairing +from bls_py import ec +from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q +import random +import numpy as np + +# Section 3.3.4 from "Why and How zk-SNARK Works" + +def rand_scalar(): + return random.randrange(1, bls12381.q) + +#x = rand_scalar() +#y = ec.y_for_x(x) + +g1 = ec.generator_Fq(bls12381) +g2 = ec.generator_Fq2(bls12381) + +null = ec.AffinePoint(Fq(Q, 0), Fq(Q, 1), True, bls12381) +assert g1 + null == g1 + +################################# +# Verifier (trusted setup) +################################# + +# samples a random value (a secret) +s = rand_scalar() + +# calculate the shift +a = rand_scalar() + +# calculates encryptions of s for all powers i in 0 to d +# E(s^i) = g^s^i +d = 10 +encrypted_powers = [ + g1 * (s**i) for i in range(d) +] +encrypted_shifted_powers = [ + g1 * (a * s**i) for i in range(d) +] + +# evaluates unencrypted target polynomial with s: t(s) +target = (s - 1) * (s - 2) + +# encrypted values of s provided to the prover +# Actual values of s are toxic waste and discarded + +################################# +# Prover +################################# + +# delta shift +delta = rand_scalar() + +# E(p(s)) = p(s)G +# = c_d s^d G + ... + c_1 s^1 G + c_0 s^0 G +# = s^3 G - 3 s^2 G + 2 s G +# E(h(s)) = sG +# t(s) = s^2 - 3s + 2 +# E(h(s)) t(s) = s^3 G - 3 s^2 G + 2 s G + +# Lets test these manually: + +e_s = encrypted_powers +e_p_s = e_s[3] - 3 * e_s[2] + 2 * e_s[1] +e_h_s = e_s[1] +t_s = s**2 - 3*s + 2 +# exponentiate with delta +e_p_s *= delta +e_h_s *= delta +assert t_s == target +assert e_p_s == e_h_s * t_s + +e_as = encrypted_shifted_powers +e_p_as = e_as[3] - 3 * e_as[2] + 2 * e_as[1] +# exponentiate with delta +e_p_as *= delta +assert e_p_s * a == e_p_as + +############################# + +# x^3 - 3x^2 + 2x +main_poly = np.poly1d([1, -3, 2, 0]) +# (x - 1)(x - 2) +target_poly = np.poly1d([1, -1]) * np.poly1d([1, -2]) + +# Calculates polynomial h(x) = p(x) / t(x) +cofactor, remainder = main_poly / target_poly +assert remainder == np.poly1d([0]) + +# Using encrypted powers and coefficients, evaluates +# E(p(s)) and E(h(s)) +def evaluate(poly, encrypted_powers): + coeffs = list(poly.coef)[::-1] + result = null + for power, coeff in zip(encrypted_powers, coeffs): + #print(coeff, power) + coeff = int(coeff) + # I have to do this for some strange reason + # Because if coeff is negative and I do += power * coeff + # then it gives me a different result than what I expect + if coeff < 0: + result -= power * (-coeff) + else: + result += power * coeff + # Add delta to the result + # Free extra obfuscation to the polynomial + return result * delta + +encrypted_poly = evaluate(main_poly, encrypted_powers) +assert encrypted_poly == e_p_s +encrypted_cofactor = evaluate(cofactor, encrypted_powers) + +# Alpha shifted powers +encrypted_shift_poly = evaluate(main_poly, encrypted_shifted_powers) + +# resulting g^p and g^h are provided to the verifier + +################################# +# Verifier +################################# + +# Last check that p = t(s) h + +assert encrypted_poly == encrypted_cofactor * target + +# Verify (g^p)^a == g^p' + +assert encrypted_poly * a == encrypted_shift_poly diff --git a/scripts/zk/3.6-trusted-setup.py b/scripts/zk/3.6-trusted-setup.py new file mode 100644 index 000000000..4a8a8f5b8 --- /dev/null +++ b/scripts/zk/3.6-trusted-setup.py @@ -0,0 +1,145 @@ +from bls_py import bls12381 +from bls_py import pairing +from bls_py import ec +from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q +import random +import numpy as np + +# Section 3.3.4 from "Why and How zk-SNARK Works" + +def rand_scalar(): + return random.randrange(1, bls12381.q) + +#x = rand_scalar() +#y = ec.y_for_x(x) + +g1 = ec.generator_Fq(bls12381) +g2 = ec.generator_Fq2(bls12381) + +null = ec.AffinePoint(Fq(Q, 0), Fq(Q, 1), True, bls12381) +assert g1 + null == g1 +null2 = ec.AffinePoint(Fq2.zero(Q), Fq2.zero(Q), True, bls12381) +assert null2 + g2 == g2 + +################################# +# Verifier (trusted setup) +################################# + +# samples a random value (a secret) +s = rand_scalar() + +# calculate the shift +a = rand_scalar() + +# calculates encryptions of s for all powers i in 0 to d +# E(s^i) = g^s^i +d = 10 +encrypted_powers = [ + g1 * (s**i) for i in range(d) +] +encrypted_powers_g2 = [ + g2 * (s**i) for i in range(d) +] +encrypted_shifted_powers = [ + g1 * (a * s**i) for i in range(d) +] + +# evaluates unencrypted target polynomial with s: t(s) +target = (s - 1) * (s - 2) +# CRS = common reference string = trusted setup parameters +target_crs = g1 * target +alpha_crs = g1 * a + +# Proving key = (encrypted_powers, encrypted_shifted_powers) +# Verify key = (target_crs, alpha_crs) + +# encrypted values of s provided to the prover +# Actual values of s are toxic waste and discarded + +################################# +# Prover +################################# + +# delta shift +delta = rand_scalar() + +# E(p(s)) = p(s)G +# = c_d s^d G + ... + c_1 s^1 G + c_0 s^0 G +# = s^3 G - 3 s^2 G + 2 s G +# E(h(s)) = sG +# t(s) = s^2 - 3s + 2 +# E(h(s)) t(s) = s^3 G - 3 s^2 G + 2 s G + +# Lets test these manually: + +e_s = encrypted_powers +e_p_s = e_s[3] - 3 * e_s[2] + 2 * e_s[1] +e_h_s = e_s[1] +t_s = s**2 - 3*s + 2 +# exponentiate with delta +e_p_s *= delta +e_h_s *= delta +assert t_s == target +assert e_p_s == e_h_s * t_s + +e_as = encrypted_shifted_powers +e_p_as = e_as[3] - 3 * e_as[2] + 2 * e_as[1] +# exponentiate with delta +e_p_as *= delta +assert e_p_s * a == e_p_as + +############################# + +# x^3 - 3x^2 + 2x +main_poly = np.poly1d([1, -3, 2, 0]) +# (x - 1)(x - 2) +target_poly = np.poly1d([1, -1]) * np.poly1d([1, -2]) + +# Calculates polynomial h(x) = p(x) / t(x) +cofactor, remainder = main_poly / target_poly +assert remainder == np.poly1d([0]) + +# Using encrypted powers and coefficients, evaluates +# E(p(s)) and E(h(s)) +def evaluate(poly, encrypted_powers, identity): + coeffs = list(poly.coef)[::-1] + result = identity + for power, coeff in zip(encrypted_powers, coeffs): + #print(coeff, power) + coeff = int(coeff) + # I have to do this for some strange reason + # Because if coeff is negative and I do += power * coeff + # then it gives me a different result than what I expect + if coeff < 0: + result -= power * (-coeff) + else: + result += power * coeff + # Add delta to the result + # Free extra obfuscation to the polynomial + return result * delta + +encrypted_poly = evaluate(main_poly, encrypted_powers, null) +assert encrypted_poly == e_p_s +encrypted_cofactor = evaluate(cofactor, encrypted_powers_g2, null2) + +# Alpha shifted powers +encrypted_shift_poly = evaluate(main_poly, encrypted_shifted_powers, null) + +# resulting g^p and g^h are provided to the verifier + +################################# +# Verifier +################################# + +# Last check that p = t(s) h + +#assert encrypted_poly == encrypted_cofactor * target +# e(g^p, g) == e(g^t, g^h) +res1 = pairing.ate_pairing(encrypted_poly, g2) +res2 = pairing.ate_pairing(g1 * target, encrypted_cofactor) +assert res1 == res2 + +# Verify (g^p)^a == g^p' + +assert encrypted_poly * a == encrypted_shift_poly + From a300de3abafa8c3d57890eab29a8c319dc0d7128 Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 17:33:02 +0100 Subject: [PATCH 07/15] correct section numbers --- scripts/zk/3.4-restricted-polynomial.py | 2 +- scripts/zk/3.5-zero-knowledge.py | 2 +- scripts/zk/3.6-trusted-setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/zk/3.4-restricted-polynomial.py b/scripts/zk/3.4-restricted-polynomial.py index dd75be50f..15200640a 100644 --- a/scripts/zk/3.4-restricted-polynomial.py +++ b/scripts/zk/3.4-restricted-polynomial.py @@ -5,7 +5,7 @@ from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q import random import numpy as np -# Section 3.3.4 from "Why and How zk-SNARK Works" +# Section 3.4 from "Why and How zk-SNARK Works" def rand_scalar(): return random.randrange(1, bls12381.q) diff --git a/scripts/zk/3.5-zero-knowledge.py b/scripts/zk/3.5-zero-knowledge.py index d8ae1b6b6..d440a2b1a 100644 --- a/scripts/zk/3.5-zero-knowledge.py +++ b/scripts/zk/3.5-zero-knowledge.py @@ -5,7 +5,7 @@ from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q import random import numpy as np -# Section 3.3.4 from "Why and How zk-SNARK Works" +# Section 3.5 from "Why and How zk-SNARK Works" def rand_scalar(): return random.randrange(1, bls12381.q) diff --git a/scripts/zk/3.6-trusted-setup.py b/scripts/zk/3.6-trusted-setup.py index 4a8a8f5b8..29f3236ee 100644 --- a/scripts/zk/3.6-trusted-setup.py +++ b/scripts/zk/3.6-trusted-setup.py @@ -5,7 +5,7 @@ from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q import random import numpy as np -# Section 3.3.4 from "Why and How zk-SNARK Works" +# Section 3.6 from "Why and How zk-SNARK Works" def rand_scalar(): return random.randrange(1, bls12381.q) From 0c173a04383892f5bd53dba3f792c98e6d0faaa8 Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 17:35:57 +0100 Subject: [PATCH 08/15] forgot to add polynomial restriction check --- scripts/zk/3.6-trusted-setup.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/zk/3.6-trusted-setup.py b/scripts/zk/3.6-trusted-setup.py index 29f3236ee..ca71500c5 100644 --- a/scripts/zk/3.6-trusted-setup.py +++ b/scripts/zk/3.6-trusted-setup.py @@ -48,7 +48,7 @@ encrypted_shifted_powers = [ target = (s - 1) * (s - 2) # CRS = common reference string = trusted setup parameters target_crs = g1 * target -alpha_crs = g1 * a +alpha_crs = g2 * a # Proving key = (encrypted_powers, encrypted_shifted_powers) # Verify key = (target_crs, alpha_crs) @@ -133,13 +133,18 @@ encrypted_shift_poly = evaluate(main_poly, encrypted_shifted_powers, null) # Last check that p = t(s) h +# Check polynomial cofactors: #assert encrypted_poly == encrypted_cofactor * target # e(g^p, g) == e(g^t, g^h) res1 = pairing.ate_pairing(encrypted_poly, g2) -res2 = pairing.ate_pairing(g1 * target, encrypted_cofactor) +res2 = pairing.ate_pairing(target_crs, encrypted_cofactor) assert res1 == res2 # Verify (g^p)^a == g^p' +# Check polynomial restriction: -assert encrypted_poly * a == encrypted_shift_poly +res1 = pairing.ate_pairing(encrypted_shift_poly, g2) +res2 = pairing.ate_pairing(encrypted_poly, alpha_crs) +assert res1 == res2 +#assert encrypted_poly * a == encrypted_shift_poly From effe1e31bd2e4fb6328555ff2021d7f388f38966 Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 17:37:19 +0100 Subject: [PATCH 09/15] add some minor documentation --- scripts/zk/3.6-trusted-setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/zk/3.6-trusted-setup.py b/scripts/zk/3.6-trusted-setup.py index ca71500c5..426517829 100644 --- a/scripts/zk/3.6-trusted-setup.py +++ b/scripts/zk/3.6-trusted-setup.py @@ -127,6 +127,8 @@ encrypted_shift_poly = evaluate(main_poly, encrypted_shifted_powers, null) # resulting g^p and g^h are provided to the verifier +# proof = (encrypted_poly, encrypted_cofactor, encrypted_shift_poly) + ################################# # Verifier ################################# From c56b2ffe6a46f321641793b5065ebf10f515f39a Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 17:43:40 +0100 Subject: [PATCH 10/15] mv to 3.3 --- .../zk/{3-encrypted-polynomial.py => 3.3-encrypted-polynomial.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/zk/{3-encrypted-polynomial.py => 3.3-encrypted-polynomial.py} (100%) diff --git a/scripts/zk/3-encrypted-polynomial.py b/scripts/zk/3.3-encrypted-polynomial.py similarity index 100% rename from scripts/zk/3-encrypted-polynomial.py rename to scripts/zk/3.3-encrypted-polynomial.py From 8d3b1e8254e4daa78046376e8b2add9b785279ff Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 20:48:06 +0100 Subject: [PATCH 11/15] 4.4 - proof of operation --- scripts/zk/4.4-proof-of-operation.py | 174 +++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 scripts/zk/4.4-proof-of-operation.py diff --git a/scripts/zk/4.4-proof-of-operation.py b/scripts/zk/4.4-proof-of-operation.py new file mode 100644 index 000000000..263d74361 --- /dev/null +++ b/scripts/zk/4.4-proof-of-operation.py @@ -0,0 +1,174 @@ +from bls_py import bls12381 +from bls_py import pairing +from bls_py import ec +from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q +import random +import numpy as np + +# Section 3.6 from "Why and How zk-SNARK Works" + +def rand_scalar(): + return random.randrange(1, bls12381.q) + +#x = rand_scalar() +#y = ec.y_for_x(x) + +g1 = ec.generator_Fq(bls12381) +g2 = ec.generator_Fq2(bls12381) + +null = ec.AffinePoint(Fq(Q, 0), Fq(Q, 1), True, bls12381) +assert g1 + null == g1 +null2 = ec.AffinePoint(Fq2.zero(Q), Fq2.zero(Q), True, bls12381) +assert null2 + g2 == g2 + +################################# +# Verifier (trusted setup) +################################# + +# samples a random value (a secret) +s = rand_scalar() + +# calculate the shift +a = rand_scalar() + +# calculates encryptions of s for all powers i in 0 to d +# E(s^i) = g^s^i +d = 10 +encrypted_powers = [ + g1 * (s**i) for i in range(d) +] +encrypted_powers_g2 = [ + g2 * (s**i) for i in range(d) +] +encrypted_shifted_powers = [ + g1 * (a * s**i) for i in range(d) +] +encrypted_shifted_powers_g2 = [ + g2 * (a * s**i) for i in range(d) +] + +# evaluates unencrypted target polynomial with s: t(s) +target = (s - 1) * (s - 2) +# CRS = common reference string = trusted setup parameters +target_crs = g1 * target +alpha_crs = g2 * a +alpha_crs_g1 = g1 * a + +# Proving key = (encrypted_powers, encrypted_shifted_powers) +# Verify key = (target_crs, alpha_crs) + +# encrypted values of s provided to the prover +# Actual values of s are toxic waste and discarded + +################################# +# Prover +################################# + +# delta shift +# Removed for now +#delta = rand_scalar() + +# E(p(s)) = p(s)G +# = c_d s^d G + ... + c_1 s^1 G + c_0 s^0 G +# = s^3 G - 3 s^2 G + 2 s G +# E(h(s)) = sG +# t(s) = s^2 - 3s + 2 +# E(h(s)) t(s) = s^3 G - 3 s^2 G + 2 s G + +# Lets test these manually: + +e_s = encrypted_powers +e_p_s = e_s[3] - 3 * e_s[2] + 2 * e_s[1] +e_h_s = e_s[1] +t_s = s**2 - 3*s + 2 +assert t_s == target +assert e_p_s == e_h_s * t_s + +e_as = encrypted_shifted_powers +e_p_as = e_as[3] - 3 * e_as[2] + 2 * e_as[1] +assert e_p_s * a == e_p_as + +############################# + +left_poly = np.poly1d([3]) +right_poly = np.poly1d([2]) +out_poly = np.poly1d([6]) + +# x^3 - 3x^2 + 2x +main_poly = left_poly * right_poly - out_poly +# (x - 1) +target_poly = np.poly1d([1, -1]) + +# Calculates polynomial h(x) = p(x) / t(x) +cofactor, remainder = main_poly / target_poly +assert remainder == np.poly1d([0]) + +# Using encrypted powers and coefficients, evaluates +# E(p(s)) and E(h(s)) +def evaluate(poly, encrypted_powers, identity): + coeffs = list(poly.coef)[::-1] + result = identity + for power, coeff in zip(encrypted_powers, coeffs): + #print(coeff, power) + coeff = int(coeff) + # I have to do this for some strange reason + # Because if coeff is negative and I do += power * coeff + # then it gives me a different result than what I expect + if coeff < 0: + result -= power * (-coeff) + else: + result += power * coeff + # Add delta to the result + # Free extra obfuscation to the polynomial + return result + +assert left_poly * right_poly == out_poly + +encrypted_left_poly = evaluate(left_poly, encrypted_powers, null) +encrypted_right_poly = evaluate(right_poly, encrypted_powers_g2, null2) +encrypted_out_poly = evaluate(out_poly, encrypted_powers, null) + +#assert encrypted_poly == e_p_s +encrypted_cofactor = evaluate(cofactor, encrypted_powers_g2, null2) + +# Alpha shifted powers +encrypted_shift_left_poly = evaluate(left_poly, encrypted_shifted_powers, null) +encrypted_shift_right_poly = evaluate(right_poly, encrypted_shifted_powers_g2, null2) +encrypted_shift_out_poly = evaluate(out_poly, encrypted_shifted_powers, null) + +# resulting g^p and g^h are provided to the verifier + +# proof = (encrypted_poly, encrypted_cofactor, encrypted_shift_poly) + +################################# +# Verifier +################################# + +# Last check that p = t(s) h + +assert pairing.ate_pairing(2 * g1, g2) == pairing.ate_pairing(g1, g2) * pairing.ate_pairing(g1, g2) + +# Verify (g^p)^a == g^p' +# Check polynomial restriction: + +def check_polynomial_restriction(encrypted_shift_poly, encrypted_poly): + res1 = pairing.ate_pairing(encrypted_shift_poly, g2) + res2 = pairing.ate_pairing(encrypted_poly, alpha_crs) + assert res1 == res2 + +def check_polynomial_restriction_swapped(encrypted_shift_poly, encrypted_poly): + res1 = pairing.ate_pairing(g1, encrypted_shift_poly) + res2 = pairing.ate_pairing(alpha_crs_g1, encrypted_poly) + assert res1 == res2 + +check_polynomial_restriction(encrypted_shift_left_poly, encrypted_left_poly) +check_polynomial_restriction_swapped(encrypted_shift_right_poly, encrypted_right_poly) +check_polynomial_restriction(encrypted_shift_out_poly, encrypted_out_poly) + +# Valid operation check +# e(g^l, g^r) == e(g^t, g^h) * e(g^o, g) +res1 = pairing.ate_pairing(encrypted_left_poly, encrypted_right_poly) +res2 = pairing.ate_pairing(target_crs, encrypted_cofactor) * \ + pairing.ate_pairing(encrypted_out_poly, g2) +assert res1 == res2 + From bb93400165809a353f0801bb6ddee9c898f248e4 Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 28 Oct 2020 21:16:58 +0100 Subject: [PATCH 12/15] lagrange interpolation --- scripts/zk/4.5.1-polynomial-interpolation.py | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 scripts/zk/4.5.1-polynomial-interpolation.py diff --git a/scripts/zk/4.5.1-polynomial-interpolation.py b/scripts/zk/4.5.1-polynomial-interpolation.py new file mode 100644 index 000000000..cd6f0f77c --- /dev/null +++ b/scripts/zk/4.5.1-polynomial-interpolation.py @@ -0,0 +1,30 @@ +import numpy as np + +def lagrange(points): + result = np.poly1d([0]) + for i, (x_i, y_i) in enumerate(points): + poly = np.poly1d([y_i]) + for j, (x_j, y_j) in enumerate(points): + if i == j: + continue + poly *= np.poly1d([1, -x_j]) / (x_i - x_j) + #print(poly) + #print(poly(1), poly(2), poly(3)) + result += poly + return result + +left = lagrange([ + (1, 2), (2, 2), (3, 6) +]) +print(left) + +right = lagrange([ + (1, 1), (2, 3), (3, 2) +]) +print(right) + +out = lagrange([ + (1, 2), (2, 6), (3, 12) +]) +print(out) + From 3f0abbd8082210ee1d5e355aa87746b275f30d4f Mon Sep 17 00:00:00 2001 From: narodnik Date: Wed, 4 Nov 2020 13:49:06 +0100 Subject: [PATCH 13/15] remove some things from zk scripts --- scripts/finite_fields/polynomial.py | 2 +- scripts/zk/4.4-proof-of-operation.py | 30 +--------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/scripts/finite_fields/polynomial.py b/scripts/finite_fields/polynomial.py index a7a2c434d..224f70e49 100644 --- a/scripts/finite_fields/polynomial.py +++ b/scripts/finite_fields/polynomial.py @@ -4,7 +4,7 @@ except ImportError: from itertools import izip_longest as zip_longest import fractions -from numbertype import * +from .numbertype import * # strip all copies of elt from the end of the list def strip(L, elt): diff --git a/scripts/zk/4.4-proof-of-operation.py b/scripts/zk/4.4-proof-of-operation.py index 263d74361..838817158 100644 --- a/scripts/zk/4.4-proof-of-operation.py +++ b/scripts/zk/4.4-proof-of-operation.py @@ -48,7 +48,7 @@ encrypted_shifted_powers_g2 = [ ] # evaluates unencrypted target polynomial with s: t(s) -target = (s - 1) * (s - 2) +target = (s - 1) # CRS = common reference string = trusted setup parameters target_crs = g1 * target alpha_crs = g2 * a @@ -64,32 +64,6 @@ alpha_crs_g1 = g1 * a # Prover ################################# -# delta shift -# Removed for now -#delta = rand_scalar() - -# E(p(s)) = p(s)G -# = c_d s^d G + ... + c_1 s^1 G + c_0 s^0 G -# = s^3 G - 3 s^2 G + 2 s G -# E(h(s)) = sG -# t(s) = s^2 - 3s + 2 -# E(h(s)) t(s) = s^3 G - 3 s^2 G + 2 s G - -# Lets test these manually: - -e_s = encrypted_powers -e_p_s = e_s[3] - 3 * e_s[2] + 2 * e_s[1] -e_h_s = e_s[1] -t_s = s**2 - 3*s + 2 -assert t_s == target -assert e_p_s == e_h_s * t_s - -e_as = encrypted_shifted_powers -e_p_as = e_as[3] - 3 * e_as[2] + 2 * e_as[1] -assert e_p_s * a == e_p_as - -############################# - left_poly = np.poly1d([3]) right_poly = np.poly1d([2]) out_poly = np.poly1d([6]) @@ -118,8 +92,6 @@ def evaluate(poly, encrypted_powers, identity): result -= power * (-coeff) else: result += power * coeff - # Add delta to the result - # Free extra obfuscation to the polynomial return result assert left_poly * right_poly == out_poly From dc293969b3a092962b42cbc62a84f5609583dee0 Mon Sep 17 00:00:00 2001 From: narodnik Date: Sat, 7 Nov 2020 23:43:46 +0100 Subject: [PATCH 14/15] 4.5.2 multi op polynomials --- .../zk/4.5.2-multi-operation-polynomials.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 scripts/zk/4.5.2-multi-operation-polynomials.py diff --git a/scripts/zk/4.5.2-multi-operation-polynomials.py b/scripts/zk/4.5.2-multi-operation-polynomials.py new file mode 100644 index 000000000..ea86379e5 --- /dev/null +++ b/scripts/zk/4.5.2-multi-operation-polynomials.py @@ -0,0 +1,65 @@ +from bls_py import bls12381 +from finite_fields.modp import IntegersModP +from finite_fields.polynomial import polynomialsOver + +n = bls12381.n + +mod_field = IntegersModP(n) +poly = polynomialsOver(mod_field).factory + +def lagrange(points): + result = poly([0]) + for i, (x_i, y_i) in enumerate(points): + p = poly([y_i]) + for j, (x_j, y_j) in enumerate(points): + if i == j: + continue + p *= poly([-x_j, 1]) / (x_i - x_j) + #print(poly) + #print(poly(1), poly(2), poly(3)) + result += p + return result + +def poly_call(poly, x): + result = mod_field(0) + for degree, coeff in enumerate(poly): + result += coeff * (x**degree) + return result.n + +left_points = [ + (1, 2), (2, 2), (3, 6) +] +left_poly = lagrange(left_points) +#l = poly([2]) * poly([1, -1]) +print("Left:") +print(left_poly) +for x, y in left_points: + assert poly_call(left_poly, x) == y + +right_points = [ + (1, 1), (2, 3), (3, 2) +] +right_poly = lagrange(right_points) +print("Right:") +print(right_poly) +for x, y in right_points: + assert poly_call(right_poly, x) == y + +out_points = [ + (1, 2), (2, 6), (3, 12) +] +out_poly = lagrange(out_points) +print("Out:") +print(out_poly) +for x, y in out_points: + assert poly_call(out_poly, x) == y + +target_poly = poly([-1, 1]) * poly([-2, 1]) * poly([-3, 1]) +assert poly_call(target_poly, 1) == 0 +assert poly_call(target_poly, 2) == 0 +assert poly_call(target_poly, 3) == 0 + +main_poly = left_poly * right_poly - out_poly +cofactor_poly = main_poly / target_poly + +assert left_poly * right_poly - out_poly == target_poly * cofactor_poly From 726ae89ee9ee0f0b068df59a664de9e310700815 Mon Sep 17 00:00:00 2001 From: narodnik Date: Sun, 8 Nov 2020 22:23:57 +0100 Subject: [PATCH 15/15] working 4.5.2 basic algorithm --- .../zk/4.5.2-multi-operation-polynomials.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/scripts/zk/4.5.2-multi-operation-polynomials.py b/scripts/zk/4.5.2-multi-operation-polynomials.py index ea86379e5..4436a2b87 100644 --- a/scripts/zk/4.5.2-multi-operation-polynomials.py +++ b/scripts/zk/4.5.2-multi-operation-polynomials.py @@ -1,9 +1,21 @@ from bls_py import bls12381 +from bls_py import pairing +from bls_py import ec +from bls_py.fields import Fq, Fq2, Fq6, Fq12, bls12381_q as Q from finite_fields.modp import IntegersModP from finite_fields.polynomial import polynomialsOver +import random n = bls12381.n +g1 = ec.generator_Fq(bls12381) +g2 = ec.generator_Fq2(bls12381) + +null = ec.AffinePoint(Fq(n, 0), Fq(n, 1), True, bls12381) +assert null + g1 == g1 +null2 = ec.AffinePoint(Fq2.zero(n), Fq2.zero(n), True, bls12381) +assert null2 + g2 == g2 + mod_field = IntegersModP(n) poly = polynomialsOver(mod_field).factory @@ -63,3 +75,93 @@ main_poly = left_poly * right_poly - out_poly cofactor_poly = main_poly / target_poly assert left_poly * right_poly - out_poly == target_poly * cofactor_poly + +def rand_scalar(): + return random.randrange(1, bls12381.q) + +################################# +# Verifier (trusted setup) +################################# + +# samples a random value (a secret) +toxic_scalar = rand_scalar() +# calculate the shift +alpha_shift = rand_scalar() + +# calculates encryptions of s for all powers i in 0 to d +# E(s^i) = g^s^i +degree = 10 +enc_s1 = [ + g1 * (toxic_scalar**i) for i in range(degree) +] +enc_s2 = [ + g2 * (toxic_scalar**i) for i in range(degree) +] +enc_s1_shift = [ + g1 * (alpha_shift * toxic_scalar**i) for i in range(degree) +] +enc_s2_shift = [ + g2 * (alpha_shift * toxic_scalar**i) for i in range(degree) +] + +# evaluates unencrypted target polynomial with s: t(s) +toxic_target = (toxic_scalar - 1) * (toxic_scalar - 2) * (toxic_scalar - 3) +# CRS = common reference string = trusted setup parameters +target_crs = g1 * toxic_target +alpha_crs = g2 * alpha_shift +alpha_crs_g1 = g1 * alpha_shift + +# Proving key = (encrypted_powers, encrypted_shifted_powers) +# Verify key = (target_crs, alpha_crs) + +# encrypted values of s provided to the prover +# Actual values of s are toxic waste and discarded + +################################# +# Prover +################################# + +# Using encrypted powers and coefficients, evaluates +# E(p(s)) and E(h(s)) +def evaluate(poly, encrypted_powers, identity): + result = identity + for power, coeff in zip(encrypted_powers, poly): + result += power * coeff.n + return result + +enc_left = evaluate(left_poly, enc_s1, null) +enc_right = evaluate(right_poly, enc_s2, null2) +enc_out = evaluate(out_poly, enc_s1, null) + +enc_cofactor = evaluate(cofactor_poly, enc_s2, null2) + +# Alpha shifted powers +enc_left_shift = evaluate(left_poly, enc_s1_shift, null) +enc_right_shift = evaluate(right_poly, enc_s2_shift, null2) +enc_out_shift = evaluate(out_poly, enc_s1_shift, null) + +################################# +# Verifier +################################# + +def restrict_polynomial_g1(encrypted_shift_poly, encrypted_poly): + res1 = pairing.ate_pairing(encrypted_shift_poly, g2) + res2 = pairing.ate_pairing(encrypted_poly, alpha_crs) + assert res1 == res2 + +def restrict_polynomial_g2(encrypted_shift_poly, encrypted_poly): + res1 = pairing.ate_pairing(g1, encrypted_shift_poly) + res2 = pairing.ate_pairing(alpha_crs_g1, encrypted_poly) + assert res1 == res2 + +restrict_polynomial_g1(enc_left_shift, enc_left) +restrict_polynomial_g2(enc_right_shift, enc_right) +restrict_polynomial_g1(enc_out_shift, enc_out) + +# Valid operation check +# e(g^l, g^r) == e(g^t, g^h) * e(g^o, g) +res1 = pairing.ate_pairing(enc_left, enc_right) +res2 = pairing.ate_pairing(target_crs, enc_cofactor) * \ + pairing.ate_pairing(enc_out, g2) +assert res1 == res2 +