def multi_mult(scalars: Sequence[Integer], points: Sequence[Point], ec: Curve = secp256k1) -> Point: """Return the multi scalar multiplication u1*Q1 + ... + un*Qn. Use Bos-Coster's algorithm for efficient computation. """ if len(scalars) != len(points): err_msg = "mismatch between number of scalars and points: " err_msg += f"{len(scalars)} vs {len(points)}" raise BTClibValueError(err_msg) jac_points: List[JacPoint] = [] ints: List[int] = [] for Q, i in zip(points, scalars): i = int_from_integer(i) % ec.n if i == 0: # early optimization, even if not strictly necessary continue ints.append(i) ec.require_on_curve(Q) jac_points.append(jac_from_aff(Q)) R = _multi_mult(ints, jac_points, ec) return ec.aff_from_jac(R)
def test_assorted_jac_mult() -> None: ec = ec23_31 H = second_generator(ec) HJ = jac_from_aff(H) for k1 in range(ec.n): K1J = _mult(k1, ec.GJ, ec) for k2 in range(ec.n): K2J = _mult(k2, HJ, ec) shamir = _double_mult(k1, ec.GJ, k2, ec.GJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) assert ec.jac_equality(shamir, _mult(k1 + k2, ec.GJ, ec)) shamir = _double_mult(k1, INFJ, k2, HJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) assert ec.jac_equality(shamir, K2J) shamir = _double_mult(k1, ec.GJ, k2, INFJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) assert ec.jac_equality(shamir, K1J) shamir = _double_mult(k1, ec.GJ, k2, HJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) K1JK2J = ec.add_jac(K1J, K2J) assert ec.jac_equality(K1JK2J, shamir) k3 = 1 + secrets.randbelow(ec.n - 1) K3J = _mult(k3, ec.GJ, ec) K1JK2JK3J = ec.add_jac(K1JK2J, K3J) assert ec.is_on_curve(ec.aff_from_jac(K1JK2JK3J)) boscoster = _multi_mult([k1, k2, k3], [ec.GJ, HJ, ec.GJ], ec) assert ec.is_on_curve(ec.aff_from_jac(boscoster)) assert ec.aff_from_jac(K1JK2JK3J) == ec.aff_from_jac(boscoster), k3 assert ec.jac_equality(K1JK2JK3J, boscoster) k4 = 1 + secrets.randbelow(ec.n - 1) K4J = _mult(k4, HJ, ec) K1JK2JK3JK4J = ec.add_jac(K1JK2JK3J, K4J) assert ec.is_on_curve(ec.aff_from_jac(K1JK2JK3JK4J)) points = [ec.GJ, HJ, ec.GJ, HJ] boscoster = _multi_mult([k1, k2, k3, k4], points, ec) assert ec.is_on_curve(ec.aff_from_jac(boscoster)) assert ec.aff_from_jac(K1JK2JK3JK4J) == ec.aff_from_jac( boscoster), k4 assert ec.jac_equality(K1JK2JK3JK4J, boscoster) assert ec.jac_equality(K1JK2JK3J, _multi_mult([k1, k2, k3, 0], points, ec)) assert ec.jac_equality(K1JK2J, _multi_mult([k1, k2, 0, 0], points, ec)) assert ec.jac_equality(K1J, _multi_mult([k1, 0, 0, 0], points, ec)) assert ec.jac_equality(INFJ, _multi_mult([0, 0, 0, 0], points, ec)) err_msg = "mismatch between number of scalars and points: " with pytest.raises(BTClibValueError, match=err_msg): _multi_mult([k1, k2, k3, k4], [ec.GJ, HJ, ec.GJ], ec) err_msg = "negative coefficient: " with pytest.raises(BTClibValueError, match=err_msg): _multi_mult([k1, k2, -k3], [ec.GJ, HJ, ec.GJ], ec) with pytest.raises(BTClibValueError, match="negative first coefficient: "): _double_mult(-5, HJ, 1, ec.GJ, ec) with pytest.raises(BTClibValueError, match="negative second coefficient: "): _double_mult(1, HJ, -5, ec.GJ, ec)
def assert_batch_as_valid_( m_hashes: Sequence[Octets], Qs: Sequence[BIP340PubKey], sigs: Sequence[Sig], hf: HashF = sha256, ) -> None: batch_size = len(Qs) if batch_size == 0: raise BTClibValueError("no signatures provided") if len(m_hashes) != batch_size: err_msg = f"mismatch between number of pub_keys ({batch_size}) " err_msg += f"and number of messages ({len(m_hashes)})" raise BTClibValueError(err_msg) if len(sigs) != batch_size: err_msg = f"mismatch between number of pub_keys ({batch_size}) " err_msg += f"and number of signatures ({len(sigs)})" raise BTClibValueError(err_msg) if batch_size == 1: assert_as_valid_(m_hashes[0], Qs[0], sigs[0], hf) return None ec = sigs[0].ec if any(sig.ec != ec for sig in sigs): raise BTClibValueError("not the same curve for all signatures") t = 0 scalars: List[int] = [] points: List[JacPoint] = [] for i, (msg_hash, Q, sig) in enumerate(zip(m_hashes, Qs, sigs)): msg_hash = bytes_from_octets(msg_hash, hf().digest_size) KJ = sig.r, ec.y_even(sig.r), 1 x_Q, y_Q = point_from_bip340pub_key(Q, ec) QJ = x_Q, y_Q, 1 c = challenge_(msg_hash, x_Q, sig.r, ec, hf) # rand in [1, n-1] # deterministically generated using a CSPRNG seeded by a # cryptographic hash (e.g., SHA256) of all inputs of the # algorithm, or randomly generated independently for each # run of the batch verification algorithm rand = 1 if i == 0 else 1 + secrets.randbelow(ec.n - 1) scalars.append(rand) points.append(KJ) scalars.append(rand * c % ec.n) points.append(QJ) t += rand * sig.s TJ = _mult(t, ec.GJ, ec) RHSJ = _multi_mult(scalars, points, ec) # return T == RHS, checked in Jacobian coordinates RHSZ2 = RHSJ[2] * RHSJ[2] TZ2 = TJ[2] * TJ[2] if (TJ[0] * RHSZ2 % ec.p != RHSJ[0] * TZ2 % ec.p) or ( TJ[1] * RHSZ2 * RHSJ[2] % ec.p != RHSJ[1] * TZ2 * TJ[2] % ec.p): raise BTClibRuntimeError("signature verification failed") return None