Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
multisig.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <numeric>
6#include <optional>
7#include <utility>
8#include <vector>
9
11
13#include "schnorr.hpp"
14
15namespace bb::crypto {
16
28template <typename G1, typename HashRegNon, typename HashSig = Blake2sHasher> class schnorr_multisig {
29
30 // ensure that a different hash function is used for signature and proof of possession/nonce.
31 // we can apply domain separation for HashRegNon but not for HashSig, so this ensures all hash functions
32 // are modeled as different random oracles.
34
35 public:
36 using Fq = typename G1::Fq;
37 using Fr = typename G1::Fr;
38 using affine_element = typename G1::affine_element;
39 using element = typename G1::element;
41
52 typedef uint8_t const* in_buf;
53 typedef uint8_t const* vec_in_buf;
54 typedef uint8_t* out_buf;
55 typedef uint8_t** vec_out_buf;
56
57 affine_element public_key = G1::affine_point_at_infinity;
58 // proof of knowledge of the secret_key for public_key
60
61 // For serialization, update with any new fields
63 // restore default constructor to enable deserialization
64 MultiSigPublicKey() = default;
65 // create a MultiSigPublicKey with a proof of possession associated with public_key of account
67 : public_key(account.public_key)
68 , proof_of_possession(account)
69 {}
70 // Needed to appease MSGPACK_FIELDS
76 };
77
79 using in_buf = const uint8_t*;
80 using out_buf = uint8_t*;
81
84 // For serialization, update with any new fields
86 };
87
89 using in_buf = const uint8_t*;
90 using vec_in_buf = const uint8_t*;
91 using out_buf = uint8_t*;
92 using vec_out_buf = uint8_t**;
93
94 // R = r⋅G
96 // S = s⋅G
98 // For serialization, update with any new fields
100 // for std::sort
101 bool operator<(const RoundOnePublicOutput& other) const
102 {
103 return ((R < other.R) || ((R == other.R) && S < (other.S)));
104 }
105
106 bool operator==(const RoundOnePublicOutput& other) const { return (R == other.R) && (S == other.S); }
107 };
108 // corresponds to z = r + as - ex,
110
111 private:
119 static bool valid_round1_nonces(const std::vector<RoundOnePublicOutput>& round1_public_outputs)
120 {
121 for (size_t i = 0; i < round1_public_outputs.size(); ++i) {
122 auto& [R_user, S_user] = round1_public_outputs[i];
123 if (!R_user.on_curve() || R_user.is_point_at_infinity()) {
124 info("Round 1 commitments contains invalid R at index ", i);
125 return false;
126 }
127 if (!S_user.on_curve() || S_user.is_point_at_infinity()) {
128 info("Round 1 commitments contains invalid S at index ", i);
129 return false;
130 }
131 }
132 if (auto duplicated = duplicated_indices(round1_public_outputs); duplicated.size() > 0) {
133 info("Round 1 commitments contains duplicate values at indices ", duplicated);
134 return false;
135 }
136 return true;
137 }
138
154 static Fr generate_nonce_challenge(const std::string& message,
155 const affine_element& aggregate_pubkey,
156 const std::vector<RoundOnePublicOutput>& round_1_nonces)
157 {
158 // Domain separation for H_non
159 const std::string domain_separator_nonce("h_nonce");
160
161 // compute nonce challenge
162 // H('domain_separator_nonce', G, X, "m_start", m.size(), m, "m_end", {(R1, S1), ..., (Rn, Sn)})
163 std::vector<uint8_t> nonce_challenge_buffer;
164 // write domain separator
165 std::copy(
166 domain_separator_nonce.begin(), domain_separator_nonce.end(), std::back_inserter(nonce_challenge_buffer));
167
168 // write the group generator
169 write(nonce_challenge_buffer, G1::affine_one);
170
171 // write X
172 write(nonce_challenge_buffer, aggregate_pubkey);
173
174 // we slightly deviate from the protocol when including 'm', since the length of 'm' is variable
175 // by writing a prefix and a suffix, we prevent the message from being interpreted as coming from a different
176 // session.
177
178 // write "m_start"
179 const std::string m_start = "m_start";
180 std::copy(m_start.begin(), m_start.end(), std::back_inserter(nonce_challenge_buffer));
181 // write m.size()
182 write(nonce_challenge_buffer, static_cast<uint32_t>(message.size()));
183 // write message
184 std::copy(message.begin(), message.end(), std::back_inserter(nonce_challenge_buffer));
185 // write "m_end"
186 const std::string m_end = "m_end";
187 std::copy(m_end.begin(), m_end.end(), std::back_inserter(nonce_challenge_buffer));
188
189 // write {(R1, S1), ..., (Rn, Sn)}
190 for (const auto& nonce : round_1_nonces) {
191 write(nonce_challenge_buffer, nonce.R);
192 write(nonce_challenge_buffer, nonce.S);
193 }
194
195 // uses the different hash function for proper domain separation
196 auto nonce_challenge_raw = HashRegNon::hash(nonce_challenge_buffer);
197 // this results in a slight bias
198 Fr nonce_challenge = Fr::serialize_from_buffer(&nonce_challenge_raw[0]);
199 return nonce_challenge;
200 }
201
211 {
212 element R_sum = round_1_nonces[0].R;
213 element S_sum = round_1_nonces[0].S;
214 for (size_t i = 1; i < round_1_nonces.size(); ++i) {
215 const auto& [R, S] = round_1_nonces[i];
216 R_sum += R;
217 S_sum += S;
218 }
219 affine_element R(R_sum + S_sum * a);
220 return R;
221 }
222
232 template <typename T> static std::vector<size_t> duplicated_indices(const std::vector<T>& input)
233 {
234 const size_t num_inputs = input.size();
235 // indices = [0,1,..., num_inputs-1]
236 std::vector<size_t> indices(num_inputs);
237 std::iota(indices.begin(), indices.end(), 0);
238
239 // sort indices according to input.
240 // input[indices[i-1]] <= input[indices[i]]
241 std::sort(indices.begin(), indices.end(), [&](size_t a, size_t b) { return input[a] < input[b]; });
242
243 // This loop will include multiple copies of the same index if an element appears more than twice.
244 std::vector<size_t> duplicates;
245 for (size_t i = 1; i < num_inputs; ++i) {
246 const size_t idx1 = indices[i - 1];
247 const size_t idx2 = indices[i];
248 if (input[idx1] == input[idx2]) {
249 duplicates.push_back(idx1);
250 duplicates.push_back(idx2);
251 }
252 }
253 return duplicates;
254 }
255
256 public:
266 const std::vector<MultiSigPublicKey>& signer_pubkeys)
267 {
269 for (const auto& [public_key, proof_of_possession] : signer_pubkeys) {
270 points.push_back(public_key);
271 }
272
273 if (auto duplicated = duplicated_indices(points); duplicated.size() > 0) {
274 info("Duplicated public keys at indices ", duplicated);
275 return std::nullopt;
276 }
277
278 element aggregate_pubkey_jac = G1::point_at_infinity;
279 for (size_t i = 0; i < signer_pubkeys.size(); ++i) {
280 const auto& [public_key, proof_of_possession] = signer_pubkeys[i];
281 if (!public_key.on_curve() || public_key.is_point_at_infinity()) {
282 info("Multisig signer pubkey not a valid point at index ", i);
283 return std::nullopt;
284 }
285 if (!proof_of_possession.verify(public_key)) {
286 info("Multisig proof of posession invalid at index ", i);
287 return std::nullopt;
288 }
289 aggregate_pubkey_jac += public_key;
290 }
291
292 // This would prevent accidentally creating an aggregate key for the point at inifinity,
293 // with the trivial secret key.
294 // While it shouldn't happen, it is a cheap check.
295 affine_element aggregate_pubkey(aggregate_pubkey_jac);
296 if (aggregate_pubkey.is_point_at_infinity()) {
297 info("Multisig aggregate public key is invalid");
298 return std::nullopt;
299 }
300 return aggregate_pubkey;
301 }
302
312 {
313 // r_user ← 𝔽
314 // TODO: securely erase `r_user`
315 Fr r_user = Fr::random_element();
316 // R_user ← r_user⋅G
317 affine_element R_user = G1::one * r_user;
318
319 // s_user ← 𝔽
320 // TODO: securely erase `s_user`
321 Fr s_user = Fr::random_element();
322 // S_user ← s_user⋅G
323 affine_element S_user = G1::one * s_user;
324
325 RoundOnePublicOutput pubOut{ R_user, S_user };
326 RoundOnePrivateOutput privOut{ r_user, s_user };
327 return { pubOut, privOut };
328 }
329
343 const std::string& message,
344 const key_pair& signer,
345 const RoundOnePrivateOutput& signer_round_1_private_output,
346 const std::vector<MultiSigPublicKey>& signer_pubkeys,
347 const std::vector<RoundOnePublicOutput>& round_1_nonces)
348 {
349 const size_t num_signers = signer_pubkeys.size();
350 if (round_1_nonces.size() != num_signers) {
351 info("Multisig mismatch round_1_nonces and signers");
352 return std::nullopt;
353 }
354
355 // check that round_1_nonces does not contain duplicates and that all points are valid
356 if (!valid_round1_nonces(round_1_nonces)) {
357 return std::nullopt;
358 }
359
360 // compute aggregate key X = X_1 + ... + X_n
361 auto aggregate_pubkey = validate_and_combine_signer_pubkeys(signer_pubkeys);
362 if (!aggregate_pubkey.has_value()) {
363 // previous call has failed
364 return std::nullopt;
365 }
366
367 // compute nonce challenge H_non(G, X, "m_start", m, "m_end", {(R1, S1), ..., (Rn, Sn)})
368 Fr a = generate_nonce_challenge(message, *aggregate_pubkey, round_1_nonces);
369
370 // compute aggregate nonce R = R1 + ... + Rn + S1 * a + ... + Sn * a
371 affine_element R = construct_multisig_nonce(a, round_1_nonces);
372
373 // Now we have the multisig nonce, compute schnorr challenge e (termed `c` in the speedyMuSig paper)
374 auto e_buf = schnorr_generate_challenge<HashSig, G1>(message, *aggregate_pubkey, R);
375 Fr e = Fr::serialize_from_buffer(&e_buf[0]);
376
377 // output of round 2 is z
378 Fr z = signer_round_1_private_output.r + signer_round_1_private_output.s * a - signer.private_key * e;
379 return z;
380 }
381
395 const std::string& message,
396 const std::vector<MultiSigPublicKey>& signer_pubkeys,
397 const std::vector<RoundOnePublicOutput>& round_1_nonces,
398 const std::vector<RoundTwoPublicOutput>& round_2_signature_shares)
399 {
400 const size_t num_signers = signer_pubkeys.size();
401 if (round_1_nonces.size() != num_signers) {
402 info("Invalid number of round1 messages");
403 return std::nullopt;
404 }
405 if (round_2_signature_shares.size() != num_signers) {
406 info("Invalid number of round2 messages");
407 return std::nullopt;
408 }
409 if (!valid_round1_nonces(round_1_nonces)) {
410 return std::nullopt;
411 }
412
413 // compute aggregate key X = X_1 + ... + X_n
414 auto aggregate_pubkey = validate_and_combine_signer_pubkeys(signer_pubkeys);
415 if (!aggregate_pubkey.has_value()) {
416 // previous call has failed
417 return std::nullopt;
418 }
419
420 // compute nonce challenge H(X, m, {(R1, S1), ..., (Rn, Sn)})
421 Fr a = generate_nonce_challenge(message, *aggregate_pubkey, round_1_nonces);
422
423 // compute aggregate nonce R = R1 + ... + Rn + S1 * a + ... + Sn * a
424 affine_element R = construct_multisig_nonce(a, round_1_nonces);
425
426 auto e_buf = schnorr_generate_challenge<HashSig, G1>(message, *aggregate_pubkey, R);
427
429 // copy e as its raw bit representation (without modular reduction)
430 std::copy(e_buf.begin(), e_buf.end(), sig.e.begin());
431
432 Fr s = 0;
433 for (auto& z : round_2_signature_shares) {
434 s += z;
435 }
436 // write s, which will always produce an integer < r
437 Fr::serialize_to_buffer(s, &sig.s[0]);
438
439 // verify the final signature before returning
440 if (!schnorr_verify_signature<HashSig, Fq, Fr, G1>(message, *aggregate_pubkey, sig)) {
441 return std::nullopt;
442 }
443
444 return sig;
445 }
446};
447} // namespace bb::crypto
Implements the SpeedyMuSig protocol; a secure 2-round interactive multisignature scheme whose signatu...
Definition multisig.hpp:28
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.
Definition multisig.hpp:232
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....
Definition multisig.hpp:394
typename G1::element element
Definition multisig.hpp:39
static std::pair< RoundOnePublicOutput, RoundOnePrivateOutput > construct_signature_round_1()
First round of SpeedyMuSig. Signers generate random nonce keypairs R = {r, [R]}, S = {s,...
Definition multisig.hpp:311
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...
Definition multisig.hpp:342
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 ...
Definition multisig.hpp:154
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...
Definition multisig.hpp:119
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...
Definition multisig.hpp:265
typename G1::affine_element affine_element
Definition multisig.hpp:38
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_...
Definition multisig.hpp:210
void info(Args... args)
Definition log.hpp:89
FF a
FF b
void write(B &buf, SchnorrProofOfPossession< G1, Hash > const &proof_of_possession)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
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...
Definition multisig.hpp:51
SchnorrProofOfPossession< G1, HashRegNon > proof_of_possession
Definition multisig.hpp:59
MSGPACK_FIELDS(public_key, proof_of_possession)
MultiSigPublicKey(const affine_element &public_key, const SchnorrProofOfPossession< G1, HashRegNon > &proof_of_possession)
Definition multisig.hpp:71
bool operator==(const RoundOnePublicOutput &other) const
Definition multisig.hpp:106
bool operator<(const RoundOnePublicOutput &other) const
Definition multisig.hpp:101
std::array< uint8_t, 32 > s
Definition schnorr.hpp:30
std::array< uint8_t, 32 > e
Definition schnorr.hpp:33
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)