sui_crypto/
bls12381.rs

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    /// The length of an bls12381 private key in bytes.
60    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) // Keys are already verified
322                .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        // Double check that the aggregated sig still verifies
443        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        // Aggregating with no sigs fails
484        aggregator.finish().unwrap_err();
485
486        aggregator
487            .add_signature(private_keys[0].sign_checkpoint_summary(&summary))
488            .unwrap();
489
490        // Aggregating with a sig from the same committee member more than once fails
491        aggregator
492            .add_signature(private_keys[0].sign_checkpoint_summary(&summary))
493            .unwrap_err();
494
495        // Aggregating with insufficient weight fails
496        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        // Aggregating with sufficient weight succeeds and verifies
506        let signature = aggregator.finish().unwrap();
507        aggregator
508            .verifier
509            .verify_checkpoint_summary(&summary, &signature)
510            .unwrap();
511
512        // We can add the last sig and still be successful
513        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}