Skip to main content

confium_tc_bls/
lib.rs

1//! Threshold BLS signature for cross-organization aggregation.
2//!
3//! **⚠️ RESEARCH PROTOTYPE — NOT FOR PRODUCTION USE.**
4//!
5//! This crate exists to validate the threshold-BLS API shape and
6//! coordinator integration. The actual aggregation is a **mock**:
7//! signature bytes are XOR-folded rather than combined via the
8//! BLS12-381 pairing. The mock:
9//!
10//! - Is NOT cryptographically secure — XOR-folding signatures is a
11//!   well-known anti-pattern (sponge attacks recover individual
12//!   signatures from aggregates).
13//! - Does NOT use the `blst` or `ark-bls12-381` crates.
14//! - Does NOT produce signatures that verify under standard BLS
15//!   libraries (randombytes, blst, py_ecc, etc.).
16//!
17//! A production BLS implementation is tracked as a separate work
18//! stream (see `TODO.roadmap/04-threshold-cryptography.md`).
19//! Until that lands, treat every output from this crate as
20//! unverified placeholder data.
21//!
22//! ## What this crate IS good for
23//!
24//! - Validating the coordinator's session-driver wiring for an
25//!   aggregation-style scheme.
26//! - Testing the FFI surface and language bindings without needing
27//!   real BLS12-381 native dependencies.
28//! - Reference for what the eventual API shape will look like.
29//!
30//! ## What this crate is NOT good for
31//!
32//! - Real signature verification.
33//! - Cross-organization MAA (Mutual Acceptance Arrangement) signing.
34//! - Any deployment where the signature protects a real asset.
35//!
36//! BLS signatures natively aggregate: many signatures over distinct
37//! messages under different public keys can be combined into a single
38//! short signature. Useful for OIML MAA: multiple IAs co-sign a
39//! single CNML certificate, aggregated into one.
40//!
41//! See `TODO.roadmap/04-threshold-cryptography.md` for full spec.
42
43#![forbid(unsafe_code)]
44#![allow(missing_docs)] // TODO: document before 1.0
45
46use serde::{Deserialize, Serialize};
47
48/// Algorithm identifier (uses BLS12-381 curve).
49pub const ALGORITHM: &str = "BLS-threshold";
50
51/// BLS public key (48 bytes on BLS12-381 G2).
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PublicKey {
54    /// Public key bytes.
55    pub bytes: Vec<u8>,
56}
57
58/// BLS signature (96 bytes on BLS12-381 G1).
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct Signature {
61    /// Signature bytes.
62    pub bytes: Vec<u8>,
63}
64
65/// Threshold share of BLS signing key.
66/// The secret `bytes` field is zeroized on drop.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Share {
69    /// Party index.
70    pub party_index: u32,
71    /// Share bytes (32 bytes).
72    pub bytes: Vec<u8>,
73}
74
75impl Drop for Share {
76    fn drop(&mut self) {
77        use zeroize::Zeroize;
78        self.bytes.zeroize();
79    }
80}
81
82/// Errors during BLS operations.
83#[derive(Debug, thiserror::Error)]
84pub enum BlsError {
85    /// Fewer than T partial signatures were supplied to an aggregation
86    /// call. The threshold was set during DKG; the caller must collect
87    /// at least T partials before aggregating.
88    /// Caller action: wait for more partials from peers.
89    #[error("threshold not met")]
90    ThresholdNotMet,
91    /// Aggregation failed — typically because the supplied partials
92    /// are inconsistent (different messages, wrong group operation).
93    /// The string describes the specific failure.
94    /// Caller action: inspect the message; restart the round if needed.
95    #[error("aggregation failed: {0}")]
96    AggregationFailed(String),
97    /// The aggregated signature failed verification against the joint
98    /// public key. Indicates either a Byzantine participant or a
99    /// corrupted public key / message.
100    /// Caller action: re-run the verification with a known-good key
101    /// before reporting the issue.
102    #[error("invalid signature")]
103    InvalidSignature,
104}
105
106/// Aggregate multiple BLS signatures over the same message into one.
107///
108/// Mock: XORs all signature bytes together.
109pub fn aggregate_signatures(signatures: &[Signature]) -> Result<Signature, BlsError> {
110    if signatures.is_empty() {
111        return Err(BlsError::AggregationFailed(
112            "no signatures to aggregate".into(),
113        ));
114    }
115    let mut combined = signatures[0].bytes.clone();
116    for sig in &signatures[1..] {
117        for (i, b) in sig.bytes.iter().enumerate() {
118            if i < combined.len() {
119                combined[i] ^= b;
120            }
121        }
122    }
123    Ok(Signature { bytes: combined })
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn aggregate_two_signatures() {
132        let s1 = Signature {
133            bytes: vec![0xFF; 96],
134        };
135        let s2 = Signature {
136            bytes: vec![0xFF; 96],
137        };
138        let combined = aggregate_signatures(&[s1, s2]).unwrap();
139        // XOR of two identical = all zeros
140        assert!(combined.bytes.iter().all(|b| *b == 0));
141    }
142
143    #[test]
144    fn empty_aggregation_fails() {
145        let result = aggregate_signatures(&[]);
146        assert!(result.is_err());
147    }
148}