Skip to main content

tf_types/
bridge_did.rs

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