Skip to main content

agent_toolprint/
did_key.rs

1//! did:key resolver and Ed25519 multicodec helpers.
2
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5
6use crate::error::Error;
7
8const ED25519_MULTICODEC: [u8; 2] = [0xed, 0x01];
9
10/// A pluggable DID resolver.
11#[async_trait]
12pub trait Resolver: Send + Sync {
13    async fn resolve(&self, did: &str, key_id: &str, at_time: DateTime<Utc>) -> Option<Vec<u8>>;
14}
15
16pub fn parse_did_key(did: &str) -> Result<Vec<u8>, Error> {
17    const PREFIX: &str = "did:key:";
18    if !did.starts_with(PREFIX) {
19        return Err(Error::DidKey("not a did:key".into()));
20    }
21    let multibase = &did[PREFIX.len()..];
22    if !multibase.starts_with('z') {
23        return Err(Error::DidKey(
24            "did:key must use 'z' (base58btc) multibase prefix".into(),
25        ));
26    }
27    let decoded = bs58::decode(&multibase[1..])
28        .into_vec()
29        .map_err(|e| Error::DidKey(format!("base58 decode: {e}")))?;
30    if decoded.len() != 34
31        || decoded[0] != ED25519_MULTICODEC[0]
32        || decoded[1] != ED25519_MULTICODEC[1]
33    {
34        return Err(Error::DidKey(
35            "did:key must encode an Ed25519 public key (multicodec 0xed01)".into(),
36        ));
37    }
38    Ok(decoded[2..].to_vec())
39}
40
41pub fn did_key_from_ed25519_pubkey(pk: &[u8]) -> Result<String, Error> {
42    if pk.len() != 32 {
43        return Err(Error::DidKey("Ed25519 public key must be 32 bytes".into()));
44    }
45    let mut mc = Vec::with_capacity(34);
46    mc.extend_from_slice(&ED25519_MULTICODEC);
47    mc.extend_from_slice(pk);
48    Ok(format!("did:key:z{}", bs58::encode(mc).into_string()))
49}
50
51/// Bundled `did:key` resolver — returns the raw public key, or None on failure.
52pub struct DidKeyResolver;
53
54#[async_trait]
55impl Resolver for DidKeyResolver {
56    async fn resolve(&self, did: &str, _key_id: &str, _at_time: DateTime<Utc>) -> Option<Vec<u8>> {
57        parse_did_key(did).ok()
58    }
59}