diff --git a/crates/auths-crypto/src/hash256.rs b/crates/auths-crypto/src/hash256.rs index f35b60ce..f42a2f25 100644 --- a/crates/auths-crypto/src/hash256.rs +++ b/crates/auths-crypto/src/hash256.rs @@ -30,6 +30,29 @@ impl Hash256 { pub fn into_inner(self) -> [u8; 32] { self.0 } + + /// Parse a 64-character hex string (with an optional prefix such as `"sha256:"`) into a `Hash256`. + pub fn from_hex_prefixed(s: &str, expected_prefix: Option<&str>) -> Result { + let clean = match expected_prefix { + Some(prefix) => s + .strip_prefix(prefix) + .ok_or_else(|| format!("expected prefix '{prefix}'")), + None => { + if let Some(stripped) = s.strip_prefix("sha256:") { + Ok(stripped) + } else { + Ok(s) + } + } + }?; + + let bytes = hex::decode(clean).map_err(|e| format!("invalid hex: {e}"))?; + let array: [u8; 32] = bytes + .try_into() + .map_err(|_| "hash digest must be exactly 32 bytes (64 hex characters)".to_string())?; + + Ok(Self(array)) + } } impl From<[u8; 32]> for Hash256 { diff --git a/crates/auths-evidence/src/attestation.rs b/crates/auths-evidence/src/attestation.rs index d4e04ebf..3c92bbab 100644 --- a/crates/auths-evidence/src/attestation.rs +++ b/crates/auths-evidence/src/attestation.rs @@ -93,10 +93,10 @@ pub fn activity_signing_bytes(doc: &ActivityV1) -> Result, EvidenceError /// let seed = activity_seed_id(&doc); /// ``` pub fn activity_seed_id(doc: &ActivityV1) -> auths_anchor::SeedId { - let agent_tail = doc - .subject - .agent - .strip_prefix("did:keri:") + let agent_did = auths_verifier::IdentityDID::parse(&doc.subject.agent).ok(); + let agent_tail = agent_did + .as_ref() + .map(|d| d.prefix()) .unwrap_or(&doc.subject.agent); auths_anchor::SeedId::derive(&doc.subject.root, &doc.subject.agent, agent_tail) } @@ -512,12 +512,9 @@ pub fn verify_activity_against_registry( let backend = GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(registry)); - let agent_tail = doc - .subject - .agent - .strip_prefix("did:keri:") - .unwrap_or(&doc.subject.agent); - let prefix = Prefix::new(agent_tail.to_string()) + let parsed_agent = auths_verifier::IdentityDID::parse(&doc.subject.agent) + .map_err(|e| EvidenceError::Input(format!("agent DID: {e}")))?; + let prefix = Prefix::new(parsed_agent.prefix().to_string()) .map_err(|e| EvidenceError::Input(format!("agent prefix: {e}")))?; let state = backend .get_key_state(&prefix) diff --git a/crates/auths-id/src/identity/rotate.rs b/crates/auths-id/src/identity/rotate.rs index 01f47b8c..ff64725f 100644 --- a/crates/auths-id/src/identity/rotate.rs +++ b/crates/auths-id/src/identity/rotate.rs @@ -100,9 +100,10 @@ pub fn rotate_keri_identity( )); } - let prefix = did.as_str().strip_prefix("did:keri:").ok_or_else(|| { + let parsed_did = auths_verifier::IdentityDID::parse(did.as_str()).map_err(|_| { InitError::InvalidData(format!("Invalid DID format, expected 'did:keri:': {}", did)) })?; + let prefix = parsed_did.prefix(); let kel = GitKel::new(&repo, prefix); let events = kel @@ -224,10 +225,10 @@ pub fn rotate_registry_identity( )); } - let prefix_str = did.as_str().strip_prefix("did:keri:").ok_or_else(|| { + let parsed_did = auths_verifier::IdentityDID::parse(did.as_str()).map_err(|_| { InitError::InvalidData(format!("Invalid DID format, expected 'did:keri:': {}", did)) })?; - let prefix = Prefix::new_unchecked(prefix_str.to_string()); + let prefix = Prefix::new_unchecked(parsed_did.prefix().to_string()); let state = backend .get_key_state(&prefix) @@ -388,10 +389,10 @@ pub fn rotate_registry_identity_multi( )); } - let prefix_str = did.as_str().strip_prefix("did:keri:").ok_or_else(|| { + let parsed_did = auths_verifier::IdentityDID::parse(did.as_str()).map_err(|_| { InitError::InvalidData(format!("Invalid DID format, expected 'did:keri:': {}", did)) })?; - let prefix = Prefix::new_unchecked(prefix_str.to_string()); + let prefix = Prefix::new_unchecked(parsed_did.prefix().to_string()); let state = backend .get_key_state(&prefix) diff --git a/crates/auths-id/src/storage/layout.rs b/crates/auths-id/src/storage/layout.rs index f709198d..c0fe0eae 100644 --- a/crates/auths-id/src/storage/layout.rs +++ b/crates/auths-id/src/storage/layout.rs @@ -244,8 +244,9 @@ pub fn keri_credential_ref(issuer: &Prefix, credential_said: &Said) -> String { /// Extracts the KERI prefix (AID) from a full `did:keri:` identifier string. pub fn did_keri_to_prefix(did: &str) -> Option { - did.strip_prefix("did:keri:") - .map(|s| Prefix::new_unchecked(s.to_string())) + auths_verifier::IdentityDID::parse(did) + .ok() + .map(|d| Prefix::new_unchecked(d.prefix().to_string())) } // --- Configurable Layout (Primarily for did:key Identity & Attestations) --- diff --git a/crates/auths-mcp-core/src/audit.rs b/crates/auths-mcp-core/src/audit.rs index 5d84e2b0..fffb65e4 100644 --- a/crates/auths-mcp-core/src/audit.rs +++ b/crates/auths-mcp-core/src/audit.rs @@ -535,7 +535,8 @@ impl fmt::Display for LogReadError { /// anything that is not `[A-Za-z0-9_-]` to `_` (defensive — a `did:keri:E…` tail is base64url and /// already safe; this only guards a malformed key from escaping the directory). fn safe_key(delegation: &str) -> String { - let tail = delegation.strip_prefix("did:keri:").unwrap_or(delegation); + let parsed = auths_verifier::IdentityDID::parse(delegation).ok(); + let tail = parsed.as_ref().map(|d| d.prefix()).unwrap_or(delegation); if tail.is_empty() || tail == "." || tail == ".." { return "_".to_string(); } diff --git a/crates/auths-mcp-core/src/budget.rs b/crates/auths-mcp-core/src/budget.rs index fe15e8ab..4941b360 100644 --- a/crates/auths-mcp-core/src/budget.rs +++ b/crates/auths-mcp-core/src/budget.rs @@ -579,7 +579,8 @@ impl CounterRef { /// safe; we strip the scheme and reject anything that is not a safe single path /// component (defensive — the same guard `usage_ledger.rs` applies to a SAID). fn safe_key(delegation: &str) -> Result { - let tail = delegation.strip_prefix("did:keri:").unwrap_or(delegation); + let parsed = auths_verifier::IdentityDID::parse(delegation).ok(); + let tail = parsed.as_ref().map(|d| d.prefix()).unwrap_or(delegation); let safe = !tail.is_empty() && tail != "." && tail != ".." diff --git a/crates/auths-sdk/src/domains/identity/local.rs b/crates/auths-sdk/src/domains/identity/local.rs index b93d06ef..f3938832 100644 --- a/crates/auths-sdk/src/domains/identity/local.rs +++ b/crates/auths-sdk/src/domains/identity/local.rs @@ -198,9 +198,9 @@ fn is_local_signing_device( /// The delegator (root) KEL tip sequence for `root_did`, or `None` if unreadable. fn root_tip_seq(ctx: &AuthsContext, root_did: &str) -> Option { - let prefix = root_did.strip_prefix("did:keri:")?; + let parsed = auths_verifier::IdentityDID::parse(root_did).ok()?; ctx.registry - .get_tip(&Prefix::new_unchecked(prefix.to_string())) + .get_tip(&Prefix::new_unchecked(parsed.prefix().to_string())) .ok() .map(|tip| tip.sequence) } diff --git a/crates/auths-sdk/src/workflows/agent_provision.rs b/crates/auths-sdk/src/workflows/agent_provision.rs index 6a138e97..ae42b19a 100644 --- a/crates/auths-sdk/src/workflows/agent_provision.rs +++ b/crates/auths-sdk/src/workflows/agent_provision.rs @@ -233,7 +233,9 @@ pub fn materialize_agent_machine_registry( agent_registry: &Path, agent_did: &str, ) -> Result<()> { - let agent_pfx = agent_did.strip_prefix("did:keri:").unwrap_or(agent_did); + let agent_pfx = auths_verifier::IdentityDID::parse(agent_did) + .map(|d| d.prefix().to_string()) + .unwrap_or_else(|_| agent_did.to_string()); if agent_registry.exists() { fs::remove_dir_all(agent_registry)?;