1use anda_cloud_cdk::TEEInfo;
9use anda_core::{BoxError, BoxPinFut, HttpFeatures, KeysFeatures};
10use candid::Principal;
11use cbor2::{from_slice, to_canonical_vec};
12use ic_auth_types::ByteBufB64;
13use ic_auth_verifier::envelope::SignedEnvelope;
14use serde::{Serialize, de::DeserializeOwned};
15use std::sync::Arc;
16
17pub struct Web3SDK {
25 client: Arc<dyn Web3ClientFeatures>,
26}
27
28impl Web3SDK {
29 pub fn from_web3(client: Arc<dyn Web3ClientFeatures>) -> Self {
31 Self { client }
32 }
33
34 pub fn not_implemented() -> Self {
36 Self {
37 client: Arc::new(NotImplemented),
38 }
39 }
40
41 pub fn get_principal(&self) -> Principal {
43 self.client.get_principal()
44 }
45
46 pub async fn sign_envelope(
48 &self,
49 message_digest: [u8; 32],
50 ) -> Result<SignedEnvelope, BoxError> {
51 self.client.sign_envelope(message_digest).await
52 }
53
54 pub async fn tee_attestation(
57 &self,
58 public_key: ByteBufB64,
59 nonce: Vec<u8>,
60 ) -> Result<Option<TEEInfo>, BoxError> {
61 self.client.tee_attestation(public_key, nonce).await
62 }
63}
64
65pub trait Web3ClientFeatures: Send + Sync + 'static {
67 fn get_principal(&self) -> Principal;
69
70 fn sign_envelope(
72 &self,
73 message_digest: [u8; 32],
74 ) -> BoxPinFut<Result<SignedEnvelope, BoxError>>;
75
76 fn tee_attestation(
83 &self,
84 public_key: ByteBufB64,
85 nonce: Vec<u8>,
86 ) -> BoxPinFut<Result<Option<TEEInfo>, BoxError>> {
87 let _ = (public_key, nonce);
88 Box::pin(futures::future::ready(Ok(None)))
89 }
90
91 fn a256gcm_key(&self, derivation_path: Vec<Vec<u8>>) -> BoxPinFut<Result<[u8; 32], BoxError>>;
93
94 fn ed25519_sign_message(
96 &self,
97 derivation_path: Vec<Vec<u8>>,
98 message: &[u8],
99 ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
100
101 fn ed25519_verify(
103 &self,
104 derivation_path: Vec<Vec<u8>>,
105 message: &[u8],
106 signature: &[u8],
107 ) -> BoxPinFut<Result<(), BoxError>>;
108
109 fn ed25519_public_key(
111 &self,
112 derivation_path: Vec<Vec<u8>>,
113 ) -> BoxPinFut<Result<[u8; 32], BoxError>>;
114
115 fn secp256k1_sign_message_bip340(
117 &self,
118 derivation_path: Vec<Vec<u8>>,
119 message: &[u8],
120 ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
121
122 fn secp256k1_verify_bip340(
124 &self,
125 derivation_path: Vec<Vec<u8>>,
126 message: &[u8],
127 signature: &[u8],
128 ) -> BoxPinFut<Result<(), BoxError>>;
129
130 fn secp256k1_sign_message_ecdsa(
133 &self,
134 derivation_path: Vec<Vec<u8>>,
135 message: &[u8],
136 ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
137
138 fn secp256k1_sign_digest_ecdsa(
140 &self,
141 derivation_path: Vec<Vec<u8>>,
142 message_hash: &[u8],
143 ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
144
145 fn secp256k1_verify_ecdsa(
147 &self,
148 derivation_path: Vec<Vec<u8>>,
149 message_hash: &[u8],
150 signature: &[u8],
151 ) -> BoxPinFut<Result<(), BoxError>>;
152
153 fn secp256k1_public_key(
155 &self,
156 derivation_path: Vec<Vec<u8>>,
157 ) -> BoxPinFut<Result<[u8; 33], BoxError>>;
158
159 fn https_call(
167 &self,
168 url: String,
169 method: http::Method,
170 headers: Option<http::HeaderMap>,
171 body: Option<Vec<u8>>, ) -> BoxPinFut<Result<reqwest::Response, BoxError>>;
173
174 fn https_signed_call(
183 &self,
184 url: String,
185 method: http::Method,
186 message_digest: [u8; 32],
187 headers: Option<http::HeaderMap>,
188 body: Option<Vec<u8>>, ) -> BoxPinFut<Result<reqwest::Response, BoxError>>;
190
191 fn https_signed_rpc_raw(
198 &self,
199 endpoint: String,
200 method: String,
201 args: Vec<u8>,
202 ) -> BoxPinFut<Result<Vec<u8>, BoxError>>;
203}
204
205struct NotImplemented;
206
207impl Web3ClientFeatures for NotImplemented {
208 fn get_principal(&self) -> Principal {
209 Principal::anonymous()
210 }
211
212 fn sign_envelope(
213 &self,
214 _message_digest: [u8; 32],
215 ) -> BoxPinFut<Result<SignedEnvelope, BoxError>> {
216 Box::pin(futures::future::ready(Err("not implemented".into())))
217 }
218
219 fn a256gcm_key(&self, _derivation_path: Vec<Vec<u8>>) -> BoxPinFut<Result<[u8; 32], BoxError>> {
220 Box::pin(futures::future::ready(Err("not implemented".into())))
221 }
222
223 fn ed25519_sign_message(
224 &self,
225 _derivation_path: Vec<Vec<u8>>,
226 _message: &[u8],
227 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
228 Box::pin(futures::future::ready(Err("not implemented".into())))
229 }
230
231 fn ed25519_verify(
232 &self,
233 _derivation_path: Vec<Vec<u8>>,
234 _message: &[u8],
235 _signature: &[u8],
236 ) -> BoxPinFut<Result<(), BoxError>> {
237 Box::pin(futures::future::ready(Err("not implemented".into())))
238 }
239
240 fn ed25519_public_key(
241 &self,
242 _derivation_path: Vec<Vec<u8>>,
243 ) -> BoxPinFut<Result<[u8; 32], BoxError>> {
244 Box::pin(futures::future::ready(Err("not implemented".into())))
245 }
246
247 fn secp256k1_sign_message_bip340(
248 &self,
249 _derivation_path: Vec<Vec<u8>>,
250 _message: &[u8],
251 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
252 Box::pin(futures::future::ready(Err("not implemented".into())))
253 }
254
255 fn secp256k1_verify_bip340(
256 &self,
257 _derivation_path: Vec<Vec<u8>>,
258 _message: &[u8],
259 _signature: &[u8],
260 ) -> BoxPinFut<Result<(), BoxError>> {
261 Box::pin(futures::future::ready(Err("not implemented".into())))
262 }
263
264 fn secp256k1_sign_message_ecdsa(
265 &self,
266 _derivation_path: Vec<Vec<u8>>,
267 _message: &[u8],
268 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
269 Box::pin(futures::future::ready(Err("not implemented".into())))
270 }
271
272 fn secp256k1_sign_digest_ecdsa(
273 &self,
274 _derivation_path: Vec<Vec<u8>>,
275 _message_hash: &[u8],
276 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
277 Box::pin(futures::future::ready(Err("not implemented".into())))
278 }
279
280 fn secp256k1_verify_ecdsa(
281 &self,
282 _derivation_path: Vec<Vec<u8>>,
283 _message_hash: &[u8],
284 _signature: &[u8],
285 ) -> BoxPinFut<Result<(), BoxError>> {
286 Box::pin(futures::future::ready(Err("not implemented".into())))
287 }
288
289 fn secp256k1_public_key(
290 &self,
291 _derivation_path: Vec<Vec<u8>>,
292 ) -> BoxPinFut<Result<[u8; 33], BoxError>> {
293 Box::pin(futures::future::ready(Err("not implemented".into())))
294 }
295
296 fn https_call(
297 &self,
298 _url: String,
299 _method: http::Method,
300 _headers: Option<http::HeaderMap>,
301 _body: Option<Vec<u8>>, ) -> BoxPinFut<Result<reqwest::Response, BoxError>> {
303 Box::pin(futures::future::ready(Err("not implemented".into())))
304 }
305
306 fn https_signed_call(
307 &self,
308 _url: String,
309 _method: http::Method,
310 _message_digest: [u8; 32],
311 _headers: Option<http::HeaderMap>,
312 _body: Option<Vec<u8>>, ) -> BoxPinFut<Result<reqwest::Response, BoxError>> {
314 Box::pin(futures::future::ready(Err("not implemented".into())))
315 }
316
317 fn https_signed_rpc_raw(
318 &self,
319 _endpoint: String,
320 _method: String,
321 _params: Vec<u8>,
322 ) -> BoxPinFut<Result<Vec<u8>, BoxError>> {
323 Box::pin(futures::future::ready(Err("not implemented".into())))
324 }
325}
326
327impl HttpFeatures for &Web3SDK {
328 async fn https_call(
336 &self,
337 url: &str,
338 method: http::Method,
339 headers: Option<http::HeaderMap>,
340 body: Option<Vec<u8>>, ) -> Result<reqwest::Response, BoxError> {
342 self.client
343 .https_call(url.to_string(), method, headers, body)
344 .await
345 }
346
347 async fn https_signed_call(
356 &self,
357 url: &str,
358 method: http::Method,
359 message_digest: [u8; 32],
360 headers: Option<http::HeaderMap>,
361 body: Option<Vec<u8>>, ) -> Result<reqwest::Response, BoxError> {
363 self.client
364 .https_signed_call(url.to_string(), method, message_digest, headers, body)
365 .await
366 }
367
368 async fn https_signed_rpc<T>(
375 &self,
376 endpoint: &str,
377 method: &str,
378 args: impl Serialize + Send,
379 ) -> Result<T, BoxError>
380 where
381 T: DeserializeOwned,
382 {
383 let args = to_canonical_vec(&args)?;
384 let res = self
385 .client
386 .https_signed_rpc_raw(endpoint.to_string(), method.to_string(), args)
387 .await?;
388 let res = from_slice(&res[..])?;
389 Ok(res)
390 }
391}
392
393impl KeysFeatures for &Web3SDK {
394 async fn a256gcm_key(&self, derivation_path: Vec<Vec<u8>>) -> Result<[u8; 32], BoxError> {
395 self.client.a256gcm_key(derivation_path).await
396 }
397
398 async fn ed25519_sign_message(
399 &self,
400 derivation_path: Vec<Vec<u8>>,
401 message: &[u8],
402 ) -> Result<[u8; 64], BoxError> {
403 self.client
404 .ed25519_sign_message(derivation_path, message)
405 .await
406 }
407
408 async fn ed25519_verify(
409 &self,
410 derivation_path: Vec<Vec<u8>>,
411 message: &[u8],
412 signature: &[u8],
413 ) -> Result<(), BoxError> {
414 self.client
415 .ed25519_verify(derivation_path, message, signature)
416 .await
417 }
418
419 async fn ed25519_public_key(
420 &self,
421 derivation_path: Vec<Vec<u8>>,
422 ) -> Result<[u8; 32], BoxError> {
423 self.client.ed25519_public_key(derivation_path).await
424 }
425
426 async fn secp256k1_sign_message_bip340(
427 &self,
428 derivation_path: Vec<Vec<u8>>,
429 message: &[u8],
430 ) -> Result<[u8; 64], BoxError> {
431 self.client
432 .secp256k1_sign_message_bip340(derivation_path, message)
433 .await
434 }
435
436 async fn secp256k1_verify_bip340(
437 &self,
438 derivation_path: Vec<Vec<u8>>,
439 message: &[u8],
440 signature: &[u8],
441 ) -> Result<(), BoxError> {
442 self.client
443 .secp256k1_verify_bip340(derivation_path, message, signature)
444 .await
445 }
446
447 async fn secp256k1_sign_message_ecdsa(
448 &self,
449 derivation_path: Vec<Vec<u8>>,
450 message: &[u8],
451 ) -> Result<[u8; 64], BoxError> {
452 self.client
453 .secp256k1_sign_message_ecdsa(derivation_path, message)
454 .await
455 }
456
457 async fn secp256k1_sign_digest_ecdsa(
458 &self,
459 derivation_path: Vec<Vec<u8>>,
460 message_hash: &[u8],
461 ) -> Result<[u8; 64], BoxError> {
462 self.client
463 .secp256k1_sign_digest_ecdsa(derivation_path, message_hash)
464 .await
465 }
466
467 async fn secp256k1_verify_ecdsa(
468 &self,
469 derivation_path: Vec<Vec<u8>>,
470 message_hash: &[u8],
471 signature: &[u8],
472 ) -> Result<(), BoxError> {
473 self.client
474 .secp256k1_verify_ecdsa(derivation_path, message_hash, signature)
475 .await
476 }
477
478 async fn secp256k1_public_key(
479 &self,
480 derivation_path: Vec<Vec<u8>>,
481 ) -> Result<[u8; 33], BoxError> {
482 self.client.secp256k1_public_key(derivation_path).await
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 struct MockWeb3Client {
491 principal: Principal,
492 }
493
494 impl MockWeb3Client {
495 fn new(principal: Principal) -> Self {
496 Self { principal }
497 }
498 }
499
500 impl Web3ClientFeatures for MockWeb3Client {
501 fn get_principal(&self) -> Principal {
502 self.principal
503 }
504
505 fn sign_envelope(
506 &self,
507 _message_digest: [u8; 32],
508 ) -> BoxPinFut<Result<SignedEnvelope, BoxError>> {
509 Box::pin(futures::future::ready(Err(
510 "mock envelope unavailable".into()
511 )))
512 }
513
514 fn a256gcm_key(
515 &self,
516 _derivation_path: Vec<Vec<u8>>,
517 ) -> BoxPinFut<Result<[u8; 32], BoxError>> {
518 Box::pin(futures::future::ready(Ok([1; 32])))
519 }
520
521 fn ed25519_sign_message(
522 &self,
523 _derivation_path: Vec<Vec<u8>>,
524 _message: &[u8],
525 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
526 Box::pin(futures::future::ready(Ok([2; 64])))
527 }
528
529 fn ed25519_verify(
530 &self,
531 _derivation_path: Vec<Vec<u8>>,
532 _message: &[u8],
533 _signature: &[u8],
534 ) -> BoxPinFut<Result<(), BoxError>> {
535 Box::pin(futures::future::ready(Ok(())))
536 }
537
538 fn ed25519_public_key(
539 &self,
540 _derivation_path: Vec<Vec<u8>>,
541 ) -> BoxPinFut<Result<[u8; 32], BoxError>> {
542 Box::pin(futures::future::ready(Ok([3; 32])))
543 }
544
545 fn secp256k1_sign_message_bip340(
546 &self,
547 _derivation_path: Vec<Vec<u8>>,
548 _message: &[u8],
549 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
550 Box::pin(futures::future::ready(Ok([4; 64])))
551 }
552
553 fn secp256k1_verify_bip340(
554 &self,
555 _derivation_path: Vec<Vec<u8>>,
556 _message: &[u8],
557 _signature: &[u8],
558 ) -> BoxPinFut<Result<(), BoxError>> {
559 Box::pin(futures::future::ready(Ok(())))
560 }
561
562 fn secp256k1_sign_message_ecdsa(
563 &self,
564 _derivation_path: Vec<Vec<u8>>,
565 _message: &[u8],
566 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
567 Box::pin(futures::future::ready(Ok([5; 64])))
568 }
569
570 fn secp256k1_sign_digest_ecdsa(
571 &self,
572 _derivation_path: Vec<Vec<u8>>,
573 _message_hash: &[u8],
574 ) -> BoxPinFut<Result<[u8; 64], BoxError>> {
575 Box::pin(futures::future::ready(Ok([6; 64])))
576 }
577
578 fn secp256k1_verify_ecdsa(
579 &self,
580 _derivation_path: Vec<Vec<u8>>,
581 _message_hash: &[u8],
582 _signature: &[u8],
583 ) -> BoxPinFut<Result<(), BoxError>> {
584 Box::pin(futures::future::ready(Ok(())))
585 }
586
587 fn secp256k1_public_key(
588 &self,
589 _derivation_path: Vec<Vec<u8>>,
590 ) -> BoxPinFut<Result<[u8; 33], BoxError>> {
591 Box::pin(futures::future::ready(Ok([7; 33])))
592 }
593
594 fn https_call(
595 &self,
596 url: String,
597 _method: http::Method,
598 _headers: Option<http::HeaderMap>,
599 _body: Option<Vec<u8>>,
600 ) -> BoxPinFut<Result<reqwest::Response, BoxError>> {
601 Box::pin(futures::future::ready(
602 Err(format!("no http: {url}").into()),
603 ))
604 }
605
606 fn https_signed_call(
607 &self,
608 url: String,
609 _method: http::Method,
610 _message_digest: [u8; 32],
611 _headers: Option<http::HeaderMap>,
612 _body: Option<Vec<u8>>,
613 ) -> BoxPinFut<Result<reqwest::Response, BoxError>> {
614 Box::pin(futures::future::ready(Err(format!(
615 "no signed http: {url}"
616 )
617 .into())))
618 }
619
620 fn https_signed_rpc_raw(
621 &self,
622 _endpoint: String,
623 method: String,
624 _args: Vec<u8>,
625 ) -> BoxPinFut<Result<Vec<u8>, BoxError>> {
626 Box::pin(futures::future::ready(
627 to_canonical_vec(&format!("rpc:{method}")).map_err(|err| err.into()),
628 ))
629 }
630 }
631
632 #[tokio::test(flavor = "current_thread")]
633 async fn web3_sdk_delegates_to_mock_client_and_decodes_results() {
634 let principal = Principal::self_authenticating([1; 32]);
635 let sdk = Web3SDK::from_web3(Arc::new(MockWeb3Client::new(principal)));
636
637 assert_eq!(sdk.get_principal(), principal);
638 let client = &sdk.client;
639 assert_eq!(
640 client.a256gcm_key(vec![b"path".to_vec()]).await.unwrap(),
641 [1; 32]
642 );
643 assert_eq!(
644 client
645 .ed25519_sign_message(vec![b"path".to_vec()], b"message")
646 .await
647 .unwrap(),
648 [2; 64]
649 );
650 client
651 .ed25519_verify(vec![b"path".to_vec()], b"message", &[0; 64])
652 .await
653 .unwrap();
654 assert_eq!(
655 client
656 .ed25519_public_key(vec![b"path".to_vec()])
657 .await
658 .unwrap(),
659 [3; 32]
660 );
661 assert_eq!(
662 client
663 .secp256k1_sign_message_bip340(vec![b"path".to_vec()], b"message")
664 .await
665 .unwrap(),
666 [4; 64]
667 );
668 client
669 .secp256k1_verify_bip340(vec![b"path".to_vec()], b"message", &[0; 64])
670 .await
671 .unwrap();
672 assert_eq!(
673 client
674 .secp256k1_sign_message_ecdsa(vec![b"path".to_vec()], b"message")
675 .await
676 .unwrap(),
677 [5; 64]
678 );
679 assert_eq!(
680 client
681 .secp256k1_sign_digest_ecdsa(vec![b"path".to_vec()], &[0; 32])
682 .await
683 .unwrap(),
684 [6; 64]
685 );
686 client
687 .secp256k1_verify_ecdsa(vec![b"path".to_vec()], &[0; 32], &[0; 64])
688 .await
689 .unwrap();
690 assert_eq!(
691 client
692 .secp256k1_public_key(vec![b"path".to_vec()])
693 .await
694 .unwrap(),
695 [7; 33]
696 );
697
698 let rpc: String = (&sdk)
699 .https_signed_rpc("https://example.test/rpc", "ping", &("arg",))
700 .await
701 .unwrap();
702 assert_eq!(rpc, "rpc:ping");
703
704 assert!(
705 (&sdk)
706 .https_call("https://example.test", http::Method::GET, None, None)
707 .await
708 .unwrap_err()
709 .to_string()
710 .contains("no http")
711 );
712 assert!(
713 (&sdk)
714 .https_signed_call(
715 "https://example.test",
716 http::Method::POST,
717 [0; 32],
718 None,
719 None,
720 )
721 .await
722 .unwrap_err()
723 .to_string()
724 .contains("no signed http")
725 );
726 }
727
728 #[tokio::test(flavor = "current_thread")]
729 async fn not_implemented_web3_client_reports_errors_for_all_operations() {
730 let sdk = Web3SDK::not_implemented();
731 assert_eq!(sdk.get_principal(), Principal::anonymous());
732 let client = &sdk.client;
733
734 assert!(client.sign_envelope([0; 32]).await.is_err());
735 assert!(
736 client
737 .tee_attestation(ByteBufB64::from(vec![1u8; 32]), vec![2u8; 16])
738 .await
739 .unwrap()
740 .is_none()
741 );
742 assert!(client.a256gcm_key(Vec::new()).await.is_err());
743 assert!(client.ed25519_sign_message(Vec::new(), b"m").await.is_err());
744 assert!(
745 client
746 .ed25519_verify(Vec::new(), b"m", &[0; 64])
747 .await
748 .is_err()
749 );
750 assert!(client.ed25519_public_key(Vec::new()).await.is_err());
751 assert!(
752 client
753 .secp256k1_sign_message_bip340(Vec::new(), b"m")
754 .await
755 .is_err()
756 );
757 assert!(
758 client
759 .secp256k1_verify_bip340(Vec::new(), b"m", &[0; 64])
760 .await
761 .is_err()
762 );
763 assert!(
764 client
765 .secp256k1_sign_message_ecdsa(Vec::new(), b"m")
766 .await
767 .is_err()
768 );
769 assert!(
770 client
771 .secp256k1_sign_digest_ecdsa(Vec::new(), &[0; 32])
772 .await
773 .is_err()
774 );
775 assert!(
776 client
777 .secp256k1_verify_ecdsa(Vec::new(), &[0; 32], &[0; 64])
778 .await
779 .is_err()
780 );
781 assert!(client.secp256k1_public_key(Vec::new()).await.is_err());
782 assert!(
783 client
784 .https_call(
785 "https://example.test".to_string(),
786 http::Method::GET,
787 None,
788 None
789 )
790 .await
791 .is_err()
792 );
793 assert!(
794 client
795 .https_signed_call(
796 "https://example.test".to_string(),
797 http::Method::POST,
798 [0; 32],
799 None,
800 None,
801 )
802 .await
803 .is_err()
804 );
805 assert!(
806 client
807 .https_signed_rpc_raw(
808 "https://example.test".to_string(),
809 "rpc".to_string(),
810 Vec::new(),
811 )
812 .await
813 .is_err()
814 );
815
816 let rpc: Result<String, BoxError> = (&sdk)
817 .https_signed_rpc("https://example.test/rpc", "ping", &())
818 .await;
819 assert!(rpc.is_err());
820 }
821}