use git2::Repository;
use ring::rand::SystemRandom;
use ring::signature::{Ed25519KeyPair, KeyPair};
use auths_crypto::CurveType;
use crate::storage::registry::backend::{RegistryBackend, RegistryError};
use auths_crypto::Pkcs8Der;
pub fn sign_with_pkcs8_for_init(
curve: CurveType,
pkcs8: &Pkcs8Der,
message: &[u8],
) -> Result<Vec<u8>, InceptionError> {
sign_with_pkcs8(curve, pkcs8, message)
}
fn sign_with_pkcs8(
curve: CurveType,
pkcs8: &Pkcs8Der,
message: &[u8],
) -> Result<Vec<u8>, InceptionError> {
let parsed = auths_crypto::parse_key_material(pkcs8.as_ref())
.map_err(|e| InceptionError::KeyGeneration(format!("pkcs8 parse: {e}")))?;
if parsed.seed.curve() != curve {
return Err(InceptionError::KeyGeneration(format!(
"pkcs8 curve mismatch: expected {curve}, got {}",
parsed.seed.curve()
)));
}
auths_crypto::typed_sign(&parsed.seed, message)
.map_err(|e| InceptionError::KeyGeneration(format!("sign: {e}")))
}
pub struct GeneratedKeypair {
pub pkcs8: Pkcs8Der,
pub public_key: Vec<u8>,
pub cesr_encoded: String,
}
impl GeneratedKeypair {
pub fn verkey(&self) -> auths_keri::KeriPublicKey {
#[allow(clippy::expect_used)]
auths_keri::KeriPublicKey::parse(&self.cesr_encoded)
.expect("self-generated keypair has a valid CESR-encoded public key")
}
}
pub fn generate_keypair_for_init(curve: CurveType) -> Result<GeneratedKeypair, InceptionError> {
let mut out = generate_keypairs_for_init(&[curve])?;
Ok(out.remove(0))
}
pub fn generate_keypairs_for_init(
curves: &[CurveType],
) -> Result<Vec<GeneratedKeypair>, InceptionError> {
if curves.is_empty() {
return Err(InceptionError::KeyGeneration(
"generate_keypairs_for_init requires at least one curve".to_string(),
));
}
curves.iter().map(|c| generate_keypair(*c)).collect()
}
fn generate_keypair(curve: CurveType) -> Result<GeneratedKeypair, InceptionError> {
match curve {
CurveType::Ed25519 => {
let rng = SystemRandom::new();
let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng)
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let keypair = Ed25519KeyPair::from_pkcs8(pkcs8_doc.as_ref())
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let public_key = keypair.public_key().as_ref().to_vec();
let cesr_encoded =
auths_keri::KeriPublicKey::from_verkey_bytes(&public_key, CurveType::Ed25519)
.and_then(|k| k.to_qb64())
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
Ok(GeneratedKeypair {
pkcs8: Pkcs8Der::new(pkcs8_doc.as_ref().to_vec()),
public_key,
cesr_encoded,
})
}
CurveType::P256 => {
use p256::ecdsa::SigningKey;
use p256::elliptic_curve::rand_core::OsRng;
use p256::pkcs8::EncodePrivateKey;
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = p256::ecdsa::VerifyingKey::from(&signing_key);
let compressed = verifying_key.to_encoded_point(true);
let public_key = compressed.as_bytes().to_vec();
let cesr_encoded =
auths_keri::KeriPublicKey::from_verkey_bytes(&public_key, CurveType::P256)
.and_then(|k| k.to_qb64())
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let pkcs8_doc = signing_key
.to_pkcs8_der()
.map_err(|e| InceptionError::KeyGeneration(format!("P-256 PKCS8: {e}")))?;
let pkcs8 = Pkcs8Der::new(pkcs8_doc.as_bytes().to_vec());
Ok(GeneratedKeypair {
pkcs8,
public_key,
cesr_encoded,
})
}
}
}
use auths_core::crypto::said::compute_next_commitment;
use super::event::{CesrKey, KeriSequence, Threshold, VersionString};
use super::types::{Prefix, Said};
use super::{Event, GitKel, IcpEvent, KelError, ValidationError, finalize_icp_event};
use crate::witness_config::WitnessConfig;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum InceptionError {
#[error("Key generation failed: {0}")]
KeyGeneration(String),
#[error("KEL error: {0}")]
Kel(#[from] KelError),
#[error("Storage error: {0}")]
Storage(RegistryError),
#[error("Validation error: {0}")]
Validation(#[from] ValidationError),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Invalid threshold {threshold} for key_count={key_count}: {reason}")]
InvalidThreshold {
threshold: String,
key_count: usize,
reason: String,
},
}
pub(crate) fn validate_threshold_for_key_count(
threshold: &Threshold,
key_count: usize,
) -> Result<(), InceptionError> {
let label = || match threshold {
Threshold::Simple(n) => format!("Simple({n})"),
Threshold::Weighted(clauses) => format!("Weighted({clauses:?})"),
};
match threshold {
Threshold::Simple(n) => {
if (*n as usize) > key_count {
return Err(InceptionError::InvalidThreshold {
threshold: label(),
key_count,
reason: format!("threshold {n} exceeds key count {key_count}"),
});
}
if *n == 0 && key_count > 0 {
return Err(InceptionError::InvalidThreshold {
threshold: label(),
key_count,
reason: "threshold 0 with non-empty key list is unsatisfiable".to_string(),
});
}
}
Threshold::Weighted(clauses) => {
if clauses.is_empty() {
return Err(InceptionError::InvalidThreshold {
threshold: label(),
key_count,
reason: "weighted threshold with no clauses is unsatisfiable".to_string(),
});
}
for (i, clause) in clauses.iter().enumerate() {
if clause.len() != key_count {
return Err(InceptionError::InvalidThreshold {
threshold: label(),
key_count,
reason: format!(
"clause {i} has {} weights for {key_count} keys",
clause.len()
),
});
}
let refs: Vec<&auths_keri::Fraction> = clause.iter().collect();
if !auths_keri::Fraction::sum_meets_one(&refs) {
return Err(InceptionError::InvalidThreshold {
threshold: label(),
key_count,
reason: format!(
"clause {i} sum < 1 even with all keys signing (unsatisfiable)"
),
});
}
}
}
}
Ok(())
}
impl auths_core::error::AuthsErrorInfo for InceptionError {
fn error_code(&self) -> &'static str {
match self {
Self::KeyGeneration(_) => "AUTHS-E4901",
Self::Kel(_) => "AUTHS-E4902",
Self::Storage(_) => "AUTHS-E4903",
Self::Validation(_) => "AUTHS-E4904",
Self::Serialization(_) => "AUTHS-E4905",
Self::InvalidThreshold { .. } => "AUTHS-E4906",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::KeyGeneration(_) => None,
Self::Kel(_) => Some("Check the KEL state; a KEL may already exist for this prefix"),
Self::Storage(_) => Some("Check storage backend connectivity"),
Self::Validation(_) => None,
Self::Serialization(_) => None,
Self::InvalidThreshold { .. } => Some(
"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",
),
}
}
}
pub struct InceptionResult {
pub prefix: Prefix,
pub current_keypair_pkcs8: Pkcs8Der,
pub next_keypair_pkcs8: Pkcs8Der,
pub current_public_key: Vec<u8>,
pub next_public_key: Vec<u8>,
}
impl std::fmt::Debug for InceptionResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InceptionResult")
.field("prefix", &self.prefix)
.field("current_keypair_pkcs8", &self.current_keypair_pkcs8)
.field("next_keypair_pkcs8", &self.next_keypair_pkcs8)
.field("current_public_key", &self.current_public_key)
.field("next_public_key", &self.next_public_key)
.finish()
}
}
impl InceptionResult {
pub fn did(&self) -> String {
format!("did:keri:{}", self.prefix.as_str())
}
}
pub struct MultiKeyInceptionResult {
pub prefix: Prefix,
pub current_keypairs_pkcs8: Vec<Pkcs8Der>,
pub next_keypairs_pkcs8: Vec<Pkcs8Der>,
pub current_public_keys: Vec<Vec<u8>>,
pub next_public_keys: Vec<Vec<u8>>,
}
impl std::fmt::Debug for MultiKeyInceptionResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MultiKeyInceptionResult")
.field("prefix", &self.prefix)
.field("device_count", &self.current_public_keys.len())
.finish()
}
}
impl MultiKeyInceptionResult {
pub fn did(&self) -> String {
format!("did:keri:{}", self.prefix.as_str())
}
}
pub fn create_keri_identity_multi(
repo: &Repository,
witness_config: Option<&WitnessConfig>,
now: chrono::DateTime<chrono::Utc>,
curves: &[CurveType],
kt: Threshold,
nt: Threshold,
) -> Result<MultiKeyInceptionResult, InceptionError> {
if curves.is_empty() {
return Err(InceptionError::KeyGeneration(
"create_keri_identity_multi requires at least one curve".to_string(),
));
}
validate_threshold_for_key_count(&kt, curves.len())?;
validate_threshold_for_key_count(&nt, curves.len())?;
let current_kps = generate_keypairs_for_init(curves)?;
let next_kps = generate_keypairs_for_init(curves)?;
let k: Vec<CesrKey> = current_kps
.iter()
.map(|kp| CesrKey::new_unchecked(kp.cesr_encoded.clone()))
.collect();
let n: Vec<Said> = next_kps
.iter()
.map(|kp| compute_next_commitment(&kp.verkey()))
.collect();
let (bt, b) = match witness_config {
Some(cfg) if cfg.is_enabled() => (
Threshold::Simple(cfg.threshold as u64),
cfg.aids().cloned().collect(),
),
_ => (Threshold::Simple(0), vec![]),
};
let icp = IcpEvent {
v: VersionString::placeholder(),
d: Said::default(),
i: Prefix::default(),
s: KeriSequence::new(0),
kt,
k,
nt,
n,
bt,
b,
c: vec![],
a: vec![],
};
let finalized = finalize_icp_event(icp)?;
let prefix = finalized.i.clone();
let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
let _sig_bytes = sign_with_pkcs8(curves[0], ¤t_kps[0].pkcs8, &canonical)?;
let kel = GitKel::new(repo, prefix.as_str());
kel.create(&finalized, now)?;
Ok(MultiKeyInceptionResult {
prefix,
current_keypairs_pkcs8: current_kps.iter().map(|kp| kp.pkcs8.clone()).collect(),
next_keypairs_pkcs8: next_kps.iter().map(|kp| kp.pkcs8.clone()).collect(),
current_public_keys: current_kps.into_iter().map(|kp| kp.public_key).collect(),
next_public_keys: next_kps.into_iter().map(|kp| kp.public_key).collect(),
})
}
pub fn create_keri_identity(
repo: &Repository,
witness_config: Option<&WitnessConfig>,
now: chrono::DateTime<chrono::Utc>,
) -> Result<InceptionResult, InceptionError> {
create_keri_identity_with_curve(repo, witness_config, now, CurveType::P256)
}
pub fn create_keri_identity_with_curve(
repo: &Repository,
witness_config: Option<&WitnessConfig>,
now: chrono::DateTime<chrono::Utc>,
curve: CurveType,
) -> Result<InceptionResult, InceptionError> {
let current = generate_keypair(curve)?;
let next = generate_keypair(curve)?;
let current_pub_encoded = current.cesr_encoded.clone();
let next_commitment = compute_next_commitment(&next.verkey());
let (bt, b) = match witness_config {
Some(cfg) if cfg.is_enabled() => (
Threshold::Simple(cfg.threshold as u64),
cfg.aids().cloned().collect(),
),
_ => (Threshold::Simple(0), vec![]),
};
let icp = IcpEvent {
v: VersionString::placeholder(),
d: Said::default(),
i: Prefix::default(),
s: KeriSequence::new(0),
kt: Threshold::Simple(1),
k: vec![CesrKey::new_unchecked(current_pub_encoded)],
nt: Threshold::Simple(1),
n: vec![next_commitment],
bt,
b,
c: vec![],
a: vec![],
};
let finalized = finalize_icp_event(icp)?;
let prefix = finalized.i.clone();
let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
let _sig_bytes = sign_with_pkcs8(curve, ¤t.pkcs8, &canonical)?;
let kel = GitKel::new(repo, prefix.as_str());
kel.create(&finalized, now)?;
#[cfg(feature = "witness-client")]
if let Some(config) = witness_config
&& config.is_enabled()
{
let canonical_for_witness = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
super::witness_integration::collect_and_store_receipts(
repo.path().parent().unwrap_or(repo.path()),
&prefix,
&finalized.d,
&canonical_for_witness,
config,
now,
)
.map_err(|e| InceptionError::Serialization(e.to_string()))?;
}
Ok(InceptionResult {
prefix,
current_keypair_pkcs8: current.pkcs8,
next_keypair_pkcs8: next.pkcs8,
current_public_key: current.public_key,
next_public_key: next.public_key,
})
}
pub fn create_keri_identity_with_backend(
backend: &impl RegistryBackend,
_witness_config: Option<&WitnessConfig>,
) -> Result<InceptionResult, InceptionError> {
let rng = SystemRandom::new();
let current_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let current_keypair = Ed25519KeyPair::from_pkcs8(current_pkcs8.as_ref())
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref())
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let current_pub_encoded =
auths_keri::KeriPublicKey::ed25519(current_keypair.public_key().as_ref())
.and_then(|k| k.to_qb64())
.map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?;
let next_commitment = compute_next_commitment(
&auths_keri::KeriPublicKey::ed25519(next_keypair.public_key().as_ref())
.map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?,
);
let icp = IcpEvent {
v: VersionString::placeholder(),
d: Said::default(),
i: Prefix::default(),
s: KeriSequence::new(0),
kt: Threshold::Simple(1),
k: vec![CesrKey::new_unchecked(current_pub_encoded)],
nt: Threshold::Simple(1),
n: vec![next_commitment],
bt: Threshold::Simple(0),
b: vec![],
c: vec![],
a: vec![],
};
let finalized = finalize_icp_event(icp)?;
let prefix = finalized.i.clone();
let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
let sig = current_keypair.sign(&canonical);
let attachment = auths_keri::serialize_attachment(&[auths_keri::IndexedSignature {
index: 0,
prior_index: None,
sig: sig.as_ref().to_vec(),
}])
.map_err(|e| InceptionError::Serialization(e.to_string()))?;
backend
.append_signed_event(&prefix, &Event::Icp(finalized), &attachment)
.map_err(InceptionError::Storage)?;
Ok(InceptionResult {
prefix,
current_keypair_pkcs8: Pkcs8Der::new(current_pkcs8.as_ref()),
next_keypair_pkcs8: Pkcs8Der::new(next_pkcs8.as_ref()),
current_public_key: current_keypair.public_key().as_ref().to_vec(),
next_public_key: next_keypair.public_key().as_ref().to_vec(),
})
}
pub fn create_keri_identity_from_key(
repo: &Repository,
current_pkcs8_bytes: &[u8],
witness_config: Option<&WitnessConfig>,
now: chrono::DateTime<chrono::Utc>,
) -> Result<InceptionResult, InceptionError> {
let rng = SystemRandom::new();
let current_keypair = Ed25519KeyPair::from_pkcs8(current_pkcs8_bytes)
.map_err(|e| InceptionError::KeyGeneration(format!("invalid PKCS8 key: {e}")))?;
let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref())
.map_err(|e| InceptionError::KeyGeneration(e.to_string()))?;
let current_pub_encoded =
auths_keri::KeriPublicKey::ed25519(current_keypair.public_key().as_ref())
.and_then(|k| k.to_qb64())
.map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?;
let next_commitment = compute_next_commitment(
&auths_keri::KeriPublicKey::ed25519(next_keypair.public_key().as_ref())
.map_err(|e| InceptionError::KeyGeneration(format!("ed25519 verkey: {e}")))?,
);
let (bt, b) = match witness_config {
Some(cfg) if cfg.is_enabled() => (
Threshold::Simple(cfg.threshold as u64),
cfg.aids().cloned().collect(),
),
_ => (Threshold::Simple(0), vec![]),
};
let icp = IcpEvent {
v: VersionString::placeholder(),
d: Said::default(),
i: Prefix::default(),
s: KeriSequence::new(0),
kt: Threshold::Simple(1),
k: vec![CesrKey::new_unchecked(current_pub_encoded)],
nt: Threshold::Simple(1),
n: vec![next_commitment],
bt,
b,
c: vec![],
a: vec![],
};
let finalized = finalize_icp_event(icp)?;
let prefix = finalized.i.clone();
let canonical = super::serialize_for_signing(&Event::Icp(finalized.clone()))?;
let _sig = current_keypair.sign(&canonical);
let kel = GitKel::new(repo, prefix.as_str());
kel.create(&finalized, now)?;
Ok(InceptionResult {
prefix,
current_keypair_pkcs8: Pkcs8Der::new(current_pkcs8_bytes),
next_keypair_pkcs8: Pkcs8Der::new(next_pkcs8.as_ref()),
current_public_key: current_keypair.public_key().as_ref().to_vec(),
next_public_key: next_keypair.public_key().as_ref().to_vec(),
})
}
pub fn prefix_to_did(prefix: &str) -> String {
format!("did:keri:{}", prefix)
}
pub fn did_to_prefix(did: &str) -> Option<&str> {
did.strip_prefix("did:keri:")
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
use crate::keri::{Event, validate_kel};
use tempfile::TempDir;
fn setup_repo() -> (TempDir, Repository) {
let dir = TempDir::new().unwrap();
let repo = Repository::init(dir.path()).unwrap();
let mut config = repo.config().unwrap();
config.set_str("user.name", "Test User").unwrap();
config.set_str("user.email", "test@example.com").unwrap();
(dir, repo)
}
#[test]
fn create_identity_returns_valid_result() {
let (_dir, repo) = setup_repo();
let result = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
assert!(result.prefix.as_str().starts_with('E'));
assert!(!result.current_keypair_pkcs8.is_empty());
assert!(!result.next_keypair_pkcs8.is_empty());
assert_eq!(result.current_public_key.len(), 32);
assert_eq!(result.next_public_key.len(), 32);
assert!(result.did().starts_with("did:keri:E"));
}
#[test]
fn create_identity_stores_kel() {
let (_dir, repo) = setup_repo();
let result = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let kel = GitKel::new(&repo, result.prefix.as_str());
assert!(kel.exists());
let events = kel.get_events().unwrap();
assert_eq!(events.len(), 1);
assert!(events[0].is_inception());
}
#[test]
fn inception_event_is_valid() {
let (_dir, repo) = setup_repo();
let result = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let kel = GitKel::new(&repo, result.prefix.as_str());
let events = kel.get_events().unwrap();
let state = validate_kel(&events).unwrap();
assert_eq!(state.prefix, result.prefix);
assert_eq!(state.sequence, 0);
assert!(!state.is_abandoned);
}
fn witness_cfg(threshold: usize, aids: &[&str]) -> WitnessConfig {
use crate::witness_config::{WitnessPolicy, WitnessRef};
WitnessConfig {
witnesses: aids
.iter()
.enumerate()
.map(|(i, aid)| WitnessRef {
url: format!("http://w{i}:3333").parse().unwrap(),
aid: Prefix::new_unchecked((*aid).to_string()),
operator_info: None,
})
.collect(),
threshold,
timeout_ms: 5000,
policy: WitnessPolicy::Warn,
..Default::default()
}
}
#[test]
fn icp_designates_configured_witnesses_and_replays_backers() {
let (_dir, repo) = setup_repo();
let aids = [
"BWitnessOne00000000000000000000000000000000",
"BWitnessTwo00000000000000000000000000000000",
];
let cfg = witness_cfg(2, &aids);
let result = create_keri_identity_with_curve(
&repo,
Some(&cfg),
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let kel = GitKel::new(&repo, result.prefix.as_str());
let state = validate_kel(&kel.get_events().unwrap()).unwrap();
let designated: Vec<&str> = state.backers.iter().map(|p| p.as_str()).collect();
assert_eq!(designated, aids);
assert_eq!(state.backer_threshold, Threshold::Simple(2));
}
#[test]
fn icp_zero_witness_path_is_valid() {
let (_dir, repo) = setup_repo();
let result = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let kel = GitKel::new(&repo, result.prefix.as_str());
let state = validate_kel(&kel.get_events().unwrap()).unwrap();
assert!(state.backers.is_empty());
assert_eq!(state.backer_threshold, Threshold::Simple(0));
}
#[test]
fn inception_event_has_correct_structure() {
let (_dir, repo) = setup_repo();
let result = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let kel = GitKel::new(&repo, result.prefix.as_str());
let events = kel.get_events().unwrap();
if let Event::Icp(icp) = &events[0] {
assert_eq!(icp.v.kind, "JSON");
assert_eq!(icp.d.as_str(), icp.i.as_str());
assert_eq!(icp.d.as_str(), result.prefix.as_str());
assert_eq!(icp.s, KeriSequence::new(0));
assert_eq!(icp.k.len(), 1);
assert!(icp.k[0].as_str().starts_with('D'));
assert_eq!(icp.n.len(), 1);
assert!(icp.n[0].as_str().starts_with('E'));
assert_eq!(icp.bt, Threshold::Simple(0));
assert!(icp.b.is_empty());
} else {
panic!("Expected inception event");
}
}
#[test]
fn next_key_commitment_is_correct() {
let (_dir, repo) = setup_repo();
let result = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let kel = GitKel::new(&repo, result.prefix.as_str());
let events = kel.get_events().unwrap();
if let Event::Icp(icp) = &events[0] {
let expected_commitment = compute_next_commitment(
&auths_keri::KeriPublicKey::ed25519(&result.next_public_key)
.expect("ed25519 verkey is 32 bytes"),
);
assert_eq!(icp.n[0], expected_commitment);
} else {
panic!("Expected inception event");
}
}
#[test]
fn prefix_to_did_works() {
assert_eq!(prefix_to_did("ETest123"), "did:keri:ETest123");
}
#[test]
fn did_to_prefix_works() {
assert_eq!(did_to_prefix("did:keri:ETest123"), Some("ETest123"));
assert_eq!(did_to_prefix("did:key:z6Mk..."), None);
}
#[test]
fn multiple_identities_have_different_prefixes() {
let (_dir, repo) = setup_repo();
let result1 = create_keri_identity_with_curve(
&repo,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
let (_dir2, repo2) = setup_repo();
let result2 = create_keri_identity_with_curve(
&repo2,
None,
chrono::Utc::now(),
auths_crypto::CurveType::Ed25519,
)
.unwrap();
assert_ne!(result1.prefix, result2.prefix);
}
#[test]
fn generate_keypairs_for_init_rejects_empty_slice() {
let res = generate_keypairs_for_init(&[]);
match res {
Ok(_) => panic!("expected error on empty slice"),
Err(InceptionError::KeyGeneration(msg)) => assert!(msg.contains("at least one")),
Err(other) => panic!("expected KeyGeneration error, got {other:?}"),
}
}
#[test]
fn generate_keypairs_for_init_single_curve_matches_legacy() {
let kps = generate_keypairs_for_init(&[auths_crypto::CurveType::P256]).unwrap();
assert_eq!(kps.len(), 1);
assert!(kps[0].cesr_encoded.starts_with("1AAJ"));
assert_eq!(kps[0].public_key.len(), 33);
let legacy = generate_keypair_for_init(auths_crypto::CurveType::P256).unwrap();
assert_eq!(legacy.public_key.len(), 33);
assert!(legacy.cesr_encoded.starts_with("1AAJ"));
}
#[test]
fn generate_keypairs_for_init_mixed_curves_distinct_keys() {
use auths_crypto::CurveType::{Ed25519, P256};
let kps = generate_keypairs_for_init(&[P256, P256, Ed25519]).unwrap();
assert_eq!(kps.len(), 3);
assert_eq!(kps[0].public_key.len(), 33);
assert!(kps[0].cesr_encoded.starts_with("1AAJ"));
assert_eq!(kps[1].public_key.len(), 33);
assert!(kps[1].cesr_encoded.starts_with("1AAJ"));
assert_eq!(kps[2].public_key.len(), 32);
assert!(kps[2].cesr_encoded.starts_with('D'));
assert_ne!(kps[0].public_key, kps[1].public_key);
assert_ne!(kps[0].public_key, kps[2].public_key);
assert_ne!(kps[1].public_key, kps[2].public_key);
}
}