1use std::collections::{BTreeMap, BTreeSet};
4
5use exo_core::Hash256;
6use frost_ristretto255 as frost;
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 GenesisCeremonyConfig, Result, RootError, RootKeyPackage, RootPublicKeyPackage,
11 dkg::{deserialize_frost, serialize_frost},
12};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct RootSignature {
17 pub signature: Vec<u8>,
19 pub signer_ids: Vec<u16>,
21}
22
23fn frost_error(error: frost::Error) -> RootError {
24 RootError::Frost {
25 detail: error.to_string(),
26 }
27}
28
29fn frost_sign_share(
30 signing_package: &frost::SigningPackage,
31 nonces: &frost::round1::SigningNonces,
32 key_package: &frost::keys::KeyPackage,
33) -> Result<frost::round2::SignatureShare> {
34 frost::round2::sign(signing_package, nonces, key_package).map_err(frost_error)
35}
36
37fn frost_aggregate_signature(
38 signing_package: &frost::SigningPackage,
39 signature_shares: &BTreeMap<frost::Identifier, frost::round2::SignatureShare>,
40 public: &frost::keys::PublicKeyPackage,
41) -> Result<frost::Signature> {
42 frost::aggregate(signing_package, signature_shares, public).map_err(frost_error)
43}
44
45pub fn threshold_sign<R>(
47 config: &GenesisCeremonyConfig,
48 public_key_package: &RootPublicKeyPackage,
49 shares: BTreeMap<u16, RootKeyPackage>,
50 message: &[u8],
51 rng: &mut R,
52) -> Result<RootSignature>
53where
54 R: frost::rand_core::RngCore + frost::rand_core::CryptoRng,
55{
56 config.validate()?;
57 if shares.len() < usize::from(config.threshold) {
58 let supplied = shares
59 .keys()
60 .take(usize::from(u16::MAX))
61 .fold(0u16, |count, _| count.saturating_add(1));
62 let error = RootError::ThresholdNotMet {
63 required: config.threshold,
64 supplied,
65 };
66 return Err(error);
67 }
68 let signer_ids: Vec<u16> = shares.keys().copied().collect();
69 validate_root_signer_ids(config, signer_ids.as_slice())?;
70
71 let public = deserialize_frost(public_key_package.public_key_package.as_slice())?;
72
73 let mut key_packages = BTreeMap::new();
74 let mut signing_nonces = BTreeMap::new();
75 let mut signing_commitments = BTreeMap::new();
76
77 for (identifier, share) in shares {
78 if share.frost_identifier != identifier {
79 let share_id = share.frost_identifier;
80 let detail = format!("share id {share_id} mismatches key {identifier}");
81 return Err(RootError::Frost { detail });
82 }
83 let frost_identifier = crate::dkg::frost_identifier(identifier)?;
84 let key_package: frost::keys::KeyPackage = deserialize_frost(share.key_package.as_slice())?;
85 if *key_package.identifier() != frost_identifier {
86 return Err(RootError::Frost {
87 detail: "deserialized key package identifier mismatch".to_owned(),
88 });
89 }
90 let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), rng);
91 signing_nonces.insert(frost_identifier, nonces);
92 signing_commitments.insert(frost_identifier, commitments);
93 key_packages.insert(frost_identifier, key_package);
94 }
95
96 let signing_package = frost::SigningPackage::new(signing_commitments, message);
97 let mut signature_shares = BTreeMap::new();
98 for (identifier, key_package) in &key_packages {
99 let nonces = &signing_nonces[identifier];
100 let share = frost_sign_share(&signing_package, nonces, key_package)?;
101 signature_shares.insert(*identifier, share);
102 }
103
104 let sig = frost_aggregate_signature(&signing_package, &signature_shares, &public)?;
105 let signature = serialize_frost(&sig)?;
106
107 verify_root_signature(&public_key_package.root_public_key, message, &signature)?;
108
109 Ok(RootSignature {
110 signature,
111 signer_ids,
112 })
113}
114
115pub fn verify_root_signature(
117 root_public_key: &[u8],
118 message: &[u8],
119 signature: &[u8],
120) -> Result<()> {
121 let verifying_key: frost::VerifyingKey = deserialize_frost(root_public_key)?;
122 let signature: frost::Signature = deserialize_frost(signature)?;
123 verifying_key
124 .verify(message, &signature)
125 .map_err(signature_rejected)
126}
127
128fn signature_rejected(error: frost::Error) -> RootError {
129 RootError::SignatureRejected {
130 reason: error.to_string(),
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151pub struct RootSigningCommitment {
152 pub frost_identifier: u16,
154 pub commitments: Vec<u8>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166pub struct RootSigningNonces {
167 pub frost_identifier: u16,
169 pub ceremony_id: String,
172 pub artifact_hash: Hash256,
178 pub commitment_hash: Hash256,
182 pub nonces: Vec<u8>,
184}
185
186fn commitment_hash(commitment_bytes: &[u8]) -> Hash256 {
189 Hash256::digest(commitment_bytes)
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195pub struct RootSigningPackage {
196 pub signing_package: Vec<u8>,
198 pub signer_ids: Vec<u16>,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205pub struct RootSignatureShareOutput {
206 pub frost_identifier: u16,
208 pub signature_share: Vec<u8>,
210}
211
212fn ensure_rostered(config: &GenesisCeremonyConfig, identifier: u16, role: &str) -> Result<()> {
213 if config.certifier_by_identifier(identifier).is_none() {
214 return Err(RootError::InvalidConfig {
215 reason: format!("{role} {identifier} is not rostered"),
216 });
217 }
218 Ok(())
219}
220
221pub(crate) fn validate_root_signer_ids(
222 config: &GenesisCeremonyConfig,
223 signer_ids: &[u16],
224) -> Result<()> {
225 if signer_ids.len() != usize::from(config.threshold) {
226 return Err(RootError::InvalidConfig {
227 reason: format!(
228 "signing selection must contain exactly {} signers",
229 config.threshold
230 ),
231 });
232 }
233 let mut selection = BTreeSet::new();
234 for identifier in signer_ids {
235 if config.certifier_by_identifier(*identifier).is_none() {
236 return Err(RootError::InvalidConfig {
237 reason: format!("signer {identifier} is not rostered"),
238 });
239 }
240 if !selection.insert(*identifier) {
241 return Err(RootError::InvalidConfig {
242 reason: format!("duplicate signer {identifier}"),
243 });
244 }
245 }
246 config.validate_signing_selection(&selection)
247}
248
249pub fn sign_commit<R>(
257 config: &GenesisCeremonyConfig,
258 key_package: &RootKeyPackage,
259 artifact: &[u8],
260 rng: &mut R,
261) -> Result<(RootSigningCommitment, RootSigningNonces)>
262where
263 R: frost::rand_core::RngCore + frost::rand_core::CryptoRng,
264{
265 config.validate()?;
266 ensure_rostered(config, key_package.frost_identifier, "signer")?;
267 let parsed: frost::keys::KeyPackage = deserialize_frost(key_package.key_package.as_slice())?;
268 let (nonces, commitments) = frost::round1::commit(parsed.signing_share(), rng);
269 let commitment_bytes = serialize_frost(&commitments)?;
270 let commitment = RootSigningCommitment {
271 frost_identifier: key_package.frost_identifier,
272 commitments: commitment_bytes.clone(),
273 };
274 let signing_nonces = RootSigningNonces {
275 frost_identifier: key_package.frost_identifier,
276 ceremony_id: config.ceremony_id.clone(),
277 artifact_hash: Hash256::digest(artifact),
278 commitment_hash: commitment_hash(commitment_bytes.as_slice()),
279 nonces: serialize_frost(&nonces)?,
280 };
281 Ok((commitment, signing_nonces))
282}
283
284pub fn build_signing_package(
287 config: &GenesisCeremonyConfig,
288 commitments: BTreeMap<u16, Vec<u8>>,
289 message: &[u8],
290) -> Result<RootSigningPackage> {
291 config.validate()?;
292 if commitments.len() < usize::from(config.threshold) {
293 return Err(RootError::ThresholdNotMet {
294 required: config.threshold,
295 supplied: u16::try_from(commitments.len()).unwrap_or(u16::MAX),
296 });
297 }
298 let signer_ids: Vec<u16> = commitments.keys().copied().collect();
302 validate_root_signer_ids(config, signer_ids.as_slice())?;
303 let mut parsed = BTreeMap::new();
304 for (identifier, bytes) in commitments {
305 ensure_rostered(config, identifier, "signer")?;
306 let frost_id = crate::dkg::frost_identifier(identifier)?;
307 let commitment: frost::round1::SigningCommitments = deserialize_frost(bytes.as_slice())?;
308 parsed.insert(frost_id, commitment);
309 }
310 let signing_package = frost::SigningPackage::new(parsed, message);
311 Ok(RootSigningPackage {
312 signing_package: serialize_frost(&signing_package)?,
313 signer_ids,
314 })
315}
316
317pub fn sign_share(
330 config: &GenesisCeremonyConfig,
331 key_package: &RootKeyPackage,
332 nonces: &RootSigningNonces,
333 signing_package: &RootSigningPackage,
334 message: &[u8],
335) -> Result<RootSignatureShareOutput> {
336 config.validate()?;
337 ensure_rostered(config, key_package.frost_identifier, "signer")?;
338 if nonces.frost_identifier != key_package.frost_identifier {
339 let nonce_id = nonces.frost_identifier;
340 let key_id = key_package.frost_identifier;
341 return Err(RootError::Frost {
342 detail: format!("nonces id {nonce_id} mismatches key {key_id}"),
343 });
344 }
345 if nonces.ceremony_id != config.ceremony_id {
346 return Err(RootError::Frost {
347 detail: "nonces were generated for a different ceremony".to_owned(),
348 });
349 }
350 if Hash256::digest(message) != nonces.artifact_hash {
353 return Err(RootError::Frost {
354 detail: "nonces are bound to a different artifact than the message".to_owned(),
355 });
356 }
357 validate_root_signer_ids(config, signing_package.signer_ids.as_slice())?;
362 let parsed_key: frost::keys::KeyPackage =
363 deserialize_frost(key_package.key_package.as_slice())?;
364 let parsed_nonces: frost::round1::SigningNonces = deserialize_frost(nonces.nonces.as_slice())?;
365 let parsed_package: frost::SigningPackage =
366 deserialize_frost(signing_package.signing_package.as_slice())?;
367 let mut commitments = BTreeMap::new();
371 for identifier in &signing_package.signer_ids {
372 let signer_frost_id = crate::dkg::frost_identifier(*identifier)?;
373 let commitment = parsed_package
374 .signing_commitment(&signer_frost_id)
375 .ok_or_else(|| RootError::Frost {
376 detail: format!("signing package is missing commitment for signer {identifier}"),
377 })?;
378 commitments.insert(signer_frost_id, commitment);
379 }
380 let frost_id = crate::dkg::frost_identifier(key_package.frost_identifier)?;
382 let package_commitment = commitments.get(&frost_id).ok_or_else(|| RootError::Frost {
383 detail: "signer is not in the signing package's signer set".to_owned(),
384 })?;
385 if commitment_hash(serialize_frost(package_commitment)?.as_slice()) != nonces.commitment_hash {
386 return Err(RootError::Frost {
387 detail: "nonces are not bound to this signing package's commitment".to_owned(),
388 });
389 }
390 let rebuilt_package = frost::SigningPackage::new(commitments, message);
391 let share =
392 frost::round2::sign(&rebuilt_package, &parsed_nonces, &parsed_key).map_err(frost_error)?;
393 Ok(RootSignatureShareOutput {
394 frost_identifier: key_package.frost_identifier,
395 signature_share: serialize_frost(&share)?,
396 })
397}
398
399pub fn aggregate_signature(
402 config: &GenesisCeremonyConfig,
403 public_key_package: &RootPublicKeyPackage,
404 signing_package: &[u8],
405 shares: BTreeMap<u16, Vec<u8>>,
406 message: &[u8],
407) -> Result<RootSignature> {
408 config.validate()?;
409 if shares.len() < usize::from(config.threshold) {
410 return Err(RootError::ThresholdNotMet {
411 required: config.threshold,
412 supplied: u16::try_from(shares.len()).unwrap_or(u16::MAX),
413 });
414 }
415 let signer_ids: Vec<u16> = shares.keys().copied().collect();
416 validate_root_signer_ids(config, signer_ids.as_slice())?;
417 let public = deserialize_frost(public_key_package.public_key_package.as_slice())?;
418 let parsed_package: frost::SigningPackage = deserialize_frost(signing_package)?;
419 let mut parsed_shares = BTreeMap::new();
420 for (identifier, bytes) in shares {
421 ensure_rostered(config, identifier, "signer")?;
422 let frost_id = crate::dkg::frost_identifier(identifier)?;
423 let share: frost::round2::SignatureShare = deserialize_frost(bytes.as_slice())?;
424 parsed_shares.insert(frost_id, share);
425 }
426 let aggregated =
427 frost::aggregate(&parsed_package, &parsed_shares, &public).map_err(frost_error)?;
428 let signature = serialize_frost(&aggregated)?;
429 verify_root_signature(&public_key_package.root_public_key, message, &signature)?;
430 Ok(RootSignature {
431 signature,
432 signer_ids,
433 })
434}
435
436#[cfg(test)]
437mod tests {
438 use exo_core::{Did, Hash256, PublicKey, Timestamp};
439 use rand::{SeedableRng, rngs::StdRng};
440
441 use super::*;
442 use crate::CertifierContact;
443
444 fn test_config() -> GenesisCeremonyConfig {
445 let certifiers = (1..=13)
446 .map(|identifier| {
447 let byte = u8::try_from(identifier).expect("identifier fits");
448 CertifierContact {
449 did: Did::new(&format!("did:exo:signing-unit-{identifier:02}"))
450 .expect("valid did"),
451 frost_identifier: identifier,
452 signing_public_key: PublicKey::from_bytes([byte; 32]),
453 transport_public_key: [byte; 32],
454 }
455 })
456 .collect();
457 GenesisCeremonyConfig {
458 ceremony_id: "unit-root".into(),
459 network_id: "unit-net".into(),
460 repo_commit: "d8927686a34bdc28ba36d53938f665685d2c4c04".into(),
461 constitution_hash: Hash256::digest(b"constitution"),
462 threshold: 7,
463 max_signers: 13,
464 created_at: Timestamp::new(1, 0),
465 certifiers,
466 signing_set: (1..=7).collect(),
467 }
468 }
469
470 #[test]
471 fn frost_error_conversion_is_diagnostic() {
472 let error = frost::Identifier::try_from(0).expect_err("zero identifier");
473 let converted = frost_error(error);
474 assert!(converted.to_string().contains("frost operation failed"));
475 }
476
477 #[test]
478 fn signature_rejection_conversion_is_diagnostic() {
479 let error = frost::Identifier::try_from(0).expect_err("zero identifier");
480 let converted = signature_rejected(error);
481 assert!(
482 converted
483 .to_string()
484 .contains("signature verification failed")
485 );
486 }
487
488 #[test]
489 fn threshold_sign_rejects_too_few_shares_before_public_deserialization() {
490 let config = test_config();
491 let public_key_package = RootPublicKeyPackage {
492 public_key_package: b"not a public package".to_vec(),
493 root_public_key: b"not a verifying key".to_vec(),
494 verifying_shares: BTreeMap::new(),
495 };
496 let mut rng = StdRng::seed_from_u64(7);
497 let error = threshold_sign(
498 &config,
499 &public_key_package,
500 BTreeMap::new(),
501 b"root artifact",
502 &mut rng,
503 )
504 .expect_err("empty signer set");
505 assert_eq!(
506 error,
507 RootError::ThresholdNotMet {
508 required: 7,
509 supplied: 0
510 }
511 );
512 }
513
514 #[test]
515 fn signer_id_validation_rejects_wrong_count_and_duplicates() {
516 let config = test_config();
517 let wrong_count = validate_root_signer_ids(&config, &[1, 2, 3, 4, 5, 6])
518 .expect_err("signer set must match threshold count");
519 assert!(wrong_count.to_string().contains("exactly 7 signers"));
520
521 let duplicate = validate_root_signer_ids(&config, &[1, 2, 3, 4, 5, 6, 6])
522 .expect_err("signer set must reject duplicates");
523 assert!(duplicate.to_string().contains("duplicate signer 6"));
524 }
525
526 #[test]
527 fn threshold_sign_covers_success_and_share_mismatch_paths() {
528 let config = test_config();
529 let mut rng = StdRng::seed_from_u64(71);
530 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
531 let selected: BTreeMap<u16, _> = dkg
532 .key_packages
533 .iter()
534 .take(7)
535 .map(|(identifier, share)| (*identifier, share.clone()))
536 .collect();
537 let message = b"unit root signing artifact";
538 let signature = threshold_sign(
539 &config,
540 &dkg.public_key_package,
541 selected.clone(),
542 message,
543 &mut rng,
544 )
545 .expect("signature");
546
547 verify_root_signature(
548 &dkg.public_key_package.root_public_key,
549 message,
550 &signature.signature,
551 )
552 .expect("signature verifies");
553
554 let mut mismatched = selected;
555 let mut share = mismatched.remove(&1).expect("share one");
556 share.frost_identifier = 2;
557 mismatched.insert(1, share);
558 let error = threshold_sign(
559 &config,
560 &dkg.public_key_package,
561 mismatched,
562 message,
563 &mut rng,
564 )
565 .expect_err("share identifier mismatch");
566 assert!(error.to_string().contains("mismatches key 1"));
567 }
568
569 #[test]
570 fn threshold_sign_rejects_non_declared_signer_set_before_public_deserialization() {
571 let config = test_config();
572 let public_key_package = RootPublicKeyPackage {
573 public_key_package: b"not a public package".to_vec(),
574 root_public_key: b"not a verifying key".to_vec(),
575 verifying_shares: BTreeMap::new(),
576 };
577 let shares = [1, 2, 3, 4, 5, 6, 9]
578 .into_iter()
579 .map(|identifier| {
580 (
581 identifier,
582 RootKeyPackage {
583 frost_identifier: identifier,
584 key_package: Vec::new(),
585 },
586 )
587 })
588 .collect();
589 let mut rng = StdRng::seed_from_u64(72);
590 let error = threshold_sign(&config, &public_key_package, shares, b"artifact", &mut rng)
591 .expect_err("non-declared signer set");
592 assert!(
593 error.to_string().contains("predeclared signing_set"),
594 "expected signer-set rejection before public package decoding, got: {error}"
595 );
596 }
597
598 #[test]
599 fn distributed_signing_matches_one_shot_and_verifies() {
600 let config = test_config();
601 let mut rng = StdRng::seed_from_u64(99);
602 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
603 let message = b"distributed root signing artifact";
604
605 let signers: Vec<(u16, _)> = dkg
607 .key_packages
608 .iter()
609 .take(7)
610 .map(|(id, kp)| (*id, kp.clone()))
611 .collect();
612
613 let mut commitments = BTreeMap::new();
615 let mut nonces = BTreeMap::new();
616 for (id, kp) in &signers {
617 let (commitment, signer_nonces) =
618 sign_commit(&config, kp, message, &mut rng).expect("commit");
619 nonces.insert(*id, signer_nonces);
620 commitments.insert(*id, commitment.commitments);
621 }
622
623 let package = build_signing_package(&config, commitments, message).expect("package");
625 assert_eq!(package.signer_ids.len(), 7);
626
627 let mut shares = BTreeMap::new();
630 for (id, kp) in &signers {
631 let share = sign_share(&config, kp, &nonces[id], &package, message).expect("share");
632 shares.insert(*id, share.signature_share);
633 }
634
635 let signature = aggregate_signature(
638 &config,
639 &dkg.public_key_package,
640 &package.signing_package,
641 shares,
642 message,
643 )
644 .expect("aggregate");
645 verify_root_signature(
646 &dkg.public_key_package.root_public_key,
647 message,
648 &signature.signature,
649 )
650 .expect("distributed signature verifies");
651 assert_eq!(signature.signer_ids.len(), 7);
652 }
653
654 #[test]
655 fn build_signing_package_rejects_sub_threshold_commitment_set() {
656 let config = test_config();
657 let mut rng = StdRng::seed_from_u64(100);
658 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
659 let mut commitments = BTreeMap::new();
660 for (id, kp) in dkg.key_packages.iter().take(3) {
661 let (commitment, _nonces) = sign_commit(&config, kp, b"msg", &mut rng).expect("commit");
662 commitments.insert(*id, commitment.commitments);
663 }
664 let error = build_signing_package(&config, commitments, b"msg")
665 .expect_err("sub-threshold commitments");
666 assert!(matches!(
667 error,
668 RootError::ThresholdNotMet {
669 required: 7,
670 supplied: 3
671 }
672 ));
673 }
674
675 #[test]
676 fn distributed_signing_rejects_unrostered_and_sub_threshold() {
677 let config = test_config();
678 let mut rng = StdRng::seed_from_u64(123);
679 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
680
681 let mut stranger = dkg.key_packages[&1].clone();
683 stranger.frost_identifier = 99;
684 assert!(matches!(
685 sign_commit(&config, &stranger, b"msg", &mut rng).expect_err("unrostered commit"),
686 RootError::InvalidConfig { .. }
687 ));
688
689 let mut commitments = BTreeMap::new();
691 for (id, kp) in dkg.key_packages.iter().take(7) {
692 let (commitment, _nonces) = sign_commit(&config, kp, b"msg", &mut rng).expect("commit");
693 commitments.insert(*id, commitment.commitments);
694 }
695 let mut unrostered = commitments.clone();
696 let stolen = unrostered.remove(&1).expect("commitment one");
697 unrostered.insert(99, stolen);
698 assert!(matches!(
699 build_signing_package(&config, unrostered, b"msg").expect_err("unrostered commitment"),
700 RootError::InvalidConfig { .. }
701 ));
702
703 let package = build_signing_package(&config, commitments, b"msg").expect("package");
705 let (_commitment, commit_nonces) =
706 sign_commit(&config, &dkg.key_packages[&1], b"msg", &mut rng).expect("commit");
707 assert!(matches!(
708 sign_share(&config, &stranger, &commit_nonces, &package, b"msg")
709 .expect_err("unrostered share"),
710 RootError::InvalidConfig { .. }
711 ));
712
713 assert!(matches!(
715 aggregate_signature(
716 &config,
717 &dkg.public_key_package,
718 &package.signing_package,
719 BTreeMap::new(),
720 b"msg",
721 )
722 .expect_err("sub-threshold aggregate"),
723 RootError::ThresholdNotMet { required: 7, .. }
724 ));
725 }
726
727 #[test]
728 fn aggregate_signature_rejects_non_declared_signer_set_before_deserialization() {
729 let config = test_config();
730 let public_key_package = RootPublicKeyPackage {
731 public_key_package: b"not a public package".to_vec(),
732 root_public_key: b"not a verifying key".to_vec(),
733 verifying_shares: BTreeMap::new(),
734 };
735 let shares = [1, 2, 3, 4, 5, 6, 9]
736 .into_iter()
737 .map(|identifier| (identifier, vec![u8::try_from(identifier).expect("id fits")]))
738 .collect();
739 let error = aggregate_signature(
740 &config,
741 &public_key_package,
742 b"not a signing package",
743 shares,
744 b"artifact",
745 )
746 .expect_err("non-declared aggregate signer set");
747 assert!(
748 error.to_string().contains("predeclared signing_set"),
749 "expected signer-set rejection before signature artifacts decode, got: {error}"
750 );
751 }
752
753 #[test]
754 fn sign_share_rejects_nonces_bound_to_a_different_signer() {
755 let config = test_config();
759 let key_package = RootKeyPackage {
760 frost_identifier: 1,
761 key_package: Vec::new(),
762 };
763 let foreign_nonces = RootSigningNonces {
764 frost_identifier: 2,
765 ceremony_id: config.ceremony_id.clone(),
766 artifact_hash: Hash256::digest(b"artifact"),
767 commitment_hash: Hash256::digest(b"unrelated commitment"),
768 nonces: Vec::new(),
769 };
770 let empty_package = RootSigningPackage {
771 signing_package: Vec::new(),
772 signer_ids: Vec::new(),
773 };
774 let error = sign_share(
775 &config,
776 &key_package,
777 &foreign_nonces,
778 &empty_package,
779 b"artifact",
780 )
781 .expect_err("nonces bound to a different signer must be rejected");
782 assert!(error.to_string().contains("mismatches key 1"));
783 }
784
785 #[test]
786 fn sign_share_rejects_nonces_from_a_different_ceremony() {
787 let config = test_config();
790 let key_package = RootKeyPackage {
791 frost_identifier: 1,
792 key_package: Vec::new(),
793 };
794 let foreign_nonces = RootSigningNonces {
795 frost_identifier: 1,
796 ceremony_id: "some-other-ceremony".to_owned(),
797 artifact_hash: Hash256::digest(b"artifact"),
798 commitment_hash: Hash256::digest(b"unrelated commitment"),
799 nonces: Vec::new(),
800 };
801 let empty_package = RootSigningPackage {
802 signing_package: Vec::new(),
803 signer_ids: Vec::new(),
804 };
805 let error = sign_share(
806 &config,
807 &key_package,
808 &foreign_nonces,
809 &empty_package,
810 b"artifact",
811 )
812 .expect_err("nonces from a different ceremony must be rejected");
813 assert!(error.to_string().contains("different ceremony"));
814 }
815
816 #[test]
817 fn sign_share_rejects_nonces_bound_to_a_different_signing_instance() {
818 let config = test_config();
822 let mut rng = StdRng::seed_from_u64(404);
823 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
824 let mut commitments = BTreeMap::new();
825 for (id, kp) in dkg.key_packages.iter().take(7) {
826 let (commitment, _nonces) =
827 sign_commit(&config, kp, b"artifact", &mut rng).expect("commit");
828 commitments.insert(*id, commitment.commitments);
829 }
830 let package = build_signing_package(&config, commitments, b"artifact").expect("package");
831 let (_other_commitment, stale_nonces) =
834 sign_commit(&config, &dkg.key_packages[&1], b"artifact", &mut rng).expect("commit");
835 let error = sign_share(
836 &config,
837 &dkg.key_packages[&1],
838 &stale_nonces,
839 &package,
840 b"artifact",
841 )
842 .expect_err("nonces not bound to the package commitment must be rejected");
843 assert!(
844 error
845 .to_string()
846 .contains("not bound to this signing package")
847 );
848 }
849
850 #[test]
851 fn sign_share_rejects_signer_absent_from_signing_package() {
852 let config = test_config();
855 let mut rng = StdRng::seed_from_u64(606);
856 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
857 let mut commitments = BTreeMap::new();
858 for (id, kp) in dkg.key_packages.iter().take(7) {
859 let (commitment, _nonces) =
860 sign_commit(&config, kp, b"artifact", &mut rng).expect("commit");
861 commitments.insert(*id, commitment.commitments);
862 }
863 let package = build_signing_package(&config, commitments, b"artifact").expect("package");
864 let (_commitment8, nonces8) =
865 sign_commit(&config, &dkg.key_packages[&8], b"artifact", &mut rng).expect("commit 8");
866 let error = sign_share(
867 &config,
868 &dkg.key_packages[&8],
869 &nonces8,
870 &package,
871 b"artifact",
872 )
873 .expect_err("a signer absent from the set must be rejected");
874 assert!(
875 error
876 .to_string()
877 .contains("signer is not in the signing package's signer set")
878 );
879 }
880
881 #[test]
882 fn sign_share_rejects_non_canonical_signer_set() {
883 let config = test_config();
889 let mut rng = StdRng::seed_from_u64(909);
890 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
891 let mut commitments = BTreeMap::new();
892 let mut signer_one_nonces = None;
893 for (id, kp) in dkg.key_packages.iter().take(7) {
894 let (commitment, nonces) =
895 sign_commit(&config, kp, b"artifact", &mut rng).expect("commit");
896 if *id == 1 {
897 signer_one_nonces = Some(nonces);
898 }
899 commitments.insert(*id, commitment.commitments);
900 }
901 let mut package =
902 build_signing_package(&config, commitments, b"artifact").expect("package");
903 package.signer_ids = vec![1, 2, 3, 4, 5, 6, 9];
905 let error = sign_share(
906 &config,
907 &dkg.key_packages[&1],
908 &signer_one_nonces.expect("signer one nonces"),
909 &package,
910 b"artifact",
911 )
912 .expect_err("a non-canonical signer set must be rejected");
913 assert!(
914 error.to_string().contains("predeclared signing_set"),
915 "expected a signing-selection-policy rejection, got: {error}"
916 );
917 }
918
919 #[test]
920 fn sign_share_refuses_to_sign_a_second_different_message_with_one_nonce_set() {
921 let config = test_config();
926 let mut rng = StdRng::seed_from_u64(707);
927 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
928 let artifact = b"the one true root artifact";
929 let mut commitments = BTreeMap::new();
930 let mut nonces = BTreeMap::new();
931 for (id, kp) in dkg.key_packages.iter().take(7) {
932 let (commitment, signer_nonces) =
933 sign_commit(&config, kp, artifact, &mut rng).expect("commit");
934 commitments.insert(*id, commitment.commitments);
935 nonces.insert(*id, signer_nonces);
936 }
937 let package = build_signing_package(&config, commitments, artifact).expect("package");
938
939 sign_share(
941 &config,
942 &dkg.key_packages[&1],
943 &nonces[&1],
944 &package,
945 artifact,
946 )
947 .expect("first share over the bound artifact");
948
949 let error = sign_share(
951 &config,
952 &dkg.key_packages[&1],
953 &nonces[&1],
954 &package,
955 b"a different message the coordinator wants signed",
956 )
957 .expect_err("a second, different message under the same nonces must be rejected");
958 assert!(error.to_string().contains("bound to a different artifact"));
959 }
960
961 #[test]
962 fn sign_share_rejects_a_signing_package_missing_a_declared_signer() {
963 let config = test_config();
966 let mut rng = StdRng::seed_from_u64(808);
967 let dkg = crate::run_complete_dkg(&config, &mut rng).expect("dkg");
968 let mut commitments = BTreeMap::new();
969 let mut signer_one_nonces = None;
970 for (id, kp) in dkg.key_packages.iter().take(7) {
971 let (commitment, nonces) =
972 sign_commit(&config, kp, b"artifact", &mut rng).expect("commit");
973 if *id == 1 {
974 signer_one_nonces = Some(nonces);
975 }
976 commitments.insert(*id, commitment.commitments);
977 }
978 let mut package =
979 build_signing_package(&config, commitments, b"artifact").expect("package");
980 let parsed_package: frost::SigningPackage =
981 deserialize_frost(package.signing_package.as_slice()).expect("parsed package");
982 let mut embedded_commitments = BTreeMap::new();
983 for id in 1..=6u16 {
984 let signer_frost_id = crate::dkg::frost_identifier(id).expect("frost id");
985 let commitment = parsed_package
986 .signing_commitment(&signer_frost_id)
987 .expect("commitment");
988 embedded_commitments.insert(signer_frost_id, commitment);
989 }
990 let (commitment8, _nonces8) =
991 sign_commit(&config, &dkg.key_packages[&8], b"artifact", &mut rng).expect("commit 8");
992 embedded_commitments.insert(
993 crate::dkg::frost_identifier(8).expect("frost id 8"),
994 deserialize_frost(commitment8.commitments.as_slice()).expect("commitment 8"),
995 );
996 let malformed_package = frost::SigningPackage::new(embedded_commitments, b"artifact");
997 package.signing_package = serialize_frost(&malformed_package).expect("serialize package");
998 package.signer_ids = vec![1, 2, 3, 4, 5, 6, 7];
999 let error = sign_share(
1000 &config,
1001 &dkg.key_packages[&1],
1002 &signer_one_nonces.expect("signer one nonces"),
1003 &package,
1004 b"artifact",
1005 )
1006 .expect_err("a signing package missing a declared signer must be rejected");
1007 assert!(
1008 error
1009 .to_string()
1010 .contains("missing commitment for signer 7")
1011 );
1012 }
1013}