affinidi_data_integrity/
did_vm.rs1#[cfg(feature = "slh-dsa")]
16use affinidi_secrets_resolver::multicodec::SLH_DSA_SHA2_128S_PUB;
17use affinidi_secrets_resolver::multicodec::{
18 ED25519_PUB, MultiEncoded, P256_PUB, P384_PUB, P521_PUB, SECP256K1_PUB,
19};
20#[cfg(feature = "ml-dsa")]
21use affinidi_secrets_resolver::multicodec::{ML_DSA_44_PUB, ML_DSA_65_PUB, ML_DSA_87_PUB};
22use affinidi_secrets_resolver::secrets::KeyType;
23use async_trait::async_trait;
24
25use crate::DataIntegrityError;
26
27#[derive(Clone, Debug)]
33#[non_exhaustive]
34pub struct ResolvedKey {
35 pub key_type: KeyType,
36 pub public_key_bytes: Vec<u8>,
37}
38
39impl ResolvedKey {
40 pub fn new(key_type: KeyType, public_key_bytes: Vec<u8>) -> Self {
43 Self {
44 key_type,
45 public_key_bytes,
46 }
47 }
48}
49
50#[async_trait]
59pub trait VerificationMethodResolver: Send + Sync {
60 async fn resolve_vm(&self, vm: &str) -> Result<ResolvedKey, DataIntegrityError>;
63}
64
65pub struct DidKeyResolver;
71
72#[async_trait]
73impl VerificationMethodResolver for DidKeyResolver {
74 async fn resolve_vm(&self, vm: &str) -> Result<ResolvedKey, DataIntegrityError> {
75 resolve_did_key(vm)
76 }
77}
78
79pub fn resolve_did_key(vm: &str) -> Result<ResolvedKey, DataIntegrityError> {
84 let did = vm.split('#').next().unwrap_or(vm);
87 let id = did.strip_prefix("did:key:").ok_or_else(|| {
88 DataIntegrityError::Resolver(format!(
89 "not a did:key URI (expected did:key:..., got {vm})"
90 ))
91 })?;
92
93 let (_base, raw) = multibase::decode(id).map_err(|e| {
95 DataIntegrityError::Resolver(format!("multibase decode of did:key id failed: {e}"))
96 })?;
97 let mc = MultiEncoded::new(&raw).map_err(|e| {
98 DataIntegrityError::Resolver(format!("multicodec decode of did:key failed: {e}"))
99 })?;
100
101 let codec = mc.codec();
102 let data = mc.data();
103
104 let (key_type, expected_len): (KeyType, usize) = match codec {
105 ED25519_PUB => (KeyType::Ed25519, 32),
106 SECP256K1_PUB => (KeyType::Secp256k1, 33),
107 P256_PUB => (KeyType::P256, 33),
108 P384_PUB => (KeyType::P384, 49),
109 P521_PUB => (KeyType::P521, 67),
110 #[cfg(feature = "ml-dsa")]
111 ML_DSA_44_PUB => (KeyType::MlDsa44, 1312),
112 #[cfg(feature = "ml-dsa")]
113 ML_DSA_65_PUB => (KeyType::MlDsa65, 1952),
114 #[cfg(feature = "ml-dsa")]
115 ML_DSA_87_PUB => (KeyType::MlDsa87, 2592),
116 #[cfg(feature = "slh-dsa")]
117 SLH_DSA_SHA2_128S_PUB => (KeyType::SlhDsaSha2_128s, 32),
118 other => {
119 return Err(DataIntegrityError::InvalidPublicKey {
120 codec: Some(other),
121 len: data.len(),
122 reason: "unknown or unsupported multicodec for did:key".to_string(),
123 });
124 }
125 };
126
127 if data.len() != expected_len {
128 return Err(DataIntegrityError::InvalidPublicKey {
129 codec: Some(codec),
130 len: data.len(),
131 reason: format!(
132 "did:key public key length {} does not match expected {} for {key_type:?}",
133 data.len(),
134 expected_len
135 ),
136 });
137 }
138
139 Ok(ResolvedKey {
140 key_type,
141 public_key_bytes: data.to_vec(),
142 })
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use affinidi_secrets_resolver::secrets::Secret;
149
150 #[tokio::test]
151 async fn resolve_did_key_ed25519_roundtrip() {
152 let secret = Secret::generate_ed25519(None, Some(&[1u8; 32]));
153 let pk_mb = secret.get_public_keymultibase().unwrap();
154 let vm = format!("did:key:{pk_mb}#{pk_mb}");
155
156 let resolved = DidKeyResolver.resolve_vm(&vm).await.unwrap();
157 assert_eq!(resolved.key_type, KeyType::Ed25519);
158 assert_eq!(resolved.public_key_bytes, secret.get_public_bytes());
159 }
160
161 #[cfg(feature = "ml-dsa")]
162 #[tokio::test]
163 async fn resolve_did_key_ml_dsa_44_roundtrip() {
164 let secret = Secret::generate_ml_dsa_44(None, Some(&[2u8; 32]));
165 let pk_mb = secret.get_public_keymultibase().unwrap();
166 let vm = format!("did:key:{pk_mb}#{pk_mb}");
167
168 let resolved = DidKeyResolver.resolve_vm(&vm).await.unwrap();
169 assert_eq!(resolved.key_type, KeyType::MlDsa44);
170 assert_eq!(resolved.public_key_bytes, secret.get_public_bytes());
171 assert_eq!(resolved.public_key_bytes.len(), 1312);
172 }
173
174 #[cfg(feature = "slh-dsa")]
175 #[tokio::test]
176 async fn resolve_did_key_slh_dsa_roundtrip() {
177 let secret = Secret::generate_slh_dsa_sha2_128s(None);
178 let pk_mb = secret.get_public_keymultibase().unwrap();
179 let vm = format!("did:key:{pk_mb}#{pk_mb}");
180
181 let resolved = DidKeyResolver.resolve_vm(&vm).await.unwrap();
182 assert_eq!(resolved.key_type, KeyType::SlhDsaSha2_128s);
183 assert_eq!(resolved.public_key_bytes.len(), 32);
184 }
185
186 #[tokio::test]
187 async fn resolve_did_key_rejects_non_did_key() {
188 let err = DidKeyResolver
189 .resolve_vm("did:web:example.com#key-1")
190 .await
191 .unwrap_err();
192 assert!(matches!(err, DataIntegrityError::Resolver(_)));
193 }
194
195 #[tokio::test]
196 async fn resolve_did_key_rejects_unknown_codec() {
197 let bogus = "did:key:z8NGuWZeMJTxQeofMjZPEdN2PC6eaDKhKCbF19UqjpDEwKzYwQnH3YzHK3#x";
200 let err = DidKeyResolver.resolve_vm(bogus).await.unwrap_err();
201 assert!(
202 matches!(
203 err,
204 DataIntegrityError::Resolver(_) | DataIntegrityError::InvalidPublicKey { .. }
205 ),
206 "got: {err:?}"
207 );
208 }
209}