Skip to main content

confium_tc_frost_ed25519/
inprocess.rs

1//! In-process synchronous driver for FROST-ed25519 DKG and signing.
2//!
3//! Thin wrapper over [`confium_tc::inprocess`] that names the
4//! FROST-ed25519 schemes and pulls the joint public key out of the
5//! first share. Completes the threshold-ECDSA / threshold-EdDSA
6//! matrix — CMP20 and GG18 cover P-256; this covers Ed25519.
7
8use confium_tc::Result;
9use confium_tc::inprocess as driver;
10
11/// Outcome of a FROST-ed25519 DKG.
12#[derive(Debug, Clone)]
13pub struct KeygenOutput {
14    /// Per-party share blobs.
15    pub shares: Vec<Vec<u8>>,
16    /// Joint Ed25519 public key (32 bytes).
17    pub public_key: Vec<u8>,
18}
19
20/// Run FROST-ed25519 DKG for `party_count` parties at threshold
21/// `threshold`.
22pub fn keygen(threshold: u32, party_count: usize) -> Result<Vec<Vec<u8>>> {
23    driver::run_dkg(crate::DKG_SCHEME, threshold, party_count)
24}
25
26/// Threshold-sign `message` with FROST-ed25519. Returns a 64-byte
27/// Ed25519 signature.
28pub fn sign(share_blobs: &[Vec<u8>], threshold: u32, message: &[u8]) -> Result<Vec<u8>> {
29    driver::run_sign(crate::SIGN_SCHEME, share_blobs, threshold, message)
30}
31
32/// Sign N messages against the same joint key.
33pub fn sign_batch(
34    share_blobs: &[Vec<u8>],
35    threshold: u32,
36    messages: &[&[u8]],
37) -> Result<Vec<Vec<u8>>> {
38    let mut out = Vec::with_capacity(messages.len());
39    for msg in messages {
40        out.push(sign(share_blobs, threshold, msg)?);
41    }
42    Ok(out)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn dkg_and_sign_round_trip() {
51        let shares = keygen(2, 3).expect("dkg");
52        assert_eq!(shares.len(), 3);
53        let sig = sign(&shares[..2], 2, b"hello ed25519").expect("sign");
54        assert!(!sig.is_empty());
55    }
56
57    #[test]
58    fn below_threshold_errors() {
59        let shares = keygen(3, 5).expect("dkg");
60        assert!(sign(&shares[..2], 3, b"msg").is_err());
61    }
62
63    #[test]
64    fn sign_batch_produces_one_sig_per_message() {
65        let shares = keygen(2, 3).expect("dkg");
66        let messages: Vec<&[u8]> = vec![b"msg-a", b"msg-b", b"msg-c", b"msg-d"];
67        let sigs = sign_batch(&shares[..2], 2, &messages).expect("batch sign");
68        assert_eq!(sigs.len(), 4);
69        for s in &sigs {
70            assert!(!s.is_empty());
71        }
72    }
73
74    #[test]
75    fn sign_batch_empty_messages_returns_empty() {
76        let shares = keygen(2, 3).expect("dkg");
77        let sigs = sign_batch(&shares[..2], 2, &[]).expect("empty batch");
78        assert!(sigs.is_empty());
79    }
80
81    #[test]
82    fn sign_batch_propagates_signing_error() {
83        let shares = keygen(3, 5).expect("dkg");
84        // threshold is 3 but we only supply 2 shares — each sign call must fail.
85        let messages: Vec<&[u8]> = vec![b"a", b"b"];
86        let err = sign_batch(&shares[..2], 3, &messages);
87        assert!(err.is_err());
88    }
89}