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}