1use std::collections::HashMap;
2
3use crate::SignatureError;
4use crate::Signer;
5use crate::Verifier;
6use blst::min_sig::AggregatePublicKey;
7use blst::min_sig::AggregateSignature;
8use blst::min_sig::PublicKey;
9use blst::min_sig::SecretKey;
10use blst::min_sig::Signature;
11use sui_sdk_types::Bls12381PublicKey;
12use sui_sdk_types::Bls12381Signature;
13use sui_sdk_types::SignatureScheme;
14use sui_sdk_types::ValidatorAggregatedSignature;
15use sui_sdk_types::ValidatorCommittee;
16use sui_sdk_types::ValidatorSignature;
17
18const DST_G1: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
19
20#[derive(Debug)]
21#[allow(unused)]
22struct BlstError(blst::BLST_ERROR);
23
24impl std::fmt::Display for BlstError {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "{self:?}")
27 }
28}
29
30impl std::error::Error for BlstError {}
31
32pub struct Bls12381PrivateKey(SecretKey);
33
34impl std::fmt::Debug for Bls12381PrivateKey {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_tuple("Bls12381PrivateKey")
37 .field(&"__elided__")
38 .finish()
39 }
40}
41
42#[cfg(test)]
43impl proptest::arbitrary::Arbitrary for Bls12381PrivateKey {
44 type Parameters = ();
45 type Strategy = proptest::strategy::BoxedStrategy<Self>;
46 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
47 use proptest::strategy::Strategy;
48
49 proptest::arbitrary::any::<[u8; Self::LENGTH]>()
50 .prop_map(|bytes| {
51 let secret_key = SecretKey::key_gen(&bytes, &[]).unwrap();
52 Self(secret_key)
53 })
54 .boxed()
55 }
56}
57
58impl Bls12381PrivateKey {
59 pub const LENGTH: usize = 32;
61
62 pub fn new(bytes: [u8; Self::LENGTH]) -> Result<Self, SignatureError> {
63 SecretKey::from_bytes(&bytes)
64 .map_err(BlstError)
65 .map_err(SignatureError::from_source)
66 .map(Self)
67 }
68
69 pub fn scheme(&self) -> SignatureScheme {
70 SignatureScheme::Bls12381
71 }
72
73 pub fn verifying_key(&self) -> Bls12381VerifyingKey {
74 let verifying_key = self.0.sk_to_pk();
75 Bls12381VerifyingKey(verifying_key)
76 }
77
78 pub fn public_key(&self) -> Bls12381PublicKey {
79 self.verifying_key().public_key()
80 }
81
82 pub fn generate<R>(mut rng: R) -> Self
83 where
84 R: rand_core::RngCore + rand_core::CryptoRng,
85 {
86 let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
87 rng.fill_bytes(&mut buf);
88 let secret_key = SecretKey::key_gen(&buf, &[]).unwrap();
89 Self(secret_key)
90 }
91
92 pub fn sign_checkpoint_summary(
93 &self,
94 summary: &sui_sdk_types::CheckpointSummary,
95 ) -> ValidatorSignature {
96 let message = summary.signing_message();
97 let signature = self.sign(&message);
98 ValidatorSignature {
99 epoch: summary.epoch,
100 public_key: self.public_key(),
101 signature,
102 }
103 }
104}
105
106impl Signer<Bls12381Signature> for Bls12381PrivateKey {
107 fn try_sign(&self, msg: &[u8]) -> Result<Bls12381Signature, SignatureError> {
108 let signature = self.0.sign(msg, DST_G1, &[]);
109 Ok(Bls12381Signature::new(signature.to_bytes()))
110 }
111}
112
113#[derive(Debug)]
114pub struct Bls12381VerifyingKey(PublicKey);
115
116impl Bls12381VerifyingKey {
117 pub fn new(public_key: &Bls12381PublicKey) -> Result<Self, SignatureError> {
118 PublicKey::key_validate(public_key.inner())
119 .map(Self)
120 .map_err(BlstError)
121 .map_err(SignatureError::from_source)
122 }
123
124 pub fn public_key(&self) -> Bls12381PublicKey {
125 Bls12381PublicKey::new(self.0.to_bytes())
126 }
127}
128
129impl Verifier<Bls12381Signature> for Bls12381VerifyingKey {
130 fn verify(&self, message: &[u8], signature: &Bls12381Signature) -> Result<(), SignatureError> {
131 let signature = Signature::sig_validate(signature.inner(), true)
132 .map_err(BlstError)
133 .map_err(SignatureError::from_source)?;
134
135 let err = signature.verify(true, message, DST_G1, &[], &self.0, false);
136 if err == blst::BLST_ERROR::BLST_SUCCESS {
137 Ok(())
138 } else {
139 Err(SignatureError::from_source(BlstError(err)))
140 }
141 }
142}
143
144#[derive(Debug)]
145struct ExtendedValidatorCommittee {
146 committee: ValidatorCommittee,
147 verifying_keys: Vec<Bls12381VerifyingKey>,
148 public_key_to_index: HashMap<Bls12381PublicKey, usize>,
149 total_weight: u64,
150 quorum_threshold: u64,
151}
152
153struct MemberInfo<'a> {
154 verifying_key: &'a Bls12381VerifyingKey,
155 weight: u64,
156 index: usize,
157}
158
159impl ExtendedValidatorCommittee {
160 fn new(committee: ValidatorCommittee) -> Result<Self, SignatureError> {
161 let mut public_key_to_index = HashMap::new();
162 let mut verifying_keys = Vec::new();
163
164 let mut total_weight = 0;
165 for (idx, member) in committee.members.iter().enumerate() {
166 assert_eq!(idx, verifying_keys.len());
167 verifying_keys.push(Bls12381VerifyingKey::new(&member.public_key)?);
168 public_key_to_index.insert(member.public_key, idx);
169 total_weight += member.stake;
170 }
171
172 let quorum_threshold = ((total_weight - 1) / 3) * 2 + 1;
173
174 Ok(Self {
175 committee,
176 verifying_keys,
177 public_key_to_index,
178 total_weight,
179 quorum_threshold,
180 })
181 }
182
183 fn committee(&self) -> &ValidatorCommittee {
184 &self.committee
185 }
186
187 #[allow(unused)]
188 fn total_weight(&self) -> u64 {
189 self.total_weight
190 }
191
192 #[allow(unused)]
193 fn quorum_threshold(&self) -> u64 {
194 self.quorum_threshold
195 }
196
197 fn verifying_key(
198 &self,
199 public_key: &Bls12381PublicKey,
200 ) -> Result<&Bls12381VerifyingKey, SignatureError> {
201 self.public_key_to_index
202 .get(public_key)
203 .and_then(|idx| self.verifying_keys.get(*idx))
204 .ok_or_else(|| {
205 SignatureError::from_source(format!(
206 "signature from public_key {public_key} does not belong to this committee",
207 ))
208 })
209 }
210
211 fn member(&self, public_key: &Bls12381PublicKey) -> Result<MemberInfo<'_>, SignatureError> {
212 self.public_key_to_index
213 .get(public_key)
214 .ok_or_else(|| {
215 SignatureError::from_source(format!(
216 "signature from public_key {public_key} does not belong to this committee",
217 ))
218 })
219 .and_then(|idx| self.member_by_idx(*idx))
220 }
221
222 fn member_by_idx(&self, idx: usize) -> Result<MemberInfo<'_>, SignatureError> {
223 let verifying_key = self.verifying_keys.get(idx).ok_or_else(|| {
224 SignatureError::from_source(format!(
225 "index {idx} out of bounds; committee has {} members",
226 self.committee().members.len(),
227 ))
228 })?;
229 let weight = self
230 .committee()
231 .members
232 .get(idx)
233 .ok_or_else(|| {
234 SignatureError::from_source(format!(
235 "index {idx} out of bounds; committee has {} members",
236 self.committee().members.len(),
237 ))
238 })?
239 .stake;
240
241 Ok(MemberInfo {
242 verifying_key,
243 weight,
244 index: idx,
245 })
246 }
247}
248
249#[derive(Debug)]
250pub struct ValidatorCommitteeSignatureVerifier {
251 committee: ExtendedValidatorCommittee,
252}
253
254impl ValidatorCommitteeSignatureVerifier {
255 pub fn new(committee: ValidatorCommittee) -> Result<Self, SignatureError> {
256 ExtendedValidatorCommittee::new(committee).map(|committee| Self { committee })
257 }
258
259 pub fn committee(&self) -> &ValidatorCommittee {
260 self.committee.committee()
261 }
262
263 pub fn verify_checkpoint_summary(
264 &self,
265 summary: &sui_sdk_types::CheckpointSummary,
266 signature: &ValidatorAggregatedSignature,
267 ) -> Result<(), SignatureError> {
268 let message = summary.signing_message();
269 self.verify(&message, signature)
270 }
271}
272
273impl Verifier<ValidatorSignature> for ValidatorCommitteeSignatureVerifier {
274 fn verify(&self, message: &[u8], signature: &ValidatorSignature) -> Result<(), SignatureError> {
275 if signature.epoch != self.committee().epoch {
276 return Err(SignatureError::from_source(format!(
277 "signature epoch {} does not match committee epoch {}",
278 signature.epoch,
279 self.committee().epoch
280 )));
281 }
282
283 let verifying_key = self.committee.verifying_key(&signature.public_key)?;
284 verifying_key.verify(message, &signature.signature)
285 }
286}
287
288impl Verifier<ValidatorAggregatedSignature> for ValidatorCommitteeSignatureVerifier {
289 fn verify(
290 &self,
291 message: &[u8],
292 signature: &ValidatorAggregatedSignature,
293 ) -> Result<(), SignatureError> {
294 if signature.epoch != self.committee().epoch {
295 return Err(SignatureError::from_source(format!(
296 "signature epoch {} does not match committee epoch {}",
297 signature.epoch,
298 self.committee().epoch
299 )));
300 }
301
302 let mut signed_weight = 0;
303 let mut bitmap = signature.bitmap.iter();
304
305 let mut aggregated_public_key = {
306 let idx = bitmap.next().ok_or_else(|| {
307 SignatureError::from_source("signature bitmap must have at least one entry")
308 })?;
309
310 let member = self.committee.member_by_idx(idx as usize)?;
311
312 signed_weight += member.weight;
313 AggregatePublicKey::from_public_key(&member.verifying_key.0)
314 };
315
316 for idx in bitmap {
317 let member = self.committee.member_by_idx(idx as usize)?;
318
319 signed_weight += member.weight;
320 aggregated_public_key
321 .add_public_key(&member.verifying_key.0, false) .map_err(BlstError)
323 .map_err(SignatureError::from_source)?;
324 }
325
326 Bls12381VerifyingKey(aggregated_public_key.to_public_key())
327 .verify(message, &signature.signature)?;
328
329 if signed_weight >= self.committee.quorum_threshold {
330 Ok(())
331 } else {
332 Err(SignatureError::from_source(format!(
333 "insufficient signing weight {}; quorum threshold is {}",
334 signed_weight, self.committee.quorum_threshold,
335 )))
336 }
337 }
338}
339
340#[derive(Debug)]
341pub struct ValidatorCommitteeSignatureAggregator {
342 verifier: ValidatorCommitteeSignatureVerifier,
343 signatures: std::collections::BTreeMap<usize, ValidatorSignature>,
344 signed_weight: u64,
345 message: Vec<u8>,
346}
347
348impl ValidatorCommitteeSignatureAggregator {
349 pub fn new_checkpoint_summary(
350 committee: ValidatorCommittee,
351 summary: &sui_sdk_types::CheckpointSummary,
352 ) -> Result<Self, SignatureError> {
353 let verifier = ValidatorCommitteeSignatureVerifier::new(committee)?;
354 let message = summary.signing_message();
355
356 Ok(Self {
357 verifier,
358 signatures: Default::default(),
359 signed_weight: 0,
360 message,
361 })
362 }
363
364 pub fn committee(&self) -> &ValidatorCommittee {
365 self.verifier.committee()
366 }
367
368 pub fn add_signature(&mut self, signature: ValidatorSignature) -> Result<(), SignatureError> {
369 use std::collections::btree_map::Entry;
370
371 if signature.epoch != self.verifier.committee().epoch {
372 return Err(SignatureError::from_source(format!(
373 "signature epoch {} does not match committee epoch {}",
374 signature.epoch,
375 self.committee().epoch
376 )));
377 }
378
379 let member = self.verifier.committee.member(&signature.public_key)?;
380
381 member
382 .verifying_key
383 .verify(&self.message, &signature.signature)?;
384
385 match self.signatures.entry(member.index) {
386 Entry::Vacant(v) => {
387 v.insert(signature);
388 }
389 Entry::Occupied(_) => {
390 return Err(SignatureError::from_source(
391 "duplicate signature from same committee member",
392 ))
393 }
394 }
395
396 self.signed_weight += member.weight;
397
398 Ok(())
399 }
400
401 pub fn finish(&self) -> Result<ValidatorAggregatedSignature, SignatureError> {
402 if self.signed_weight < self.verifier.committee.quorum_threshold {
403 return Err(SignatureError::from_source(format!(
404 "signature weight of {} is insufficient to reach quorum threshold of {}",
405 self.signed_weight, self.verifier.committee.quorum_threshold
406 )));
407 }
408
409 let mut iter = self.signatures.iter();
410 let (member_idx, signature) = iter.next().ok_or_else(|| {
411 SignatureError::from_source("signature map must have at least one entry")
412 })?;
413
414 let mut bitmap = sui_sdk_types::roaring::RoaringBitmap::new();
415 bitmap.insert(*member_idx as u32);
416 let agg_sig = AggregateSignature::from_signature(
417 &Signature::from_bytes(signature.signature.inner())
418 .expect("signature was already verified"),
419 );
420
421 let (agg_sig, bitmap) = iter.fold(
422 (agg_sig, bitmap),
423 |(mut agg_sig, mut bitmap), (member_idx, signature)| {
424 bitmap.insert(*member_idx as u32);
425 agg_sig
426 .add_signature(
427 &Signature::from_bytes(signature.signature.inner())
428 .expect("signature was already verified"),
429 false,
430 )
431 .expect("signature was already verified");
432 (agg_sig, bitmap)
433 },
434 );
435
436 let aggregated_signature = ValidatorAggregatedSignature {
437 epoch: self.verifier.committee().epoch,
438 signature: Bls12381Signature::new(agg_sig.to_signature().to_bytes()),
439 bitmap,
440 };
441
442 self.verifier.verify(&self.message, &aggregated_signature)?;
444
445 Ok(aggregated_signature)
446 }
447}
448
449#[cfg(test)]
450mod test {
451 use super::*;
452 use sui_sdk_types::CheckpointSummary;
453 use sui_sdk_types::ValidatorCommittee;
454 use sui_sdk_types::ValidatorCommitteeMember;
455 use test_strategy::proptest;
456
457 #[cfg(target_arch = "wasm32")]
458 use wasm_bindgen_test::wasm_bindgen_test as test;
459
460 #[proptest]
461 fn basic_signing(signer: Bls12381PrivateKey, message: Vec<u8>) {
462 let signature = signer.sign(&message);
463 signer.verifying_key().verify(&message, &signature).unwrap();
464 }
465
466 #[proptest]
467 fn basic_aggregation(private_keys: [Bls12381PrivateKey; 4], summary: CheckpointSummary) {
468 let committee = ValidatorCommittee {
469 epoch: summary.epoch,
470 members: private_keys
471 .iter()
472 .map(|key| ValidatorCommitteeMember {
473 public_key: key.public_key(),
474 stake: 1,
475 })
476 .collect(),
477 };
478
479 let mut aggregator =
480 ValidatorCommitteeSignatureAggregator::new_checkpoint_summary(committee, &summary)
481 .unwrap();
482
483 aggregator.finish().unwrap_err();
485
486 aggregator
487 .add_signature(private_keys[0].sign_checkpoint_summary(&summary))
488 .unwrap();
489
490 aggregator
492 .add_signature(private_keys[0].sign_checkpoint_summary(&summary))
493 .unwrap_err();
494
495 aggregator.finish().unwrap_err();
497
498 aggregator
499 .add_signature(private_keys[1].sign_checkpoint_summary(&summary))
500 .unwrap();
501 aggregator
502 .add_signature(private_keys[2].sign_checkpoint_summary(&summary))
503 .unwrap();
504
505 let signature = aggregator.finish().unwrap();
507 aggregator
508 .verifier
509 .verify_checkpoint_summary(&summary, &signature)
510 .unwrap();
511
512 aggregator
514 .add_signature(private_keys[3].sign_checkpoint_summary(&summary))
515 .unwrap();
516 let signature = aggregator.finish().unwrap();
517 aggregator
518 .verifier
519 .verify_checkpoint_summary(&summary, &signature)
520 .unwrap();
521 }
522}