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}