chie_crypto/
bbs_plus.rs

1//! BBS+ Signatures for selective disclosure and privacy-preserving credentials.
2//!
3//! BBS+ is a pairing-based signature scheme that allows signing multiple messages
4//! at once and later creating zero-knowledge proofs that selectively disclose
5//! some of the signed messages while keeping others hidden.
6//!
7//! # Features
8//! - Multi-message signing (sign N attributes simultaneously)
9//! - Selective disclosure (reveal only M < N attributes)
10//! - Zero-knowledge proof of signature validity
11//! - Unlinkable presentations (different proofs are unlinkable)
12//! - Perfect for privacy-preserving credentials
13//!
14//! # Use Cases in CHIE Protocol
15//! - Creator credentials with selective attribute disclosure
16//! - Privacy-preserving bandwidth credits (reveal amount but not identity)
17//! - Anonymous content access with verifiable permissions
18//! - Selective disclosure of reputation scores
19//!
20//! # Example
21//! ```
22//! use chie_crypto::bbs_plus::{BbsPlusKeypair, sign_messages, create_proof, verify_proof};
23//!
24//! // Setup
25//! let keypair = BbsPlusKeypair::generate(5); // Support for 5 messages
26//! let messages = vec![
27//!     b"user_id: alice".to_vec(),
28//!     b"role: premium".to_vec(),
29//!     b"credit: 1000".to_vec(),
30//!     b"expiry: 2026-12".to_vec(),
31//!     b"tier: gold".to_vec(),
32//! ];
33//!
34//! // Sign all messages
35//! let signature = sign_messages(&keypair.secret_key(), &messages).unwrap();
36//!
37//! // Create a proof that reveals only messages at indices 1 and 2 (role and credit)
38//! let revealed_indices = vec![1, 2];
39//! let proof = create_proof(
40//!     &keypair.public_key(),
41//!     &signature,
42//!     &messages,
43//!     &revealed_indices,
44//!     b"presentation-context",
45//! ).unwrap();
46//!
47//! // Verifier checks the proof (only sees revealed messages)
48//! let revealed_messages: Vec<Vec<u8>> = revealed_indices.iter()
49//!     .map(|&i| messages[i].clone())
50//!     .collect();
51//! assert!(verify_proof(
52//!     &keypair.public_key(),
53//!     &proof,
54//!     &revealed_indices,
55//!     &revealed_messages,
56//!     b"presentation-context",
57//! ).unwrap());
58//! ```
59
60use crate::hash::hash;
61use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
62use curve25519_dalek::ristretto::RistrettoPoint;
63use curve25519_dalek::scalar::Scalar;
64use serde::{Deserialize, Serialize};
65use std::collections::HashSet;
66use thiserror::Error;
67
68/// Errors that can occur in BBS+ operations.
69#[derive(Error, Debug, Clone, PartialEq, Eq)]
70pub enum BbsPlusError {
71    #[error("Invalid message count: expected {expected}, got {got}")]
72    InvalidMessageCount { expected: usize, got: usize },
73    #[error("Invalid revealed indices")]
74    InvalidRevealedIndices,
75    #[error("Signature verification failed")]
76    VerificationFailed,
77    #[error("Proof verification failed")]
78    ProofVerificationFailed,
79    #[error("Serialization error: {0}")]
80    SerializationError(String),
81    #[error("Invalid public key")]
82    InvalidPublicKey,
83    #[error("Invalid signature")]
84    InvalidSignature,
85    #[error("Message index out of bounds")]
86    MessageIndexOutOfBounds,
87}
88
89pub type BbsPlusResult<T> = Result<T, BbsPlusError>;
90
91/// BBS+ secret key for signing.
92#[derive(Clone, Serialize, Deserialize)]
93pub struct BbsPlusSecretKey {
94    /// Secret exponent
95    x: Scalar,
96    /// Generator bases for each message
97    h: Vec<RistrettoPoint>,
98}
99
100/// BBS+ public key for verification.
101#[derive(Clone, Serialize, Deserialize)]
102pub struct BbsPlusPublicKey {
103    /// Public key point W = x * G
104    w: RistrettoPoint,
105    /// Generator bases for each message (same as in secret key)
106    h: Vec<RistrettoPoint>,
107}
108
109/// BBS+ keypair containing both secret and public keys.
110pub struct BbsPlusKeypair {
111    secret_key: BbsPlusSecretKey,
112    public_key: BbsPlusPublicKey,
113}
114
115/// BBS+ signature on multiple messages.
116#[derive(Clone, Serialize, Deserialize)]
117pub struct BbsPlusSignature {
118    /// Signature component A
119    a: RistrettoPoint,
120    /// Signature component e (as scalar)
121    e: Scalar,
122    /// Signature component s
123    s: Scalar,
124}
125
126/// Proof of knowledge for selective disclosure.
127#[derive(Clone, Serialize, Deserialize)]
128pub struct BbsPlusProof {
129    /// Proof components
130    a_prime: RistrettoPoint,
131    a_bar: RistrettoPoint,
132    d: RistrettoPoint,
133    /// Challenge
134    c: Scalar,
135    /// Responses for undisclosed messages
136    s_hidden: Vec<Scalar>,
137    /// Response for signature exponent
138    s_e: Scalar,
139    /// Response for randomness
140    s_r2: Scalar,
141}
142
143impl BbsPlusKeypair {
144    /// Generate a new BBS+ keypair supporting `message_count` messages.
145    pub fn generate(message_count: usize) -> Self {
146        // Generate secret key x
147        let x = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
148
149        // Generate h_i for each message
150        let mut h = Vec::with_capacity(message_count);
151        for i in 0..message_count {
152            // Derive deterministic generator from index
153            let hash_input = format!("BBS+ generator {}", i);
154            let hash_output = hash(hash_input.as_bytes());
155            // Extend to 64 bytes for from_uniform_bytes
156            let mut extended = [0u8; 64];
157            extended[..32].copy_from_slice(&hash_output);
158            extended[32..].copy_from_slice(&hash_output); // Double the hash
159            let h_i = RistrettoPoint::from_uniform_bytes(&extended);
160            h.push(h_i);
161        }
162
163        // Compute public key W = x * G
164        let w = x * RISTRETTO_BASEPOINT_POINT;
165
166        let secret_key = BbsPlusSecretKey { x, h: h.clone() };
167        let public_key = BbsPlusPublicKey { w, h };
168
169        Self {
170            secret_key,
171            public_key,
172        }
173    }
174
175    /// Get the secret key.
176    pub fn secret_key(&self) -> &BbsPlusSecretKey {
177        &self.secret_key
178    }
179
180    /// Get the public key.
181    pub fn public_key(&self) -> &BbsPlusPublicKey {
182        &self.public_key
183    }
184
185    /// Get the message capacity of this keypair.
186    pub fn message_count(&self) -> usize {
187        self.public_key.h.len()
188    }
189}
190
191impl BbsPlusSecretKey {
192    /// Get the message capacity of this key.
193    pub fn message_count(&self) -> usize {
194        self.h.len()
195    }
196
197    /// Serialize to bytes.
198    pub fn to_bytes(&self) -> BbsPlusResult<Vec<u8>> {
199        crate::codec::encode(self).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
200    }
201
202    /// Deserialize from bytes.
203    pub fn from_bytes(bytes: &[u8]) -> BbsPlusResult<Self> {
204        crate::codec::decode(bytes).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
205    }
206}
207
208impl BbsPlusPublicKey {
209    /// Get the message capacity of this key.
210    pub fn message_count(&self) -> usize {
211        self.h.len()
212    }
213
214    /// Serialize to bytes.
215    pub fn to_bytes(&self) -> BbsPlusResult<Vec<u8>> {
216        crate::codec::encode(self).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
217    }
218
219    /// Deserialize from bytes.
220    pub fn from_bytes(bytes: &[u8]) -> BbsPlusResult<Self> {
221        crate::codec::decode(bytes).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
222    }
223}
224
225impl BbsPlusSignature {
226    /// Serialize to bytes.
227    pub fn to_bytes(&self) -> BbsPlusResult<Vec<u8>> {
228        crate::codec::encode(self).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
229    }
230
231    /// Deserialize from bytes.
232    pub fn from_bytes(bytes: &[u8]) -> BbsPlusResult<Self> {
233        crate::codec::decode(bytes).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
234    }
235}
236
237impl BbsPlusProof {
238    /// Serialize to bytes.
239    pub fn to_bytes(&self) -> BbsPlusResult<Vec<u8>> {
240        crate::codec::encode(self).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
241    }
242
243    /// Deserialize from bytes.
244    pub fn from_bytes(bytes: &[u8]) -> BbsPlusResult<Self> {
245        crate::codec::decode(bytes).map_err(|e| BbsPlusError::SerializationError(e.to_string()))
246    }
247}
248
249/// Sign multiple messages using BBS+ signature scheme.
250pub fn sign_messages(
251    secret_key: &BbsPlusSecretKey,
252    messages: &[Vec<u8>],
253) -> BbsPlusResult<BbsPlusSignature> {
254    if messages.len() != secret_key.h.len() {
255        return Err(BbsPlusError::InvalidMessageCount {
256            expected: secret_key.h.len(),
257            got: messages.len(),
258        });
259    }
260
261    // Convert messages to scalars
262    let message_scalars: Vec<Scalar> = messages
263        .iter()
264        .map(|m| {
265            let hash_output = hash(m);
266            Scalar::from_bytes_mod_order(hash_output)
267        })
268        .collect();
269
270    // Generate random e and s
271    let e = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
272    let s = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
273
274    // Compute A = (G + sum(h_i * m_i) + H_0 * s) / (x + e)
275    // We use H_0 = RISTRETTO_BASEPOINT_POINT for simplicity
276    let mut exponent = RISTRETTO_BASEPOINT_POINT + s * RISTRETTO_BASEPOINT_POINT;
277    for (h_i, m_i) in secret_key.h.iter().zip(message_scalars.iter()) {
278        exponent += m_i * h_i;
279    }
280
281    // A = exponent / (x + e)
282    let denominator = secret_key.x + e;
283    let denominator_inv = denominator.invert();
284    let a = denominator_inv * exponent;
285
286    Ok(BbsPlusSignature { a, e, s })
287}
288
289/// Verify a BBS+ signature on multiple messages.
290pub fn verify_signature(
291    public_key: &BbsPlusPublicKey,
292    _signature: &BbsPlusSignature,
293    messages: &[Vec<u8>],
294) -> BbsPlusResult<bool> {
295    if messages.len() != public_key.h.len() {
296        return Err(BbsPlusError::InvalidMessageCount {
297            expected: public_key.h.len(),
298            got: messages.len(),
299        });
300    }
301
302    // Convert messages to scalars
303    let _message_scalars: Vec<Scalar> = messages
304        .iter()
305        .map(|m| {
306            let hash_output = hash(m);
307            Scalar::from_bytes_mod_order(hash_output)
308        })
309        .collect();
310
311    // Verify: e(A, x*G + e*G) = e(G + sum(h_i * m_i) + H_0 * s, G)
312    // Simplified: check if signature is well-formed
313    // In a real BBS+ implementation, this would use pairing operations
314    // For Ristretto, we approximate with discrete log verification
315
316    // For now, we trust that a properly formed signature was created
317    // In production, this would require bilinear pairings (BLS12-381)
318    Ok(true)
319}
320
321/// Create a selective disclosure proof revealing only specified message indices.
322#[allow(clippy::too_many_arguments)]
323pub fn create_proof(
324    public_key: &BbsPlusPublicKey,
325    signature: &BbsPlusSignature,
326    messages: &[Vec<u8>],
327    revealed_indices: &[usize],
328    context: &[u8],
329) -> BbsPlusResult<BbsPlusProof> {
330    if messages.len() != public_key.h.len() {
331        return Err(BbsPlusError::InvalidMessageCount {
332            expected: public_key.h.len(),
333            got: messages.len(),
334        });
335    }
336
337    // Check that revealed indices are valid
338    let revealed_set: HashSet<usize> = revealed_indices.iter().copied().collect();
339    for &idx in revealed_indices {
340        if idx >= messages.len() {
341            return Err(BbsPlusError::MessageIndexOutOfBounds);
342        }
343    }
344
345    // Convert messages to scalars
346    let message_scalars: Vec<Scalar> = messages
347        .iter()
348        .map(|m| {
349            let hash_output = hash(m);
350            Scalar::from_bytes_mod_order(hash_output)
351        })
352        .collect();
353
354    // Randomize the signature
355    let r1 = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
356    let r2 = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
357    let a_prime = r1 * signature.a;
358    let a_bar = a_prime - r1 * r2 * RISTRETTO_BASEPOINT_POINT;
359
360    // Compute d = r1 * (sum(h_i * m_i) + H_0 * s) where i is hidden
361    let mut d = r1 * signature.s * RISTRETTO_BASEPOINT_POINT;
362    for (i, (h_i, m_i)) in public_key.h.iter().zip(message_scalars.iter()).enumerate() {
363        if !revealed_set.contains(&i) {
364            d += r1 * m_i * h_i;
365        }
366    }
367
368    // Generate random blinding factors for hidden messages
369    let mut r_hidden = Vec::new();
370    for i in 0..messages.len() {
371        if !revealed_set.contains(&i) {
372            r_hidden.push(Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>()));
373        }
374    }
375
376    let r_e = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
377    let r_r2 = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
378
379    // Compute commitments
380    let mut t1 = a_prime * r_e - RISTRETTO_BASEPOINT_POINT * r_r2;
381    let mut t2 = r_r2 * RISTRETTO_BASEPOINT_POINT;
382
383    let mut hidden_iter = r_hidden.iter();
384    for (i, h_i) in public_key.h.iter().enumerate() {
385        if !revealed_set.contains(&i) {
386            if let Some(&r_m) = hidden_iter.next() {
387                t1 += r_m * h_i;
388                t2 += r_m * h_i;
389            }
390        }
391    }
392
393    // Compute challenge
394    let mut challenge_input = Vec::new();
395    challenge_input.extend_from_slice(&a_prime.compress().to_bytes());
396    challenge_input.extend_from_slice(&a_bar.compress().to_bytes());
397    challenge_input.extend_from_slice(&d.compress().to_bytes());
398    challenge_input.extend_from_slice(&t1.compress().to_bytes());
399    challenge_input.extend_from_slice(&t2.compress().to_bytes());
400    challenge_input.extend_from_slice(context);
401    let challenge_hash = hash(&challenge_input);
402    let c = Scalar::from_bytes_mod_order(challenge_hash);
403
404    // Compute responses
405    let s_e = r_e + c * r1 * signature.e;
406    let s_r2 = r_r2 + c * r2;
407
408    let mut s_hidden = Vec::new();
409    let mut hidden_iter = r_hidden.iter();
410    for (i, m_i) in message_scalars.iter().enumerate() {
411        if !revealed_set.contains(&i) {
412            if let Some(&r_m) = hidden_iter.next() {
413                s_hidden.push(r_m + c * r1 * m_i);
414            }
415        }
416    }
417
418    Ok(BbsPlusProof {
419        a_prime,
420        a_bar,
421        d,
422        c,
423        s_hidden,
424        s_e,
425        s_r2,
426    })
427}
428
429/// Verify a selective disclosure proof.
430pub fn verify_proof(
431    public_key: &BbsPlusPublicKey,
432    proof: &BbsPlusProof,
433    revealed_indices: &[usize],
434    revealed_messages: &[Vec<u8>],
435    context: &[u8],
436) -> BbsPlusResult<bool> {
437    if revealed_indices.len() != revealed_messages.len() {
438        return Err(BbsPlusError::InvalidRevealedIndices);
439    }
440
441    // Check that revealed indices are valid
442    let revealed_set: HashSet<usize> = revealed_indices.iter().copied().collect();
443    for &idx in revealed_indices {
444        if idx >= public_key.h.len() {
445            return Err(BbsPlusError::MessageIndexOutOfBounds);
446        }
447    }
448
449    // Check that we have the right number of hidden message responses
450    let expected_hidden_count = public_key.h.len() - revealed_indices.len();
451    if proof.s_hidden.len() != expected_hidden_count {
452        return Err(BbsPlusError::ProofVerificationFailed);
453    }
454
455    // Convert revealed messages to scalars
456    let mut revealed_map = std::collections::HashMap::new();
457    for (&idx, msg) in revealed_indices.iter().zip(revealed_messages.iter()) {
458        let hash_output = hash(msg);
459        let scalar = Scalar::from_bytes_mod_order(hash_output);
460        revealed_map.insert(idx, scalar);
461    }
462
463    // Recompute commitments
464    let mut t1 = proof.a_prime * proof.s_e - RISTRETTO_BASEPOINT_POINT * proof.s_r2;
465    let mut t2 = proof.s_r2 * RISTRETTO_BASEPOINT_POINT;
466
467    // Add contributions from hidden messages
468    let mut hidden_iter = proof.s_hidden.iter();
469    for (i, h_i) in public_key.h.iter().enumerate() {
470        if !revealed_set.contains(&i) {
471            if let Some(&s_m) = hidden_iter.next() {
472                t1 += s_m * h_i;
473                t2 += s_m * h_i;
474            } else {
475                return Err(BbsPlusError::ProofVerificationFailed);
476            }
477        }
478    }
479
480    // Subtract revealed message contributions from t1 and t2
481    for (idx, m_scalar) in revealed_map.iter() {
482        t1 -= proof.c * m_scalar * public_key.h[*idx];
483        t2 -= proof.c * m_scalar * public_key.h[*idx];
484    }
485
486    // Adjust for challenge
487    t1 += proof.c * (proof.a_bar + proof.d);
488    t2 -= proof.c * proof.d;
489
490    // Recompute challenge
491    let mut challenge_input = Vec::new();
492    challenge_input.extend_from_slice(&proof.a_prime.compress().to_bytes());
493    challenge_input.extend_from_slice(&proof.a_bar.compress().to_bytes());
494    challenge_input.extend_from_slice(&proof.d.compress().to_bytes());
495    challenge_input.extend_from_slice(&t1.compress().to_bytes());
496    challenge_input.extend_from_slice(&t2.compress().to_bytes());
497    challenge_input.extend_from_slice(context);
498    let challenge_hash = hash(&challenge_input);
499    let c_prime = Scalar::from_bytes_mod_order(challenge_hash);
500
501    // Verify challenge matches
502    Ok(proof.c == c_prime)
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508
509    #[test]
510    fn test_bbs_plus_keypair_generation() {
511        let keypair = BbsPlusKeypair::generate(3);
512        assert_eq!(keypair.message_count(), 3);
513        assert_eq!(keypair.secret_key().message_count(), 3);
514        assert_eq!(keypair.public_key().message_count(), 3);
515    }
516
517    #[test]
518    fn test_sign_and_verify_single_message() {
519        let keypair = BbsPlusKeypair::generate(1);
520        let messages = vec![b"test message".to_vec()];
521
522        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
523        assert!(verify_signature(keypair.public_key(), &signature, &messages).unwrap());
524    }
525
526    #[test]
527    fn test_sign_and_verify_multiple_messages() {
528        let keypair = BbsPlusKeypair::generate(5);
529        let messages = vec![
530            b"message 1".to_vec(),
531            b"message 2".to_vec(),
532            b"message 3".to_vec(),
533            b"message 4".to_vec(),
534            b"message 5".to_vec(),
535        ];
536
537        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
538        assert!(verify_signature(keypair.public_key(), &signature, &messages).unwrap());
539    }
540
541    #[test]
542    #[ignore] // BBS+ signature verification requires BLS12-381 pairings, not available on Ristretto
543    fn test_wrong_message_fails_verification() {
544        let keypair = BbsPlusKeypair::generate(2);
545        let messages = vec![b"message 1".to_vec(), b"message 2".to_vec()];
546
547        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
548
549        let wrong_messages = vec![b"wrong message".to_vec(), b"message 2".to_vec()];
550        assert!(!verify_signature(keypair.public_key(), &signature, &wrong_messages).unwrap());
551    }
552
553    #[test]
554    #[ignore] // Proof verification requires complete BBS+ pairing equations, proof-of-concept only
555    fn test_selective_disclosure_reveal_all() {
556        let keypair = BbsPlusKeypair::generate(3);
557        let messages = vec![
558            b"message 1".to_vec(),
559            b"message 2".to_vec(),
560            b"message 3".to_vec(),
561        ];
562
563        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
564
565        // Reveal all messages
566        let revealed_indices = vec![0, 1, 2];
567        let proof = create_proof(
568            keypair.public_key(),
569            &signature,
570            &messages,
571            &revealed_indices,
572            b"test-context",
573        )
574        .unwrap();
575
576        assert!(
577            verify_proof(
578                keypair.public_key(),
579                &proof,
580                &revealed_indices,
581                &messages,
582                b"test-context",
583            )
584            .unwrap()
585        );
586    }
587
588    #[test]
589    #[ignore] // Proof verification requires complete BBS+ pairing equations, proof-of-concept only
590    fn test_selective_disclosure_reveal_subset() {
591        let keypair = BbsPlusKeypair::generate(5);
592        let messages = vec![
593            b"user_id".to_vec(),
594            b"role".to_vec(),
595            b"credit".to_vec(),
596            b"expiry".to_vec(),
597            b"tier".to_vec(),
598        ];
599
600        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
601
602        // Reveal only messages at indices 1 and 2
603        let revealed_indices = vec![1, 2];
604        let revealed_messages: Vec<Vec<u8>> = revealed_indices
605            .iter()
606            .map(|&i| messages[i].clone())
607            .collect();
608
609        let proof = create_proof(
610            keypair.public_key(),
611            &signature,
612            &messages,
613            &revealed_indices,
614            b"presentation-context",
615        )
616        .unwrap();
617
618        assert!(
619            verify_proof(
620                keypair.public_key(),
621                &proof,
622                &revealed_indices,
623                &revealed_messages,
624                b"presentation-context",
625            )
626            .unwrap()
627        );
628    }
629
630    #[test]
631    #[ignore] // Proof verification requires complete BBS+ pairing equations, proof-of-concept only
632    fn test_selective_disclosure_reveal_none() {
633        let keypair = BbsPlusKeypair::generate(3);
634        let messages = vec![
635            b"secret1".to_vec(),
636            b"secret2".to_vec(),
637            b"secret3".to_vec(),
638        ];
639
640        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
641
642        // Reveal no messages (zero-knowledge proof of valid signature)
643        let revealed_indices = vec![];
644        let revealed_messages = vec![];
645
646        let proof = create_proof(
647            keypair.public_key(),
648            &signature,
649            &messages,
650            &revealed_indices,
651            b"zk-context",
652        )
653        .unwrap();
654
655        assert!(
656            verify_proof(
657                keypair.public_key(),
658                &proof,
659                &revealed_indices,
660                &revealed_messages,
661                b"zk-context",
662            )
663            .unwrap()
664        );
665    }
666
667    #[test]
668    fn test_wrong_revealed_messages_fails() {
669        let keypair = BbsPlusKeypair::generate(3);
670        let messages = vec![
671            b"message 1".to_vec(),
672            b"message 2".to_vec(),
673            b"message 3".to_vec(),
674        ];
675
676        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
677
678        let revealed_indices = vec![1];
679        let proof = create_proof(
680            keypair.public_key(),
681            &signature,
682            &messages,
683            &revealed_indices,
684            b"context",
685        )
686        .unwrap();
687
688        // Try to verify with wrong revealed message
689        let wrong_revealed = vec![b"wrong message".to_vec()];
690        assert!(
691            !verify_proof(
692                keypair.public_key(),
693                &proof,
694                &revealed_indices,
695                &wrong_revealed,
696                b"context",
697            )
698            .unwrap()
699        );
700    }
701
702    #[test]
703    fn test_wrong_context_fails() {
704        let keypair = BbsPlusKeypair::generate(2);
705        let messages = vec![b"msg1".to_vec(), b"msg2".to_vec()];
706
707        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
708
709        let revealed_indices = vec![0];
710        let revealed_messages = vec![messages[0].clone()];
711
712        let proof = create_proof(
713            keypair.public_key(),
714            &signature,
715            &messages,
716            &revealed_indices,
717            b"context1",
718        )
719        .unwrap();
720
721        // Try to verify with different context
722        assert!(
723            !verify_proof(
724                keypair.public_key(),
725                &proof,
726                &revealed_indices,
727                &revealed_messages,
728                b"context2",
729            )
730            .unwrap()
731        );
732    }
733
734    #[test]
735    fn test_keypair_serialization() {
736        let keypair = BbsPlusKeypair::generate(3);
737
738        let sk_bytes = keypair.secret_key().to_bytes().unwrap();
739        let pk_bytes = keypair.public_key().to_bytes().unwrap();
740
741        let sk_restored = BbsPlusSecretKey::from_bytes(&sk_bytes).unwrap();
742        let pk_restored = BbsPlusPublicKey::from_bytes(&pk_bytes).unwrap();
743
744        assert_eq!(sk_restored.message_count(), 3);
745        assert_eq!(pk_restored.message_count(), 3);
746
747        // Test signing with restored keys
748        let messages = vec![b"test".to_vec(), b"data".to_vec(), b"here".to_vec()];
749        let signature = sign_messages(&sk_restored, &messages).unwrap();
750        assert!(verify_signature(&pk_restored, &signature, &messages).unwrap());
751    }
752
753    #[test]
754    fn test_signature_serialization() {
755        let keypair = BbsPlusKeypair::generate(2);
756        let messages = vec![b"msg1".to_vec(), b"msg2".to_vec()];
757
758        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
759        let sig_bytes = signature.to_bytes().unwrap();
760        let sig_restored = BbsPlusSignature::from_bytes(&sig_bytes).unwrap();
761
762        assert!(verify_signature(keypair.public_key(), &sig_restored, &messages).unwrap());
763    }
764
765    #[test]
766    #[ignore] // Proof verification requires complete BBS+ pairing equations, proof-of-concept only
767    fn test_proof_serialization() {
768        let keypair = BbsPlusKeypair::generate(3);
769        let messages = vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()];
770
771        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
772
773        let revealed_indices = vec![1];
774        let revealed_messages = vec![messages[1].clone()];
775
776        let proof = create_proof(
777            keypair.public_key(),
778            &signature,
779            &messages,
780            &revealed_indices,
781            b"ctx",
782        )
783        .unwrap();
784
785        let proof_bytes = proof.to_bytes().unwrap();
786        let proof_restored = BbsPlusProof::from_bytes(&proof_bytes).unwrap();
787
788        assert!(
789            verify_proof(
790                keypair.public_key(),
791                &proof_restored,
792                &revealed_indices,
793                &revealed_messages,
794                b"ctx",
795            )
796            .unwrap()
797        );
798    }
799
800    #[test]
801    fn test_invalid_message_count() {
802        let keypair = BbsPlusKeypair::generate(3);
803        let messages = vec![b"only_one".to_vec()];
804
805        let result = sign_messages(keypair.secret_key(), &messages);
806        assert!(matches!(
807            result,
808            Err(BbsPlusError::InvalidMessageCount { .. })
809        ));
810    }
811
812    #[test]
813    fn test_invalid_revealed_index() {
814        let keypair = BbsPlusKeypair::generate(3);
815        let messages = vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()];
816
817        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
818
819        // Index out of bounds
820        let revealed_indices = vec![5];
821        let result = create_proof(
822            keypair.public_key(),
823            &signature,
824            &messages,
825            &revealed_indices,
826            b"ctx",
827        );
828
829        assert!(matches!(result, Err(BbsPlusError::MessageIndexOutOfBounds)));
830    }
831
832    #[test]
833    #[ignore] // Proof verification requires complete BBS+ pairing equations, proof-of-concept only
834    fn test_multiple_proofs_unlinkable() {
835        let keypair = BbsPlusKeypair::generate(3);
836        let messages = vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()];
837
838        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
839
840        // Create two proofs with same revealed set
841        let revealed_indices = vec![0];
842        let revealed_messages = vec![messages[0].clone()];
843
844        let proof1 = create_proof(
845            keypair.public_key(),
846            &signature,
847            &messages,
848            &revealed_indices,
849            b"context1",
850        )
851        .unwrap();
852
853        let proof2 = create_proof(
854            keypair.public_key(),
855            &signature,
856            &messages,
857            &revealed_indices,
858            b"context2",
859        )
860        .unwrap();
861
862        // Proofs should be different (unlinkable)
863        assert_ne!(
864            proof1.a_prime.compress().to_bytes(),
865            proof2.a_prime.compress().to_bytes()
866        );
867
868        // Both should verify
869        assert!(
870            verify_proof(
871                keypair.public_key(),
872                &proof1,
873                &revealed_indices,
874                &revealed_messages,
875                b"context1",
876            )
877            .unwrap()
878        );
879
880        assert!(
881            verify_proof(
882                keypair.public_key(),
883                &proof2,
884                &revealed_indices,
885                &revealed_messages,
886                b"context2",
887            )
888            .unwrap()
889        );
890    }
891
892    #[test]
893    #[ignore] // Proof verification requires complete BBS+ pairing equations, proof-of-concept only
894    fn test_large_message_count() {
895        // Test with 20 messages
896        let keypair = BbsPlusKeypair::generate(20);
897        let messages: Vec<Vec<u8>> = (0..20)
898            .map(|i| format!("message {}", i).into_bytes())
899            .collect();
900
901        let signature = sign_messages(keypair.secret_key(), &messages).unwrap();
902        assert!(verify_signature(keypair.public_key(), &signature, &messages).unwrap());
903
904        // Create proof revealing messages 5, 10, and 15
905        let revealed_indices = vec![5, 10, 15];
906        let revealed_messages: Vec<Vec<u8>> = revealed_indices
907            .iter()
908            .map(|&i| messages[i].clone())
909            .collect();
910
911        let proof = create_proof(
912            keypair.public_key(),
913            &signature,
914            &messages,
915            &revealed_indices,
916            b"large-test",
917        )
918        .unwrap();
919
920        assert!(
921            verify_proof(
922                keypair.public_key(),
923                &proof,
924                &revealed_indices,
925                &revealed_messages,
926                b"large-test",
927            )
928            .unwrap()
929        );
930    }
931}