use std::sync::Arc;
use crate::keri::inception::create_keri_identity_with_curve;
use git2::Repository;
use std::path::Path;
use crate::error::InitError;
use crate::keri::{
CesrKey, Event, IcpEvent, KeriSequence, Prefix, Said, Threshold, VersionString,
finalize_icp_event, serialize_for_signing,
};
use crate::storage::identity::IdentityStorage;
use crate::storage::registry::RegistryBackend;
use crate::witness_config::WitnessConfig;
use auths_core::{
crypto::said::compute_next_commitment,
crypto::signer::encrypt_keypair,
signing::PassphraseProvider,
storage::keychain::{IdentityDID, KeyAlias, KeyRole, KeyStorage},
};
#[allow(clippy::too_many_arguments)]
pub fn initialize_keri_identity(
repo_path: &Path,
local_key_alias: &KeyAlias,
metadata: Option<serde_json::Value>,
passphrase_provider: &dyn PassphraseProvider,
identity_storage: &dyn IdentityStorage,
keychain: &(dyn KeyStorage + Send + Sync),
now: chrono::DateTime<chrono::Utc>,
curve: auths_crypto::CurveType,
) -> Result<(IdentityDID, KeyAlias), InitError> {
let is_hardware_backend = keychain.is_hardware_backend();
let passphrase = if is_hardware_backend {
None
} else {
let pass = passphrase_provider
.get_passphrase(&format!("Enter passphrase for key '{}':", local_key_alias))?;
auths_core::crypto::encryption::validate_passphrase(&pass)?;
Some(pass)
};
let repo = Repository::open(repo_path)?;
let result = create_keri_identity_with_curve(&repo, None, now, curve)
.map_err(|e| InitError::Keri(e.to_string()))?;
#[allow(clippy::disallowed_methods)]
let controller_did = IdentityDID::new_unchecked(result.did());
let (current_data, next_data) = match &passphrase {
None => (Vec::new(), Vec::new()),
Some(pass) => (
encrypt_keypair(result.current_keypair_pkcs8.as_ref(), pass)?,
encrypt_keypair(result.next_keypair_pkcs8.as_ref(), pass)?,
),
};
let next_alias = KeyAlias::new_unchecked(format!("{}--next-0", local_key_alias));
keychain.store_key(
local_key_alias,
&controller_did,
KeyRole::Primary,
¤t_data,
)?;
if let Err(e) = keychain.store_key(
&next_alias,
&controller_did,
KeyRole::NextRotation,
&next_data,
) {
let _ = keychain.delete_key(local_key_alias);
return Err(e.into());
}
if let Err(e) = identity_storage.create_identity(controller_did.as_str(), metadata) {
let _ = keychain.delete_key(local_key_alias);
let _ = keychain.delete_key(&next_alias);
return Err(e.into());
}
Ok((controller_did, local_key_alias.clone()))
}
pub fn initialize_registry_identity(
backend: Arc<dyn RegistryBackend + Send + Sync>,
local_key_alias: &KeyAlias,
passphrase_provider: &dyn PassphraseProvider,
keychain: &(dyn KeyStorage + Send + Sync),
witness_config: Option<&WitnessConfig>,
curve: auths_crypto::CurveType,
) -> Result<(IdentityDID, KeyAlias), InitError> {
backend
.init_if_needed()
.map_err(|e| InitError::Registry(e.to_string()))?;
if keychain.is_hardware_backend() {
return initialize_hardware_registry_identity(
backend,
local_key_alias,
passphrase_provider,
keychain,
witness_config,
curve,
);
}
let current = crate::keri::inception::generate_keypair_for_init(curve)
.map_err(|e| InitError::Crypto(e.to_string()))?;
let next = crate::keri::inception::generate_keypair_for_init(curve)
.map_err(|e| InitError::Crypto(e.to_string()))?;
let passphrase = passphrase_provider
.get_passphrase(&format!("Enter passphrase for key '{}':", local_key_alias))?;
let encrypted_current = encrypt_keypair(current.pkcs8.as_ref(), &passphrase)?;
let encrypted_next = encrypt_keypair(next.pkcs8.as_ref(), &passphrase)?;
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).map_err(|e| InitError::Keri(e.to_string()))?;
let prefix = finalized.i.clone();
let canonical = serialize_for_signing(&Event::Icp(finalized.clone()))
.map_err(|e| InitError::Keri(e.to_string()))?;
let sig_bytes =
crate::keri::inception::sign_with_pkcs8_for_init(curve, ¤t.pkcs8, &canonical)
.map_err(|e| InitError::Crypto(e.to_string()))?;
let attachment = auths_keri::serialize_attachment(&[auths_keri::IndexedSignature {
index: 0,
prior_index: None,
sig: sig_bytes,
}])
.map_err(|e| InitError::Keri(format!("attachment serialization: {e}")))?;
#[allow(clippy::disallowed_methods)]
let controller_did = IdentityDID::new_unchecked(format!("did:keri:{}", prefix));
let next_alias = KeyAlias::new_unchecked(format!("{}--next-0", local_key_alias));
keychain.store_key(
local_key_alias,
&controller_did,
KeyRole::Primary,
&encrypted_current,
)?;
if let Err(e) = keychain.store_key(
&next_alias,
&controller_did,
KeyRole::NextRotation,
&encrypted_next,
) {
let _ = keychain.delete_key(local_key_alias);
return Err(e.into());
}
if let Err(e) = backend.append_signed_event(&prefix, &Event::Icp(finalized), &attachment) {
let _ = keychain.delete_key(local_key_alias);
let _ = keychain.delete_key(&next_alias);
return Err(InitError::Registry(e.to_string()));
}
Ok((controller_did, local_key_alias.clone()))
}
fn initialize_hardware_registry_identity(
backend: Arc<dyn RegistryBackend + Send + Sync>,
local_key_alias: &KeyAlias,
passphrase_provider: &dyn PassphraseProvider,
keychain: &(dyn KeyStorage + Send + Sync),
witness_config: Option<&WitnessConfig>,
curve: auths_crypto::CurveType,
) -> Result<(IdentityDID, KeyAlias), InitError> {
if curve != auths_crypto::CurveType::P256 {
return Err(InitError::Crypto(format!(
"hardware-backed inception supports P-256 only, got {curve:?}"
)));
}
#[allow(clippy::disallowed_methods)]
let placeholder = IdentityDID::new_unchecked("did:keri:pending");
let next_alias = KeyAlias::new_unchecked(format!("{}--next-0", local_key_alias));
keychain.store_key(local_key_alias, &placeholder, KeyRole::Primary, &[])?;
if let Err(e) = keychain.store_key(&next_alias, &placeholder, KeyRole::NextRotation, &[]) {
let _ = keychain.delete_key(local_key_alias);
return Err(e.into());
}
let result = incept_with_hardware_keys(
&backend,
local_key_alias,
&next_alias,
keychain,
witness_config,
curve,
);
if result.is_err() {
let _ = keychain.delete_key(local_key_alias);
let _ = keychain.delete_key(&next_alias);
}
let _ = passphrase_provider;
result.map(|controller_did| (controller_did, local_key_alias.clone()))
}
fn incept_with_hardware_keys(
backend: &Arc<dyn RegistryBackend + Send + Sync>,
local_key_alias: &KeyAlias,
next_alias: &KeyAlias,
keychain: &(dyn KeyStorage + Send + Sync),
witness_config: Option<&WitnessConfig>,
curve: auths_crypto::CurveType,
) -> Result<IdentityDID, InitError> {
let current_pub = keychain.export_public_key(local_key_alias)?;
let next_pub = keychain.export_public_key(next_alias)?;
let current_norm = auths_crypto::normalize_verkey(¤t_pub, curve)
.map_err(|e| InitError::Crypto(format!("hardware current key: {e}")))?;
let next_norm = auths_crypto::normalize_verkey(&next_pub, curve)
.map_err(|e| InitError::Crypto(format!("hardware next key: {e}")))?;
let current_verkey = auths_keri::KeriPublicKey::from_verkey_bytes(¤t_norm, curve)
.map_err(|e| InitError::Crypto(format!("hardware current key: {e}")))?;
let next_verkey = auths_keri::KeriPublicKey::from_verkey_bytes(&next_norm, curve)
.map_err(|e| InitError::Crypto(format!("hardware next key: {e}")))?;
let current_cesr = current_verkey
.to_qb64()
.map_err(|e| InitError::Crypto(e.to_string()))?;
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_cesr)],
nt: Threshold::Simple(1),
n: vec![compute_next_commitment(&next_verkey)],
bt,
b,
c: vec![],
a: vec![],
};
let finalized = finalize_icp_event(icp).map_err(|e| InitError::Keri(e.to_string()))?;
let prefix = finalized.i.clone();
let canonical = serialize_for_signing(&Event::Icp(finalized.clone()))
.map_err(|e| InitError::Keri(e.to_string()))?;
let sig_bytes = keychain.sign_raw(local_key_alias, &canonical)?;
let attachment = auths_keri::serialize_attachment(&[auths_keri::IndexedSignature {
index: 0,
prior_index: None,
sig: sig_bytes,
}])
.map_err(|e| InitError::Keri(format!("attachment serialization: {e}")))?;
backend
.append_signed_event(&prefix, &Event::Icp(finalized), &attachment)
.map_err(|e| InitError::Registry(e.to_string()))?;
#[allow(clippy::disallowed_methods)]
let controller_did = IdentityDID::new_unchecked(format!("did:keri:{}", prefix));
keychain.rebind_identity(local_key_alias, &controller_did)?;
keychain.rebind_identity(next_alias, &controller_did)?;
Ok(controller_did)
}
#[allow(clippy::too_many_arguments)]
pub fn initialize_registry_identity_multi(
backend: Arc<dyn RegistryBackend + Send + Sync>,
local_key_alias: &KeyAlias,
passphrase_provider: &dyn PassphraseProvider,
keychain: &(dyn KeyStorage + Send + Sync),
witness_config: Option<&WitnessConfig>,
curves: &[auths_crypto::CurveType],
kt: Threshold,
nt: Threshold,
) -> Result<(IdentityDID, KeyAlias), InitError> {
if curves.is_empty() {
return Err(InitError::Crypto(
"initialize_registry_identity_multi requires at least one curve".to_string(),
));
}
crate::keri::inception::validate_threshold_for_key_count(&kt, curves.len())
.map_err(|e| InitError::Crypto(e.to_string()))?;
crate::keri::inception::validate_threshold_for_key_count(&nt, curves.len())
.map_err(|e| InitError::Crypto(e.to_string()))?;
backend
.init_if_needed()
.map_err(|e| InitError::Registry(e.to_string()))?;
let current_kps = crate::keri::inception::generate_keypairs_for_init(curves)
.map_err(|e| InitError::Crypto(e.to_string()))?;
let next_kps = crate::keri::inception::generate_keypairs_for_init(curves)
.map_err(|e| InitError::Crypto(e.to_string()))?;
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).map_err(|e| InitError::Keri(e.to_string()))?;
let prefix = finalized.i.clone();
let canonical = serialize_for_signing(&Event::Icp(finalized.clone()))
.map_err(|e| InitError::Keri(e.to_string()))?;
let sig_bytes = crate::keri::inception::sign_with_pkcs8_for_init(
curves[0],
¤t_kps[0].pkcs8,
&canonical,
)
.map_err(|e| InitError::Crypto(e.to_string()))?;
let attachment = auths_keri::serialize_attachment(&[auths_keri::IndexedSignature {
index: 0,
prior_index: None,
sig: sig_bytes,
}])
.map_err(|e| InitError::Keri(format!("attachment serialization: {e}")))?;
#[allow(clippy::disallowed_methods)]
let controller_did = IdentityDID::new_unchecked(format!("did:keri:{}", prefix));
let is_hardware_backend = keychain.is_hardware_backend();
let passphrase = if is_hardware_backend {
None
} else {
Some(
passphrase_provider
.get_passphrase(&format!("Enter passphrase for key '{}':", local_key_alias))?,
)
};
let mut stored: Vec<KeyAlias> = Vec::with_capacity(current_kps.len() * 2);
let commit_result = store_multi_slot_keys(
keychain,
&controller_did,
local_key_alias,
¤t_kps,
&next_kps,
passphrase.as_ref().map(|p| p.as_str()),
&mut stored,
)
.and_then(|()| {
backend
.append_signed_event(&prefix, &Event::Icp(finalized), &attachment)
.map_err(|e| InitError::Registry(e.to_string()))
});
if let Err(e) = commit_result {
for alias in &stored {
let _ = keychain.delete_key(alias);
}
return Err(e);
}
Ok((controller_did, local_key_alias.clone()))
}
fn store_multi_slot_keys(
keychain: &(dyn KeyStorage + Send + Sync),
controller_did: &IdentityDID,
local_key_alias: &KeyAlias,
current_kps: &[crate::keri::inception::GeneratedKeypair],
next_kps: &[crate::keri::inception::GeneratedKeypair],
passphrase: Option<&str>,
stored: &mut Vec<KeyAlias>,
) -> Result<(), InitError> {
for (idx, (cur, nxt)) in current_kps.iter().zip(next_kps.iter()).enumerate() {
let cur_alias = KeyAlias::new_unchecked(format!("{}--{}", local_key_alias, idx));
let nxt_alias = KeyAlias::new_unchecked(format!("{}--next-0-{}", local_key_alias, idx));
let (cur_data, nxt_data) = match passphrase {
None => (Vec::new(), Vec::new()),
Some(pass) => (
encrypt_keypair(cur.pkcs8.as_ref(), pass)?,
encrypt_keypair(nxt.pkcs8.as_ref(), pass)?,
),
};
keychain.store_key(&cur_alias, controller_did, KeyRole::Primary, &cur_data)?;
stored.push(cur_alias);
keychain.store_key(&nxt_alias, controller_did, KeyRole::NextRotation, &nxt_data)?;
stored.push(nxt_alias);
}
Ok(())
}