def makeEncoderDecoder(n, k, p): if not k <= n <= p: raise Exception( "Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" % (n, k, p)) Fp = FiniteField(p) Poly = polynomialsOver(Fp) maxE = ((n - k) // 2) # maximum allowed number of errors # message is a list of integers at most p def encode(message): if not all(x < p for x in message): raise Exception( "Message is improperly encoded as integers < p. It was:\n%r" % message) thePoly = Poly(message) return [[Fp(i), thePoly(Fp(i))] for i in range(n)] def solveSystem(encodedMessage, debug=False): for e in range(maxE, 0, -1): ENumVars = e + 1 QNumVars = e + k def row(i, a, b): return ([b * a**j for j in range(ENumVars)] + [-1 * a**j for j in range(QNumVars)] + [0] ) # the "extended" part of the linear system system = ( [row(i, a, b) for (i, (a, b)) in enumerate(encodedMessage)] + [[0] * (ENumVars - 1) + [1] + [0] * (QNumVars) + [1]]) # ensure coefficient of x^e in E(x) is 1 if debug: print("\ne is %r" % e) print("\nsystem is:\n\n") for row in system: print("\t%r" % (row, )) solution = someSolution(system, freeVariableValue=1) E = Poly([solution[j] for j in range(e + 1)]) Q = Poly([solution[j] for j in range(e + 1, len(solution))]) if debug: print("\nreduced system is:\n\n") for row in system: print("\t%r" % (row, )) print("solution is %r" % (solution, )) print("Q is %r" % (Q, )) print("E is %r" % (E, )) P, remainder = Q.__divmod__(E) if remainder == 0: return Q, E raise Exception("found no divisors!") def decode(encodedMessage): Q, E = solveSystem(encodedMessage) P, remainder = Q.__divmod__(E) if remainder != 0: raise Exception("Q is not divisibly by E!") return P.coefficients return encode, decode, solveSystem
def makeEncoderDecoder(n, k, p): if not k <= n <= p: raise Exception("Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" % (n,k,p)) Fp = FiniteField(p) Poly = polynomialsOver(Fp) maxE = ((n - k) // 2) # maximum allowed number of errors # message is a list of integers at most p def encode(message): if not all(x < p for x in message): raise Exception("Message is improperly encoded as integers < p. It was:\n%r" % message) thePoly = Poly(message) return [[Fp(i), thePoly(Fp(i))] for i in range(n)] def solveSystem(encodedMessage, debug=False): for e in range(maxE, 0, -1): ENumVars = e+1 QNumVars = e+k def row(i, a, b): return ([b * a**j for j in range(ENumVars)] + [-1 * a**j for j in range(QNumVars)] + [0]) # the "extended" part of the linear system system = ([row(i, a, b) for (i, (a,b)) in enumerate(encodedMessage)] + [[0] * (ENumVars-1) + [1] + [0] * (QNumVars) + [1]]) # ensure coefficient of x^e in E(x) is 1 if debug: print("\ne is %r" % e) print("\nsystem is:\n\n") for row in system: print("\t%r" % (row,)) solution = someSolution(system, freeVariableValue=1) E = Poly([solution[j] for j in range(e + 1)]) Q = Poly([solution[j] for j in range(e + 1, len(solution))]) if debug: print("\nreduced system is:\n\n") for row in system: print("\t%r" % (row,)) print("solution is %r" % (solution,)) print("Q is %r" % (Q,)) print("E is %r" % (E,)) P, remainder = Q.__divmod__(E) if remainder == 0: return Q, E raise Exception("found no divisors!") def decode(encodedMessage): Q,E = solveSystem(encodedMessage) P, remainder = Q.__divmod__(E) if remainder != 0: raise Exception("Q is not divisibly by E!") return P.coefficients return encode, decode, solveSystem
def to_dense(self): mat = np.empty((self.m, self.n), dtype='O') mat.fill(self.zero) for (i, j), val in self.items(): mat[i, j] = val return mat def __repr__(self): return repr(self.rowdicts) #- # Examples if __name__ == '__main__': Fp = FiniteField( 52435875175126190479447740508185965837690552500527637822603658699938581184513, 1) # (# noqa: E501) Poly = polynomialsOver(Fp) n = 8 omega = get_omega(Fp, n) PolyEvalRep = polynomialsEvalRep(Fp, omega, n) f = Poly([1, 2, 3, 4, 5]) xs = tuple([omega**i for i in range(n)]) ys = tuple(map(f, xs)) # print('xs:', xs) # print('ys:', ys) assert f == PolyEvalRep(xs, ys).to_coeffs()
def polynomialsEvalRep(field, omega, n): assert n & n - 1 == 0, "n must be a power of 2" # Check that omega is an n'th primitive root of unity assert type(omega) is field omega = field(omega) assert omega**(n) == 1 _powers = [omega**i for i in range(n)] assert len(set(_powers)) == n _poly_coeff = polynomialsOver(field) class PolynomialEvalRep(object): def __init__(self, xs, ys): # Each element of xs must be a power of omega. # There must be a corresponding y for every x. if type(xs) is not tuple: xs = tuple(xs) if type(ys) is not tuple: ys = tuple(ys) assert len(xs) <= n + 1 assert len(xs) == len(ys) for x in xs: assert x in _powers for y in ys: assert type(y) is field self.evalmap = dict(zip(xs, ys)) @classmethod def from_coeffs(cls, poly): assert type(poly) is _poly_coeff assert poly.degree() <= n padded_coeffs = poly.coefficients + [field(0)] * ( n - len(poly.coefficients)) ys = fft_helper(padded_coeffs, omega, field) xs = [omega**i for i in range(n) if ys[i] != 0] ys = [y for y in ys if y != 0] return cls(xs, ys) def to_coeffs(self): # To convert back to the coefficient form, we use polynomial interpolation. # The non-zero elements stored in self.evalmap, so we fill in the zero values # here. ys = [ self.evalmap[x] if x in self.evalmap else field(0) for x in _powers ] coeffs = [b / field(n) for b in fft_helper(ys, 1 / omega, field)] return _poly_coeff(coeffs) _lagrange_cache = {} def __call__(self, x): if type(x) is int: x = field(x) assert type(x) is field xs = _powers def lagrange(x, xi): # Let's cache lagrange values if (x, xi) in PolynomialEvalRep._lagrange_cache: return PolynomialEvalRep._lagrange_cache[(x, xi)] mul = lambda a, b: a * b num = reduce(mul, [x - xj for xj in xs if xj != xi], field(1)) den = reduce(mul, [xi - xj for xj in xs if xj != xi], field(1)) PolynomialEvalRep._lagrange_cache[(x, xi)] = num / den return PolynomialEvalRep._lagrange_cache[(x, xi)] y = field(0) for xi, yi in self.evalmap.items(): y += yi * lagrange(x, xi) return y def __mul__(self, other): # Scale by integer if type(other) is int: other = field(other) if type(other) is field: return PolynomialEvalRep( self.evalmap.keys(), [other * y for y in self.evalmap.values()]) # Multiply another polynomial in the same representation if type(other) is type(self): xs = [] ys = [] for x, y in self.evalmap.items(): if x in other.evalmap: xs.append(x) ys.append(y * other.evalmap[x]) return PolynomialEvalRep(xs, ys) @typecheck def __iadd__(self, other): # Add another polynomial to this one in place. # This is especially efficient when the other polynomial is sparse, # since we only need to add the non-zero elements. for x, y in other.evalmap.items(): if x not in self.evalmap: self.evalmap[x] = y else: self.evalmap[x] += y return self @typecheck def __add__(self, other): res = PolynomialEvalRep(self.evalmap.keys(), self.evalmap.values()) res += other return res def __sub__(self, other): return self + (-other) def __neg__(self): return PolynomialEvalRep(self.evalmap.keys(), [-y for y in self.evalmap.values()]) def __truediv__(self, divisor): # Scale by integer if type(divisor) is int: other = field(divisor) if type(divisor) is field: return self * (1 / divisor) if type(divisor) is type(self): res = PolynomialEvalRep((), ()) for x, y in self.evalmap.items(): assert x in divisor.evalmap res.evalmap[x] = y / divisor.evalmap[x] return res return NotImplemented def __copy__(self): return PolynomialEvalRep(self.evalmap.keys(), self.evalmap.values()) def __repr__(self): return f'PolyEvalRep[{hex(omega.n)[:15]}...,{n}]({len(self.evalmap)} elements)' @classmethod def divideWithCoset(cls, p, t, c=field(3)): """ This assumes that p and t are polynomials in coefficient representation, and that p is divisible by t. This function is useful when t has roots at some or all of the powers of omega, in which case we cannot just convert to evalrep and use division above (since it would cause a divide by zero. Instead, we evaluate p(X) at powers of (c*omega) for some constant cofactor c. To do this efficiently, we create new polynomials, pc(X) = p(cX), tc(X) = t(cX), and evaluate these at powers of omega. This conversion can be done efficiently on the coefficient representation. See also: cosetFFT in libsnark / libfqfft. https://github.com/scipr-lab/libfqfft/blob/master/libfqfft/evaluation_domain/domains/extended_radix2_domain.tcc """ assert type(p) is _poly_coeff assert type(t) is _poly_coeff # Compute p(cX), t(cX) by multiplying coefficients c_acc = field(1) pc = _poly_coeff(list(p.coefficients)) # make a copy for i in range(p.degree() + 1): pc.coefficients[-i - 1] *= c_acc c_acc *= c c_acc = field(1) tc = _poly_coeff(list(t.coefficients)) # make a copy for i in range(t.degree() + 1): tc.coefficients[-i - 1] *= c_acc c_acc *= c # Divide using evalrep pc_rep = cls.from_coeffs(pc) tc_rep = cls.from_coeffs(tc) hc_rep = pc_rep / tc_rep hc = hc_rep.to_coeffs() # Compute h(X) from h(cX) by dividing coefficients c_acc = field(1) h = _poly_coeff(list(hc.coefficients)) # make a copy for i in range(hc.degree() + 1): h.coefficients[-i - 1] /= c_acc c_acc *= c # Correctness checks # assert pc == tc * hc # assert p == t * h return h return PolynomialEvalRep
def makeEncoderDecoder(n, k, p): if not k <= n <= p: raise Exception( "Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" % (n, k, p)) Fp = FiniteField(p) Poly = polynomialsOver(Fp) maxE = ((n - k) // 2) # maximum allowed number of errors # message is a list of integers at most p def encode(message): if not all(x < p for x in message): raise Exception( "Message is improperly encoded as integers < p. It was:\n%r" % message) def row(i, b): return [Fp(i**(j)) for j in range(k)] + [Fp(b)] system = [row(i, message[i]) for i in range(k)] interpolated = someSolution(system, freeVariableValue=1) thePoly = Poly(interpolated) print("Original message is:") print(message) print("The polynomial encoding the message is:") print(thePoly) return [thePoly(Fp(i)) for i in range(n)] def solveSystem(encodedMessage, debug=True): for e in range(maxE, 0, -1): ENumVars = e QNumVars = e + k def row(i, x, r): return ([r * x**j for j in range(ENumVars)] + [-1 * x**j for j in range(QNumVars)] + [-r * x**ENumVars] ) # the "extended" part of the linear system system = ([ row(i, a, b) for (i, (a, b)) in enumerate(encodedMessage) ]) # Add one more row in the end ensure coefficient of x^e in E(x) is 1 if debug: print("\nSystem of equations is:\n\n") for row in system: print("\t%r" % (row, )) solution = someSolution(system, freeVariableValue=1) E = Poly([solution[j] for j in range(ENumVars)] + [Fp(1)]) Q = Poly([solution[j] for j in range(ENumVars, len(solution))]) if debug: print("\nReduced system is:\n\n") for row in system: print("\t%r" % (row, )) print("Solution is %r" % (solution, )) print("Q is %r" % (Q, )) print("E is %r" % (E, )) P, remainder = Q.__divmod__(E) if remainder == 0: return Q, E raise Exception("found no divisors!") def decode(encodedMessage): encodedMessage = [[Fp(i), encodedMessage[i]] for i in range(len(encodedMessage))] Q, E = solveSystem(encodedMessage) Pcoefs, remainder = Q.__divmod__(E) if remainder != 0: raise Exception("Q is not divisibly by E!") P = Poly(Pcoefs) print("Decoded polynomial r(x) = Q(x) / E(x) is: ") print(P) return [P(Fp(i)) for i in range(k)] return encode, decode, solveSystem