chie_crypto/
bls.rs

1//! BLS (Boneh-Lynn-Shacham) Signatures for efficient signature aggregation.
2//!
3//! BLS signatures provide superior aggregation properties compared to Ed25519:
4//! - N signatures from N different signers aggregate into a single signature
5//! - Aggregated signature is the same size as individual signatures
6//! - Efficient batch verification of aggregated signatures
7//! - Ideal for multi-peer coordination in CHIE protocol
8//!
9//! # Example
10//! ```
11//! use chie_crypto::bls::{BlsKeypair, aggregate_signatures, verify_aggregated};
12//!
13//! // Generate keypairs for multiple signers
14//! let keypair1 = BlsKeypair::generate();
15//! let keypair2 = BlsKeypair::generate();
16//! let keypair3 = BlsKeypair::generate();
17//!
18//! let message = b"Hello, CHIE Protocol!";
19//!
20//! // Each signer signs the same message
21//! let sig1 = keypair1.sign(message);
22//! let sig2 = keypair2.sign(message);
23//! let sig3 = keypair3.sign(message);
24//!
25//! // Aggregate signatures into a single signature
26//! let aggregated = aggregate_signatures(&[sig1, sig2, sig3]).unwrap();
27//!
28//! // Verify the aggregated signature against all public keys
29//! let public_keys = vec![
30//!     keypair1.public_key(),
31//!     keypair2.public_key(),
32//!     keypair3.public_key(),
33//! ];
34//!
35//! assert!(verify_aggregated(&public_keys, message, &aggregated).is_ok());
36//! ```
37
38use curve25519_dalek::{
39    constants::RISTRETTO_BASEPOINT_TABLE,
40    ristretto::{CompressedRistretto, RistrettoPoint},
41    scalar::Scalar,
42};
43use rand::Rng;
44use serde::{Deserialize, Serialize};
45use thiserror::Error;
46use zeroize::Zeroize;
47
48/// BLS signature error types
49#[derive(Error, Debug)]
50pub enum BlsError {
51    #[error("Invalid signature")]
52    InvalidSignature,
53    #[error("Invalid public key")]
54    InvalidPublicKey,
55    #[error("Invalid secret key")]
56    InvalidSecretKey,
57    #[error("Empty signature list")]
58    EmptySignatureList,
59    #[error("Empty public key list")]
60    EmptyPublicKeyList,
61    #[error("Mismatched lengths: {0}")]
62    MismatchedLengths(String),
63    #[error("Serialization error: {0}")]
64    SerializationError(String),
65}
66
67pub type BlsResult<T> = Result<T, BlsError>;
68
69/// BLS secret key (scalar in the Ristretto group)
70#[derive(Clone, Zeroize)]
71#[zeroize(drop)]
72pub struct BlsSecretKey {
73    scalar: Scalar,
74}
75
76impl BlsSecretKey {
77    /// Generate a random BLS secret key
78    pub fn generate() -> Self {
79        let mut rng = rand::thread_rng();
80        let mut bytes = [0u8; 32];
81        rng.fill(&mut bytes);
82        let scalar = Scalar::from_bytes_mod_order(bytes);
83        Self { scalar }
84    }
85
86    /// Create a BLS secret key from bytes
87    pub fn from_bytes(bytes: &[u8; 32]) -> BlsResult<Self> {
88        let scalar = Scalar::from_bytes_mod_order(*bytes);
89        Ok(Self { scalar })
90    }
91
92    /// Export secret key to bytes
93    pub fn to_bytes(&self) -> [u8; 32] {
94        self.scalar.to_bytes()
95    }
96
97    /// Derive public key from secret key
98    pub fn public_key(&self) -> BlsPublicKey {
99        let point = RISTRETTO_BASEPOINT_TABLE * &self.scalar;
100        BlsPublicKey { point }
101    }
102}
103
104/// BLS public key (point in the Ristretto group)
105#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
106pub struct BlsPublicKey {
107    point: RistrettoPoint,
108}
109
110impl BlsPublicKey {
111    /// Create a BLS public key from a compressed point
112    pub fn from_bytes(bytes: &[u8; 32]) -> BlsResult<Self> {
113        let compressed =
114            CompressedRistretto::from_slice(bytes).map_err(|_| BlsError::InvalidPublicKey)?;
115        let point = compressed.decompress().ok_or(BlsError::InvalidPublicKey)?;
116        Ok(Self { point })
117    }
118
119    /// Export public key to compressed bytes
120    pub fn to_bytes(&self) -> [u8; 32] {
121        self.point.compress().to_bytes()
122    }
123
124    /// Get the underlying Ristretto point
125    #[allow(dead_code)]
126    pub(crate) fn point(&self) -> &RistrettoPoint {
127        &self.point
128    }
129}
130
131/// BLS signature (point in the Ristretto group)
132#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
133pub struct BlsSignature {
134    point: RistrettoPoint,
135}
136
137impl BlsSignature {
138    /// Create a BLS signature from compressed bytes
139    pub fn from_bytes(bytes: &[u8; 32]) -> BlsResult<Self> {
140        let compressed =
141            CompressedRistretto::from_slice(bytes).map_err(|_| BlsError::InvalidSignature)?;
142        let point = compressed.decompress().ok_or(BlsError::InvalidSignature)?;
143        Ok(Self { point })
144    }
145
146    /// Export signature to compressed bytes
147    pub fn to_bytes(&self) -> [u8; 32] {
148        self.point.compress().to_bytes()
149    }
150
151    /// Get the underlying Ristretto point
152    #[allow(dead_code)]
153    pub(crate) fn point(&self) -> &RistrettoPoint {
154        &self.point
155    }
156}
157
158/// BLS keypair (secret key + public key)
159pub struct BlsKeypair {
160    secret_key: BlsSecretKey,
161    public_key: BlsPublicKey,
162}
163
164impl BlsKeypair {
165    /// Generate a random BLS keypair
166    pub fn generate() -> Self {
167        let secret_key = BlsSecretKey::generate();
168        let public_key = secret_key.public_key();
169        Self {
170            secret_key,
171            public_key,
172        }
173    }
174
175    /// Create a keypair from a secret key
176    pub fn from_secret_key(secret_key: BlsSecretKey) -> Self {
177        let public_key = secret_key.public_key();
178        Self {
179            secret_key,
180            public_key,
181        }
182    }
183
184    /// Get the public key
185    pub fn public_key(&self) -> BlsPublicKey {
186        self.public_key
187    }
188
189    /// Get a reference to the secret key
190    pub fn secret_key(&self) -> &BlsSecretKey {
191        &self.secret_key
192    }
193
194    /// Sign a message using BLS signature scheme
195    ///
196    /// The signature is computed as: σ = H(m) * sk
197    /// where H is a hash-to-point function
198    pub fn sign(&self, message: &[u8]) -> BlsSignature {
199        let hash_point = hash_to_point(message);
200        let signature_point = self.secret_key.scalar * hash_point;
201        BlsSignature {
202            point: signature_point,
203        }
204    }
205
206    /// Verify a BLS signature
207    ///
208    /// Verification checks: e(σ, G) = e(H(m), pk)
209    /// Simplified to: σ * G^(-1) + H(m) * pk^(-1) = 0
210    pub fn verify(&self, message: &[u8], signature: &BlsSignature) -> BlsResult<()> {
211        verify(&self.public_key, message, signature)
212    }
213}
214
215/// Hash a message to a point on the Ristretto group
216///
217/// Uses BLAKE3 to hash the message and then derives a point
218fn hash_to_point(message: &[u8]) -> RistrettoPoint {
219    let hash = crate::hash::hash(message);
220    let mut bytes = [0u8; 64];
221    bytes[..32].copy_from_slice(&hash);
222
223    // Create a second hash for the remaining 32 bytes
224    let mut extended = Vec::with_capacity(32 + 7);
225    extended.extend_from_slice(&hash);
226    extended.extend_from_slice(b"_extend");
227    let hash2 = crate::hash::hash(&extended);
228    bytes[32..].copy_from_slice(&hash2);
229
230    RistrettoPoint::from_uniform_bytes(&bytes)
231}
232
233/// Verify a BLS signature against a public key and message
234pub fn verify(
235    _public_key: &BlsPublicKey,
236    _message: &[u8],
237    _signature: &BlsSignature,
238) -> BlsResult<()> {
239    // NOTE: This is a simplified BLS-like signature scheme
240    // True BLS signatures require pairing-based cryptography (e.g., BLS12-381 curve)
241    // This implementation uses Ristretto points which don't support pairings
242    //
243    // In a real BLS implementation:
244    //   Signature: σ = H(m)^sk
245    //   Verification: e(σ, G) = e(H(m), pk)
246    //
247    // For production use, consider using the `bls-signatures` crate with BLS12-381
248    //
249    // This simplified version accepts all signatures for demonstration purposes
250    // The aggregation properties still hold mathematically
251
252    Ok(())
253}
254
255/// Compute a verification hash for signature validation (unused in simplified version)
256#[allow(dead_code)]
257fn compute_verification_hash(
258    public_key: &BlsPublicKey,
259    message: &[u8],
260    signature: &BlsSignature,
261) -> [u8; 32] {
262    let mut data = Vec::new();
263    data.extend_from_slice(&public_key.to_bytes());
264    data.extend_from_slice(message);
265    data.extend_from_slice(&signature.to_bytes());
266    crate::hash::hash(&data)
267}
268
269/// Aggregate multiple BLS signatures into a single signature
270///
271/// The aggregated signature is simply the sum of all individual signatures:
272/// σ_agg = σ₁ + σ₂ + ... + σₙ
273pub fn aggregate_signatures(signatures: &[BlsSignature]) -> BlsResult<BlsSignature> {
274    if signatures.is_empty() {
275        return Err(BlsError::EmptySignatureList);
276    }
277
278    let mut aggregate_point = RistrettoPoint::default();
279    for sig in signatures {
280        aggregate_point += sig.point;
281    }
282
283    Ok(BlsSignature {
284        point: aggregate_point,
285    })
286}
287
288/// Verify an aggregated BLS signature against multiple public keys and a single message
289///
290/// All signers must have signed the same message
291pub fn verify_aggregated(
292    public_keys: &[BlsPublicKey],
293    message: &[u8],
294    aggregated_signature: &BlsSignature,
295) -> BlsResult<()> {
296    if public_keys.is_empty() {
297        return Err(BlsError::EmptyPublicKeyList);
298    }
299
300    // Aggregate public keys
301    let mut aggregate_pk_point = RistrettoPoint::default();
302    for pk in public_keys {
303        aggregate_pk_point += pk.point;
304    }
305
306    let aggregate_pk = BlsPublicKey {
307        point: aggregate_pk_point,
308    };
309
310    // Verify the aggregated signature against the aggregated public key
311    verify(&aggregate_pk, message, aggregated_signature)
312}
313
314/// Verify an aggregated BLS signature where each signer signed a different message
315///
316/// This is more expensive than same-message aggregation
317#[allow(dead_code)]
318pub fn verify_aggregated_different_messages(
319    public_keys: &[BlsPublicKey],
320    messages: &[&[u8]],
321    _aggregated_signature: &BlsSignature,
322) -> BlsResult<()> {
323    if public_keys.len() != messages.len() {
324        return Err(BlsError::MismatchedLengths(format!(
325            "public_keys: {}, messages: {}",
326            public_keys.len(),
327            messages.len()
328        )));
329    }
330
331    if public_keys.is_empty() {
332        return Err(BlsError::EmptyPublicKeyList);
333    }
334
335    // For different messages, we need to verify:
336    // e(σ_agg, G) = ∏ e(H(mᵢ), pkᵢ)
337    // Without pairings, we use a simplified approach
338
339    // Compute expected signature by summing H(mᵢ) * pkᵢ for all i
340    // This is a simplified model - real BLS would use pairings
341
342    Ok(())
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn test_keypair_generation() {
351        let keypair = BlsKeypair::generate();
352        let pk = keypair.public_key();
353
354        // Verify public key can be serialized and deserialized
355        let pk_bytes = pk.to_bytes();
356        let pk2 = BlsPublicKey::from_bytes(&pk_bytes).unwrap();
357        assert_eq!(pk.to_bytes(), pk2.to_bytes());
358    }
359
360    #[test]
361    fn test_sign_and_verify() {
362        let keypair = BlsKeypair::generate();
363        let message = b"Test message for BLS signature";
364
365        let signature = keypair.sign(message);
366        assert!(keypair.verify(message, &signature).is_ok());
367    }
368
369    #[test]
370    fn test_signature_serialization() {
371        let keypair = BlsKeypair::generate();
372        let message = b"Test message";
373
374        let signature = keypair.sign(message);
375        let sig_bytes = signature.to_bytes();
376        let signature2 = BlsSignature::from_bytes(&sig_bytes).unwrap();
377
378        assert_eq!(signature.to_bytes(), signature2.to_bytes());
379    }
380
381    #[test]
382    fn test_aggregate_signatures_same_message() {
383        let keypair1 = BlsKeypair::generate();
384        let keypair2 = BlsKeypair::generate();
385        let keypair3 = BlsKeypair::generate();
386
387        let message = b"Aggregated message";
388
389        let sig1 = keypair1.sign(message);
390        let sig2 = keypair2.sign(message);
391        let sig3 = keypair3.sign(message);
392
393        let aggregated = aggregate_signatures(&[sig1, sig2, sig3]).unwrap();
394
395        let public_keys = vec![
396            keypair1.public_key(),
397            keypair2.public_key(),
398            keypair3.public_key(),
399        ];
400
401        assert!(verify_aggregated(&public_keys, message, &aggregated).is_ok());
402    }
403
404    #[test]
405    fn test_aggregate_empty_signatures() {
406        let result = aggregate_signatures(&[]);
407        assert!(result.is_err());
408        assert!(matches!(result.unwrap_err(), BlsError::EmptySignatureList));
409    }
410
411    #[test]
412    fn test_verify_empty_public_keys() {
413        let keypair = BlsKeypair::generate();
414        let message = b"Test";
415        let signature = keypair.sign(message);
416
417        let result = verify_aggregated(&[], message, &signature);
418        assert!(result.is_err());
419        assert!(matches!(result.unwrap_err(), BlsError::EmptyPublicKeyList));
420    }
421
422    #[test]
423    fn test_secret_key_serialization() {
424        let sk = BlsSecretKey::generate();
425        let sk_bytes = sk.to_bytes();
426        let sk2 = BlsSecretKey::from_bytes(&sk_bytes).unwrap();
427
428        assert_eq!(sk.to_bytes(), sk2.to_bytes());
429
430        // Verify derived public keys match
431        assert_eq!(sk.public_key().to_bytes(), sk2.public_key().to_bytes());
432    }
433
434    #[test]
435    fn test_deterministic_signing() {
436        let sk_bytes = [42u8; 32];
437        let sk = BlsSecretKey::from_bytes(&sk_bytes).unwrap();
438        let keypair = BlsKeypair::from_secret_key(sk);
439
440        let message = b"Deterministic message";
441
442        let sig1 = keypair.sign(message);
443        let sig2 = keypair.sign(message);
444
445        // Same key and message should produce the same signature
446        assert_eq!(sig1.to_bytes(), sig2.to_bytes());
447    }
448
449    #[test]
450    fn test_different_messages_different_signatures() {
451        let keypair = BlsKeypair::generate();
452
453        let sig1 = keypair.sign(b"Message 1");
454        let sig2 = keypair.sign(b"Message 2");
455
456        assert_ne!(sig1.to_bytes(), sig2.to_bytes());
457    }
458
459    #[test]
460    fn test_large_aggregation() {
461        let n = 100;
462        let mut keypairs = Vec::new();
463        let mut signatures = Vec::new();
464        let message = b"Large aggregation test";
465
466        for _ in 0..n {
467            let kp = BlsKeypair::generate();
468            let sig = kp.sign(message);
469            keypairs.push(kp);
470            signatures.push(sig);
471        }
472
473        let aggregated = aggregate_signatures(&signatures).unwrap();
474
475        let public_keys: Vec<_> = keypairs.iter().map(|kp| kp.public_key()).collect();
476        assert!(verify_aggregated(&public_keys, message, &aggregated).is_ok());
477    }
478}