diff --git a/src/precompiled_circuits/zk_ecip.py b/src/precompiled_circuits/zk_ecip.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/precompiled_circuits/zk_ecip.sage b/src/precompiled_circuits/zk_ecip.sage deleted file mode 100644 index 0b925cc5..00000000 --- a/src/precompiled_circuits/zk_ecip.sage +++ /dev/null @@ -1,315 +0,0 @@ -# Makes the script deterministic -#set_random_seed(0) - -# This demonstrates some of the computations of https://eprint.iacr.org/2022/596. -# First, that the logarithmic derivative of the Weil reciprocity equality holds -# over random principal divisors using both mixed and duplicate challenge points. -# Then, that by taking linear combinations of divisor proofs, can check an ECIP -# and combine multiplicities for the same basis points. Example ECIP uses base -# -3 and no CM. - -## STEP 1 -# Initialize an elliptic curve -p = 21888242871839275222246405745257275088696311157297823662689037894645226208583 -r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 -Fp = GF(p) # Base Field -Fr = GF(r) # Scalar Field -A = 0 -B = 3 -E = EllipticCurve(GF(p), [A,B]) -assert(E.cardinality() == r) - -## STEP 2 -K. = Fp[] -L. = K[] -eqn = y^2 - x^3 - A * x - B - -## STEP 3 -# Returns line passing through points, works for all points and returns 1 for O + O = O -def line(A, B): - if A == 0 and B == 0: - return 1 - else: - [a, b, c] = Matrix([A, B, -(A+B)]).transpose().kernel().basis()[0] - return a*x + b*y + c - -## STEP 4 -# Works for A == B but not A == -A, as the line has no slope or intercept -def slope_intercept(A, B): - [a, b, c] = Matrix([A, B, -(A+B)]).transpose().kernel().basis()[0] - return (-a/b, -c/b) - -## STEP 5 -# Fails at 0 -def eval_point(f, P): - (x, y) = P.xy() - return f(x=x, y=y) - -## STEP 6 -# f(x) + y g(x) -> (f(x), g(x)), should reduce mod eqn first -def get_polys(D): - return ( K(D(y=0)), K(D(y=1) - D(y=0)) ) - -## STEP 7 -# Accepts arbitrary list of points, including duplicates and inverses, and constructs function -# intersecting exactly those points if they form a principal divisor (i.e. sum to zero). -def construct_function(Ps): - # List of intermediate sums/principal divisors, removes 0 - xs = [(P, line(P, -P)) for P in Ps if P != 0] - - while len(xs) != 1: - assert(sum(P for (P, _) in xs) == 0) - xs2 = [] - - # Carry extra point forward - if mod(len(xs), 2) == 1: - x0 = xs[0] - xs = xs[1:] - else: - x0 = None - - # Combine the functions for all pairs - for n in range(0, floor(len(xs)/2)): - (A, aNum) = xs[2*n] - (B, bNum) = xs[2*n+1] - - # Divide out intermediate (P, -P) factors - num = L((aNum * bNum * line(A, B)).mod(eqn)) - den = line(A, -A) * line(B, -B) - D = num / K(den) - - # Add new element - xs2.append((A+B, D)) - - if x0 != None: - xs2.append(x0) - - xs = xs2 - - assert(xs[0][0] == 0) - - # Normalize, might fail but negl probability for random points. Must be done for zkps - # although free to use any coefficient - return D / D(x=0, y=0) - -## STEP 8 -# Random principal divisor with n points -def random_principal(n): - # For general elliptic curves, might want to clear cofactor depending on application - # Works for arbitrary curve groups - Ps = [E.random_element() for _ in range(0, n-1)] - Ps.append(-sum(Ps)) - return Ps - -## STEP 9 -# Random principal divisor with points with given multiplicities -def random_principal_mults(ms): - # Need to invert the last multiplicity to find the correct final value - m0 = ms[-1] - m0Inv = ZZ(Fr(m0)^(-1)) - - Ps = [E.random_element() for _ in range(0, len(ms)-1)] - Q = -m0Inv * sum(m * P for (m, P) in zip(ms[:-1], Ps)) - Ps.append(Q) - - assert(sum(m * P for (m, P) in zip(ms, Ps)) == 0) - return sum(( m * [P] for (m, P) in zip(ms, Ps) ), []) - -## STEP 10 -# Test at a random principal divisor -Ps = random_principal(33) -D = construct_function(Ps) -(f, g) = get_polys(D) - -# Should be the same up to constant -assert((f^2 - (x^3 + A * x + B) * g^2) / product(x - P.xy()[0] for P in Ps) in Fp) -assert(all(eval_point(D, P) == 0 for P in Ps)) - -## STEP 11 -# Test at random principal divisor with multiplicity. For a divisor that does not contain -# both P and -P for any P, it is sufficient to check the previous conditions and that -# gcd(f, g) = 1 -Ps = random_principal_mults([1,2,3,4,5,6]) -D = construct_function(Ps) -(f, g) = get_polys(D) - -assert((f^2 - (x^3 + A * x + B) * g^2) / product(x - P.xy()[0] for P in Ps) in Fp) -assert(all(eval_point(D, P) == 0 for P in Ps)) -assert(gcd(f, g) == 1) - -# The test to check that a function hits exactly a certain set of points uses -# Weil reciprocity to check that the product of one function over the points of -# the divisor of the other is the same quantity, up to leading coefficients. -# Taking the logarithmic derivative wrt a coordinate of one divisor gives a sum -# of rational functions. That is what is being checked here. While the proof -# will evaluate the dlog function of at the points, it is important to note -# that this is also a rational function in the coefficients of the other -# function. - -## STEP 12 -# Return logarithmic derivative wrt x -def dlog(D): - # Derivative via partials - Dx = D.differentiate(x) - Dy = D.differentiate(y) - Dz = Dx + Dy * ((3*x^2 + A) / (2*y)) - - # This is necessary because Sage fails when diving by D - U = L(2*y * Dz) - V = L(2*y * D) - - Den = K((V * V(y=-y)).mod(eqn)) - Num = L((U * V(y=-y)).mod(eqn)) - - # Must clear the denonimator so mod(eqn) well defined - assert(L(y * (Num * D - Den * Dz)).mod(eqn) == 0) - - return Num/Den # == Dz/D - -## STEP 13 -# Given a pair of distinct challenge points/line evaluate the function field element -def eval_function_challenge_mixed(A0, A1, D): - assert(A0 != A1) - A2 = -(A0 + A1) - (m, b) = slope_intercept(A0, A1) - DLog = dlog(D) - - # Coefficient per point - coeff = 1/((3 * x^2 + A) / (2 * y) - m) - expr = DLog * coeff - - # From paper, check that expr sum is 0, equals slope derivative wrt intercept - assert(sum(eval_point(coeff, P) for P in [A0, A1, A2]) == 0) - - # Evaluate - return sum(eval_point(expr, P) for P in [A0, A1, A2]) - -## STEP 14 -# Given a duplicated challenge point/line evaluate the function field element -def eval_function_challenge_dupl(A0, D): - A2 = -(2*A0) - (m, b) = slope_intercept(A0, A2) - DLog = dlog(D) - - # Coefficient for A2 - (xA0, yA0) = A0.xy() - (xA2, yA2) = A2.xy() - coeff2 = (2 * yA2) * (xA0 - xA2) / (3 * xA2^2 + A - 2 * m * yA2) - coeff0 = (coeff2 + 2 * m) - - return eval_point(DLog * coeff0, A0) - eval_point(DLog * coeff2, A2) - -## STEP 15 -# Given a pair of challenge points, detect if duplicate/mixed and modify numerator -def eval_point_challenge(A0, A1, P, mult=1): - (m, b) = slope_intercept(A0, A1) - (xP, yP) = P.xy() - - if A0 == A1: - (xA, _) = A0.xy() - num = (xA - xP) - else: - num = -1 - - den = yP - m * xP - b - return mult*num/den - -## STEP 16 -# Both should be true (uses same points as higher mult test) -[A0, A1] = [E.random_element() for _ in range(0, 2)] -assert(eval_function_challenge_mixed(A0, A1, D) == sum(eval_point_challenge(A0, A1, P) for P in Ps)) -assert(eval_function_challenge_dupl(A0, D) == sum(eval_point_challenge(A0, A0, P) for P in Ps)) - -# The ECIP takes advantage of the linearity of the right hand sides of the -# equations to sum multiplicities of the same point in different functions. -# The following shows how this works in base with scalars that are half the -# length of the field. Note this is important; if the scalars can exceed field -# length protocol can fail to be sound. Also works for random linear -# combinations. - -## STEP 17 -# return base -3 digits from {-1, 0, 1} from starting with least signficant -def base_neg3(n,k): - ds = [] - for i in range(0, k): - q = -floor(n/3) - r = ZZ(mod(n, 3)) - if r == 2: - q = q - 1 - r = -1 - ds.append(r) - n = q - - assert(n == 0) - assert(sum(d * (-3)^i for (i, d) in enumerate(ds))) - - return ds - -## STEP 18 -# P and -P are counted separately in basis -def pos_neg_mults(ds): - a = sum((-3)^i for (i, d) in enumerate(ds) if d == 1) - b = sum((-3)^i for (i, d) in enumerate(ds) if d == -1) - return (a, b) - -## STEP 19 -# Construct the principal divisor for each row given sum from previous row -def row_function(ds, Ps, Q): - # Construct divisor for row - Q2 = -3*Q + sum(d * P for (d, P) in zip(ds, Ps)) - div_ = [-Q, -Q, -Q, -Q2] + [d * P for (d, P) in zip(ds, Ps)] - div = [P for P in div_ if P != 0] - assert(sum(div) == 0) - - # Check that polynomial for row is correct - D = construct_function(div) - LHS = eval_function_challenge_dupl(A0, D) - RHS = sum(eval_point_challenge(A0, A0, P) for P in div) - assert(LHS == RHS) - - return (D, Q2, div) - -## STEP 20 -# Compute the function for each row using Shamir's trick and -3 -def ecip_functions(dss): - rows = list(dss) - rows.reverse() - - Q = 0 - Ds = [] - for ds in rows: - (p, Q, _) = row_function(ds, Bs, Q) - Ds.append(p) - - # Want lowest order first - Ds.reverse() - return (Q, Ds) - -## STEP 21 -# Construct digit vectors, note scalars are smaller than characteristic by construction -Bs = [E.random_element() for _ in range(0, 20)] # Basis Points -es = [ZZ.random_element(-2^127, 2^127) for _ in range(0, len(Bs))] # Linear combination -dss_ = [base_neg3(e, 81) for e in es] # Base -3 digits -epns = list(map(pos_neg_mults, dss_)) # list of P and -P mults per e -dss = Matrix(dss_).transpose() - -## STEP 22 -# Kinda slow -(Q, Ds) = ecip_functions(dss) - -## STEP 23 -# Q is the final sum -assert(Q == sum(e * B for (e, B) in zip(es, Bs))) -assert(Q == sum((ep - en) * B for ((ep, en), B) in zip(epns, Bs))) - -## STEP 24 -# Takes two mults and evaluates both P and -P -def eval_point_challenge_signed(A0, A1, P, mp, mn): - return eval_point_challenge(A0, A1, P, mult=mp) + eval_point_challenge(A0, A1, -P, mult=mn) - -## STEP 26 -# Sides should equal, remember to account for result point (-Q) -LHS = sum((-3)^i * eval_function_challenge_dupl(A0, D) for (i, D) in enumerate(Ds)) -basisSum = sum(eval_point_challenge_signed(A0, A0, B, ep, en) for ((ep, en), B) in zip(epns, Bs)) -RHS = basisSum + eval_point_challenge(A0, A0, -Q) -assert(LHS == RHS) diff --git a/src/precompiled_circuits/zk_ecip_wip.py b/src/precompiled_circuits/zk_ecip_wip.py deleted file mode 100644 index 3655381c..00000000 --- a/src/precompiled_circuits/zk_ecip_wip.py +++ /dev/null @@ -1,368 +0,0 @@ -from dataclasses import dataclass - -from sage.all_cmdline import EllipticCurve as _EllipticCurve, GF, Matrix as _Matrix, ZZ, floor as _floor, gcd as _gcd, product as _product, set_random_seed - -@dataclass(slots=True) -class Matrix: - _M: _Matrix - def __init__(self, ps): - if len(ps) > 0 and isinstance(ps[0], G1Point): ps = [E_ecpoint(p) for p in ps] - self._M = _Matrix(ps) - def __iter__(self): return self._M.__iter__() - def __next__(self): return self._M.__next__() - def transpose(self): M = Matrix([]); M._M = self._M.transpose(); return M - def kernel(self): return Kernel(self._M.kernel()) - -@dataclass(slots=True, frozen=True) -class Kernel: - _K: any - def basis(self): return [(Fp(int(x)), Fp(int(y)), Fp(int(z))) for (x, y, z) in self._K.basis()] - -@dataclass(slots=True) -class EllipticCurve: - _E: _EllipticCurve - def __init__(self, gf, p): self._E = _EllipticCurve(gf, p) - def cardinality(self): return self._E.cardinality() - def random_element(self): return E_g1point(self._E.random_element()) - -@dataclass(slots=True, frozen=True) -class G1Point: - x: int - y: int - def __radd__(self, x): assert x == 0; return self - def __add__(self, p): return E_g1point(E_ecpoint(self) + E_ecpoint(p)) - def __neg__(self): return E_g1point(-E_ecpoint(self)) - def __rmul__(self, x): return E_g1point(x * E_ecpoint(self)) - def __eq__(self, p): return self.x == 0 and self.y == 0 if p == 0 else self.x == p.x and self.y == p.y - def xy(self): (x, y) = E_ecpoint(self).xy(); return (int(x), int(y)) - -def E_ecpoint(p1): return E._E(0) if p1.x == 0 and p1.y == 0 else E._E.point([p1.x, p1.y]) -def E_g1point(_p1): (x, y) = (0, 0) if _p1.is_zero() else _p1.xy(); return G1Point(x=int(x), y=int(y)) - -# polynominal gcd: (poly, poly) -> poly -def gcd(x, y): return _gcd(x, y) - -# polynominal product: list[poly] -def product(iterable): return _product(iterable) - -# performs floor operation: rational -> rational -def floor(x): return _floor(x) - -# performs mod operation: (int, int) -> int -def mod(x, y): return int(x) % int(y) - -# Makes the script deterministic -#set_random_seed(0) - -# This demonstrates some of the computations of https://eprint.iacr.org/2022/596. -# First, that the logarithmic derivative of the Weil reciprocity equality holds -# over random principal divisors using both mixed and duplicate challenge points. -# Then, that by taking linear combinations of divisor proofs, can check an ECIP -# and combine multiplicities for the same basis points. Example ECIP uses base -# -3 and no CM. - -## STEP 1 -# Initialize an elliptic curve -p = 21888242871839275222246405745257275088696311157297823662689037894645226208583 -r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 -Fp = GF(p) # Base Field -Fr = GF(r) # Scalar Field -A = 0 -B = 3 -E = EllipticCurve(GF(p), [A,B]) -assert(E.cardinality() == r) - -## STEP 2 -K = Fp['x']; (x,) = K._first_ngens(1) -L = K['y']; (y,) = L._first_ngens(1) -eqn = y**2 - x**3 - A * x - B - -## STEP 3 -# Returns line passing through points, works for all points and returns 1 for O + O = O -def line(A, B): - if A == 0 and B == 0: - return 1 - else: - [a, b, c] = Matrix([A, B, -(A+B)]).transpose().kernel().basis()[0] - return a*x + b*y + c - -## STEP 4 -# Works for A == B but not A == -A, as the line has no slope or intercept -def slope_intercept(A, B): - [a, b, c] = Matrix([A, B, -(A+B)]).transpose().kernel().basis()[0] - return (-a/b, -c/b) - -## STEP 5 -# Fails at 0 -def eval_point(f, P): - (x, y) = P.xy() - return f(x=x, y=y) - -## STEP 6 -# f(x) + y g(x) -> (f(x), g(x)), should reduce mod eqn first -def get_polys(D): - return ( K(D(y=0)), K(D(y=1) - D(y=0)) ) - -## STEP 7 -# Accepts arbitrary list of points, including duplicates and inverses, and constructs function -# intersecting exactly those points if they form a principal divisor (i.e. sum to zero). -def construct_function(Ps): - # List of intermediate sums/principal divisors, removes 0 - xs = [(P, line(P, -P)) for P in Ps if P != 0] - - while len(xs) != 1: - assert(sum(P for (P, _) in xs) == 0) - xs2 = [] - - # Carry extra point forward - if mod(len(xs), 2) == 1: - x0 = xs[0] - xs = xs[1:] - else: - x0 = None - - # Combine the functions for all pairs - for n in range(0, floor(len(xs)/2)): - (A, aNum) = xs[2*n] - (B, bNum) = xs[2*n+1] - - # Divide out intermediate (P, -P) factors - num = L((aNum * bNum * line(A, B)).mod(eqn)) - den = line(A, -A) * line(B, -B) - D = num / K(den) - - # Add new element - xs2.append((A+B, D)) - - if x0 != None: - xs2.append(x0) - - xs = xs2 - - assert(xs[0][0] == 0) - - # Normalize, might fail but negl probability for random points. Must be done for zkps - # although free to use any coefficient - return D / D(x=0, y=0) - -## STEP 8 -# Random principal divisor with n points -def random_principal(n): - # For general elliptic curves, might want to clear cofactor depending on application - # Works for arbitrary curve groups - Ps = [E.random_element() for _ in range(0, n-1)] - Ps.append(-sum(Ps)) - return Ps - -## STEP 9 -# Random principal divisor with points with given multiplicities -def random_principal_mults(ms): - # Need to invert the last multiplicity to find the correct final value - m0 = ms[-1] - m0Inv = ZZ(Fr(m0)**(-1)) - - Ps = [E.random_element() for _ in range(0, len(ms)-1)] - Q = -m0Inv * sum(m * P for (m, P) in zip(ms[:-1], Ps)) - Ps.append(Q) - - assert(sum(m * P for (m, P) in zip(ms, Ps)) == 0) - return sum(( m * [P] for (m, P) in zip(ms, Ps) ), []) - -## STEP 10 -# Test at a random principal divisor -Ps = random_principal(33) -D = construct_function(Ps) -(f, g) = get_polys(D) - -# Should be the same up to constant -assert((f**2 - (x**3 + A * x + B) * g**2) / product(x - P.xy()[0] for P in Ps) in Fp) -assert(all(eval_point(D, P) == 0 for P in Ps)) - -## STEP 11 -# Test at random principal divisor with multiplicity. For a divisor that does not contain -# both P and -P for any P, it is sufficient to check the previous conditions and that -# gcd(f, g) = 1 -Ps = random_principal_mults([1,2,3,4,5,6]) -D = construct_function(Ps) -(f, g) = get_polys(D) - -assert((f**2 - (x**3 + A * x + B) * g**2) / product(x - P.xy()[0] for P in Ps) in Fp) -assert(all(eval_point(D, P) == 0 for P in Ps)) -assert(gcd(f, g) == 1) - -# The test to check that a function hits exactly a certain set of points uses -# Weil reciprocity to check that the product of one function over the points of -# the divisor of the other is the same quantity, up to leading coefficients. -# Taking the logarithmic derivative wrt a coordinate of one divisor gives a sum -# of rational functions. That is what is being checked here. While the proof -# will evaluate the dlog function of at the points, it is important to note -# that this is also a rational function in the coefficients of the other -# function. - -## STEP 12 -# Return logarithmic derivative wrt x -def dlog(D): - # Derivative via partials - Dx = D.differentiate(x) - Dy = D.differentiate(y) - Dz = Dx + Dy * ((3*x**2 + A) / (2*y)) - - # This is necessary because Sage fails when diving by D - U = L(2*y * Dz) - V = L(2*y * D) - - Den = K((V * V(y=-y)).mod(eqn)) - Num = L((U * V(y=-y)).mod(eqn)) - - # Must clear the denonimator so mod(eqn) well defined - assert(L(y * (Num * D - Den * Dz)).mod(eqn) == 0) - - return Num/Den # == Dz/D - -## STEP 13 -# Given a pair of distinct challenge points/line evaluate the function field element -def eval_function_challenge_mixed(A0, A1, D): - assert(A0 != A1) - A2 = -(A0 + A1) - (m, b) = slope_intercept(A0, A1) - DLog = dlog(D) - - # Coefficient per point - coeff = 1/((3 * x**2 + A) / (2 * y) - m) - expr = DLog * coeff - - # From paper, check that expr sum is 0, equals slope derivative wrt intercept - assert(sum(eval_point(coeff, P) for P in [A0, A1, A2]) == 0) - - # Evaluate - return sum(eval_point(expr, P) for P in [A0, A1, A2]) - -## STEP 14 -# Given a duplicated challenge point/line evaluate the function field element -def eval_function_challenge_dupl(A0, D): - A2 = -(2*A0) - (m, b) = slope_intercept(A0, A2) - DLog = dlog(D) - - # Coefficient for A2 - (xA0, yA0) = A0.xy() - (xA2, yA2) = A2.xy() - coeff2 = (2 * yA2) * (xA0 - xA2) / (3 * xA2**2 + A - 2 * m * yA2) - coeff0 = (coeff2 + 2 * m) - - return eval_point(DLog * coeff0, A0) - eval_point(DLog * coeff2, A2) - -## STEP 15 -# Given a pair of challenge points, detect if duplicate/mixed and modify numerator -def eval_point_challenge(A0, A1, P, mult=1): - (m, b) = slope_intercept(A0, A1) - (xP, yP) = P.xy() - - if A0 == A1: - (xA, _) = A0.xy() - num = (xA - xP) - else: - num = -1 - - den = yP - m * xP - b - return mult*num/den - -## STEP 16 -# Both should be true (uses same points as higher mult test) -[A0, A1] = [E.random_element() for _ in range(0, 2)] -assert(eval_function_challenge_mixed(A0, A1, D) == sum(eval_point_challenge(A0, A1, P) for P in Ps)) -assert(eval_function_challenge_dupl(A0, D) == sum(eval_point_challenge(A0, A0, P) for P in Ps)) - -# The ECIP takes advantage of the linearity of the right hand sides of the -# equations to sum multiplicities of the same point in different functions. -# The following shows how this works in base with scalars that are half the -# length of the field. Note this is important; if the scalars can exceed field -# length protocol can fail to be sound. Also works for random linear -# combinations. - -## STEP 17 -# return base -3 digits from {-1, 0, 1} from starting with least signficant -def base_neg3(n,k): - ds = [] - for i in range(0, k): - q = -floor(n/3) - r = ZZ(mod(n, 3)) - if r == 2: - q = q - 1 - r = -1 - ds.append(r) - n = q - - assert(n == 0) - assert(sum(d * (-3)**i for (i, d) in enumerate(ds))) - - return ds - -## STEP 18 -# P and -P are counted separately in basis -def pos_neg_mults(ds): - a = sum((-3)**i for (i, d) in enumerate(ds) if d == 1) - b = sum((-3)**i for (i, d) in enumerate(ds) if d == -1) - return (a, b) - -## STEP 19 -# Construct the principal divisor for each row given sum from previous row -def row_function(ds, Ps, Q): - # Construct divisor for row - Q2 = -3*Q + sum(d * P for (d, P) in zip(ds, Ps)) - div_ = [-Q, -Q, -Q, -Q2] + [d * P for (d, P) in zip(ds, Ps)] - div = [P for P in div_ if P != 0] - assert(sum(div) == 0) - - # Check that polynomial for row is correct - D = construct_function(div) - LHS = eval_function_challenge_dupl(A0, D) - RHS = sum(eval_point_challenge(A0, A0, P) for P in div) - assert(LHS == RHS) - - return (D, Q2, div) - -## STEP 20 -# Compute the function for each row using Shamir's trick and -3 -def ecip_functions(dss): - rows = list(dss) - rows.reverse() - - Q = 0 - Ds = [] - for ds in rows: - (p, Q, _) = row_function(ds, Bs, Q) - Ds.append(p) - - # Want lowest order first - Ds.reverse() - return (Q, Ds) - -## STEP 21 -# Construct digit vectors, note scalars are smaller than characteristic by construction -Bs = [E.random_element() for _ in range(0, 20)] # Basis Points -es = [ZZ.random_element(-2**127, 2**127) for _ in range(0, len(Bs))] # Linear combination -dss_ = [base_neg3(e, 81) for e in es] # Base -3 digits -epns = list(map(pos_neg_mults, dss_)) # list of P and -P mults per e -dss = Matrix(dss_).transpose() - -## STEP 22 -# Kinda slow -(Q, Ds) = ecip_functions(dss) - -## STEP 23 -# Q is the final sum -assert(Q == sum(e * B for (e, B) in zip(es, Bs))) -assert(Q == sum((ep - en) * B for ((ep, en), B) in zip(epns, Bs))) - -## STEP 24 -# Takes two mults and evaluates both P and -P -def eval_point_challenge_signed(A0, A1, P, mp, mn): - return eval_point_challenge(A0, A1, P, mult=mp) + eval_point_challenge(A0, A1, -P, mult=mn) - -## STEP 26 -# Sides should equal, remember to account for result point (-Q) -LHS = sum((-3)**i * eval_function_challenge_dupl(A0, D) for (i, D) in enumerate(Ds)) -basisSum = sum(eval_point_challenge_signed(A0, A0, B, ep, en) for ((ep, en), B) in zip(epns, Bs)) -RHS = basisSum + eval_point_challenge(A0, A0, -Q) -assert(LHS == RHS)