Skip to main content

confium_tc_frost_p256/
lib.rs

1//! FROST threshold signature over ECDSA P-256.
2//!
3//! Implements real Shamir secret sharing over the P-256 scalar field
4//! plus real P-256 ECDSA signing/verification. Used by OIML CNML IA
5//! quorum and Mode 2 enterprise PKI replacement for compatibility
6//! with existing P-256 PKI.
7//!
8//! ## Important: threshold ECDSA caveats
9//!
10//! True threshold ECDSA signing (where the secret is never reconstructed)
11//! requires the Multiplicative-to-Additive (MtA) protocol used by
12//! `confium-tc-cmp20` and `confium-tc-gg18`. This crate provides:
13//!
14//! - **Real Shamir secret sharing** over P-256 scalars
15//! - **Real Lagrange interpolation** to reconstruct the secret from T shares
16//! - **Real P-256 ECDSA** sign/verify
17//!
18//! For demonstration, integration testing, and as the underlying primitives
19//! for FROST-style schemes. For production threshold ECDSA signing,
20//! use `confium-tc-cmp20`.
21//!
22//! See `TODO.roadmap/04-threshold-cryptography.md` for the FFI spec.
23//!
24//! # Example
25//!
26//! ```
27//! use confium_tc_frost_p256::{generate_keypair, split_secret, recover_secret};
28//!
29//! let kp = generate_keypair();
30//! // 3-of-5 secret sharing of the keypair's secret scalar.
31//! let shares = split_secret(&kp.secret_scalar, 3, 5);
32//! // Any 3 shares reconstruct the original secret.
33//! let subset: Vec<_> = shares.iter().take(3).collect();
34//! let recovered = recover_secret(&subset)?;
35//! assert_eq!(recovered, kp.secret_scalar);
36//! # Ok::<(), confium_tc_frost_p256::ShamirError>(())
37//! ```
38
39#![forbid(unsafe_code)]
40#![allow(missing_docs)] // TODO: document before 1.0
41
42pub mod keys;
43pub mod scalar;
44pub mod shamir;
45pub mod sign;
46
47#[cfg(test)]
48mod props;
49
50pub use keys::*;
51pub use shamir::*;
52pub use sign::*;
53
54/// Algorithm identifier for FROST-P256.
55pub const ALGORITHM: &str = "FROST-P256";
56
57/// Re-export for convenience.
58pub use p256;
59
60#[cfg(test)]
61mod integration_tests {
62    use super::*;
63
64    #[test]
65    fn full_threshold_signing_lifecycle() {
66        // 1. Trusted dealer generates a keypair and splits into 5 shares, T=3.
67        let keypair = keys::generate_keypair();
68        let shares = shamir::split_secret(&keypair.secret_scalar, 3, 5);
69
70        // 2. Any 3 shares can reconstruct the secret.
71        let subset: Vec<&shamir::Share> = shares.iter().take(3).collect();
72        let reconstructed = shamir::recover_secret(&subset).expect("recover");
73        assert_eq!(reconstructed, keypair.secret_scalar);
74
75        // 3. Sign with the keypair.
76        let message = b"hello, threshold world";
77        let signature = sign::sign_message(&keypair, message).expect("sign");
78
79        // 4. Verify under the public key using standard p256::ecdsa.
80        use p256::ecdsa::{Signature, signature::Verifier};
81        let verifying = keypair.to_verifying_key();
82        let sig = Signature::from_der(&signature.der_bytes).expect("parse sig");
83        verifying.verify(message, &sig).expect("verify");
84    }
85
86    #[test]
87    fn insufficient_shares_fail() {
88        let keypair = keys::generate_keypair();
89        let shares = shamir::split_secret(&keypair.secret_scalar, 3, 5);
90        let subset: Vec<&shamir::Share> = shares.iter().take(2).collect();
91        let result = shamir::recover_secret(&subset);
92        // With 2 shares when 3 needed, recovery should fail (return wrong value or error).
93        // Real Shamir: recovery is undefined for insufficient shares.
94        // We accept either an error or a wrong (non-matching) result.
95        if let Ok(r) = result {
96            assert_ne!(r, keypair.secret_scalar, "should not match with <T shares")
97        }
98    }
99
100    #[test]
101    fn different_share_subsets_recover_same_secret() {
102        let keypair = keys::generate_keypair();
103        let shares = shamir::split_secret(&keypair.secret_scalar, 3, 5);
104
105        let subset_a: Vec<&shamir::Share> = vec![&shares[0], &shares[1], &shares[2]];
106        let subset_b: Vec<&shamir::Share> = vec![&shares[1], &shares[3], &shares[4]];
107
108        let recovered_a = shamir::recover_secret(&subset_a).expect("recover A");
109        let recovered_b = shamir::recover_secret(&subset_b).expect("recover B");
110
111        assert_eq!(recovered_a, recovered_b);
112        assert_eq!(recovered_a, keypair.secret_scalar);
113    }
114}