arcanum-threshold 0.1.0

Threshold cryptography for the Arcanum cryptographic engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! Distributed Key Generation (DKG) for FROST.
//!
//! DKG allows a group of participants to collaboratively generate
//! a shared group key without any trusted dealer knowing the complete
//! secret key.
//!
//! ## Protocol Overview
//!
//! 1. **Round 1**: Each participant generates a secret polynomial and
//!    broadcasts commitments to the polynomial coefficients.
//!
//! 2. **Round 2**: Each participant computes and sends encrypted shares
//!    to all other participants.
//!
//! 3. **Verification**: Each participant verifies received shares against
//!    the commitments and computes their final signing share.

use crate::error::{Result, ThresholdError};
use crate::frost::PublicKeyPackage;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[cfg(feature = "frost-ed25519")]
use frost_ed25519 as frost;

#[cfg(all(feature = "frost-secp256k1", not(feature = "frost-ed25519")))]
use frost_secp256k1 as frost;

/// A participant in the DKG protocol.
pub struct DkgParticipant {
    /// Participant identifier as u16 (1-based).
    id: u16,
    /// Participant identifier for FROST.
    identifier: frost::Identifier,
    /// Threshold for the scheme.
    threshold: u16,
    /// Total number of participants.
    total: u16,
    /// Round 1 secret package (kept private).
    round1_secret: Option<frost::keys::dkg::round1::SecretPackage>,
    /// Round 2 secret package (kept private).
    round2_secret: Option<frost::keys::dkg::round2::SecretPackage>,
}

impl DkgParticipant {
    /// Create a new DKG participant.
    ///
    /// # Arguments
    /// * `id` - Participant identifier (1-based, must be <= total)
    /// * `threshold` - Minimum number of participants needed to sign
    /// * `total` - Total number of participants
    pub fn new(id: u16, threshold: u16, total: u16) -> Result<Self> {
        if id == 0 || id > total {
            return Err(ThresholdError::InvalidParticipant(id));
        }
        if threshold == 0 || threshold > total {
            return Err(ThresholdError::InvalidThreshold {
                threshold: threshold as usize,
                total: total as usize,
            });
        }

        let identifier = frost::Identifier::try_from(id)
            .map_err(|e| ThresholdError::InternalError(e.to_string()))?;

        Ok(Self {
            id,
            identifier,
            threshold,
            total,
            round1_secret: None,
            round2_secret: None,
        })
    }

    /// Get participant identifier as u16.
    pub fn id(&self) -> u16 {
        self.id
    }

    /// Execute Round 1 of DKG.
    ///
    /// Generates a secret polynomial and returns the public package
    /// to broadcast to all participants.
    pub fn round1(&mut self) -> Result<DkgRound1> {
        let mut rng = rand::rngs::OsRng;

        let (secret_package, public_package) =
            frost::keys::dkg::part1(self.identifier, self.total, self.threshold, &mut rng)
                .map_err(|e| ThresholdError::DkgError(e.to_string()))?;

        self.round1_secret = Some(secret_package);

        DkgRound1::from_frost(self.identifier, &public_package)
    }

    /// Execute Round 2 of DKG.
    ///
    /// Receives Round 1 packages from all participants and generates
    /// encrypted shares for each participant.
    ///
    /// # Arguments
    /// * `round1_packages` - Round 1 packages from all participants (including self)
    pub fn round2(&mut self, round1_packages: &[DkgRound1]) -> Result<Vec<DkgRound2>> {
        let secret_package = self
            .round1_secret
            .take()
            .ok_or_else(|| ThresholdError::DkgError("Round 1 not executed".to_string()))?;

        // Convert to FROST format, excluding self's package
        let mut frost_packages = BTreeMap::new();
        for pkg in round1_packages {
            let (id, frost_pkg) = pkg.to_frost()?;
            // FROST expects packages from OTHER participants only
            if id != self.identifier {
                frost_packages.insert(id, frost_pkg);
            }
        }

        let (secret_package, round2_packages) =
            frost::keys::dkg::part2(secret_package, &frost_packages)
                .map_err(|e| ThresholdError::DkgError(e.to_string()))?;

        self.round2_secret = Some(secret_package);

        // Convert output packages
        round2_packages
            .into_iter()
            .map(|(recipient, pkg)| DkgRound2::from_frost(self.identifier, recipient, &pkg))
            .collect()
    }

