Skip to main content

dkms_wasm/
lib.rs

1use ed25519_dalek::SigningKey;
2use gloo_net::http::Request;
3use js_sys::Uint8Array;
4use keri_core::{
5    actor::{parse_event_stream, prelude::Message},
6    event_message::signed_event_message::Op,
7    oobi::{LocationScheme, Oobi},
8    prefix::{
9        BasicPrefix, IdentifierPrefix, IndexedSignature, SeedPrefix,
10        SelfSigningPrefix,
11    },
12    query::query_event::{SignedKelQuery, SignedQueryMessage},
13    signer::Signer,
14};
15use keri_sdk::{Controller, Identifier};
16use said::SelfAddressingIdentifier;
17use teliox::state::vc_state::TelState;
18use std::sync::Arc;
19use url::Url;
20use wasm_bindgen::prelude::*;
21
22pub mod database;
23use crate::database::indexed_db::IndexedDbDatabase as Database;
24
25#[wasm_bindgen]
26pub enum VcState {
27    Issued,
28    Revoked,
29    NotIssued,
30}
31
32#[wasm_bindgen]
33pub struct JsIdentifier {
34    inner: Identifier<Database>,
35    db: Arc<Database>,
36    alias: String,
37    signer: Arc<Signer>,
38    watcher_oobi: Option<LocationScheme>,
39}
40
41impl JsIdentifier {
42    pub fn new(alias: String, inner: Identifier<Database>, signer: Arc<Signer>, db: Arc<Database>, watcher_oobi: Option<LocationScheme>) -> Self {
43        Self {
44            inner,
45            db,
46            alias,
47            signer,
48            watcher_oobi,
49        }
50    }
51}
52
53#[wasm_bindgen]
54impl JsIdentifier {
55    pub fn get_prefix(&self) -> String {
56        self.inner.get_prefix().to_string()
57    }
58
59    pub fn get_kel(&self) -> String {
60        format!("{:?}", self.inner.get_own_kel().unwrap())
61    }
62
63    pub fn set_alias(&mut self, alias: String) -> Result<(), JsValue> {
64        self.db.update_identifier_alias(&self.alias, &alias).map_err(|e| JsValue::from_str(&format!("Failed to update alias in DB: {}", e)))?;
65        self.alias = alias;
66        Ok(())
67    }
68
69    pub async fn add_watcher(&mut self, url: String) -> Result<(), JsValue> {
70        let url = Url::parse(&url)
71            .map_err(|e| JsValue::from_str(&format!("Invalid URL: {}", e)))?;
72        let res = Request::get(url.join("introduce").unwrap().as_str())
73            .send()
74            .await
75            .map_err(|e| JsValue::from_str(&e.to_string()))?;
76        let res_str = res.text().await.map_err(|e| {
77            JsValue::from_str(&format!("Failed to get response: {}", e))
78        })?;
79        let oobi: LocationScheme =
80            serde_json::from_str(&res_str).map_err(|e| {
81                JsValue::from_str(&format!("Failed to parse OOBI: {}", e))
82            })?;
83        self.watcher_oobi = Some(oobi.clone());
84        let watcher_prefix = oobi.clone().eid;
85
86        let add_watcher_event = self
87            .inner
88            .add_watcher(watcher_prefix.clone())
89            .map_err(|e| {
90                JsValue::from_str(&format!("Failed to add watcher: {}", e))
91            })?;
92
93        let sig = SelfSigningPrefix::new(
94            cesrox::primitives::codes::self_signing::SelfSigning::Ed25519Sha512,
95            self.signer.sign(add_watcher_event.as_bytes()).unwrap(),
96        );
97        let (_, messages) = self
98            .inner
99            .finalize_add_watcher(add_watcher_event.as_bytes(), sig)
100            .unwrap();
101
102        for message in messages {
103            let request_url: Option<String> = match message {
104                Message::Notice(_) => {
105                    Some(url.join("process").unwrap().to_string())
106                }
107                Message::Op(Op::Reply(_)) => {
108                    Some(url.join("register").unwrap().to_string())
109                }
110                _ => {
111                    log::warn!("Unsupported message type: {:?}", message);
112                    None
113                }
114            };
115            if let Some(request_url) = request_url {
116                let body =
117                    Uint8Array::from(message.to_cesr().unwrap().as_slice());
118                let _ = Request::post(&request_url)
119                    .header("Content-Type", "application/json")
120                    .body(&body)
121                    .unwrap()
122                    .send()
123                    .await
124                    .map_err(|e| JsValue::from_str(&e.to_string()))?;
125            }
126        }
127
128        self.db.update_identifier_watcher(
129            &self.alias,
130            oobi.clone(),
131        ).map_err(|e| JsValue::from_str(&format!("Failed to update identifier in DB: {}", e)))?;
132
133        Ok(())
134    }
135
136    pub fn get_watcher(&self) -> Option<String> {
137        self.watcher_oobi.as_ref().map(|scheme| scheme.url.to_string())
138    }
139}
140
141#[wasm_bindgen]
142pub struct JsController {
143    inner: Controller<Database, Database>,
144    db: Arc<Database>,
145}
146
147struct KeysConfig {
148    pub current: SeedPrefix,
149    pub next: SeedPrefix,
150}
151
152impl Default for KeysConfig {
153    fn default() -> Self {
154        let current = SigningKey::generate(&mut rand::rngs::OsRng);
155        let next = SigningKey::generate(&mut rand::rngs::OsRng);
156        Self {
157            current: SeedPrefix::RandomSeed256Ed25519(
158                current.as_bytes().to_vec(),
159            ),
160            next: SeedPrefix::RandomSeed256Ed25519(next.as_bytes().to_vec()),
161        }
162    }
163}
164
165#[wasm_bindgen]
166impl JsController {
167    #[wasm_bindgen(constructor)]
168    pub fn new() -> Result<JsController, JsValue> {
169        log::set_logger(&wasm_bindgen_console_logger::DEFAULT_LOGGER).unwrap();
170        log::set_max_level(log::LevelFilter::Info);
171
172        let event_database = Arc::new(Database::new());
173        let tel_database = Arc::new(Database::new());
174        let inner = Controller::new(event_database, tel_database);
175
176        let identifiers_db = Arc::new(Database::new());
177
178        Ok(Self {
179            inner,
180            db: identifiers_db,
181        })
182    }
183
184    pub fn load_identifier(
185        &self,
186        alias: String,
187    ) -> Result<JsIdentifier, JsValue> {
188        let id_record = self
189            .db
190            .get_identifier(&alias)
191            .ok_or_else(|| JsValue::from_str("Identifier not found"))?;
192
193        let identifier = self
194            .inner
195            .load_identifier(&id_record.said)
196            .map_err(|e| JsValue::from_str(&format!("Load identifier error: {}", e)))?;
197        let signer = Signer::new_with_seed(&id_record.seed)
198            .map_err(|e| JsValue::from_str(&format!("Signer creation error: {}", e)))?;
199
200        Ok(JsIdentifier::new(alias, identifier, Arc::new(signer), self.db.clone(), id_record.watcher_oobi))
201    }
202
203    pub fn get_identifier_aliases(&self) -> Result<Vec<JsValue>, JsValue> {
204        let aliases: Vec<JsValue> = self
205            .db
206            .get_identifiers()
207            .iter()
208            .map(|(alias, _)| JsValue::from_str(alias))
209            .collect();
210        Ok(aliases)
211    }
212
213    pub fn incept(&self) -> Result<JsIdentifier, JsValue> {
214        let keys = KeysConfig::default();
215        let (next_pub_key, _next_secret_keys) =
216            match keys.next.derive_key_pair() {
217                Ok(pair) => pair,
218                Err(e) => {
219                    return Err(JsValue::from_str(&format!(
220                        "Failed to derive keys: {}",
221                        e
222                    )))
223                }
224            };
225
226        let signer = match Signer::new_with_seed(&keys.current.clone()) {
227            Ok(s) => Arc::new(s),
228            Err(e) => {
229                return Err(JsValue::from_str(&format!(
230                    "Failed to create signer: {}",
231                    e
232                )))
233            }
234        };
235
236        let next_pub_keys = vec![BasicPrefix::Ed25519NT(next_pub_key)];
237        let public_keys = vec![BasicPrefix::Ed25519(signer.public_key())];
238
239        let signing_inception =
240            self.inner
241                .incept(public_keys, next_pub_keys)
242                .map_err(|_| JsValue::from_str("Incept error"))?;
243        let signature = SelfSigningPrefix::new(
244            cesrox::primitives::codes::self_signing::SelfSigning::Ed25519Sha512,
245            signer.sign(signing_inception.as_bytes()).unwrap(),
246        );
247        let signing_identifier = self
248            .inner
249            .finalize_incept(signing_inception.as_bytes(), &signature)
250            .map_err(|_| JsValue::from_str("Finalize error"))?;
251
252        let kel = format!("{:?}", signing_identifier.get_own_kel().unwrap());
253        self.process_kel(kel)?;
254
255        let prefix = signing_identifier.get_prefix();
256        let alias = prefix.to_string();
257        self.db.add_identifier(&alias, &prefix.clone(), &keys.current).map_err(|e| {
258            JsValue::from_str(&format!("Failed to add identifier to DB: {}", e))
259        })?;
260
261        Ok(JsIdentifier::new(alias, signing_identifier, signer.clone(), self.db.clone(), None))
262    }
263
264    pub fn process_kel(
265        &self,
266        kel: String,
267    ) -> Result<(), JsValue> {
268        let parsed_kel: Vec<Message> = parse_event_stream(kel.as_bytes())
269            .map_err(|e| {
270                JsValue::from_str(&format!("Failed to parse KEL: {}", e))
271            })?;
272        let parsed_kel = parsed_kel
273            .into_iter()
274            .collect::<Vec<_>>();
275
276        self.inner.process_kel(&parsed_kel).map_err(|e| {
277            JsValue::from_str(&format!("Process events error: {}", e))
278        })?;
279
280        Ok(())
281    }
282
283    pub fn process_tel(&self, tel: String) -> Result<(), JsValue> {
284        self.inner.process_tel(tel.as_bytes()).map_err(|e| {
285            JsValue::from_str(&format!("Process events error: {}", e))
286        })?;
287
288        Ok(())
289    }
290
291    pub fn get_vc_state(&self, prefix: String) -> Result<VcState, JsValue> {
292        let said: SelfAddressingIdentifier = prefix.parse().map_err(|e| {
293            JsValue::from_str(&format!("Invalid prefix: {}", e))
294        })?;
295
296        self.inner.get_vc_state(&said)
297            .map_err(|e| JsValue::from_str(&format!("Get VC state error: {}", e)))
298            .map(|state| {
299                match state {
300                    Some(TelState::Issued(_)) => VcState::Issued,
301                    Some(TelState::Revoked) => VcState::Revoked,
302                    None | Some(TelState::NotIssued) => VcState::NotIssued,
303                }
304            })
305    }
306
307    pub async fn verify(
308        &self,
309        identifier: &JsIdentifier,
310        oobi_array: JsValue,
311        message: String,
312    ) -> Result<JsValue, JsValue> {
313        let oobis: Vec<Oobi> =
314            serde_wasm_bindgen::from_value(oobi_array).unwrap_or(vec![]);
315        let watcher_url = identifier
316            .watcher_oobi
317            .clone()
318            .ok_or_else(|| JsValue::from_str("Watcher for identifier not set"))?
319            .url;
320        self.resolve_oobis(&watcher_url.to_string(), oobis.clone())
321            .await
322            .map_err(|e| {
323                JsValue::from_str(&format!("Failed to resolve OOBIs: {:?}", e))
324            })?;
325
326        let (_rest, cesr) = cesrox::parse(message.as_bytes()).map_err(|e| {
327            JsValue::from_str(&format!("Failed to parse CESR: {}", e))
328        })?;
329        let att: acdc::Attestation = match cesr.payload {
330            cesrox::payload::Payload::JSON(items) => {
331                serde_json::from_slice(&items).map_err(|_e| ()).map_err(
332                    |_| JsValue::from_str("Failed to parse JSON payload"),
333                )?
334            }
335            cesrox::payload::Payload::CBOR(items) => {
336                serde_cbor::from_slice(&items).map_err(|_e| ()).map_err(
337                    |_| JsValue::from_str("Failed to parse CBOR payload"),
338                )?
339            }
340            cesrox::payload::Payload::MGPK(_items) => todo!(),
341        };
342
343        let issuer_id: IdentifierPrefix = att.issuer.parse().map_err(|e| {
344            JsValue::from_str(&format!("Failed to parse issuer ID: {}", e))
345        })?;
346
347        let kel =
348            self.query_kel(identifier, issuer_id).await.map_err(|e| {
349                JsValue::from_str(&format!("Failed to query KEL: {:?}", e))
350            })?;
351        self.process_kel(kel).map_err(|e| {
352            JsValue::from_str(&format!("Failed to process KEL: {:?}", e))
353        })?;
354
355        let tel =
356            self.query_tel(identifier, att.clone())
357                .await
358                .map_err(|e| {
359                    JsValue::from_str(&format!("Failed to query TEL: {:?}", e))
360                })?;
361        self.process_tel(tel).map_err(|e| {
362            JsValue::from_str(&format!("Failed to process TEL: {:?}", e))
363        })?;
364
365        let vc_said = att.digest.unwrap();
366        let vc_state = self.get_vc_state(vc_said.to_string())?;
367        let result = match vc_state {
368            VcState::Issued => VerificationResult {
369                verified: true,
370                status: "issued".to_string(),
371            },
372            VcState::Revoked => VerificationResult {
373                verified: false,
374                status: "revoked".to_string(),
375            },
376            VcState::NotIssued => VerificationResult {
377                verified: false,
378                status: "not issued".to_string(),
379            },
380        };
381
382        Ok(result.into())
383    }
384}
385
386impl JsController {
387    async fn resolve_oobis(
388        &self,
389        watcher_url: &String,
390        oobis: Vec<Oobi>,
391    ) -> Result<(), JsValue> {
392        for oobi in oobis {
393            let _ = Request::post(&format!("{}resolve", watcher_url))
394                .header("Content-Type", "application/json")
395                .body(serde_json::to_string(&oobi).unwrap())
396                .unwrap()
397                .send()
398                .await
399                .map_err(|e| JsValue::from_str(&e.to_string()));
400        }
401        Ok(())
402    }
403
404    async fn query_kel(
405        &self,
406        signing_id: &JsIdentifier,
407        id: IdentifierPrefix,
408    ) -> Result<String, JsValue> {
409        let watcher_url = signing_id.watcher_oobi.clone().unwrap().url;
410        let watcher_id = signing_id.watcher_oobi.clone().unwrap().eid;
411        let qry = signing_id.inner.get_log_query(id, watcher_id);
412        let signer = signing_id.signer.clone();
413
414        let sig = SelfSigningPrefix::new(
415            cesrox::primitives::codes::self_signing::SelfSigning::Ed25519Sha512,
416            signer.sign(qry.encode().unwrap()).unwrap(),
417        );
418        let signatures = vec![IndexedSignature::new_both_same(sig, 0)];
419        let singed_kel_qry = SignedKelQuery::new_trans(
420            qry.clone(),
421            signing_id.inner.get_prefix().clone(),
422            signatures,
423        );
424
425        let mut delay = std::time::Duration::from_secs(1);
426        let mut kel = "".to_string();
427        for _i in 0..5 {
428            let signed_qry =
429                SignedQueryMessage::KelQuery(singed_kel_qry.clone());
430
431            let body_msg =
432                Message::Op(Op::Query(signed_qry)).to_cesr().unwrap();
433            let body = js_sys::Uint8Array::from(body_msg.as_slice());
434            let response =
435                Request::post(watcher_url.join("query").unwrap().as_str())
436                    .header("Content-Type", "application/json")
437                    .body(&body)
438                    .unwrap()
439                    .send()
440                    .await
441                    .map_err(|e| {
442                        JsValue::from_str(&format!(
443                            "Failed to send request: {}",
444                            e
445                        ))
446                    })?;
447
448            let code = response.status();
449            if code == 200 {
450                kel = response.text().await.map_err(|e| {
451                    JsValue::from_str(&format!(
452                        "Failed to get response text: {}",
453                        e
454                    ))
455                })?;
456                break;
457            } else {
458                gloo_timers::future::TimeoutFuture::new(
459                    delay.as_millis() as u32
460                )
461                .await;
462                delay *= 2;
463            }
464        }
465
466        Ok(kel)
467    }
468
469    async fn query_tel(
470        &self,
471        id: &JsIdentifier,
472        acdc_attestation: acdc::Attestation,
473    ) -> Result<String, JsValue> {
474        let watcher_url = id.watcher_oobi.clone().unwrap().url;
475        let vc_said = acdc_attestation.digest.unwrap();
476        let registry_id: said::SelfAddressingIdentifier =
477            acdc_attestation.registry_identifier.parse().unwrap();
478        let signer = id.signer.clone();
479
480        let tel_qry = id
481            .inner
482            .get_tel_query(
483                IdentifierPrefix::SelfAddressing(registry_id.into()),
484                IdentifierPrefix::SelfAddressing(vc_said.clone().into()),
485            )
486            .unwrap();
487
488        let signature_tel_query = SelfSigningPrefix::new(
489            cesrox::primitives::codes::self_signing::SelfSigning::Ed25519Sha512,
490            signer.sign(tel_qry.encode().unwrap()).unwrap(),
491        );
492
493        let tel_query = match &id.inner.id {
494            IdentifierPrefix::Basic(bp) => {
495                teliox::query::SignedTelQuery::new_nontrans(
496                    tel_qry.clone(),
497                    bp.clone(),
498                    signature_tel_query,
499                )
500            }
501            _ => {
502                let signatures =
503                    vec![keri_core::prefix::IndexedSignature::new_both_same(
504                        signature_tel_query,
505                        0,
506                    )];
507                teliox::query::SignedTelQuery::new_trans(
508                    tel_qry.clone(),
509                    id.inner.id.clone(),
510                    signatures,
511                )
512            }
513        };
514
515        let mut delay = std::time::Duration::from_secs(1);
516        let mut tel = "".to_string();
517        for _i in 0..5 {
518            let body = js_sys::Uint8Array::from(
519                tel_query.to_cesr().unwrap().as_slice(),
520            );
521            let response =
522                Request::post(watcher_url.join("query/tel").unwrap().as_str())
523                    .header("Content-Type", "application/json")
524                    .body(&body)
525                    .unwrap()
526                    .send()
527                    .await
528                    .map_err(|e| {
529                        JsValue::from_str(&format!(
530                            "Failed to send request: {}",
531                            e
532                        ))
533                    })?;
534
535            let code = response.status();
536            if code == 200 {
537                tel = response.text().await.map_err(|e| {
538                    JsValue::from_str(&format!(
539                        "Failed to get response text: {}",
540                        e
541                    ))
542                })?;
543                break;
544            } else {
545                gloo_timers::future::TimeoutFuture::new(
546                    delay.as_millis() as u32
547                )
548                .await;
549                delay *= 2;
550            }
551        }
552
553        Ok(tel)
554    }
555}
556
557pub struct VerificationResult {
558    verified: bool,
559    status: String,
560}
561
562impl From<VerificationResult> for JsValue {
563    fn from(val: VerificationResult) -> Self {
564        let obj = js_sys::Object::new();
565
566        js_sys::Reflect::set(&obj, &JsValue::from_str("verified"), &JsValue::from_bool(val.verified))
567            .expect("setting verified failed");
568
569        js_sys::Reflect::set(&obj, &JsValue::from_str("status"), &JsValue::from_str(&val.status))
570            .expect("setting status failed");
571
572        obj.into()
573    }
574}