def euclid2d(m, n, P, Q, PQ, A): """ The 2-dimensional scalar pseudomultiplication: x([r]P + [s - r]P) with r = s / {Golden Ratio}') """ s0, s1 = m, n x0 = list(P) x1 = list(Q) diff= list(PQ) while s0 != 0: if s1 < s0: x0, x1 = cswap(x0, x1, 1) s0, s1 = s1, s0 if s1 <= 4*s0: # Fibonacci step x = list(x0) x0 = xadd(x1, x0, diff) diff= list(x) s1 -= s0 elif (s0 % 2) == (s1 % 2): x0 = xadd(x1, x0, diff) x1 = xdbl(x1, A) s1 = (s1 - s0) // 2 elif (s1 % 2) == 0: diff=xadd(x1, diff, x0) x1 =xdbl(x1, A) s1 //= 2 else: diff= xadd(x0, diff, x1) x0 = xdbl(x0, A) s0 //= 2 while s1 % 2 == 0: x1 = xdbl(x1, A) s1 //= 2 if s1 > 1: # Ladder step on the missing part: x0 will correspond with Ladder(x1) diff= list(x1) x0 = xdbl(x1, A) s1_binary = bin(s1)[2:][::-1] s1_length = len(s1_binary) for i in range(s1_length - 2, -1, -1): x0, x1 = cswap(x0, x1, int(s1_binary[i + 1]) ^ int(s1_binary[i])) x1 = xadd(x0, x1, diff) x0 = xdbl(x0, A) x0, x1 = cswap(x0, x1, int(s1_binary[0])) else: # In this case, the output should correspond with x1, thus we swap to x0 x0, x1 = cswap(x0, x1, 1) return x0
def Ladder3pt(m, P, Q, PQ, A): """ ---------------------------------------------------------------------- Ladder3pt() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ, the affine Montgomery constant A where E : y^2 = x^3 + Ax^2 + x, and a positive integer m output: the projective Montgomery x-coordinate point x(P + [m]Q) ---------------------------------------------------------------------- """ X0 = list([field(Q[0]), field(Q[1])]) X1 = list([field(P[0]), field(P[1])]) X2 = list([field(PQ[0]), field(PQ[1])]) t = 0x1 for i in range(0, bitlength(p), 1): X1, X2 = cswap(X1, X2, t & m == 0) X0, X1 = xdbladd(X0, X1, X2, A) X1, X2 = cswap(X1, X2, t & m == 0) t <<= 1 return X1
def elligator(A): """ elligator() samples two points on E[pi + 1] or E[pi - 1] """ Ap = (A[0] + A[0]) Ap = (Ap - A[1]) Ap = (Ap + Ap) Cp = A[1] u = field(random.randint(2, p_minus_one_halves)) u_squared = (u ** 2) u_squared_plus_one = (u_squared + 1) u_squared_minus_one = (u_squared - 1) C_times_u_squared_minus_one = (Cp * u_squared_minus_one) AC_times_u_squared_minus_one = (Ap * C_times_u_squared_minus_one) tmp = (Ap ** 2) tmp = (tmp * u_squared) aux = (C_times_u_squared_minus_one ** 2) tmp = (tmp + aux) tmp = (AC_times_u_squared_minus_one * tmp) alpha, beta = 0, u alpha, beta = cswap(alpha, beta, tmp == 0) u_squared_plus_one = (alpha * u_squared_plus_one) alpha = (alpha * C_times_u_squared_minus_one) Tp_X = (Ap + alpha) Tm_X = (Ap * u_squared) Tm_X = (Tm_X + alpha) Tm_X = (0 - Tm_X) tmp = (tmp + u_squared_plus_one) Tp_X, Tm_X = cswap(Tp_X, Tm_X, not tmp.issquare()) return ( [Tp_X, C_times_u_squared_minus_one], [Tm_X, C_times_u_squared_minus_one], )
def evaluate_strategy(self, E, P, L, strategy, n, m, e): """ evaluate_strategy(): inputs : a projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, a list of two projective Montgomery x-coordinate torsion-(l_1 x ... l_n) points x(T_+) := XP/ZP and x(T_-) := XM/ZM, the list of small odd primes [l_1, l_2, ..., l_n], an strategy and length of the given list of small odd primes, maximum number of degree-l_i isogeny constructions, and the secret integer vector to be evaluated output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is E / <P>, the new maximum and current number of degree-l_i isogeny constructions to be performed after the strategy evaluation. NOTE: T_+ belongs to E[pi - 1] and T_- belongs to E[pi + 1]. In particular, P = [T_-, T_+] """ v = list(m) u = list(e) ramifications = [] moves = [ 0 ] # moves: this list determines whether an isogeny construction must be performed k = 0 # k: current element of the strategy ramifications.append(P) E_i = list(E) for i in range(len(strategy)): pos = self.formula.L.index( L[n - 1 - i]) # Current element of self.formula.L to be required # Reaching the vertex (n - 1 - i, i) # Vertical edges prev = sum(moves) while prev < (n - 1 - i): moves.append( strategy[k]) # Number of vertical edges to be performed T = list(ramifications[-1]) # New ramification for j in range(prev, prev + strategy[k], 1): T = self.curve.xmul(T, E_i, self.formula.L.index(L[j])) ramifications.append(T) prev += strategy[k] k += 1 # At this point, vertex (n - 1 - i, i) has been reached if (v[pos] > 0 ): # Maximum number of degree-l_{pos} isogeny constructions? # At this step, ramifications[-1] is the i-th leaf if self.curve.isinfinity(ramifications[-1]) == False: # Dummy or NOT Dummy degree-(l_{n-1-i}) isogeny construction, that's the question? b_i = isequal[u[pos] == 0] ramifications[-1][0], ramifications[0][0] = cswap( ramifications[-1][0], ramifications[0][0], b_i) ramifications[-1][1], ramifications[0][1] = cswap( ramifications[-1][1], ramifications[0][1], b_i) if self.formula_name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if self.tuned: self.formula.set_parameters_velu( self.formula.sJ_list[pos], self.formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and xEVAL if self.formula.L[pos] == 3: b = 0 c = 0 else: b = int( floor(sqrt(self.formula.L[pos] - 1) / 2.0)) c = int( floor((self.formula.L[pos] - 1.0) / (4.0 * b))) self.formula.set_parameters_velu(b, c, pos) if (self.formula_name == 'hvelu' and self.formula.L[pos] <= self.formula.HYBRID_BOUND): K = self.formula.kps(ramifications[-1], E_i, pos) else: self.formula.kps(ramifications[-1], E_i, pos) T = self.curve.xmul(ramifications[-1], E_i, pos) else: K = self.formula.kps(ramifications[-1], E_i, pos) ramifications[-1][0], ramifications[0][0] = cswap( ramifications[-1][0], ramifications[0][0], b_i) ramifications[-1][1], ramifications[0][1] = cswap( ramifications[-1][1], ramifications[0][1], b_i) # New isogeny construction C_i = self.formula.xisog(E_i, pos) # Next, the horizontal edge [(0,i),(0,i+1)] is performed if self.formula_name == 'tvelu' or ( self.formula_name == 'hvelu' and self.formula.L[pos] <= self.formula.HYBRID_BOUND): d_i = (self.formula.L[pos] - 1) // 2 mask = isequal[(self.formula.L[pos] == 3 )] # catching special case when l = 3 Z = self.formula.yadd(K[(d_i + mask) - 1], K[0], K[(d_i + mask) - 2]) # y([d_i + 1]K[0]) Z[0], K[d_i][0] = cswap(Z[0], K[d_i][0], mask ^ 1) Z[1], K[d_i][1] = cswap(Z[1], K[d_i][1], mask ^ 1) T = self.formula.yadd( K[d_i], K[d_i - 1], K[0]) # y([2*d_i + 1]K[0]) := y([l_i]K[0]) T = [ (T[1] + T[0]), (T[1] - T[0]), ] # x([l_i]K[0]) if self.formula_name == 'tvelu' or ( self.formula_name == 'hvelu' and self.formula.L[pos] <= self.formula.HYBRID_BOUND): ramifications[0] = self.formula.xeval( ramifications[0], pos) else: ramifications[0] = self.formula.xeval( ramifications[0], E_i) T[0], ramifications[0][0] = cswap(T[0], ramifications[0][0], b_i) T[1], ramifications[0][1] = cswap(T[1], ramifications[0][1], b_i) # The remainder horizontal edges are performed for j in range(1, len(moves) - 1, 1): T = self.curve.xmul(ramifications[j], E_i, pos) if self.formula_name == 'tvelu' or ( self.formula_name == 'hvelu' and self.formula.L[pos] <= self.formula.HYBRID_BOUND): ramifications[j] = self.formula.xeval( ramifications[j], pos) else: ramifications[j] = self.formula.xeval( ramifications[j], E_i) T[0], ramifications[j][0] = cswap( T[0], ramifications[j][0], b_i) T[1], ramifications[j][1] = cswap( T[1], ramifications[j][1], b_i) C_i[0], E_i[0] = cswap(C_i[0], E_i[0], b_i ^ 1) C_i[1], E_i[1] = cswap(C_i[1], E_i[1], b_i ^ 1) v[pos] -= 1 u[pos] -= b_i ^ 1 else: for j in range(0, len(moves) - 1, 1): ramifications[j] = self.curve.xmul(ramifications[j], E_i, pos) moves.pop() ramifications.pop() pos = self.formula.L.index( L[0]) # Current element of self.formula.L to be required if self.curve.isinfinity(ramifications[0]) == False: if m[pos] > 0: b_i = isequal[e[pos] == 0] if self.formula_name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if self.tuned: self.formula.set_parameters_velu( self.formula.sJ_list[pos], self.formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and xEVAL if self.formula.L[pos] == 3: b = 0 c = 0 else: b = int(floor(sqrt(self.formula.L[pos] - 1) / 2.0)) c = int( floor((self.formula.L[pos] - 1.0) / (4.0 * b))) self.formula.set_parameters_velu(b, c, pos) self.formula.kps(ramifications[0], E_i, pos) else: self.formula.kps(ramifications[0], E_i, pos) C_i = self.formula.xisog(E_i, pos) C_i[0], E_i[0] = cswap(C_i[0], E_i[0], b_i ^ 1) C_i[1], E_i[1] = cswap(C_i[1], E_i[1], b_i ^ 1) v[pos] -= 1 u[pos] -= b_i ^ 1 return E_i, v, u
def bsidh_test(ctx): """ GF(p²)-operation cost of kps, xisog, and xeval """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] p = algo.params.p np = algo.params.np Ep = algo.params.Ep nm = algo.params.nm Em = algo.params.Em Lp = algo.params.Lp Lm = algo.params.Lm L = list(Lp + Lm) A = [algo.curve.field(8), algo.curve.field(4)] xmul = algo.curve.xmul isinfinity = algo.curve.isinfinity coeff = algo.curve.coeff isfullorder = algo.curve.isfullorder cofactor_multiples = algo.curve.cofactor_multiples Ladder3pt = algo.curve.Ladder3pt if algo.formula.name != 'tvelu': set_parameters_velu = algo.formula.set_parameters_velu print_parameters_velu = algo.formula.print_parameters_velu HYBRID_BOUND = algo.formula.HYBRID_BOUND init_runtime_field = algo.field.init_runtime show_runtime_field = algo.field.show_runtime init_runtime_basefield = algo.basefield.init_runtime show_runtime_basefield = algo.basefield.show_runtime kps = algo.formula.kps xisog = algo.formula.xisog xeval = algo.formula.xeval field = algo.field random = SystemRandom() print("p := 0x%X;" % p) print("fp := GF(p);") print("_<x> := PolynomialRing(fp);") print("fp2<u> := ext<fp | x^2 + 1>;") print("Pr<x> := PolynomialRing(fp2);") # ---Generators in E[p + 1] PA = list(algo.strategy.PA) QA = list(algo.strategy.QA) PQA = list(algo.strategy.PQA) # ---Generators in E[p - 1] PB = list(algo.strategy.PB) QB = list(algo.strategy.QB) PQB = list(algo.strategy.PQB) print("E := EllipticCurve(x^3 + (%s) * x^2 + x);" % coeff(A)) S = list(PA) T = list(QA) ST = list(PQA) for i in range(0, np, 1): for idx in range(0, Ep[i], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) print("\n// Verifying torsion-(p + 1) points") print("// x([p + 1]PA) = (1:0)?\t", isinfinity(S)) print("// x([p + 1]QA) = (1:0)?\t", isinfinity(T)) print("// x([p + 1]PQA) = (1:0)?\t", isinfinity(ST)) S = list(PB) T = list(QB) ST = list(PQB) for i in range(np, np + nm, 1): for idx in range(0, Em[i - np], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) print("\n// Verifying torsion-(p - 1) points") print("// x([p - 1]PB) = (1:0)?\t", isinfinity(S)) print("// x([p - 1]QB) = (1:0)?\t", isinfinity(T)) print("// x([p - 1]PQB) = (1:0)?\t", isinfinity(ST)) # Case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) print("\n// Verifying orders") assert isfullorder(cofactor_multiples(S, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(T, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(ST, A, range(0, np, 1))) print( "// PA is a full order point?\t", isfullorder(cofactor_multiples(S, A, range(0, np, 1))), ) print( "// QA is a full order point?\t", isfullorder(cofactor_multiples(T, A, range(0, np, 1))), ) print( "// QPA is a full order point?\t", isfullorder(cofactor_multiples(ST, A, range(0, np, 1))), ) # Case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) print("\n// Verifying orders") assert isfullorder(cofactor_multiples(S, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(T, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(ST, A, range(np, np + nm, 1))) print( "// PB is a full order point?\t", isfullorder(cofactor_multiples(S, A, range(np, np + nm, 1))), ) print( "// QB is a full order point?\t", isfullorder(cofactor_multiples(T, A, range(np, np + nm, 1))), ) print( "// QPB is a full order point?\t", isfullorder(cofactor_multiples(ST, A, range(np, np + nm, 1))), ) # Three point ladder: case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, algo.curve.field(2)) T_p = list(R) T_m = list(S) print( "\n// Now, we proceed by performing xisog with input curve equals the output curve of the previous one experiment, and using torsion-(p + 1) points" ) for idx in range(0, np, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(idx + 1, np, 1): Tp = xmul(Tp, A, i) print("// l:\t%7d |" % L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: set_parameters_velu(algo.formula.sJ_list[idx], algo.formula.sI_list[idx], idx) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' # from example 4.12 of https://eprint.iacr.org/2020/341 These # paramters are required in kps, xisog, and xeval if L[idx] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) print_parameters_velu() # ------------------------------------------------------------- # kps procedure init_runtime_basefield() init_runtime_field() kps(Tp, A, idx) show_runtime_field("kps") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xisog init_runtime_basefield() init_runtime_field() Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) B = xisog(A, idx) Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) show_runtime_field("xisog") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xeval: kernel point determined by the next isogeny evaluation init_runtime_basefield() init_runtime_field() if (setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND) or (L[idx] == 4)): T_p = xeval(T_p, idx) else: T_p = xeval(T_p, A) # xeval bench init_runtime_basefield() init_runtime_field() if (setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND) or (L[idx] == 4)): T_m = xeval(T_m, idx) else: T_m = xeval(T_m, A) show_runtime_field("xeval") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (L[idx] + 2.0))) # assert(validate(B)) A = list(B) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) # print("assert(Random(B) * (p + 1) eq B!0);") # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1])) # print("assert(BOOL);") print( "\n// All the l_i's have been processed, output of xisog corresponds with the given below" ) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) print("B := EllipticCurve(x^3 + (%s) * x^2 + x);" % coeff(A)) print("assert(Random(B) * (p + 1) eq B!0);") A = [algo.curve.field(8), algo.curve.field(4)] # Three point ladder: case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, algo.curve.field(2)) T_p = list(R) T_m = list(S) print( "\n// Now, we proceed by performing xisog with input curve equals the output curve of the previous one experiment, and using torsion-(p - 1) points" ) for idx in range(np, np + nm, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(idx + 1, np + nm, 1): Tp = xmul(Tp, A, i) print("// l:\t%7d |" % L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: set_parameters_velu(algo.formula.sJ_list[idx], algo.formula.sI_list[idx], idx) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' # from example 4.12 of https://eprint.iacr.org/2020/341 These # paramters are required in kps, xisog, and xeval if L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) print_parameters_velu() # ------------------------------------------------------------- # kps procedure init_runtime_basefield() init_runtime_field() kps(Tp, A, idx) show_runtime_field("kps") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xisog init_runtime_basefield() init_runtime_field() B = xisog(A, idx) show_runtime_field("xisog") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xeval: kernel point determined by the next isogeny evaluation init_runtime_basefield() init_runtime_field() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): T_p = xeval(T_p, idx) else: T_p = xeval(T_p, A) # xeval bench init_runtime_basefield() init_runtime_field() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): T_m = xeval(T_m, idx) else: T_m = xeval(T_m, A) show_runtime_field("xeval") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (L[idx] + 2.0))) # assert(validate(B)) A = list(B) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) # print("assert(Random(B) * (p + 1) eq B!0);") # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1])) # print("assert(BOOL);") print( "\n// All the l_i's have been processed, output of xisog corresponds with the given below" ) print("B := EllipticCurve(x^3 + (%s) * x^2 + x);" % coeff(A)) print("assert(Random(B) * (p + 1) eq B!0);") # """ print( "\n\"If no errors were showed using magma calculator, then all experiments were successfully passed!\";" ) print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n") return attrdict(name='bsidh-test', **locals())
def evaluate_strategy(self, EVAL, S_in, T_in, ST_in, E, P, L, strategy, n): ''' evaluate_strategy(): primes; output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is E / <P> ''' ramifications = [] moves = [ 0 ] # moves: this list determines whether an isogeny construction must be performed k = 0 # k: current element of the strategy ramifications.append(list(P)) E_i = list(E) if EVAL: # Public points to be evaluated S_out = list( S_in ) # x(S) should be a torsion point with not common factors in L T_out = list( T_in ) # x(T) should be a torsion point with not common factors in L ST_out = list( ST_in ) # x(S - T) should be a torsion point with not common factors in L else: S_out = None T_out = None ST_out = None assert len(strategy) == (n - 1) for i in range(len(strategy)): pos = self.L.index( L[n - 1 - i]) # Current element of self.L to be required # Reaching the vertex (n - 1 - i, i) # Vertical edges (scalar multiplications) prev = sum(moves) while prev < (n - 1 - i): moves.append( strategy[k]) # Number of vertical edges to be performed T = list(ramifications[-1]) # New ramification for j in range(prev, prev + strategy[k], 1): T = self.curve.xmul(T, E_i, self.L.index(L[j])) ramifications.append(list(T)) prev += strategy[k] k += 1 # Deciding which velu variant will be used if self.formula_name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if self.tuned: self.formula.set_parameters_velu(self.formula.sJ_list[pos], self.formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in self.formula.kps, self.formula.xisog, and self.formula.xeval if self.L[pos] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(self.L[pos] - 1) / 2.0)) c = int(floor((self.L[pos] - 1.0) / (4.0 * b))) if self.formula_name != 'tvelu': self.formula.set_parameters_velu(b, c, pos) # Kernel Points computation self.formula.kps(ramifications[-1], E_i, pos) if not EVAL: # Isogeny construction ramifications[-1][0], E_i[0] = cswap(ramifications[-1][0], E_i[0], self.L[pos] == 4) ramifications[-1][1], E_i[1] = cswap(ramifications[-1][1], E_i[1], self.L[pos] == 4) C_i = self.formula.xisog(E_i, pos) ramifications[-1][0], E_i[0] = cswap(ramifications[-1][0], E_i[0], self.L[pos] == 4) ramifications[-1][1], E_i[1] = cswap(ramifications[-1][1], E_i[1], self.L[pos] == 4) # Now, we proceed by perform horizontal edges (isogeny evaluations) for j in range(0, len(moves) - 1, 1): if (self.formula_name == 'tvelu' or (self.formula_name == 'hvelu' and self.L[pos] <= self.formula.HYBRID_BOUND) or (self.L[pos] == 4)): ramifications[j] = self.formula.xeval( ramifications[j], pos) else: ramifications[j] = self.formula.xeval( ramifications[j], E_i) if EVAL: # Evaluating public points if (self.formula_name == 'tvelu' or (self.formula_name == 'hvelu' and self.L[pos] <= self.formula.HYBRID_BOUND) or (self.L[pos] == 4)): S_out = self.formula.xeval(S_out, pos) T_out = self.formula.xeval(T_out, pos) ST_out = self.formula.xeval(ST_out, pos) else: S_out = self.formula.xeval(S_out, E_i) T_out = self.formula.xeval(T_out, E_i) ST_out = self.formula.xeval(ST_out, E_i) C_i = self.curve.get_A(S_out, T_out, ST_out) # Updating the Montogmery curve coefficients E_i = [self.field(C_i[0]), self.field(C_i[1])] moves.pop() ramifications.pop() pos = self.L.index(L[0]) # Current element of self.L to be required if self.formula_name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if self.tuned: self.formula.set_parameters_velu(self.formula.sJ_list[pos], self.formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in self.formula.kps, self.formula.xisog, and self.formula.xeval if self.L[pos] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(self.L[pos] - 1) / 2.0)) c = int(floor((self.L[pos] - 1.0) / (4.0 * b))) self.formula.set_parameters_velu(b, c, pos) # Kernel Points computations self.formula.kps(ramifications[0], E_i, pos) if not EVAL: # Isogeny construction ramifications[0][0], E_i[0] = cswap(ramifications[0][0], E_i[0], self.L[pos] == 4) ramifications[0][1], E_i[1] = cswap(ramifications[0][1], E_i[1], self.L[pos] == 4) C_i = self.formula.xisog(E_i, pos) ramifications[0][0], E_i[0] = cswap(ramifications[0][0], E_i[0], self.L[pos] == 4) ramifications[0][1], E_i[1] = cswap(ramifications[0][1], E_i[1], self.L[pos] == 4) else: # Evaluating public points if (self.formula_name == 'tvelu' or (self.formula_name == 'hvelu' and self.L[pos] <= self.formula.HYBRID_BOUND) or (self.L[pos] == 4)): S_out = self.formula.xeval(S_out, pos) T_out = self.formula.xeval(T_out, pos) ST_out = self.formula.xeval(ST_out, pos) else: S_out = self.formula.xeval(S_out, E_i) T_out = self.formula.xeval(T_out, E_i) ST_out = self.formula.xeval(ST_out, E_i) C_i = self.curve.get_A(S_out, T_out, ST_out) # Updating the Montogmery curve coefficients E_i = [self.field(C_i[0]), self.field(C_i[1])] return E_i, S_out, T_out, ST_out
def bsidh_precompute_parameters(ctx): """ Precomputation of tuned velusqrt parameters """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] p = algo.params.p np = algo.params.np Ep = algo.params.Ep nm = algo.params.nm Em = algo.params.Em Lp = algo.params.Lp Lm = algo.params.Lm L = list(Lp + Lm) A = [algo.curve.field(8), algo.curve.field(4)] xmul = algo.curve.xmul isinfinity = algo.curve.isinfinity coeff = algo.curve.coeff isfullorder = algo.curve.isfullorder cofactor_multiples = algo.curve.cofactor_multiples Ladder3pt = algo.curve.Ladder3pt if algo.formula.name != 'tvelu': set_parameters_velu = algo.formula.set_parameters_velu print_parameters_velu = algo.formula.print_parameters_velu HYBRID_BOUND = algo.formula.HYBRID_BOUND init_runtime_field = algo.field.init_runtime show_runtime_field = algo.field.show_runtime init_runtime_basefield = algo.basefield.init_runtime show_runtime_basefield = algo.basefield.show_runtime kps = algo.formula.kps xisog = algo.formula.xisog xeval = algo.formula.xeval field = algo.field random = SystemRandom() # ---Generators in E[p + 1] PA = list(algo.strategy.PA) QA = list(algo.strategy.QA) PQA = list(algo.strategy.PQA) # ---Generators in E[p - 1] PB = list(algo.strategy.PB) QB = list(algo.strategy.QB) PQB = list(algo.strategy.PQB) S = list(PA) T = list(QA) ST = list(PQA) for i in range(0, np, 1): for idx in range(0, Ep[i], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) S = list(PB) T = list(QB) ST = list(PQB) for i in range(np, np + nm, 1): for idx in range(0, Em[i - np], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) # Case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isfullorder(cofactor_multiples(S, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(T, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(ST, A, range(0, np, 1))) # Case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isfullorder(cofactor_multiples(S, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(T, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(ST, A, range(np, np + nm, 1))) # Three point ladder: case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, A) T_p = list(R) T_m = list(S) original_stdout = sys.stdout # Save a reference to the original standard output multievaluation = { True: 'scaled', False: 'unscaled' }[setting.multievaluation] path = resource_filename( 'sibc', "data/ijk/" + algo.curve.model + '/' + algo.curve.name + '-' + multievaluation) with open(path, 'w') as f: sys.stdout = f # Change the standard output to the file we created. parameters = dict() for idx in range(0, np, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(0, np, 1): if i != idx: Tp = xmul(Tp, A, i) # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in kps, xisog, and xEVAL if L[idx] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) parameters[str(idx)] = [] b += 1 for j in range(0, int(floor(sqrt(pi * (b - 1)) / 1.0)), 1): b -= 1 c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) total_cost = [0, 0, 0] # ------------------------------------------------------------- # kps procedure init_runtime_basefield() kps(Tp, A, idx) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xisog init_runtime_basefield() Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) B = xisog(A, idx) Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xEVAL bench init_runtime_basefield() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): Tm = xeval(T_m, idx) else: Tm = xeval(T_m, A) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd parameters[str(idx)].append((b, c, [ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ], total_cost[0] + total_cost[1])) if L[idx] <= 4: parameters[str(idx)] = (0, 0, None, None) else: parameters[str(idx)] = min(parameters[str(idx)], key=lambda tup: tup[3]) print(parameters[str(idx)][0], parameters[str(idx)][1]) # -------------------------------------------------------------------------------------------------------------------- A = [algo.curve.field(8), algo.curve.field(4)] # Three point ladder: case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, A) T_p = list(R) T_m = list(S) for idx in range(np, np + nm, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(np, np + nm, 1): if i != idx: Tp = xmul(Tp, A, i) # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in kps, xisog, and xEVAL if L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) parameters[str(idx)] = [] b += 1 for j in range(0, int(floor(sqrt(pi * (b - 1)) / 1.0)), 1): b -= 1 c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) total_cost = [0, 0, 0] # ------------------------------------------------------------- # kps procedure init_runtime_basefield() kps(Tp, A, idx) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xisog init_runtime_basefield() B = xisog(A, idx) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xEVAL bench init_runtime_basefield() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): Tm = xeval(T_m, idx) else: Tm = xeval(T_m, A) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd parameters[str(idx)].append((b, c, [ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ], total_cost[0] + total_cost[1])) if L[idx] == 3: parameters[str(idx)] = (0, 0, None, None) else: parameters[str(idx)] = min(parameters[str(idx)], key=lambda tup: tup[3]) print(parameters[str(idx)][0], parameters[str(idx)][1]) sys.stdout = original_stdout # Reset the standard output to its original value return attrdict(name='bsidh-precompute-parameters', **locals())
def evaluate_strategy(self, E, P, L, strategy, n, m, e): """ evaluate_strategy(): inputs : a projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, a list of two projective Montgomery x-coordinate torsion-(l_1 x ... l_n) points x(T_+) := XP/ZP and x(T_-) := XM/ZM, the list of small odd primes [l_1, l_2, ..., l_n], an strategy and length of the given list of small odd primes, maximum number of degree-l_i isogeny constructions, and the secret integer vector to be evaluated output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is E / <P>, the new maximum and current number of degree-l_i isogeny constructions to be performed after the strategy evaluation. NOTE: T_+ belongs to E[pi - 1] and T_- belongs to E[pi + 1]. In particular, P = [T_-, T_+] """ v = list(m) u = list(e) ramifications = [] moves = [ 0 ] # moves: this list determines whether an isogeny construction must be performed k = 0 # k: current element of the strategy t = 1 # sign to be flip ramifications.append( list(P) ) # list of pair of points (T_-, T_+) such that T_- in E[\pi + 1] and T_+ in E[\pi - 1] E_i = list(E) for i in range(len(strategy)): pos = self.formula.L.index( L[n - 1 - i] ) # Current element of self.formula.L to be required # Reaching the vertex (n - 1 - i, i) # Vertical edges prev = sum(moves) while (prev + strategy[k]) < (n - 1 - i): moves.append( strategy[k] ) # Number of vertical edges to be performed T = list( [list(ramifications[-1][0]), list(ramifications[-1][1])] ) for j in range(prev, prev + strategy[k], 1): T = list( [ self.curve.xmul( T[0], E_i, self.formula.L.index(L[j]) ), self.curve.xmul( T[1], E_i, self.formula.L.index(L[j]) ), ] ) ramifications.append(list([list(T[0]), list(T[1])])) prev += strategy[k] k += 1 # Vertical edges without ramifications s_i = sign(e[pos]) # Sign of e[pos] c_i = (s_i + 1) // 2 # Constant-swap of T_+ and T_- if prev < (n - 1 - i): moves.append( strategy[k] ) # Number of vertical edges (without ramifications) to be performed T = list( [list(ramifications[-1][0]), list(ramifications[-1][1])] ) T[0][0], T[1][0] = cswap(T[0][0], T[1][0], c_i) T[0][1], T[1][1] = cswap(T[0][1], T[1][1], c_i) for j in range(prev, prev + strategy[k], 1): T[0] = list( self.curve.xmul( T[0], E_i, self.formula.L.index(L[j]) ) ) # A single scalar multiplication is required T[0][0], T[1][0] = cswap(T[0][0], T[1][0], c_i) T[0][1], T[1][1] = cswap(T[0][1], T[1][1], c_i) ramifications.append(list([list(T[0]), list(T[1])])) prev += strategy[k] k += 1 # At this point, vertex (n - 1 - i, i) has been reached if ( v[pos] > 0 ): # Maximum number of degree-l_{pos} isogeny constructions? # At this step, ramifications[-1] is the i-th leaf # Swap the torsion-(l_{n-j}) elliptic curve points on the current leaf # T_- <- ramifications[-1][0] # T_+ <- ramifications[-1][1] ( ramifications[-1][0][0], ramifications[-1][1][0], ) = cswap( ramifications[-1][0][0], ramifications[-1][1][0], c_i ) # XT_+ <-> XT_- ( ramifications[-1][0][1], ramifications[-1][1][1], ) = cswap( ramifications[-1][0][1], ramifications[-1][1][1], c_i ) # ZT_+ <-> ZT_- if self.curve.isinfinity(ramifications[-1][0]) == False: if self.formula_name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes E_prev = list(E_i) if self.tuned: self.formula.set_parameters_velu( self.formula.sJ_list[pos], self.formula.sI_list[pos], pos, ) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in kps, xisog, and xeval if self.formula.L[pos] == 3: b = 0 c = 0 else: b = int( floor( sqrt(self.formula.L[pos] - 1) / 2.0 ) ) c = int( floor( (self.formula.L[pos] - 1.0) / (4.0 * b) ) ) self.formula.set_parameters_velu(b, c, pos) self.formula.kps(ramifications[-1][0], E_i, pos) # New isogeny construction E_i = self.formula.xisog(E_i, pos) # Horizontal edges are performed for j in range(0, len(moves) - 1, 1): # Swap points in E[\pi - 1] and E[\pi + 1] ( ramifications[j][0][0], ramifications[j][1][0], ) = cswap( ramifications[j][0][0], ramifications[j][1][0], c_i ) ( ramifications[j][0][1], ramifications[j][1][1], ) = cswap( ramifications[j][0][1], ramifications[j][1][1], c_i ) if self.formula_name == 'tvelu' or ( self.formula_name == 'hvelu' and self.formula.L[pos] <= self.formula.HYBRID_BOUND ): # This branchs corresponds with the use of the tradicional velu's formulaes # T_- (or T_) ramifications[j][0] = self.formula.xeval( ramifications[j][0], pos ) # T_+ or (T_+) ramifications[j][1] = self.formula.xeval( ramifications[j][1], pos ) else: # This branchs corresponds with the use of the new velu's formulaes # T_- (or T_) ramifications[j][0] = self.formula.xeval( ramifications[j][0], E_prev ) # T_+ or (T_+) ramifications[j][1] = self.formula.xeval( ramifications[j][1], E_prev ) # Multiplying by the current small odd prime number in order to decrease the order of both points T_+ and T_- ramifications[j][1] = self.curve.xmul( ramifications[j][1], E_i, pos ) # Undo swap of points in E[\pi - 1] and E[\pi + 1] ( ramifications[j][0][0], ramifications[j][1][0], ) = cswap( ramifications[j][0][0], ramifications[j][1][0], c_i ) ( ramifications[j][0][1], ramifications[j][1][1], ) = cswap( ramifications[j][0][1], ramifications[j][1][1], c_i ) b_i = ( isequal[u[pos] == 1] ^ isequal[u[pos] == -1] ) # 0 if u[pos] != +1,-1; otherwise, 1 [This ask must be performed in constant-time] v[pos] -= 1 u[pos] -= s_i * ( 1 + b_i ) # reduced by 1 unit if it was different from +1 and -1; otherwise, reduced by 2 units else: for j in range(0, len(moves) - 1, 1): ( ramifications[j][0][0], ramifications[j][1][0], ) = cswap( ramifications[j][0][0], ramifications[j][1][0], c_i ) ( ramifications[j][0][1], ramifications[j][1][1], ) = cswap( ramifications[j][0][1], ramifications[j][1][1], c_i ) ramifications[j][1] = self.curve.xmul( ramifications[j][1], E_i, pos ) ( ramifications[j][0][0], ramifications[j][1][0], ) = cswap( ramifications[j][0][0], ramifications[j][1][0], c_i ) ( ramifications[j][0][1], ramifications[j][1][1], ) = cswap( ramifications[j][0][1], ramifications[j][1][1], c_i ) else: # This branch only depends on randomness # At this step, ramifications[-1] is the i-th leaf ( ramifications[-1][0][0], ramifications[-1][1][0], ) = cswap( ramifications[-1][0][0], ramifications[-1][1][0], c_i ) ( ramifications[-1][0][1], ramifications[-1][1][1], ) = cswap( ramifications[-1][0][1], ramifications[-1][1][1], c_i ) if self.curve.isinfinity(ramifications[-1][0]) == False: for j in range(0, len(moves) - 1, 1): ramifications[j][0] = self.curve.xmul( ramifications[j][0], E_i, pos ) ramifications[j][1] = self.curve.xmul( ramifications[j][1], E_i, pos ) else: for j in range(0, len(moves) - 1, 1): ( ramifications[j][0][0], ramifications[j][1][0], ) = cswap( ramifications[j][0][0], ramifications[j][1][0], c_i ) ( ramifications[j][0][1], ramifications[j][1][1], ) = cswap( ramifications[j][0][1], ramifications[j][1][1], c_i ) ramifications[j][1] = self.curve.xmul( ramifications[j][1], E_i, pos ) ( ramifications[j][0][0], ramifications[j][1][0], ) = cswap( ramifications[j][0][0], ramifications[j][1][0], c_i ) ( ramifications[j][0][1], ramifications[j][1][1], ) = cswap( ramifications[j][0][1], ramifications[j][1][1], c_i ) moves.pop() ramifications.pop() pos = self.formula.L.index( L[0] ) # Current element of self.formula.L to be required s_i = sign(e[pos]) # Sign of e[pos] c_i = (s_i + 1) // 2 # Constant-swap of T_+ and T_- # T_+ or T_- ? # Root ramifications[0][0][0], ramifications[0][1][0] = cswap( ramifications[0][0][0], ramifications[0][1][0], c_i ) ramifications[0][0][1], ramifications[0][1][1] = cswap( ramifications[0][0][1], ramifications[0][1][1], c_i ) if self.curve.isinfinity(ramifications[0][0]) == False: if m[pos] > 0: if self.formula_name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if self.tuned: self.formula.set_parameters_velu( self.formula.sJ_list[pos], self.formula.sI_list[pos], pos, ) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in kps, xisog, and xeval if self.formula.L[pos] == 3: b = 0 c = 0 else: b = int( floor( sqrt(self.formula.L[pos] - 1) / 2.0 ) ) c = int( floor( (self.formula.L[pos] - 1.0) / (4.0 * b) ) ) self.formula.set_parameters_velu(b, c, pos) self.formula.kps(ramifications[0][0], E_i, pos) E_i = self.formula.xisog(E_i, pos) b_i = ( isequal[u[pos] == 1] ^ isequal[u[pos] == -1] ) # 0 if u[pos] != +1,-1; otherwise, 1 [This ask must be performed in constant-time] v[pos] -= 1 u[pos] -= s_i * ( 1 + b_i ) # reduced by 1 unit if it was different from +1 and -1; otherwise, reduced by 2 units return E_i, v, u