    /// Finalize DKG and compute the signing share.
    ///
    /// # Arguments
    /// * `round1_packages` - Round 1 packages from all participants
    /// * `round2_packages` - Round 2 packages addressed to this participant
    pub fn finalize(
        &mut self,
        round1_packages: &[DkgRound1],
        round2_packages: &[DkgRound2],
    ) -> Result<(frost::keys::KeyPackage, PublicKeyPackage)> {
        let secret_package = self
            .round2_secret
            .take()
            .ok_or_else(|| ThresholdError::DkgError("Round 2 not executed".to_string()))?;

        // Convert round 1 packages, excluding self's package
        let mut frost_round1 = BTreeMap::new();
        for pkg in round1_packages {
            let (id, frost_pkg) = pkg.to_frost()?;
            // FROST expects packages from OTHER participants only
            if id != self.identifier {
                frost_round1.insert(id, frost_pkg);
            }
        }

        // Convert round 2 packages (only those for this participant)
        let mut frost_round2 = BTreeMap::new();
        for pkg in round2_packages {
            if pkg.recipient_id == self.identifier {
                let (sender, frost_pkg) = pkg.to_frost()?;
                frost_round2.insert(sender, frost_pkg);
            }
        }

        let (key_package, pubkey_package) =
            frost::keys::dkg::part3(&secret_package, &frost_round1, &frost_round2)
                .map_err(|e| ThresholdError::DkgError(e.to_string()))?;

        Ok((key_package, PublicKeyPackage::from_frost(pubkey_package)))
    }
}

impl std::fmt::Debug for DkgParticipant {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "DkgParticipant(id={}, threshold={}, total={})",
            self.id(),
            self.threshold,
            self.total
        )
    }
}

/// Round 1 DKG package (broadcast to all participants).
#[derive(Clone, Serialize, Deserialize)]
pub struct DkgRound1 {
    /// Sender identifier (serialized).
    sender_bytes: Vec<u8>,
    /// Serialized package.
    bytes: Vec<u8>,
}

impl DkgRound1 {
    fn from_frost(id: frost::Identifier, pkg: &frost::keys::dkg::round1::Package) -> Result<Self> {
        Ok(Self {
            sender_bytes: id.serialize(),
            bytes: pkg
                .serialize()
                .map_err(|e| ThresholdError::SerializationError(e.to_string()))?,
        })
    }

    fn to_frost(&self) -> Result<(frost::Identifier, frost::keys::dkg::round1::Package)> {
        let id = frost::Identifier::deserialize(&self.sender_bytes)
            .map_err(|e| ThresholdError::InternalError(e.to_string()))?;

        let pkg = frost::keys::dkg::round1::Package::deserialize(&self.bytes)
            .map_err(|e| ThresholdError::DkgError(format!("invalid round1 package: {}", e)))?;

        Ok((id, pkg))
    }
}

impl std::fmt::Debug for DkgRound1 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "DkgRound1({} bytes)", self.bytes.len())
    }
}

/// Round 2 DKG package (sent to specific recipient).
#[derive(Clone)]
pub struct DkgRound2 {
    /// Sender identifier.
    sender_bytes: Vec<u8>,
    /// Recipient identifier.
    recipient_id: frost::Identifier,
    /// Serialized package (encrypted for recipient).
    bytes: Vec<u8>,
}

impl DkgRound2 {
    fn from_frost(
        sender: frost::Identifier,
        recipient: frost::Identifier,
        pkg: &frost::keys::dkg::round2::Package,
    ) -> Result<Self> {
        Ok(Self {
            sender_bytes: sender.serialize(),
            recipient_id: recipient,
            bytes: pkg
                .serialize()
                .map_err(|e| ThresholdError::SerializationError(e.to_string()))?,
        })
    }

    fn to_frost(&self) -> Result<(frost::Identifier, frost::keys::dkg::round2::Package)> {
        let sender = frost::Identifier::deserialize(&self.sender_bytes)
            .map_err(|e| ThresholdError::InternalError(e.to_string()))?;

        let pkg = frost::keys::dkg::round2::Package::deserialize(&self.bytes)
            .map_err(|e| ThresholdError::DkgError(format!("invalid round2 package: {}", e)))?;

        Ok((sender, pkg))
    }
}

