Skip to main content

confium_tc_frost_ml_dsa_65/
lib.rs

1//! Threshold FROST over ML-DSA-65 — research prototype.
2//!
3//! Lattice-based threshold signature. Based on FIPS 204 (ML-DSA-65).
4//! Threshold variants require MPC over the lattice signing operations;
5//! academic work ongoing (Boneh et al. 2024).
6//!
7//! See `TODO.roadmap/35-pq-composite-signatures.md` for full spec.
8
9#![forbid(unsafe_code)]
10#![allow(missing_docs)] // TODO: document before 1.0
11
12use serde::{Deserialize, Serialize};
13
14/// Algorithm identifier.
15pub const ALGORITHM: &str = "FROST-ML-DSA-65";
16
17/// ML-DSA-65 public key size (FIPS 204).
18pub const PUBLIC_KEY_SIZE: usize = 1952;
19
20/// ML-DSA-65 signature size.
21pub const SIGNATURE_SIZE: usize = 3309;
22
23/// Threshold FROST-ML-DSA-65 public key.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ThresholdPublicKey {
26    /// Public key bytes.
27    pub bytes: Vec<u8>,
28}
29
30/// Share of the threshold signing key.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Share {
33    /// Party index.
34    pub party_index: u32,
35    /// Share bytes.
36    pub bytes: Vec<u8>,
37}
38
39/// Errors during FROST-ML-DSA operations.
40#[derive(Debug, thiserror::Error)]
41pub enum FrostMlDsaError {
42    /// Threshold not met.
43    #[error("threshold not met: have {have}, need {need}")]
44    ThresholdNotMet {
45        /// Have count.
46        have: usize,
47        /// Need count.
48        need: u32,
49    },
50    /// Research-only operation.
51    #[error("operation requires academic collaborator: {0}")]
52    ResearchOnly(String),
53}
54
55/// Validate that a public key has the correct length for ML-DSA-65.
56pub fn validate_public_key(pk: &ThresholdPublicKey) -> Result<(), FrostMlDsaError> {
57    if pk.bytes.len() != PUBLIC_KEY_SIZE {
58        return Err(FrostMlDsaError::ResearchOnly(format!(
59            "expected public key length {PUBLIC_KEY_SIZE}, got {}",
60            pk.bytes.len()
61        )));
62    }
63    Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn algorithm_id() {
72        assert_eq!(ALGORITHM, "FROST-ML-DSA-65");
73    }
74
75    #[test]
76    fn validate_correct_size() {
77        let pk = ThresholdPublicKey {
78            bytes: vec![0u8; PUBLIC_KEY_SIZE],
79        };
80        validate_public_key(&pk).unwrap();
81    }
82
83    #[test]
84    fn validate_wrong_size_fails() {
85        let pk = ThresholdPublicKey {
86            bytes: vec![0u8; 100],
87        };
88        let result = validate_public_key(&pk);
89        assert!(matches!(result, Err(FrostMlDsaError::ResearchOnly(_))));
90    }
91}