Skip to main content

tf_types/
bridge_did.rs

1//! DID (W3C DID Core 1.0) bridge — Rust mirror of TS.
2
3use base64::engine::general_purpose::URL_SAFE_NO_PAD;
4use base64::Engine;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::bridges::{Bridge, BridgeError, BridgeKind};
9use crate::generated::{
10    ActorIdentity, ActorIdentity_IdentityVersion, ActorType, AuthorityRoot, AuthorityRoot_Kind,
11    PublicKey, PublicKey_Purpose, TrustLevel,
12};
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct DidVerificationMethod {
16    pub id: String,
17    #[serde(rename = "type")]
18    pub kind: String,
19    pub controller: String,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub public_key_multibase: Option<String>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub public_key_jwk: Option<Value>,
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize)]
27pub struct DidDocument {
28    pub id: String,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub verification_method: Option<Vec<DidVerificationMethod>>,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub authentication: Option<Vec<Value>>,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub controller: Option<Value>,
35}
36
37#[derive(Clone, Debug, Default)]
38pub struct DidBridgeConfig {
39    pub bridge_id: String,
40    pub trust_domain: String,
41    pub allowed_methods: Option<Vec<String>>,
42}
43
44pub struct DidBridge {
45    cfg: DidBridgeConfig,
46}
47
48impl DidBridge {
49    pub fn new(cfg: DidBridgeConfig) -> Self {
50        DidBridge { cfg }
51    }
52
53    pub fn resolve_did_key(&self, did_url: &str) -> Result<DidDocument, BridgeError> {
54        let method = parse_did_method(did_url)?;
55        if let Some(allow) = &self.cfg.allowed_methods {
56            if !allow.iter().any(|m| m == method) {
57                return Err(BridgeError::Rejected(format!(
58                    "DID method {} not in allow-list",
59                    method
60                )));
61            }
62        }
63        if method != "key" {
64            return Err(BridgeError::Unsupported(format!(
65                "method {} not supported by built-in resolver; provide a custom resolver",
66                method
67            )));
68        }
69        let multibase = did_url
70            .strip_prefix("did:key:")
71            .ok_or_else(|| BridgeError::InvalidInput(format!("not did:key: {}", did_url)))?;
72        let id = format!("did:key:{}", multibase);
73        Ok(DidDocument {
74            id: id.clone(),
75            verification_method: Some(vec![DidVerificationMethod {
76                id: format!("{}#{}", id, multibase),
77                kind: "Ed25519VerificationKey2020".into(),
78                controller: id.clone(),
79                public_key_multibase: Some(multibase.to_string()),
80                public_key_jwk: None,
81            }]),
82            authentication: Some(vec![Value::String(format!("{}#{}", id, multibase))]),
83            controller: Some(Value::String(id)),
84        })
85    }
86
87    pub fn accept(&self, document: &DidDocument) -> Result<ActorIdentity, BridgeError> {
88        let vms = document
89            .verification_method
90            .as_ref()
91            .ok_or_else(|| BridgeError::Rejected("DID has no verification methods".into()))?;
92        if vms.is_empty() {
93            return Err(BridgeError::Rejected(
94                "DID verification_method is empty".into(),
95            ));
96        }
97        let vm = &vms[0];
98        let pk = extract_public_key(vm).ok_or_else(|| {
99            BridgeError::Unsupported(format!("vm {} has no usable public key", vm.id))
100        })?;
101        let actor_id = format!(
102            "tf:actor:human:{}/{}",
103            self.cfg.trust_domain,
104            url_encode(&document.id)
105        );
106        Ok(ActorIdentity {
107            identity_version: ActorIdentity_IdentityVersion::V1,
108            actor_id,
109            actor_type: ActorType::Human,
110            instance_id: None,
111            public_keys: vec![PublicKey {
112                key_id: vm.id.clone(),
113                algorithm: pk.algorithm,
114                public_key: base64::engine::general_purpose::STANDARD.encode(&pk.bytes),
115                purpose: PublicKey_Purpose::Signing,
116                valid_from: None,
117                valid_until: None,
118            }],
119            trust_levels: vec![TrustLevel::T2],
120            authority_roots: vec![AuthorityRoot {
121                kind: AuthorityRoot_Kind::Federation,
122                id: vm.controller.clone(),
123            }],
124            attestations: None,
125            valid_from: now_iso8601(),
126            valid_until: None,
127            revocation_ref: None,
128            signature: None,
129        })
130    }
131}
132
133impl Bridge for DidBridge {
134    fn bridge_id(&self) -> &str {
135        &self.cfg.bridge_id
136    }
137    fn kind(&self) -> BridgeKind {
138        BridgeKind::Did
139    }
140    fn trust_domain(&self) -> &str {
141        &self.cfg.trust_domain
142    }
143}
144
145struct ProjectedKey {
146    algorithm: String,
147    bytes: Vec<u8>,
148}
149
150fn extract_public_key(vm: &DidVerificationMethod) -> Option<ProjectedKey> {
151    if let Some(mb) = &vm.public_key_multibase {
152        let decoded = decode_multibase(mb)?;
153        if decoded.len() >= 2 && decoded[0] == 0xed && decoded[1] == 0x01 {
154            return Some(ProjectedKey {
155                algorithm: "ed25519".into(),
156                bytes: decoded[2..].to_vec(),
157            });
158        }
159        return Some(ProjectedKey {
160            algorithm: vm.kind.to_lowercase(),
161            bytes: decoded,
162        });
163    }
164    if let Some(jwk) = &vm.public_key_jwk {
165        if jwk.get("kty").and_then(Value::as_str) == Some("OKP")
166            && jwk.get("crv").and_then(Value::as_str) == Some("Ed25519")
167        {
168            if let Some(x) = jwk.get("x").and_then(Value::as_str) {
169                let bytes = URL_SAFE_NO_PAD.decode(x).ok()?;
170                return Some(ProjectedKey {
171                    algorithm: "ed25519".into(),
172                    bytes,
173                });
174            }
175        }
176    }
177    None
178}
179
180fn parse_did_method(did: &str) -> Result<&str, BridgeError> {
181    let rest = did
182        .strip_prefix("did:")
183        .ok_or_else(|| BridgeError::InvalidInput(format!("not a DID: {}", did)))?;
184    let end = rest
185        .find(':')
186        .ok_or_else(|| BridgeError::InvalidInput(format!("not a DID: {}", did)))?;
187    Ok(&rest[..end])
188}
189
190fn url_encode(s: &str) -> String {
191    let mut out = String::with_capacity(s.len());
192    for b in s.bytes() {
193        match b {
194            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
195                out.push(b as char);
196            }
197            _ => out.push_str(&format!("%{:02X}", b)),
198        }
199    }
200    out
201}
202
203fn decode_multibase(s: &str) -> Option<Vec<u8>> {
204    if s.is_empty() {
205        return None;
206    }
207    let prefix = s.as_bytes()[0];
208    let body = &s[1..];
209    match prefix {
210        b'z' => base58btc_decode(body),
211        b'm' => base64::engine::general_purpose::STANDARD.decode(body).ok(),
212        b'u' => URL_SAFE_NO_PAD.decode(body).ok(),
213        _ => None,
214    }
215}
216
217const BASE58_ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
218
219fn base58btc_decode(s: &str) -> Option<Vec<u8>> {
220    if s.is_empty() {
221        return Some(Vec::new());
222    }
223    let mut zeros = 0usize;
224    while zeros < s.len() && s.as_bytes()[zeros] == b'1' {
225        zeros += 1;
226    }
227    let size = ((s.len() - zeros) as f64 * 0.733).ceil() as usize + 1;
228    let mut b256 = vec![0u8; size];
229    for i in zeros..s.len() {
230        let c = s.as_bytes()[i];
231        let idx = BASE58_ALPHABET.iter().position(|&b| b == c)?;
232        let mut carry = idx;
233        for j in (0..size).rev() {
234            carry += b256[j] as usize * 58;
235            b256[j] = (carry & 0xff) as u8;
236            carry >>= 8;
237        }
238        if carry != 0 {
239            return None;
240        }
241    }
242    let mut start = 0usize;
243    while start < size && b256[start] == 0 {
244        start += 1;
245    }
246    let mut out = vec![0u8; zeros];
247    out.extend_from_slice(&b256[start..]);
248    Some(out)
249}
250
251pub fn ed25519_public_key_to_did_key(pub_bytes: &[u8]) -> Result<String, BridgeError> {
252    if pub_bytes.len() != 32 {
253        return Err(BridgeError::InvalidInput(format!(
254            "ed25519 public key must be 32 bytes, got {}",
255            pub_bytes.len()
256        )));
257    }
258    let mut prefixed = Vec::with_capacity(2 + 32);
259    prefixed.push(0xed);
260    prefixed.push(0x01);
261    prefixed.extend_from_slice(pub_bytes);
262    Ok(format!("z{}", base58btc_encode(&prefixed)))
263}
264
265fn base58btc_encode(bytes: &[u8]) -> String {
266    if bytes.is_empty() {
267        return String::new();
268    }
269    let mut zeros = 0usize;
270    while zeros < bytes.len() && bytes[zeros] == 0 {
271        zeros += 1;
272    }
273    let size = ((bytes.len() as f64) * 1.366).ceil() as usize + 1;
274    let mut b58 = vec![0u8; size];
275    for &byte in bytes.iter().skip(zeros) {
276        let mut carry = byte as usize;
277        for j in (0..size).rev() {
278            carry += b58[j] as usize * 256;
279            b58[j] = (carry % 58) as u8;
280            carry /= 58;
281        }
282    }
283    let mut start = 0usize;
284    while start < size && b58[start] == 0 {
285        start += 1;
286    }
287    let mut out = String::new();
288    for _ in 0..zeros {
289        out.push('1');
290    }
291    for &b in &b58[start..] {
292        out.push(BASE58_ALPHABET[b as usize] as char);
293    }
294    out
295}
296
297fn now_iso8601() -> String {
298    let secs = std::time::SystemTime::now()
299        .duration_since(std::time::UNIX_EPOCH)
300        .unwrap_or_default()
301        .as_secs() as i64;
302    let (y, m, d, h, mi, s) = secs_to_ymdhms(secs);
303    format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", y, m, d, h, mi, s)
304}
305
306fn secs_to_ymdhms(secs: i64) -> (i32, u32, u32, u32, u32, u32) {
307    let days = secs.div_euclid(86_400);
308    let time = secs.rem_euclid(86_400);
309    let hour = (time / 3600) as u32;
310    let minute = ((time % 3600) / 60) as u32;
311    let second = (time % 60) as u32;
312    let z = days + 719_468;
313    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
314    let doe = (z - era * 146_097) as u64;
315    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
316    let y = yoe as i64 + era * 400;
317    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
318    let mp = (5 * doy + 2) / 153;
319    let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
320    let m = if mp < 10 {
321        (mp + 3) as u32
322    } else {
323        (mp - 9) as u32
324    };
325    let year = if m <= 2 { y + 1 } else { y };
326    (year as i32, m, d, hour, minute, second)
327}