use auths_core::error::AuthsErrorInfo;
use auths_core::signing::PassphraseProvider;
use auths_core::storage::keychain::{KeyAlias, KeyStorage};
use auths_crypto::CurveType;
use auths_keri::{Iss, Rev, TelAnchorSeal, TelEvent, Vcp, encode_tel_nonce, tel_to_wire_bytes};
use rand::Rng;
use crate::error::InitError;
use crate::keri::delegation::stage_root_anchor_ixn;
use crate::keri::{KeriSequence, Prefix, Said, Seal};
use crate::storage::registry::backend::{AtomicWriteBatch, RegistryBackend, RegistryError};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CredentialRegistryError {
#[error(
"issuer '{issuer}' is multi-signature (kt≥2); credential registry anchoring is single-author only"
)]
ThresholdUnsupported {
issuer: String,
},
#[error("TEL event error: {0}")]
Tel(String),
#[error("KEL anchoring failed: {0}")]
Anchor(#[from] InitError),
#[error("registry storage error: {0}")]
Storage(#[from] RegistryError),
}
impl AuthsErrorInfo for CredentialRegistryError {
fn error_code(&self) -> &'static str {
match self {
Self::ThresholdUnsupported { .. } => "AUTHS-E4981",
Self::Tel(_) => "AUTHS-E4982",
Self::Anchor(_) => "AUTHS-E4983",
Self::Storage(_) => "AUTHS-E4984",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::ThresholdUnsupported { .. } => {
Some("Credential issuance currently requires a single-signature (kt=1) issuer")
}
_ => None,
}
}
}
fn ensure_single_sig(
backend: &(dyn RegistryBackend + Send + Sync),
issuer: &Prefix,
) -> Result<(), CredentialRegistryError> {
let state = backend.get_key_state(issuer)?;
if state.threshold.simple_value() == Some(1) {
Ok(())
} else {
Err(CredentialRegistryError::ThresholdUnsupported {
issuer: issuer.as_str().to_string(),
})
}
}
fn tel_coordinate(event: &TelEvent) -> (Said, Said, Said, u128, Said) {
match event {
TelEvent::Vcp(vcp) => (
vcp.d.clone(),
vcp.d.clone(),
vcp.i.clone(),
vcp.s.value(),
vcp.d.clone(),
),
TelEvent::Iss(iss) => (
iss.ri.clone(),
iss.i.clone(),
iss.i.clone(),
iss.s.value(),
iss.d.clone(),
),
TelEvent::Rev(rev) => (
rev.ri.clone(),
rev.i.clone(),
rev.i.clone(),
rev.s.value(),
rev.d.clone(),
),
}
}
fn tel_event_bytes(event: &TelEvent) -> Result<Vec<u8>, CredentialRegistryError> {
let bytes = match event {
TelEvent::Vcp(vcp) => tel_to_wire_bytes(vcp),
TelEvent::Iss(iss) => tel_to_wire_bytes(iss),
TelEvent::Rev(rev) => tel_to_wire_bytes(rev),
};
bytes.map_err(|e| CredentialRegistryError::Tel(e.to_string()))
}
#[allow(clippy::too_many_arguments)]
pub fn anchor_tel_event(
backend: &(dyn RegistryBackend + Send + Sync),
issuer_prefix: &Prefix,
issuer_alias: &KeyAlias,
issuer_curve: CurveType,
tel_event: &TelEvent,
credential_blob: Option<(Said, Vec<u8>)>,
passphrase_provider: &dyn PassphraseProvider,
keychain: &(dyn KeyStorage + Send + Sync),
) -> Result<(), CredentialRegistryError> {
ensure_single_sig(backend, issuer_prefix)?;
let (registry_said, credential_said, seal_aid, sn, event_said) = tel_coordinate(tel_event);
let event_bytes = tel_event_bytes(tel_event)?;
let seal = Seal::KeyEvent {
i: Prefix::new_unchecked(seal_aid.as_str().to_string()),
s: KeriSequence::new(sn),
d: event_said,
};
let mut batch = AtomicWriteBatch::new();
stage_root_anchor_ixn(
backend,
issuer_prefix,
issuer_alias,
issuer_curve,
vec![seal],
passphrase_provider,
keychain,
&mut batch,
)?;
batch.stage_tel_event(
issuer_prefix.clone(),
registry_said.clone(),
credential_said.clone(),
sn,
event_bytes,
);
if let Some((cred_said, cred_bytes)) = credential_blob {
batch.stage_credential(issuer_prefix.clone(), cred_said, cred_bytes);
}
backend.commit_batch(&batch)?;
Ok(())
}
pub fn anchor_seal_for(seal: &TelAnchorSeal) -> Seal {
Seal::KeyEvent {
i: seal.i.clone(),
s: seal.s,
d: seal.d.clone(),
}
}
pub fn ensure_registry(
backend: &(dyn RegistryBackend + Send + Sync),
issuer_prefix: &Prefix,
issuer_alias: &KeyAlias,
issuer_curve: CurveType,
passphrase_provider: &dyn PassphraseProvider,
keychain: &(dyn KeyStorage + Send + Sync),
) -> Result<Said, CredentialRegistryError> {
ensure_single_sig(backend, issuer_prefix)?;
if let Some(existing) = find_registry(backend, issuer_prefix)? {
return Ok(existing);
}
let mut nonce_bytes = [0u8; 16];
rand::rng().fill_bytes(&mut nonce_bytes);
let nonce =
encode_tel_nonce(&nonce_bytes).map_err(|e| CredentialRegistryError::Tel(e.to_string()))?;
let vcp = Vcp::new(issuer_prefix.clone(), nonce)
.saidify()
.map_err(|e| CredentialRegistryError::Tel(e.to_string()))?;
let registry_said = vcp.registry().clone();
anchor_tel_event(
backend,
issuer_prefix,
issuer_alias,
issuer_curve,
&TelEvent::Vcp(vcp),
None,
passphrase_provider,
keychain,
)?;
Ok(registry_said)
}
pub fn find_registry(
backend: &(dyn RegistryBackend + Send + Sync),
issuer_prefix: &Prefix,
) -> Result<Option<Said>, CredentialRegistryError> {
use std::ops::ControlFlow;
let mut candidates: Vec<Said> = Vec::new();
backend.visit_events(issuer_prefix, 0, &mut |event| {
for seal in event.anchors() {
if let Seal::KeyEvent { i, s, .. } = seal
&& s.value() == 0
{
candidates.push(Said::new_unchecked(i.as_str().to_string()));
}
}
ControlFlow::Continue(())
})?;
for candidate in candidates {
if registry_exists(backend, issuer_prefix, &candidate)? {
return Ok(Some(candidate));
}
}
Ok(None)
}
fn registry_exists(
backend: &(dyn RegistryBackend + Send + Sync),
issuer_prefix: &Prefix,
registry_said: &Said,
) -> Result<bool, CredentialRegistryError> {
use std::ops::ControlFlow;
let mut found = false;
backend.visit_tel_events(issuer_prefix, registry_said, registry_said, &mut |_bytes| {
found = true;
ControlFlow::Break(())
})?;
Ok(found)
}
pub fn read_credential_tel(
backend: &(dyn RegistryBackend + Send + Sync),
issuer_prefix: &Prefix,
registry_said: &Said,
credential_said: &Said,
) -> Result<Vec<TelEvent>, CredentialRegistryError> {
use std::ops::ControlFlow;
let mut events = Vec::new();
let mut parse_err: Option<String> = None;
let mut collect = |bytes: &[u8]| match TelEvent::from_wire_bytes(bytes) {
Ok(event) => {
events.push(event);
ControlFlow::Continue(())
}
Err(e) => {
parse_err = Some(e.to_string());
ControlFlow::Break(())
}
};
backend.visit_tel_events(issuer_prefix, registry_said, registry_said, &mut collect)?;
if credential_said != registry_said {
backend.visit_tel_events(issuer_prefix, registry_said, credential_said, &mut collect)?;
}
if let Some(detail) = parse_err {
return Err(CredentialRegistryError::Tel(detail));
}
Ok(events)
}
pub fn build_iss(
credential_said: &Said,
registry_said: &Said,
dt: String,
) -> Result<Iss, CredentialRegistryError> {
Iss::new(credential_said.clone(), registry_said.clone(), dt)
.saidify()
.map_err(|e| CredentialRegistryError::Tel(e.to_string()))
}
pub fn build_rev(
credential_said: &Said,
registry_said: &Said,
prior_iss_said: &Said,
dt: String,
) -> Result<Rev, CredentialRegistryError> {
Rev::new(
credential_said.clone(),
registry_said.clone(),
prior_iss_said.clone(),
dt,
)
.saidify()
.map_err(|e| CredentialRegistryError::Tel(e.to_string()))
}