def GetNIZKPChallenges(n, y, kappa, secparams): """ Algorithm 7.5: Computes n challenges c ∈ Z_q for a given of public value y. The domain Y of the input value is unspecified. The results in c = (c_1, ..., c_n) are identical to c_i = RecHash(y, i), but precomputing H makes the algorithm more efficient. Args: y (unspecified): Public value t (unspecified): Commitment kappa (int): Soundness strength 1 <= kappa <= 8L secparams (SecurityParams): Collection of public security parameters Retuns: list of mpz: List containing n computed challenges (mpz) """ AssertInt(n) AssertInt(kappa) assert kappa >= 1 and kappa <= 8 * secparams.L, "Constraint for kappa: 1 <= kappa <= 8L" AssertClass(secparams, SecurityParams) H = RecHash(y, secparams) c = [] for i in range(1, n + 1): I = RecHash(i, secparams) c.append(mpz(ToInteger(secparams.hash(H + I)) % 2 ^ kappa)) return c
def GetPrimes(n, secparams): """ Algorithm 7.1: Computes the first n prime numbers from G_q. The computation possibly fails if n is large and p is small, but this case is very unlikely in practice. In a more efficient implementation of this algorithm, the resulting list of primes is precomputed for the largest expected value n. Args: n (int): Number of primes to be calculated secparams (SecurityParams): Collection of public security parameters Returns: list of mpz : A list with length n containing the first n prime numbers in G_p """ AssertInt(n) AssertClass(secparams, SecurityParams) x = mpz(1) primes = [] for i in range(n): # i = 0, ... , n-1 while True: x += 1 if x <= 2 else mpz(2) if x >= secparams.p: return [] # n is incompatible with p if gmpy2.is_prime(x) and IsMember(x, secparams): # see Alg. 7.2 break primes.append(x) return primes # p \elementof G_p \cap P)^n
def CheckShuffleProofs(pi_bold, e_0_bold, E_bold, pk, i, secparams): """ Algorithm 7.47: Checks if a chain of shuffle proofs generated by s different authorities is correct. Args: pi_bold (list of ShuffleProof): Shuffle proof e_0_bold (list): ElGamal Encryptions E_bold (list): Shuffled ElGamal Encryptions pk (mpz): Encryption key pk i (int): Authority index secparams (SecurityParams): Collection of public security parameters Returns: bool """ AssertList(pi_bold) AssertList(e_0_bold) AssertList(E_bold) AssertMpz(pk) AssertInt(i) AssertClass(secparams, SecurityParams) N = len(e_0_bold) e_bold_tmp = [] e_bold_tmp.append(e_0_bold) e_bold_tmp.extend(E_bold) for j in range(secparams.s): if i != j: if not CheckShuffleProof(pi_bold[j], e_bold_tmp[j], e_bold_tmp[j+1],pk, secparams): return False return True
def GenPermutation(N, secparams): """ Algorithm 7.42: Generates a random permutation ψ ∈ Ψ following Knuth's shuffle algorithm. Args: N (int): Permutation size secparams (SecurityParams): Collection of public security parameters Returns: list of int: Permutation """ AssertInt(N) AssertClass(secparams, SecurityParams) I = list(range(N)) res = [] for i in range(N): k = randomBoundedInt(i, N - 1, secparams) j_i = I[k] I[k] = I[i] res.append(j_i) return res
def GetFinalization(v, P_bold, B, secparams): """ Algorithm 7.36: Computes the finalization code F_v for voter v from the given points p_i and returns F_v together with the randomizations r_v used in the OT response. Args: v (int): Voter index p_bold (list of points): Points B (list): Ballot list secparams (SecurityParams): Collection of public security parameters Returns: tuple: delta (F_v, r_bold_v) """ AssertInt(v) AssertList(P_bold) AssertList(B) AssertClass(secparams, SecurityParams) p_bold_v = P_bold[v] F = Truncate(RecHash(p_bold_v, secparams), secparams.L_F) for i in range(len(B)): if (B[i].voterId == v): z_i = B[i].randomizations delta = (F, z_i) return delta
def ToString(x, k, A): """ Algorithm 4.6: Computes a string representation of length k in big-endian order of a given non negative integer x in N Args: x (mpz): Integer x ∈ N k (int) String length k >= log_N (x) A (list) Alphabet A = {c_1, ..., c_N} Returns: string: The string representation of x """ AssertMpz(x) AssertInt(k) AssertList(A) S = "" N = len(A) for i in reversed(range(k)): s_k = A[x % N] x = x // N S = s_k + S return S
def GenPolynomial(d, secparams): """ Algorithm 7.8: Generates the coefficients a_0,...,a_d of a random polynomial A(X) = Sigma(i=0...d) a_i X^i mod p' of degree d >= 0. The algorithm also accepts d = -1 as input, which we interpret as the polynomial A(X) = 0. In this case, the algorithm returns the coefficient list a = (0). Args: d (int): Degree d >= -1 secparams (SecurityParams): Collection of public security parameters Returns: list of mpz: A list of coefficients a_0 ... a_d of polynomial A(X) """ AssertInt(d) AssertClass(secparams, SecurityParams) assert (d >= -1) a_bold = [] a_d = 0 if (d == -1): return [0] else: for i in range(d): a_bold.append(randomMpz(secparams.p_prime, secparams)) # generate the last coefficient != 0 while a_d == 0: a_d = randomMpz(secparams.p_prime, secparams) a_bold.append(a_d) return a_bold
def GenPoints(n, k, secparams): """ Algorithm 7.7: Generates a list of n random points picket from t random polynomials A_j(X) of degree k_j - 1 (by picking n_j different random points from each polynomial). Additional, the values y_j = A_j(0) are computed for all random polynomials and returned together with the random points. Args: n (int): Number of candidates n >= 2 k (int): Number of selections 0 < k < n secparams (SecurityParams): Collection of public security parameters Returns: tuple: (p,y'), points p ∈ (Z_p^2)^n, and the y value of x=0 """ AssertInt(n) AssertInt(k) p_bold = [] a_bold = GenPolynomial( k - 1, secparams ) # the number of 1's in the eligibility matrix indicate how many selections the voter can make and therefore decides the degree of the polynomial X = [] for i in range(n): x = mpz(0) # get a unique x from Z_p' while True: x = randomMpz(secparams.p_prime, secparams) if x not in X or secparams.deterministicRandomGen: # if randomMpz is deterministic, we would be stuck in an endless loop X.append(x) break y = GetYValue( x, a_bold, secparams ) # get the corresponding y value of x on the polynomial a_j p_i = Point(x, y) # Point tuple p_bold.append(p_i) # part of the private voter data y_prime = GetYValue(mpz(0), a_bold, secparams) # Point (0,Y(0)) return (p_bold, y_prime)
def GetVotingPage(v, c_bold, n_bold, k_bold): """ Algorithm 7.17: Computes a string P in A_UCS^*, which represents the voting page displayed to voter. Specifying the details of presenting the information on the voting page is beyond the scope of this document. Args: v (int): Voter index c_bold (list): Candidate descriptions c = (C_1, ..., C_n), c_v ∈ A_UCS^* n_bold (list): Number of candidates n =(n_1, ..., n_t), n_j >= 2 k_bold (list): Number of selections k = (k_1, ..., k_t), 0 <= k_j < n_j Returns: string: String P ∈ A_UCS^* displayed to the voter """ AssertInt(v) AssertList(c_bold) AssertList(n_bold) AssertList(k_bold) electionString = "" candidateIndex = 0 for j in range(len(k_bold)): electionString += "# Election {}\n".format(j) for l in range(n_bold[j]): electionString += "Candidate {}: {}\n".format( candidateIndex, c_bold[candidateIndex]) candidateIndex += 1 electionString += "You can make {} selection(s) for this particular election\n\n".format( k_bold[j]) P = \ """ Voting page for voter {v}: Simultaneous elections: {elections} """.format( v = v , elections = electionString ) return P
def CheckBallot(v, alpha, pk, k_bold, E_bold, x_hat_bold, B, secparams): """ Algorithm 7.22: Checks if a ballot alpha obtained from voter v is valid. For this, voter i must not have submitted a valid ballot before, pi must be valid, and x_hat must be the public voting credential of voter v. Note that parameter checking |a| ki for ki °tj1 kij is an important initial step of this algorithm. Args: v (int): Voter index alpha (Ballot): Ballot pk (mpz): Public Key k_bold (list): Number of selections E_bold (list): Eligibility matrix E x_hat_bold (list): Public voting credentials B (list): Ballot List secparams (SecurityParams): Collection of public security parameters Returns: bool: True if the ballot is valid, False if not """ AssertInt(v) AssertClass(alpha, Ballot) AssertMpz(pk) assert IsMember(pk, secparams), "pk must be in G_q" AssertList(k_bold) AssertList(x_hat_bold) AssertList(B) AssertClass(secparams, SecurityParams) k_prime = 0 for j in range(len(k_bold)): k_prime = k_prime + E_bold[v][j] * k_bold[ j] # if voter i is eligible to cast a vote in election j, multiply 1 * the number of selections in j hasBallot = HasBallot(v, B, secparams) credentialCheck = x_hat_bold[v] == alpha.x_hat queryLength = len(alpha.a_bold) == k_prime if not hasBallot and credentialCheck and queryLength: ballotProofCheck = CheckBallotProof(alpha.pi, alpha.x_hat, alpha.a_bold, pk, secparams) if ballotProofCheck: return True, [] return (False, [False, hasBallot, credentialCheck, queryLength])
def HasBallot(v, B_bold, secparams): """ Algorithm 7.23: Checks if the ballot list B contains an entry for v. Args: v (int): Voter index B_bold (list): Ballot list secparams (SecurityParams): Collection of public security parameters Returns: bool """ AssertInt(v) AssertList(B_bold) AssertClass(secparams, SecurityParams) for j in range(len(B_bold)): # (v_j, alpha, r) = B_bold[j] if v == B_bold[j].voterId: return True return False
def GetNIZKPChallenge(y, t, kappa, secparams): """ Algorithm 7.4: Computes a NIZKP challenge c ∈ Z_q for a given public value y and a public commitment t. The domains Y and T of the input values are unspecified. Args: y (unspecified): Public value t (unspecified): Commitment kappa (int): Soundness strength 1 <= kappa <= 8L secparams (SecurityParams): Collection of public security parameters Returns: mpz: The NIZKP challenge """ AssertInt(kappa) assert kappa >= 1 and kappa <= 8 * secparams.L, "Constraint for kappa: 1 <= kappa <= 8L" AssertClass(secparams, SecurityParams) c = mpz(ToInteger(RecHash([y, t], secparams)) % 2 ^ kappa) return c
def GetGenerators(n, secparams): """ Algorithm 7.3: Computes n independent generators of G_q. The algorithm is an adaption of the NIST standard FIPS PUB 186-4 [1, Appendix A.2.3]. The string "chVote" guarantees that the resulting values are specific for chVote. In a more efficient implementation of this algorithm, the list of resulting generators is accumulated in a cache or precomputed for the largest expected value n_max >= n. Args: n (int): The number of primes to be calculated secparams (SecurityParams): Collection of public security parameters Returns: list of mpz: a list with independent generators of G_p (mpz) """ AssertInt(n) AssertClass(secparams, SecurityParams) assert n >= 0, "n must be greater than or equal 0" generators = [] for i in range(n): x = 0 while True: x += 1 h = mpz( ToInteger(RecHash(["chVote", "ggen", i, x], secparams)) % secparams.p) h = (h**2) % secparams.p if h not in (0, 1): break generators.append(h) return generators
def securityLevel(self, value): AssertInt(value) self.state.securityLevel = value
def id(self, value): AssertInt(value) self.state.id = value
def numberOfParallelElections(self, value): AssertInt(value) self.state.numberOfParallelElections = value
def GenResponse(v, a_bold, pk, n_bold, k_bold, E_bold, P_bold, secparams): """ Algorithm 7.25: Generates the response beta for the given OT query a. The messages to transfer are byte array representations of the n points (p_v,1, ... p_v,n). Along with beta, the algorithm also returns the randomizations r used to generate the response. Args: v (int): Voter Index a_bold (list of mpz): Queries pk (mpz): Encryption Key n_bold (list of int): Number of candidates k_bold (list of int): Number of selections E_bold (list of list): Eligibility matrix P_bold (list of list): Points N x n secparams (SecurityParams): Collection of public security parameters Returns: tuple: (beta, r) """ AssertInt(v) AssertList(a_bold) for a in a_bold: assert IsMember(a[0], secparams), "All elements of a_bold must be in G_q" for a in a_bold: assert IsMember(a[1], secparams), "All elements of a_bold must be in G_q" AssertMpz(pk) assert IsMember(pk, secparams), "Public key must be in G_q" assert pk != mpz(1), "Public key cannot be equal to 1" AssertList(n_bold) AssertList(k_bold) AssertList(E_bold) AssertList(P_bold) AssertClass(secparams, SecurityParams) n = sum(n_bold) k = len(a_bold) t = len(n_bold) M = [] z_1 = randomMpz(secparams.q, secparams) z_2 = randomMpz(secparams.q, secparams) beta = [] b_bold = [] for j in range(k): beta.append(randomQuadResMpz(secparams)) b_j = gmpy2.powmod(a_bold[j][0], z_1, secparams.p) b_j *= gmpy2.powmod(a_bold[j][1], z_2, secparams.p) b_j *= beta[j] b_j %= secparams.p b_bold.append(b_j) l_M = ceil(secparams.L_M / secparams.L) p_bold = GetPrimes(n, secparams) n_prime = 0 k_prime = 0 C_bold = [[None for i in range(sum(k_bold))] for i in range(n)] for l in range(len(k_bold)): if E_bold[v][l] != 0: for i in range(n_prime, n_prime + n_bold[l]): p_prime_i = gmpy2.powmod(p_bold[i], z_1, secparams.p) M.append( ToByteArrayN(P_bold[v][i].x, secparams.L_M // 2) + ToByteArrayN(P_bold[v][i].y, secparams.L_M // 2)) for j in range(k_prime, k_prime + E_bold[v][l] * k_bold[l]): k_ij = p_prime_i * beta[j] % secparams.p k_tmp = bytearray() for c in range(l_M): k_tmp += RecHash([k_ij, c], secparams) K_ij = Truncate(k_tmp, secparams.L_M) C_bold[i][j] = XorByteArray([M[i], K_ij]) k_prime = k_prime + E_bold[v][l] * k_bold[l] n_prime = n_prime + n_bold[l] d = (gmpy2.powmod(pk, z_1, secparams.p) * gmpy2.powmod(secparams.g, z_2, secparams.p)) % secparams.p beta = Response(b_bold, C_bold, d) z = (z_1, z_2) return (beta, z)