use anima_core::error::{AnimaError, AnimaResult};
const ED25519_MULTICODEC_PREFIX: [u8; 2] = [0xed, 0x01];
const DID_KEY_PREFIX: &str = "did:key:z";
pub fn generate_did_key(public_key: &[u8; 32]) -> String {
let mut bytes = Vec::with_capacity(34);
bytes.extend_from_slice(&ED25519_MULTICODEC_PREFIX);
bytes.extend_from_slice(public_key);
let encoded = bs58::encode(&bytes).into_string();
format!("{DID_KEY_PREFIX}{encoded}")
}
pub fn resolve_did_key(did: &str) -> AnimaResult<[u8; 32]> {
let encoded = did
.strip_prefix(DID_KEY_PREFIX)
.ok_or_else(|| AnimaError::Identity(format!("invalid did:key format: {did}")))?;
let bytes = bs58::decode(encoded)
.into_vec()
.map_err(|e| AnimaError::Identity(format!("base58 decode failed: {e}")))?;
if bytes.len() < 2 {
return Err(AnimaError::Identity(
"decoded DID too short for multicodec prefix".into(),
));
}
if bytes[0] != ED25519_MULTICODEC_PREFIX[0] || bytes[1] != ED25519_MULTICODEC_PREFIX[1] {
return Err(AnimaError::Identity(format!(
"unexpected multicodec prefix: [{:#04x}, {:#04x}] (expected Ed25519: [0xed, 0x01])",
bytes[0], bytes[1]
)));
}
let key_bytes = &bytes[2..];
if key_bytes.len() != 32 {
return Err(AnimaError::Identity(format!(
"Ed25519 public key must be 32 bytes, got {}",
key_bytes.len()
)));
}
let mut key = [0u8; 32];
key.copy_from_slice(key_bytes);
Ok(key)
}
pub fn verify_did_key(did: &str, public_key: &[u8; 32]) -> bool {
generate_did_key(public_key) == did
}
pub fn verification_method_id(did: &str) -> String {
format!("{did}#key-1")
}
#[cfg(test)]
mod tests {
use super::*;
fn test_public_key() -> [u8; 32] {
[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20,
]
}
#[test]
fn generate_did_key_format() {
let did = generate_did_key(&test_public_key());
assert!(did.starts_with("did:key:z"));
assert!(
did.starts_with("did:key:z6Mk"),
"Ed25519 did:key should start with z6Mk, got: {did}"
);
}
#[test]
fn generate_did_key_deterministic() {
let key = test_public_key();
let did1 = generate_did_key(&key);
let did2 = generate_did_key(&key);
assert_eq!(did1, did2);
}
#[test]
fn different_keys_produce_different_dids() {
let key1 = [1u8; 32];
let key2 = [2u8; 32];
let did1 = generate_did_key(&key1);
let did2 = generate_did_key(&key2);
assert_ne!(did1, did2);
}
#[test]
fn resolve_did_key_roundtrip() {
let original = test_public_key();
let did = generate_did_key(&original);
let resolved = resolve_did_key(&did).unwrap();
assert_eq!(original, resolved);
}
#[test]
fn resolve_did_key_all_zeros() {
let key = [0u8; 32];
let did = generate_did_key(&key);
let resolved = resolve_did_key(&did).unwrap();
assert_eq!(key, resolved);
}
#[test]
fn resolve_did_key_all_ones() {
let key = [0xff; 32];
let did = generate_did_key(&key);
let resolved = resolve_did_key(&did).unwrap();
assert_eq!(key, resolved);
}
#[test]
fn resolve_invalid_prefix_fails() {
let result = resolve_did_key("did:web:example.com");
assert!(result.is_err());
}
#[test]
fn resolve_invalid_base58_fails() {
let result = resolve_did_key("did:key:z0OOO");
assert!(result.is_err());
}
#[test]
fn resolve_wrong_multicodec_fails() {
let mut bytes = vec![0xe7, 0x01];
bytes.extend_from_slice(&[1u8; 32]);
let encoded = bs58::encode(&bytes).into_string();
let did = format!("did:key:z{encoded}");
let result = resolve_did_key(&did);
assert!(result.is_err());
}
#[test]
fn resolve_short_key_fails() {
let mut bytes = vec![0xed, 0x01];
bytes.extend_from_slice(&[1u8; 16]);
let encoded = bs58::encode(&bytes).into_string();
let did = format!("did:key:z{encoded}");
let result = resolve_did_key(&did);
assert!(result.is_err());
}
#[test]
fn verify_did_key_succeeds() {
let key = test_public_key();
let did = generate_did_key(&key);
assert!(verify_did_key(&did, &key));
}
#[test]
fn verify_did_key_fails_for_wrong_key() {
let key = test_public_key();
let did = generate_did_key(&key);
let wrong_key = [99u8; 32];
assert!(!verify_did_key(&did, &wrong_key));
}
#[test]
fn verification_method_id_format() {
let key = test_public_key();
let did = generate_did_key(&key);
let vm_id = verification_method_id(&did);
assert!(vm_id.starts_with("did:key:z"));
assert!(vm_id.ends_with("#key-1"));
}
#[test]
fn roundtrip_100_random_keys() {
for i in 0u8..100 {
let mut key = [0u8; 32];
key[0] = i;
key[31] = 255 - i;
let did = generate_did_key(&key);
let resolved = resolve_did_key(&did).unwrap();
assert_eq!(key, resolved, "roundtrip failed for key variant {i}");
}
}
#[test]
fn known_ed25519_did_key_test_vector() {
use crate::seed::MasterSeed;
let seed = MasterSeed::from_bytes([42u8; 32]);
let ed25519_key = seed.derive_ed25519_key();
let ed25519_id = crate::ed25519::Ed25519Identity::from_key_bytes(&ed25519_key).unwrap();
let did_from_module =
generate_did_key(ed25519_id.public_key_bytes().as_slice().try_into().unwrap());
let did_from_identity = ed25519_id.did_key();
assert_eq!(did_from_module, did_from_identity);
}
}