34template <
typename G1,
typename HashRegNon,
typename HashSig = Blake2sHasher>
class schnorr_multisig {
42 using Fq =
typename G1::Fq;
43 using Fr =
typename G1::Fr;
109 return ((
R < other.
R) || ((
R == other.
R) &&
S < (other.
S)));
127 for (
size_t i = 0; i < round1_public_outputs.size(); ++i) {
128 auto& [R_user, S_user] = round1_public_outputs[i];
129 if (!R_user.on_curve() || R_user.is_point_at_infinity()) {
130 info(
"Round 1 commitments contains invalid R at index ", i);
133 if (!S_user.on_curve() || S_user.is_point_at_infinity()) {
134 info(
"Round 1 commitments contains invalid S at index ", i);
138 if (
auto duplicated =
duplicated_indices(round1_public_outputs); duplicated.size() > 0) {
139 info(
"Round 1 commitments contains duplicate values at indices ", duplicated);
165 const std::string domain_separator_nonce(
"h_nonce");
169 std::vector<uint8_t> nonce_challenge_buffer;
172 domain_separator_nonce.begin(), domain_separator_nonce.end(),
std::back_inserter(nonce_challenge_buffer));
175 write(nonce_challenge_buffer, G1::affine_one);
178 write(nonce_challenge_buffer, aggregate_pubkey);
185 const std::string m_start =
"m_start";
188 write(nonce_challenge_buffer,
static_cast<uint32_t
>(message.size()));
192 const std::string m_end =
"m_end";
196 for (
const auto& nonce : round_1_nonces) {
197 write(nonce_challenge_buffer, nonce.R);
198 write(nonce_challenge_buffer, nonce.S);
202 auto nonce_challenge_raw = HashRegNon::hash(nonce_challenge_buffer);
205 return nonce_challenge;
218 element R_sum = round_1_nonces[0].R;
219 element S_sum = round_1_nonces[0].S;
220 for (
size_t i = 1; i < round_1_nonces.size(); ++i) {
221 const auto& [R, S] = round_1_nonces[i];
240 const size_t num_inputs = input.size();
242 std::vector<size_t> indices(num_inputs);
243 std::iota(indices.begin(), indices.end(), 0);
247 std::sort(indices.begin(), indices.end(), [&](
size_t a,
size_t b) { return input[a] < input[b]; });
250 std::vector<size_t> duplicates;
251 for (
size_t i = 1; i < num_inputs; ++i) {
252 const size_t idx1 = indices[i - 1];
253 const size_t idx2 = indices[i];
254 if (input[idx1] == input[idx2]) {
255 duplicates.push_back(idx1);
256 duplicates.push_back(idx2);
275 for (
const auto& [public_key, proof_of_possession] : signer_pubkeys) {
276 points.push_back(public_key);
280 info(
"Duplicated public keys at indices ", duplicated);
284 element aggregate_pubkey_jac = G1::point_at_infinity;
285 for (
size_t i = 0; i < signer_pubkeys.size(); ++i) {
286 const auto& [public_key, proof_of_possession] = signer_pubkeys[i];
287 if (!public_key.on_curve() || public_key.is_point_at_infinity()) {
288 info(
"Multisig signer pubkey not a valid point at index ", i);
291 if (!proof_of_possession.verify(public_key)) {
292 info(
"Multisig proof of posession invalid at index ", i);
295 aggregate_pubkey_jac += public_key;
302 if (aggregate_pubkey.is_point_at_infinity()) {
303 info(
"Multisig aggregate public key is invalid");
306 return aggregate_pubkey;
333 return { pubOut, privOut };
349 const std::string& message,
355 const size_t num_signers = signer_pubkeys.size();
356 if (round_1_nonces.size() != num_signers) {
357 info(
"Multisig mismatch round_1_nonces and signers");
368 if (!aggregate_pubkey.has_value()) {
380 auto e_buf = schnorr_generate_challenge<HashSig, G1>(message, *aggregate_pubkey, R);
384 Fr z = signer_round_1_private_output.
r + signer_round_1_private_output.
s *
a - signer.
private_key * e;
401 const std::string& message,
406 const size_t num_signers = signer_pubkeys.size();
407 if (round_1_nonces.size() != num_signers) {
408 info(
"Invalid number of round1 messages");
411 if (round_2_signature_shares.size() != num_signers) {
412 info(
"Invalid number of round2 messages");
421 if (!aggregate_pubkey.has_value()) {
432 auto e_buf = schnorr_generate_challenge<HashSig, G1>(message, *aggregate_pubkey, R);
436 std::copy(e_buf.begin(), e_buf.end(), sig.
e.begin());
439 for (
auto& z : round_2_signature_shares) {
446 if (!schnorr_verify_signature<HashSig, Fq, Fr, G1>(message, *aggregate_pubkey, sig)) {
Implements the SpeedyMuSig protocol; a secure 2-round interactive multisignature scheme whose signatu...
static std::vector< size_t > duplicated_indices(const std::vector< T > &input)
Returns a vector of indices of elements in input that are included more than once.
static std::optional< schnorr_signature > combine_signatures(const std::string &message, const std::vector< MultiSigPublicKey > &signer_pubkeys, const std::vector< RoundOnePublicOutput > &round_1_nonces, const std::vector< RoundTwoPublicOutput > &round_2_signature_shares)
the final step in the SpeedyMuSig multisig scheme. Can be computed by an untrusted 3rd party....
typename G1::element element
static std::pair< RoundOnePublicOutput, RoundOnePrivateOutput > construct_signature_round_1()
First round of SpeedyMuSig. Signers generate random nonce keypairs R = {r, [R]}, S = {s,...
static std::optional< RoundTwoPublicOutput > construct_signature_round_2(const std::string &message, const key_pair &signer, const RoundOnePrivateOutput &signer_round_1_private_output, const std::vector< MultiSigPublicKey > &signer_pubkeys, const std::vector< RoundOnePublicOutput > &round_1_nonces)
Second round of SpeedyMuSig. Given the signer pubkeys and the output of round 1, round 2 has each sig...
static Fr generate_nonce_challenge(const std::string &message, const affine_element &aggregate_pubkey, const std::vector< RoundOnePublicOutput > &round_1_nonces)
Generates the Fiat-Shamir challenge a that is used to create a Schnorr signature nonce group element ...
static bool valid_round1_nonces(const std::vector< RoundOnePublicOutput > &round1_public_outputs)
given a list of commitments to nonces produced in round 1, we check that all points are valid and tha...
static std::optional< affine_element > validate_and_combine_signer_pubkeys(const std::vector< MultiSigPublicKey > &signer_pubkeys)
Computes the sum of all signer pubkeys. Output is the public key of the public-facing schnorr multisi...
typename G1::affine_element affine_element
static affine_element construct_multisig_nonce(const Fr &a, const std::vector< RoundOnePublicOutput > &round_1_nonces)
Compute the Schnorr signature scheme's nonce group element [R], given each signer's public nonces [R_...
void write(B &buf, SchnorrProofOfPossession< G1, Hash > const &proof_of_possession)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
A proof of possession is a Schnorr proof of knowledge of a secret key corresponding to a given public...
MultiSigPublicKey wraps a signer's public key g1::affine_element along with a proof of posession: a s...
MultiSigPublicKey(const key_pair &account)
affine_element public_key
SchnorrProofOfPossession< G1, HashRegNon > proof_of_possession
MSGPACK_FIELDS(public_key, proof_of_possession)
MultiSigPublicKey()=default
MultiSigPublicKey(const affine_element &public_key, const SchnorrProofOfPossession< G1, HashRegNon > &proof_of_possession)
uint8_t const * vec_in_buf
const uint8_t * vec_in_buf
bool operator==(const RoundOnePublicOutput &other) const
bool operator<(const RoundOnePublicOutput &other) const
std::array< uint8_t, 32 > s
std::array< uint8_t, 32 > e
static field random_element(numeric::RNG *engine=nullptr) noexcept
static field serialize_from_buffer(const uint8_t *buffer)
static void serialize_to_buffer(const field &value, uint8_t *buffer)