1use crate::types::{DidDocument, KeyType, Manifest, ResolvedKey, VerificationMethod};
2use std::collections::HashMap;
3
4#[cfg(not(target_arch = "wasm32"))]
5use reqwest::blocking::Client;
6
7pub(crate) fn resolve_verification_methods(
8 manifest: &Manifest,
9) -> HashMap<String, VerificationMethod> {
10 let mut methods = HashMap::new();
11 if let Some(list) = manifest.verification_method.as_ref() {
12 for method in list {
13 methods.insert(method.id.clone(), method.clone());
14 }
15 }
16 methods
17}
18
19pub fn decode_multibase(input: &str) -> Result<Vec<u8>, String> {
20 let mut chars = input.chars();
21 let prefix = chars
22 .next()
23 .ok_or_else(|| "Empty multibase value".to_string())?;
24 let payload: String = chars.collect();
25 match prefix {
26 'z' => bs58::decode(payload)
27 .into_vec()
28 .map_err(|err| format!("Base58 decode failed: {}", err)),
29 _ => Err(format!("Unsupported multibase prefix: {}", prefix)),
30 }
31}
32
33fn read_varint(bytes: &[u8]) -> Result<(u64, usize), String> {
34 let mut result = 0u64;
35 let mut shift = 0u32;
36 for (index, byte) in bytes.iter().enumerate() {
37 let value = (byte & 0x7F) as u64;
38 result |= value << shift;
39 if (byte & 0x80) == 0 {
40 return Ok((result, index + 1));
41 }
42 shift += 7;
43 }
44 Err("Invalid varint".to_string())
45}
46
47fn key_type_from_method(method_type: &str) -> Result<KeyType, String> {
48 match method_type {
49 "Ed25519VerificationKey2020" => Ok(KeyType::Ed25519),
50 "EcdsaSecp256k1VerificationKey2019" => Ok(KeyType::Secp256k1),
51 other => Err(format!("Unsupported key type: {}", other)),
52 }
53}
54
55fn key_type_from_multicodec(code: u64) -> Result<KeyType, String> {
56 match code {
57 0xed => Ok(KeyType::Ed25519),
58 0xe7 => Ok(KeyType::Secp256k1),
59 other => Err(format!("Unsupported multicodec: {}", other)),
60 }
61}
62
63#[cfg(not(target_arch = "wasm32"))]
64pub(crate) fn resolve_did_web(did: &str) -> Result<DidDocument, String> {
65 let did_parts = did
66 .strip_prefix("did:web:")
67 .ok_or_else(|| "Invalid did:web format".to_string())?;
68 let parts: Vec<&str> = did_parts.split(':').collect();
69 let domain = parts
70 .first()
71 .ok_or_else(|| "Missing did:web domain".to_string())?;
72 let path = if parts.len() > 1 {
73 format!("/{}/did.json", parts[1..].join("/"))
74 } else {
75 "/.well-known/did.json".to_string()
76 };
77 let url = format!("https://{}{}", domain, path);
78
79 let client = Client::new();
80 let response = client
81 .get(&url)
82 .send()
83 .map_err(|err| format!("Failed to fetch DID document: {}", err))?;
84 response
85 .json::<DidDocument>()
86 .map_err(|err| format!("Failed to parse DID document: {}", err))
87}
88
89#[cfg(target_arch = "wasm32")]
90pub(crate) fn resolve_did_web(_did: &str) -> Result<DidDocument, String> {
91 Err("DID web resolution not supported on WASM - use browser fetch".to_string())
92}
93
94pub(crate) fn resolve_did_key(did: &str) -> Result<ResolvedKey, String> {
95 let encoded = did
96 .strip_prefix("did:key:")
97 .ok_or_else(|| "Invalid did:key format".to_string())?;
98 let decoded = decode_multibase(encoded)?;
99 let (codec, offset) = read_varint(&decoded)?;
100 let key_type = key_type_from_multicodec(codec)?;
101 let key_bytes = decoded
102 .get(offset..)
103 .ok_or_else(|| "Missing key material".to_string())?
104 .to_vec();
105 let method_id = format!("{}#key-1", did);
106 Ok(ResolvedKey {
107 id: method_id,
108 controller: did.to_string(),
109 key_type,
110 public_key: key_bytes,
111 })
112}
113
114pub(crate) fn resolve_key_from_method(method: &VerificationMethod) -> Result<ResolvedKey, String> {
115 let key_type = key_type_from_method(&method.method_type)?;
116 let public_key_multibase = method
117 .public_key_multibase
118 .as_ref()
119 .ok_or_else(|| "Missing publicKeyMultibase".to_string())?;
120 let key_bytes = decode_multibase(public_key_multibase)?;
121 Ok(ResolvedKey {
122 id: method.id.clone(),
123 controller: method.controller.clone(),
124 key_type,
125 public_key: key_bytes,
126 })
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub enum BlockchainNetwork {
132 Ethereum,
133 Polygon,
134 Bsc,
135 Arbitrum,
136 Optimism,
137 Avalanche,
138 Other(String),
139}
140
141#[derive(Debug, Clone)]
143pub struct DidPkhInfo {
144 pub network: BlockchainNetwork,
145 pub address: String,
146 pub chain_id: Option<u64>,
147}
148
149pub fn parse_did_pkh(did: &str) -> Result<DidPkhInfo, String> {
152 let parts: Vec<&str> = did.split(':').collect();
153
154 if parts.len() < 4 {
155 return Err(format!("Invalid did:pkh format: {}", did));
156 }
157
158 let did_method = format!("{}:{}", parts[0], parts[1]);
160 if did_method != "did:pkh" {
161 return Err(format!("Expected did:pkh, got: {}", did_method));
162 }
163
164 if parts.len() < 5 {
166 return Err(format!(
167 "Invalid did:pkh format: expected 5 parts, got {}",
168 parts.len()
169 ));
170 }
171
172 let method = parts[2];
173 if method != "eip155" {
174 return Err(format!("Unsupported did:pkh method: {}", method));
175 }
176
177 let chain_id: u64 = parts[3]
178 .parse()
179 .map_err(|e| format!("Invalid chain ID: {}", e))?;
180
181 let address = parts[4];
182
183 if address.len() != 42 || !address.starts_with("0x") {
185 return Err("Invalid Ethereum address format".to_string());
186 }
187
188 let network = match chain_id {
189 1 => BlockchainNetwork::Ethereum,
190 137 => BlockchainNetwork::Polygon,
191 56 => BlockchainNetwork::Bsc,
192 42161 => BlockchainNetwork::Arbitrum,
193 10 => BlockchainNetwork::Optimism,
194 43114 => BlockchainNetwork::Avalanche,
195 _ => BlockchainNetwork::Other(format!("chain-{}", chain_id)),
196 };
197
198 Ok(DidPkhInfo {
199 network,
200 address: address.to_lowercase(),
201 chain_id: Some(chain_id),
202 })
203}
204
205pub(crate) fn resolve_key_for_method(
206 method_id: &str,
207 manifest: &Manifest,
208) -> Result<ResolvedKey, String> {
209 let methods = resolve_verification_methods(manifest);
210 if let Some(method) = methods.get(method_id) {
211 return resolve_key_from_method(method);
212 }
213
214 let did = method_id
215 .split('#')
216 .next()
217 .ok_or_else(|| "Invalid verification method".to_string())?;
218
219 if did.starts_with("did:key:") {
220 let mut resolved = resolve_did_key(did)?;
221 resolved.id = method_id.to_string();
222 return Ok(resolved);
223 }
224
225 if did.starts_with("did:web:") {
226 let document = resolve_did_web(did)?;
227 if let Some(methods) = document.verification_method {
228 if let Some(method) = methods.iter().find(|entry| entry.id == method_id) {
229 return resolve_key_from_method(method);
230 }
231 return Err(format!("Verification method {} not found", method_id));
232 }
233 return Err(format!("No verification methods for {}", did));
234 }
235
236 if did.starts_with("did:pkh:") {
237 let _pkh_info = parse_did_pkh(did)?;
238 return Err(format!(
239 "did:pkh is for solvency links, not cryptographic verification. \
240 Network: {:?}, Address: {}",
241 _pkh_info.network, _pkh_info.address
242 ));
243 }
244
245 Err(format!("Unsupported DID method in {}", method_id))
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn resolve_did_key_decodes_multibase() {
254 let bytes = vec![0xed, 0x01, 0x02, 0x03, 0x04];
255 let encoded = format!("z{}", bs58::encode(&bytes).into_string());
256 let did = format!("did:key:{}", encoded);
257 let resolved = resolve_did_key(&did).expect("should resolve did:key");
258 assert_eq!(resolved.key_type, KeyType::Ed25519);
259 assert_eq!(resolved.public_key, vec![0x02, 0x03, 0x04]);
260 assert_eq!(resolved.controller, did);
261 }
262}