def verify_batch(self, proofs, single_optim=True, proof_v8=False): """ BP batch verification :param proofs: :param single_optim: single proof memory optimization :param proof_v8: previous testnet version :return: """ max_length = 0 for proof in proofs: utils.ensure(is_reduced(proof.taux), "Input scalar not in range") utils.ensure(is_reduced(proof.mu), "Input scalar not in range") utils.ensure(is_reduced(proof.a), "Input scalar not in range") utils.ensure(is_reduced(proof.b), "Input scalar not in range") utils.ensure(is_reduced(proof.t), "Input scalar not in range") utils.ensure(len(proof.V) >= 1, "V does not have at least one element") utils.ensure(len(proof.L) == len(proof.R), "|L| != |R|") utils.ensure(len(proof.L) > 0, "Empty proof") max_length = max(max_length, len(proof.L)) utils.ensure(max_length < 32, "At least one proof is too large") maxMN = 1 << max_length logN = 6 N = 1 << logN tmp = _ensure_dst_key() # setup weighted aggregates is_single = len(proofs) == 1 and single_optim # ph4 z1 = init_key(_ZERO) z3 = init_key(_ZERO) m_z4 = vector_dup(_ZERO, maxMN) if not is_single else None m_z5 = vector_dup(_ZERO, maxMN) if not is_single else None m_y0 = init_key(_ZERO) y1 = init_key(_ZERO) muex_acc = init_key(_ONE) Gprec = self._gprec_aux(maxMN) Hprec = self._hprec_aux(maxMN) for proof in proofs: M = 1 logM = 0 while M <= _BP_M and M < len(proof.V): logM += 1 M = 1 << logM utils.ensure(len(proof.L) == 6 + logM, "Proof is not the expected size") MN = M * N weight_y = crypto.encodeint(crypto.random_scalar()) weight_z = crypto.encodeint(crypto.random_scalar()) # Reconstruct the challenges hash_cache = hash_vct_to_scalar(None, proof.V) y = hash_cache_mash(None, hash_cache, proof.A, proof.S) utils.ensure(y != _ZERO, "y == 0") z = hash_to_scalar(None, y) copy_key(hash_cache, z) utils.ensure(z != _ZERO, "z == 0") x = hash_cache_mash(None, hash_cache, z, proof.T1, proof.T2) utils.ensure(x != _ZERO, "x == 0") x_ip = hash_cache_mash(None, hash_cache, x, proof.taux, proof.mu, proof.t) utils.ensure(x_ip != _ZERO, "x_ip == 0") # PAPER LINE 61 sc_mulsub(m_y0, proof.taux, weight_y, m_y0) zpow = vector_powers(z, M + 3) k = _ensure_dst_key() ip1y = vector_power_sum(y, MN) sc_mulsub(k, zpow[2], ip1y, _ZERO) for j in range(1, M + 1): utils.ensure(j + 2 < len(zpow), "invalid zpow index") sc_mulsub(k, zpow.to(j + 2), _BP_IP12, k) # VERIFY_line_61rl_new sc_muladd(tmp, z, ip1y, k) sc_sub(tmp, proof.t, tmp) sc_muladd(y1, tmp, weight_y, y1) weight_y8 = init_key(weight_y) if not proof_v8: weight_y8 = sc_mul(None, weight_y, _EIGHT) muex = MultiExpSequential(points=[pt for pt in proof.V]) for j in range(len(proof.V)): sc_mul(tmp, zpow[j + 2], weight_y8) muex.add_scalar(init_key(tmp)) sc_mul(tmp, x, weight_y8) muex.add_pair(init_key(tmp), proof.T1) xsq = _ensure_dst_key() sc_mul(xsq, x, x) sc_mul(tmp, xsq, weight_y8) muex.add_pair(init_key(tmp), proof.T2) weight_z8 = init_key(weight_z) if not proof_v8: weight_z8 = sc_mul(None, weight_z, _EIGHT) muex.add_pair(weight_z8, proof.A) sc_mul(tmp, x, weight_z8) muex.add_pair(init_key(tmp), proof.S) multiexp(tmp, muex, False) add_keys(muex_acc, muex_acc, tmp) del muex # Compute the number of rounds for the inner product rounds = logM + logN utils.ensure(rounds > 0, "Zero rounds") # PAPER LINES 21-22 # The inner product challenges are computed per round w = _ensure_dst_keyvect(None, rounds) for i in range(rounds): hash_cache_mash(tmp_bf_0, hash_cache, proof.L[i], proof.R[i]) w.read(i, tmp_bf_0) utils.ensure(w[i] != _ZERO, "w[i] == 0") # Basically PAPER LINES 24-25 # Compute the curvepoints from G[i] and H[i] yinvpow = init_key(_ONE) ypow = init_key(_ONE) yinv = invert(None, y) self.gc(61) winv = _ensure_dst_keyvect(None, rounds) for i in range(rounds): invert(tmp_bf_0, w.to(i)) winv.read(i, tmp_bf_0) self.gc(62) g_scalar = _ensure_dst_key() h_scalar = _ensure_dst_key() twoN = self._two_aux(N) for i in range(MN): copy_key(g_scalar, proof.a) sc_mul(h_scalar, proof.b, yinvpow) for j in range(rounds - 1, -1, -1): J = len(w) - j - 1 if (i & (1 << j)) == 0: sc_mul(g_scalar, g_scalar, winv.to(J)) sc_mul(h_scalar, h_scalar, w.to(J)) else: sc_mul(g_scalar, g_scalar, w.to(J)) sc_mul(h_scalar, h_scalar, winv.to(J)) # Adjust the scalars using the exponents from PAPER LINE 62 sc_add(g_scalar, g_scalar, z) utils.ensure(2 + i // N < len(zpow), "invalid zpow index") utils.ensure(i % N < len(twoN), "invalid twoN index") sc_mul(tmp, zpow.to(2 + i // N), twoN.to(i % N)) sc_muladd(tmp, z, ypow, tmp) sc_mulsub(h_scalar, tmp, yinvpow, h_scalar) if not is_single: # ph4 sc_mulsub(m_z4[i], g_scalar, weight_z, m_z4[i]) sc_mulsub(m_z5[i], h_scalar, weight_z, m_z5[i]) else: sc_mul(tmp, g_scalar, weight_z) sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Gprec.to(i), tmp)) sc_mul(tmp, h_scalar, weight_z) sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Hprec.to(i), tmp)) if i != MN - 1: sc_mul(yinvpow, yinvpow, yinv) sc_mul(ypow, ypow, y) if i & 15 == 0: self.gc(62) del (g_scalar, h_scalar, twoN) self.gc(63) sc_muladd(z1, proof.mu, weight_z, z1) muex = MultiExpSequential( point_fnc=lambda i, d: proof.L[i // 2] if i & 1 == 0 else proof.R[i // 2] ) for i in range(rounds): sc_mul(tmp, w[i], w[i]) sc_mul(tmp, tmp, weight_z8) muex.add_scalar(tmp) sc_mul(tmp, winv[i], winv[i]) sc_mul(tmp, tmp, weight_z8) muex.add_scalar(tmp) acc = multiexp(None, muex, False) add_keys(muex_acc, muex_acc, acc) sc_mulsub(tmp, proof.a, proof.b, proof.t) sc_mul(tmp, tmp, x_ip) sc_muladd(z3, tmp, weight_z, z3) sc_sub(tmp, m_y0, z1) z3p = sc_sub(None, z3, y1) check2 = crypto.encodepoint( crypto.ge25519_double_scalarmult_base_vartime( crypto.decodeint(z3p), crypto.xmr_H(), crypto.decodeint(tmp) ) ) add_keys(muex_acc, muex_acc, check2) if not is_single: # ph4 muex = MultiExpSequential( point_fnc=lambda i, d: Gprec.to(i // 2) if i & 1 == 0 else Hprec.to(i // 2) ) for i in range(maxMN): muex.add_scalar(m_z4[i]) muex.add_scalar(m_z5[i]) add_keys(muex_acc, muex_acc, multiexp(None, muex, True)) if muex_acc != _ONE: raise ValueError("Verification failure at step 2") return True
def generate_ring_signature( prefix_hash: bytes, image: Ge25519, pubs: list[Ge25519], sec: Sc25519, sec_idx: int, test: bool = False, ) -> Sig: """ Generates ring signature with key image. void crypto_ops::generate_ring_signature() """ from trezor.utils import memcpy if test: t = crypto.scalarmult_base(sec) if not crypto.point_eq(t, pubs[sec_idx]): raise ValueError("Invalid sec key") k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) if not crypto.point_eq(k_i, image): raise ValueError("Key image invalid") for k in pubs: crypto.check_ed25519point(k) buff_off = len(prefix_hash) buff = bytearray(buff_off + 2 * 32 * len(pubs)) memcpy(buff, 0, prefix_hash, 0, buff_off) mvbuff = memoryview(buff) sum = crypto.sc_0() k = crypto.sc_0() sig = [] for _ in range(len(pubs)): sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r for i in range(len(pubs)): if i == sec_idx: k = crypto.random_scalar() tmp3 = crypto.scalarmult_base(k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp3) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i])) tmp2 = crypto.scalarmult(tmp3, k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 else: sig[i] = [crypto.random_scalar(), crypto.random_scalar()] tmp3 = pubs[i] tmp2 = crypto.ge25519_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3)) tmp2 = crypto.ge25519_double_scalarmult_vartime2( sig[i][1], tmp3, sig[i][0], image) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) sig[sec_idx][0] = crypto.sc_sub(h, sum) sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) return sig