1use git2::Repository;
10use ring::rand::SystemRandom;
11use ring::signature::{Ed25519KeyPair, KeyPair};
12
13use auths_crypto::CurveType;
14
15use crate::storage::registry::backend::{RegistryBackend, RegistryError};
16use auths_crypto::Pkcs8Der;
17
18pub fn sign_with_pkcs8_for_init(
21 curve: CurveType,
22 pkcs8: &Pkcs8Der,
23 message: &[u8],
24) -> Result<Vec<u8>, InceptionError> {
25 sign_with_pkcs8(curve, pkcs8, message)
26}
27
28fn sign_with_pkcs8(
29 curve: CurveType,
30 pkcs8: &Pkcs8Der,
31 message: &[u8],
32) -> Result<Vec<u8>, InceptionError> {
33 let parsed = auths_crypto::parse_key_material(pkcs8.as_ref())
36 .map_err(|e| InceptionError::KeyGeneration(format!("pkcs8 parse: {e}")))?;
37 if parsed.seed.curve() != curve {
38 return Err(InceptionError::KeyGeneration(format!(
39 "pkcs8 curve mismatch: expected {curve}, got {}",
40 parsed.seed.curve()
41 )));
42 }
43 auths_crypto::typed_sign(&parsed.seed, message)
44 .map_err(|e| InceptionError::KeyGeneration(format!("sign: {e}")))
45}
46
47pub struct GeneratedKeypair {
49 pub pkcs8: Pkcs8Der,
51 pub public_key: Vec<u8>,
53 pub cesr_encoded: String,
55}
56
57impl GeneratedKeypair {
58 pub fn verkey(&self) -> auths_keri::KeriPublicKey {
63 #[allow(clippy::expect_used)]
64 auths_keri::KeriPublicKey::parse(&self.cesr_encoded)
66 .expect("self-generated keypair has a valid CESR-encoded public key")
67 }
68}
69
70pub fn generate_keypair_for_init(curve: CurveType) -> Result<GeneratedKeypair, InceptionError> {
76 let mut out = generate_keypairs_for_init(&[curve])?;
77 Ok(out.remove(0))
80}
81
82pub fn generate_keypairs_for_init(
100 curves: &[CurveType],
101) -> Result<Vec<GeneratedKeypair>, InceptionError> {
102 if curves.is_empty() {
103 return Err(InceptionError::KeyGeneration(
104 "generate_keypairs_for_init requires at least one curve".to_string(),
105 ));
106 }
107 curves.iter().map(|c| generate_keypair(*c)).collect()
108}
109
110fn generate_keypair(curve: CurveType) -> Result<GeneratedKeypair, InceptionError> {
112 match curve {
113 CurveType::Ed25519 => {
114 let rng = SystemRandom::new();
115 let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng)
116 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
117 let keypair = Ed25519KeyPair::from_pkcs8(pkcs8_doc.as_ref())
118 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
119 let public_key = keypair.public_key().as_ref().to_vec();
120 let cesr_encoded =
121 auths_keri::KeriPublicKey::from_verkey_bytes(&public_key, CurveType::Ed25519)
122 .and_then(|k| k.to_qb64())
123 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
124 Ok(GeneratedKeypair {
125 pkcs8: Pkcs8Der::new(pkcs8_doc.as_ref().to_vec()),
126 public_key,
127 cesr_encoded,
128 })
129 }
130 CurveType::P256 => {
131 use p256::ecdsa::SigningKey;
132 use p256::elliptic_curve::rand_core::OsRng;
133 use p256::pkcs8::EncodePrivateKey;
134
135 let signing_key = SigningKey::random(&mut OsRng);
136 let verifying_key = p256::ecdsa::VerifyingKey::from(&signing_key);
137
138 let compressed = verifying_key.to_encoded_point(true);
140 let public_key = compressed.as_bytes().to_vec();
141
142 let cesr_encoded =
144 auths_keri::KeriPublicKey::from_verkey_bytes(&public_key, CurveType::P256)
145 .and_then(|k| k.to_qb64())
146 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
147
148 let pkcs8_doc = signing_key
150 .to_pkcs8_der()
151 .map_err(|e| InceptionError::KeyGeneration(format!("P-256 PKCS8: {e}")))?;
152 let pkcs8 = Pkcs8Der::new(pkcs8_doc.as_bytes().to_vec());
153
154 Ok(GeneratedKeypair {
155 pkcs8,
156 public_key,
157 cesr_encoded,
158 })
159 }
160 }
161}
162
163use auths_core::crypto::said::compute_next_commitment;
164
165use super::event::{CesrKey, KeriSequence, Threshold, VersionString};
166use super::types::{Prefix, Said};
167use super::{Event, GitKel, IcpEvent, KelError, ValidationError, finalize_icp_event};
168use crate::witness_config::WitnessConfig;
169
170#[derive(Debug, thiserror::Error)]
172#[non_exhaustive]
173pub enum InceptionError {
174 #[error("Key generation failed: {0}")]
175 KeyGeneration(String),
176
177 #[error("KEL error: {0}")]
178 Kel(#[from] KelError),
179
180 #[error("Storage error: {0}")]
181 Storage(RegistryError),
182
183 #[error("Validation error: {0}")]
184 Validation(#[from] ValidationError),
185
186 #[error("Serialization error: {0}")]
187 Serialization(String),
188
189 #[error("Invalid threshold {threshold} for key_count={key_count}: {reason}")]
190 InvalidThreshold {
191 threshold: String,
192 key_count: usize,
193 reason: String,
194 },
195}
196
197pub(crate) fn validate_threshold_for_key_count(
207 threshold: &Threshold,
208 key_count: usize,
209) -> Result<(), InceptionError> {
210 let label = || match threshold {
211 Threshold::Simple(n) => format!("Simple({n})"),
212 Threshold::Weighted(clauses) => format!("Weighted({clauses:?})"),
213 };
214 match threshold {
215 Threshold::Simple(n) => {
216 if (*n as usize) > key_count {
217 return Err(InceptionError::InvalidThreshold {
218 threshold: label(),
219 key_count,
220 reason: format!("threshold {n} exceeds key count {key_count}"),
221 });
222 }
223 if *n == 0 && key_count > 0 {
224 return Err(InceptionError::InvalidThreshold {
225 threshold: label(),
226 key_count,
227 reason: "threshold 0 with non-empty key list is unsatisfiable".to_string(),
228 });
229 }
230 }
231 Threshold::Weighted(clauses) => {
232 if clauses.is_empty() {
233 return Err(InceptionError::InvalidThreshold {
234 threshold: label(),
235 key_count,
236 reason: "weighted threshold with no clauses is unsatisfiable".to_string(),
237 });
238 }
239 for (i, clause) in clauses.iter().enumerate() {
240 if clause.len() != key_count {
241 return Err(InceptionError::InvalidThreshold {
242 threshold: label(),
243 key_count,
244 reason: format!(
245 "clause {i} has {} weights for {key_count} keys",
246 clause.len()
247 ),
248 });
249 }
250 let refs: Vec<&auths_keri::Fraction> = clause.iter().collect();
252 if !auths_keri::Fraction::sum_meets_one(&refs) {
253 return Err(InceptionError::InvalidThreshold {
254 threshold: label(),
255 key_count,
256 reason: format!(
257 "clause {i} sum < 1 even with all keys signing (unsatisfiable)"
258 ),
259 });
260 }
261 }
262 }
263 }
264 Ok(())
265}
266
267impl auths_core::error::AuthsErrorInfo for InceptionError {
268 fn error_code(&self) -> &'static str {
269 match self {
270 Self::KeyGeneration(_) => "AUTHS-E4901",
271 Self::Kel(_) => "AUTHS-E4902",
272 Self::Storage(_) => "AUTHS-E4903",
273 Self::Validation(_) => "AUTHS-E4904",
274 Self::Serialization(_) => "AUTHS-E4905",
275 Self::InvalidThreshold { .. } => "AUTHS-E4906",
276 }
277 }
278
279 fn suggestion(&self) -> Option<&'static str> {
280 match self {
281 Self::KeyGeneration(_) => None,
282 Self::Kel(_) => Some("Check the KEL state; a KEL may already exist for this prefix"),
283 Self::Storage(_) => Some("Check storage backend connectivity"),
284 Self::Validation(_) => None,
285 Self::Serialization(_) => None,
286 Self::InvalidThreshold { .. } => Some(
287 "Ensure the threshold count does not exceed the number of keys, and that weighted clauses have one weight per key summing to at least 1",
288 ),
289 }
290 }
291}
292
293pub struct InceptionResult {
295 pub prefix: Prefix,
297
298 pub current_keypair_pkcs8: Pkcs8Der,
300
301 pub next_keypair_pkcs8: Pkcs8Der,
303
304 pub current_public_key: Vec<u8>,
306
307 pub next_public_key: Vec<u8>,
309}
310
311impl std::fmt::Debug for InceptionResult {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 f.debug_struct("InceptionResult")
314 .field("prefix", &self.prefix)
315 .field("current_keypair_pkcs8", &self.current_keypair_pkcs8)
316 .field("next_keypair_pkcs8", &self.next_keypair_pkcs8)
317 .field("current_public_key", &self.current_public_key)
318 .field("next_public_key", &self.next_public_key)
319 .finish()
320 }
321}
322
323impl InceptionResult {
324 pub fn did(&self) -> String {
326 format!("did:keri:{}", self.prefix.as_str())
327 }
328}
329
330pub struct MultiKeyInceptionResult {
337 pub prefix: Prefix,
338 pub current_keypairs_pkcs8: Vec<Pkcs8Der>,
339 pub next_keypairs_pkcs8: Vec<Pkcs8Der>,
340 pub current_public_keys: Vec<Vec<u8>>,
341 pub next_public_keys: Vec<Vec<u8>>,
342}
343
344impl std::fmt::Debug for MultiKeyInceptionResult {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 f.debug_struct("MultiKeyInceptionResult")
347 .field("prefix", &self.prefix)
348 .field("device_count", &self.current_public_keys.len())
349 .finish()
350 }
351}
352
353impl MultiKeyInceptionResult {
354 pub fn did(&self) -> String {
355 format!("did:keri:{}", self.prefix.as_str())
356 }
357}
358
359pub fn create_keri_identity_multi(
379 repo: &Repository,
380 witness_config: Option<&WitnessConfig>,
381 now: chrono::DateTime<chrono::Utc>,
382 curves: &[CurveType],
383 kt: Threshold,
384 nt: Threshold,
385) -> Result<MultiKeyInceptionResult, InceptionError> {
386 if curves.is_empty() {
387 return Err(InceptionError::KeyGeneration(
388 "create_keri_identity_multi requires at least one curve".to_string(),
389 ));
390 }
391 validate_threshold_for_key_count(&kt, curves.len())?;
392 validate_threshold_for_key_count(&nt, curves.len())?;
393
394 let current_kps = generate_keypairs_for_init(curves)?;
395 let next_kps = generate_keypairs_for_init(curves)?;
396
397 let k: Vec<CesrKey> = current_kps
398 .iter()
399 .map(|kp| CesrKey::new_unchecked(kp.cesr_encoded.clone()))
400 .collect();
401 let n: Vec<Said> = next_kps
402 .iter()
403 .map(|kp| compute_next_commitment(&kp.verkey()))
404 .collect();
405
406 let (bt, b) = match witness_config {
407 Some(cfg) if cfg.is_enabled() => (
408 Threshold::Simple(cfg.threshold as u64),
409 cfg.aids().cloned().collect(),
410 ),
411 _ => (Threshold::Simple(0), vec![]),
412 };
413
414 let icp = IcpEvent {
415 v: VersionString::placeholder(),
416 d: Said::default(),
417 i: Prefix::default(),
418 s: KeriSequence::new(0),
419 kt,
420 k,
421 nt,
422 n,
423 bt,
424 b,
425 c: vec![],
426 a: vec![],
427 };
428
429 let finalized = finalize_icp_event(icp)?;
430 let prefix = finalized.i.clone();
431
432 let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
436 let _sig_bytes = sign_with_pkcs8(curves[0], ¤t_kps[0].pkcs8, &canonical)?;
437
438 let kel = GitKel::new(repo, prefix.as_str());
439 kel.create(&finalized, now)?;
440
441 Ok(MultiKeyInceptionResult {
442 prefix,
443 current_keypairs_pkcs8: current_kps.iter().map(|kp| kp.pkcs8.clone()).collect(),
444 next_keypairs_pkcs8: next_kps.iter().map(|kp| kp.pkcs8.clone()).collect(),
445 current_public_keys: current_kps.into_iter().map(|kp| kp.public_key).collect(),
446 next_public_keys: next_kps.into_iter().map(|kp| kp.public_key).collect(),
447 })
448}
449
450pub fn create_keri_identity(
467 repo: &Repository,
468 witness_config: Option<&WitnessConfig>,
469 now: chrono::DateTime<chrono::Utc>,
470) -> Result<InceptionResult, InceptionError> {
471 create_keri_identity_with_curve(repo, witness_config, now, CurveType::P256)
472}
473
474pub fn create_keri_identity_with_curve(
482 repo: &Repository,
483 witness_config: Option<&WitnessConfig>,
484 now: chrono::DateTime<chrono::Utc>,
485 curve: CurveType,
486) -> Result<InceptionResult, InceptionError> {
487 let current = generate_keypair(curve)?;
489 let next = generate_keypair(curve)?;
490
491 let current_pub_encoded = current.cesr_encoded.clone();
492
493 let next_commitment = compute_next_commitment(&next.verkey());
496
497 let (bt, b) = match witness_config {
499 Some(cfg) if cfg.is_enabled() => (
500 Threshold::Simple(cfg.threshold as u64),
501 cfg.aids().cloned().collect(),
502 ),
503 _ => (Threshold::Simple(0), vec![]),
504 };
505
506 let icp = IcpEvent {
508 v: VersionString::placeholder(),
509 d: Said::default(),
510 i: Prefix::default(),
511 s: KeriSequence::new(0),
512 kt: Threshold::Simple(1),
513 k: vec![CesrKey::new_unchecked(current_pub_encoded)],
514 nt: Threshold::Simple(1),
515 n: vec![next_commitment],
516 bt,
517 b,
518 c: vec![],
519 a: vec![],
520 };
521
522 let finalized = finalize_icp_event(icp)?;
524 let prefix = finalized.i.clone();
525
526 let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
528 let _sig_bytes = sign_with_pkcs8(curve, ¤t.pkcs8, &canonical)?;
529
530 let kel = GitKel::new(repo, prefix.as_str());
532 kel.create(&finalized, now)?;
533
534 #[cfg(feature = "witness-client")]
536 if let Some(config) = witness_config
537 && config.is_enabled()
538 {
539 let canonical_for_witness = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
540 super::witness_integration::collect_and_store_receipts(
541 repo.path().parent().unwrap_or(repo.path()),
542 &prefix,
543 &finalized.d,
544 &canonical_for_witness,
545 config,
546 now,
547 )
548 .map_err(|e| InceptionError::Serialization(e.to_string()))?;
549 }
550
551 Ok(InceptionResult {
552 prefix,
553 current_keypair_pkcs8: current.pkcs8,
554 next_keypair_pkcs8: next.pkcs8,
555 current_public_key: current.public_key,
556 next_public_key: next.public_key,
557 })
558}
559
560pub fn create_keri_identity_with_backend(
572 backend: &impl RegistryBackend,
573 _witness_config: Option<&WitnessConfig>,
574) -> Result<InceptionResult, InceptionError> {
575 let rng = SystemRandom::new();
576
577 let current_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
578 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
579 let current_keypair = Ed25519KeyPair::from_pkcs8(current_pkcs8.as_ref())
580 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
581
582 let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
583 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
584 let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref())
585 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
586
587 let current_pub_encoded =
588 auths_keri::KeriPublicKey::ed25519(current_keypair.public_key().as_ref())
589 .and_then(|k| k.to_qb64())
590 .map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?;
591 let next_commitment = compute_next_commitment(
592 &auths_keri::KeriPublicKey::ed25519(next_keypair.public_key().as_ref())
593 .map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?,
594 );
595
596 let icp = IcpEvent {
597 v: VersionString::placeholder(),
598 d: Said::default(),
599 i: Prefix::default(),
600 s: KeriSequence::new(0),
601 kt: Threshold::Simple(1),
602 k: vec![CesrKey::new_unchecked(current_pub_encoded)],
603 nt: Threshold::Simple(1),
604 n: vec![next_commitment],
605 bt: Threshold::Simple(0),
606 b: vec![],
607 c: vec![],
608 a: vec![],
609 };
610
611 let finalized = finalize_icp_event(icp)?;
612 let prefix = finalized.i.clone();
613
614 let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
615 let sig = current_keypair.sign(&canonical);
616 let attachment = auths_keri::serialize_attachment(&[auths_keri::IndexedSignature {
617 index: 0,
618 prior_index: None,
619 sig: sig.as_ref().to_vec(),
620 }])
621 .map_err(|e| InceptionError::Serialization(e.to_string()))?;
622
623 backend
624 .append_signed_event(&prefix, &Event::Icp(finalized), &attachment)
625 .map_err(InceptionError::Storage)?;
626
627 Ok(InceptionResult {
628 prefix,
629 current_keypair_pkcs8: Pkcs8Der::new(current_pkcs8.as_ref()),
630 next_keypair_pkcs8: Pkcs8Der::new(next_pkcs8.as_ref()),
631 current_public_key: current_keypair.public_key().as_ref().to_vec(),
632 next_public_key: next_keypair.public_key().as_ref().to_vec(),
633 })
634}
635
636pub fn create_keri_identity_from_key(
648 repo: &Repository,
649 current_pkcs8_bytes: &[u8],
650 witness_config: Option<&WitnessConfig>,
651 now: chrono::DateTime<chrono::Utc>,
652) -> Result<InceptionResult, InceptionError> {
653 let rng = SystemRandom::new();
654
655 let current_keypair = Ed25519KeyPair::from_pkcs8(current_pkcs8_bytes)
657 .map_err(|e| InceptionError::KeyGeneration(format!("invalid PKCS8 key: {e}")))?;
658
659 let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
661 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
662 let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref())
663 .map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
664
665 let current_pub_encoded =
666 auths_keri::KeriPublicKey::ed25519(current_keypair.public_key().as_ref())
667 .and_then(|k| k.to_qb64())
668 .map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?;
669 let next_commitment = compute_next_commitment(
670 &auths_keri::KeriPublicKey::ed25519(next_keypair.public_key().as_ref())
671 .map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?,
672 );
673
674 let (bt, b) = match witness_config {
675 Some(cfg) if cfg.is_enabled() => (
676 Threshold::Simple(cfg.threshold as u64),
677 cfg.aids().cloned().collect(),
678 ),
679 _ => (Threshold::Simple(0), vec![]),
680 };
681
682 let icp = IcpEvent {
683 v: VersionString::placeholder(),
684 d: Said::default(),
685 i: Prefix::default(),
686 s: KeriSequence::new(0),
687 kt: Threshold::Simple(1),
688 k: vec![CesrKey::new_unchecked(current_pub_encoded)],
689 nt: Threshold::Simple(1),
690 n: vec![next_commitment],
691 bt,
692 b,
693 c: vec![],
694 a: vec![],
695 };
696
697 let finalized = finalize_icp_event(icp)?;
698 let prefix = finalized.i.clone();
699
700 let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
701 let _sig = current_keypair.sign(&canonical);
702
703 let kel = GitKel::new(repo, prefix.as_str());
704 kel.create(&finalized, now)?;
705
706 Ok(InceptionResult {
707 prefix,
708 current_keypair_pkcs8: Pkcs8Der::new(current_pkcs8_bytes),
709 next_keypair_pkcs8: Pkcs8Der::new(next_pkcs8.as_ref()),
710 current_public_key: current_keypair.public_key().as_ref().to_vec(),
711 next_public_key: next_keypair.public_key().as_ref().to_vec(),
712 })
713}
714
715pub fn prefix_to_did(prefix: &str) -> String {
717 format!("did:keri:{}", prefix)
718}
719
720pub fn did_to_prefix(did: &str) -> Option<&str> {
724 did.strip_prefix("did:keri:")
725}
726
727#[cfg(test)]
728#[allow(clippy::disallowed_methods)]
729mod tests {
730 use super::*;
731 use crate::keri::{Event, validate_kel};
732 use tempfile::TempDir;
733
734 fn setup_repo() -> (TempDir, Repository) {
735 let dir = TempDir::new().unwrap();
736 let repo = Repository::init(dir.path()).unwrap();
737
738 let mut config = repo.config().unwrap();
740 config.set_str("user.name", "Test User").unwrap();
741 config.set_str("user.email", "test@example.com").unwrap();
742
743 (dir, repo)
744 }
745
746 #[test]
747 fn create_identity_returns_valid_result() {
748 let (_dir, repo) = setup_repo();
749
750 let result = create_keri_identity_with_curve(
751 &repo,
752 None,
753 chrono::Utc::now(),
754 auths_crypto::CurveType::Ed25519,
755 )
756 .unwrap();
757
758 assert!(result.prefix.as_str().starts_with('E'));
760
761 assert!(!result.current_keypair_pkcs8.is_empty());
763 assert!(!result.next_keypair_pkcs8.is_empty());
764 assert_eq!(result.current_public_key.len(), 32);
765 assert_eq!(result.next_public_key.len(), 32);
766
767 assert!(result.did().starts_with("did:keri:E"));
769 }
770
771 #[test]
772 fn create_identity_stores_kel() {
773 let (_dir, repo) = setup_repo();
774
775 let result = create_keri_identity_with_curve(
776 &repo,
777 None,
778 chrono::Utc::now(),
779 auths_crypto::CurveType::Ed25519,
780 )
781 .unwrap();
782
783 let kel = GitKel::new(&repo, result.prefix.as_str());
785 assert!(kel.exists());
786
787 let events = kel.get_events().unwrap();
788 assert_eq!(events.len(), 1);
789 assert!(events[0].is_inception());
790 }
791
792 #[test]
793 fn inception_event_is_valid() {
794 let (_dir, repo) = setup_repo();
795
796 let result = create_keri_identity_with_curve(
797 &repo,
798 None,
799 chrono::Utc::now(),
800 auths_crypto::CurveType::Ed25519,
801 )
802 .unwrap();
803 let kel = GitKel::new(&repo, result.prefix.as_str());
804 let events = kel.get_events().unwrap();
805
806 let state = validate_kel(&events).unwrap();
808 assert_eq!(state.prefix, result.prefix);
809 assert_eq!(state.sequence, 0);
810 assert!(!state.is_abandoned);
811 }
812
813 fn witness_cfg(threshold: usize, aids: &[&str]) -> WitnessConfig {
814 use crate::witness_config::{WitnessPolicy, WitnessRef};
815 WitnessConfig {
816 witnesses: aids
817 .iter()
818 .enumerate()
819 .map(|(i, aid)| WitnessRef {
820 url: format!("http://w{i}:3333").parse().unwrap(),
821 aid: Prefix::new_unchecked((*aid).to_string()),
822 operator_info: None,
823 })
824 .collect(),
825 threshold,
826 timeout_ms: 5000,
827 policy: WitnessPolicy::Warn,
831 ..Default::default()
832 }
833 }
834
835 #[test]
836 fn icp_designates_configured_witnesses_and_replays_backers() {
837 let (_dir, repo) = setup_repo();
838 let aids = [
839 "BWitnessOne00000000000000000000000000000000",
840 "BWitnessTwo00000000000000000000000000000000",
841 ];
842 let cfg = witness_cfg(2, &aids);
843 let result = create_keri_identity_with_curve(
844 &repo,
845 Some(&cfg),
846 chrono::Utc::now(),
847 auths_crypto::CurveType::Ed25519,
848 )
849 .unwrap();
850 let kel = GitKel::new(&repo, result.prefix.as_str());
851 let state = validate_kel(&kel.get_events().unwrap()).unwrap();
853 let designated: Vec<&str> = state.backers.iter().map(|p| p.as_str()).collect();
854 assert_eq!(designated, aids);
855 assert_eq!(state.backer_threshold, Threshold::Simple(2));
856 }
857
858 #[test]
859 fn icp_zero_witness_path_is_valid() {
860 let (_dir, repo) = setup_repo();
861 let result = create_keri_identity_with_curve(
862 &repo,
863 None,
864 chrono::Utc::now(),
865 auths_crypto::CurveType::Ed25519,
866 )
867 .unwrap();
868 let kel = GitKel::new(&repo, result.prefix.as_str());
869 let state = validate_kel(&kel.get_events().unwrap()).unwrap();
870 assert!(state.backers.is_empty());
871 assert_eq!(state.backer_threshold, Threshold::Simple(0));
872 }
873
874 #[test]
875 fn inception_event_has_correct_structure() {
876 let (_dir, repo) = setup_repo();
877
878 let result = create_keri_identity_with_curve(
879 &repo,
880 None,
881 chrono::Utc::now(),
882 auths_crypto::CurveType::Ed25519,
883 )
884 .unwrap();
885 let kel = GitKel::new(&repo, result.prefix.as_str());
886 let events = kel.get_events().unwrap();
887
888 if let Event::Icp(icp) = &events[0] {
889 assert_eq!(icp.v.kind, "JSON");
891
892 assert_eq!(icp.d.as_str(), icp.i.as_str());
894 assert_eq!(icp.d.as_str(), result.prefix.as_str());
895
896 assert_eq!(icp.s, KeriSequence::new(0));
898
899 assert_eq!(icp.k.len(), 1);
901 assert!(icp.k[0].as_str().starts_with('D')); assert_eq!(icp.n.len(), 1);
905 assert!(icp.n[0].as_str().starts_with('E')); assert_eq!(icp.bt, Threshold::Simple(0));
909 assert!(icp.b.is_empty());
910 } else {
911 panic!("Expected inception event");
912 }
913 }
914
915 #[test]
916 fn next_key_commitment_is_correct() {
917 let (_dir, repo) = setup_repo();
918
919 let result = create_keri_identity_with_curve(
920 &repo,
921 None,
922 chrono::Utc::now(),
923 auths_crypto::CurveType::Ed25519,
924 )
925 .unwrap();
926 let kel = GitKel::new(&repo, result.prefix.as_str());
927 let events = kel.get_events().unwrap();
928
929 if let Event::Icp(icp) = &events[0] {
930 let expected_commitment = compute_next_commitment(
932 &auths_keri::KeriPublicKey::ed25519(&result.next_public_key)
933 .expect("ed25519 verkey is 32 bytes"),
934 );
935 assert_eq!(icp.n[0], expected_commitment);
936 } else {
937 panic!("Expected inception event");
938 }
939 }
940
941 #[test]
942 fn prefix_to_did_works() {
943 assert_eq!(prefix_to_did("ETest123"), "did:keri:ETest123");
944 }
945
946 #[test]
947 fn did_to_prefix_works() {
948 assert_eq!(did_to_prefix("did:keri:ETest123"), Some("ETest123"));
949 assert_eq!(did_to_prefix("did:key:z6Mk..."), None);
950 }
951
952 #[test]
953 fn multiple_identities_have_different_prefixes() {
954 let (_dir, repo) = setup_repo();
955
956 let result1 = create_keri_identity_with_curve(
957 &repo,
958 None,
959 chrono::Utc::now(),
960 auths_crypto::CurveType::Ed25519,
961 )
962 .unwrap();
963
964 let (_dir2, repo2) = setup_repo();
966 let result2 = create_keri_identity_with_curve(
967 &repo2,
968 None,
969 chrono::Utc::now(),
970 auths_crypto::CurveType::Ed25519,
971 )
972 .unwrap();
973
974 assert_ne!(result1.prefix, result2.prefix);
976 }
977
978 #[test]
979 fn generate_keypairs_for_init_rejects_empty_slice() {
980 let res = generate_keypairs_for_init(&[]);
981 match res {
982 Ok(_) => panic!("expected error on empty slice"),
983 Err(InceptionError::KeyGeneration(msg)) => assert!(msg.contains("at least one")),
984 Err(other) => panic!("expected KeyGeneration error, got {other:?}"),
985 }
986 }
987
988 #[test]
989 fn generate_keypairs_for_init_single_curve_matches_legacy() {
990 let kps = generate_keypairs_for_init(&[auths_crypto::CurveType::P256]).unwrap();
993 assert_eq!(kps.len(), 1);
994 assert!(kps[0].cesr_encoded.starts_with("1AAJ"));
995 assert_eq!(kps[0].public_key.len(), 33);
996
997 let legacy = generate_keypair_for_init(auths_crypto::CurveType::P256).unwrap();
998 assert_eq!(legacy.public_key.len(), 33);
999 assert!(legacy.cesr_encoded.starts_with("1AAJ"));
1000 }
1001
1002 #[test]
1003 fn generate_keypairs_for_init_mixed_curves_distinct_keys() {
1004 use auths_crypto::CurveType::{Ed25519, P256};
1005 let kps = generate_keypairs_for_init(&[P256, P256, Ed25519]).unwrap();
1006 assert_eq!(kps.len(), 3);
1007
1008 assert_eq!(kps[0].public_key.len(), 33);
1011 assert!(kps[0].cesr_encoded.starts_with("1AAJ"));
1012 assert_eq!(kps[1].public_key.len(), 33);
1013 assert!(kps[1].cesr_encoded.starts_with("1AAJ"));
1014 assert_eq!(kps[2].public_key.len(), 32);
1015 assert!(kps[2].cesr_encoded.starts_with('D'));
1016
1017 assert_ne!(kps[0].public_key, kps[1].public_key);
1019 assert_ne!(kps[0].public_key, kps[2].public_key);
1020 assert_ne!(kps[1].public_key, kps[2].public_key);
1021 }
1022}