impl std::fmt::Debug for DkgRound2 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "DkgRound2({} bytes)", self.bytes.len())
    }
}

/// Run a complete DKG ceremony for all participants.
///
/// This is a helper for testing that simulates the full DKG protocol.
/// In production, participants would communicate over a network.
pub fn run_dkg(
    threshold: u16,
    total: u16,
) -> Result<Vec<(frost::keys::KeyPackage, PublicKeyPackage)>> {
    // Create participants
    let mut participants: Vec<DkgParticipant> = (1..=total)
        .map(|id| DkgParticipant::new(id, threshold, total))
        .collect::<Result<Vec<_>>>()?;

    // Round 1: Generate and collect all round1 packages
    let round1_packages: Vec<DkgRound1> = participants
        .iter_mut()
        .map(|p| p.round1())
        .collect::<Result<Vec<_>>>()?;

    // Round 2: Generate all round2 packages
    let mut all_round2_packages: Vec<DkgRound2> = Vec::new();
    for participant in &mut participants {
        let packages = participant.round2(&round1_packages)?;
        all_round2_packages.extend(packages);
    }

    // Finalize: Each participant computes their key package
    participants
        .iter_mut()
        .map(|p| p.finalize(&round1_packages, &all_round2_packages))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::frost::{FrostSigner, FrostVerifier, SigningPackage};

    #[test]
    fn test_dkg_basic() {
        let threshold = 2u16;
        let total = 3u16;

        let results = run_dkg(threshold, total).unwrap();
        assert_eq!(results.len(), total as usize);

        // All participants should have the same group verifying key
        let group_key = results[0].1.group_verifying_key().unwrap();
        for (_, pubkey_pkg) in &results[1..] {
            assert_eq!(
                group_key.as_bytes(),
                pubkey_pkg.group_verifying_key().unwrap().as_bytes()
            );
        }
    }

    #[test]
    fn test_dkg_then_sign() {
        let threshold = 2u16;
        let total = 3u16;
        let message = b"DKG test message";

        // Run DKG
        let results = run_dkg(threshold, total).unwrap();

        // Create signers from first `threshold` participants
        let signers: Vec<FrostSigner> = results
            .iter()
            .take(threshold as usize)
            .map(|(kp, _)| FrostSigner::new(kp.clone()))
            .collect();

        // Round 1: Collect commitments
        let mut all_nonces = Vec::new();
        let mut all_commitments = Vec::new();
        for signer in &signers {
            let (nonces, commitments) = signer.round1().unwrap();
            all_nonces.push(nonces);
            all_commitments.push(commitments);
        }

        // Create signing package
        let signing_package = SigningPackage::new(&all_commitments, message).unwrap();

        // Round 2: Generate signature shares
        let signature_shares: Vec<_> = signers
            .iter()
            .enumerate()
            .map(|(i, signer)| {
                signer
                    .round2(message, &all_nonces[i], &signing_package)
                    .unwrap()
            })
            .collect();

        // Aggregate and verify
        let (_, pubkey_package) = &results[0];
        let group_key = pubkey_package.group_verifying_key().unwrap();
        let verifier = FrostVerifier::new(&group_key).unwrap();

        let signature = verifier
            .aggregate(&signing_package, &signature_shares, pubkey_package)
            .unwrap();

        assert!(verifier.verify(message, &signature).unwrap());
    }

    #[test]
    fn test_dkg_3_of_5() {
        let threshold = 3u16;
        let total = 5u16;

        let results = run_dkg(threshold, total).unwrap();
        assert_eq!(results.len(), 5);

        // Verify group key consistency
        let group_key = results[0].1.group_verifying_key().unwrap();
        for (_, pkg) in &results {
            assert_eq!(
                group_key.as_bytes(),
                pkg.group_verifying_key().unwrap().as_bytes()
            );
        }
    }

    #[test]
    fn test_dkg_invalid_params() {
        // Threshold > total
        assert!(run_dkg(5, 3).is_err());

        // Zero threshold
        assert!(run_dkg(0, 3).is_err());

        // Invalid participant ID
        assert!(DkgParticipant::new(0, 2, 3).is_err());
        assert!(DkgParticipant::new(4, 2, 3).is_err());
    }
}