Skip to main content

anda_engine/context/
web3.rs

1//! Web3 capability adapter used by engine contexts.
2//!
3//! Host applications supply a [`Web3ClientFeatures`] implementation, which this
4//! module normalizes into [`Web3SDK`] and exposes to engine contexts through the
5//! cryptographic and HTTP capability traits. Canister access is intentionally
6//! not part of this surface.
7
8use 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
17/// Backend-agnostic Web3 capability handle used by engine contexts.
18///
19/// The engine does not know about any concrete backend (TEE gateway, host Web3
20/// client, etc.). Every backend is supplied as a [`Web3ClientFeatures`] trait
21/// object; TEE-specific behaviour is expressed through
22/// [`Web3ClientFeatures::tee_attestation`], so the engine itself stays free of
23/// any Web3 or TEE dependency.
24pub struct Web3SDK {
25    client: Arc<dyn Web3ClientFeatures>,
26}
27
28impl Web3SDK {
29    /// Wraps a Web3 client implementation.
30    pub fn from_web3(client: Arc<dyn Web3ClientFeatures>) -> Self {
31        Self { client }
32    }
33
34    /// Returns a placeholder client whose operations fail with `not implemented`.
35    pub fn not_implemented() -> Self {
36        Self {
37            client: Arc::new(NotImplemented),
38        }
39    }
40
41    /// Returns the principal associated with the underlying client.
42    pub fn get_principal(&self) -> Principal {
43        self.client.get_principal()
44    }
45
46    /// Signs a challenge digest with the underlying client identity.
47    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    /// Produces optional TEE attestation bound to the authentication public key
55    /// and challenge nonce. Non-TEE clients return `Ok(None)`.
56    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
65/// Object-safe Web3 capability surface required by [`Web3SDK`].
66pub trait Web3ClientFeatures: Send + Sync + 'static {
67    /// Returns the principal associated with the client identity.
68    fn get_principal(&self) -> Principal;
69
70    /// Signs a digest and returns an authorization envelope.
71    fn sign_envelope(
72        &self,
73        message_digest: [u8; 32],
74    ) -> BoxPinFut<Result<SignedEnvelope, BoxError>>;
75
76    /// Produces optional TEE attestation bound to the authentication
77    /// `public_key` and challenge `nonce`.
78    ///
79    /// Non-TEE clients rely on the default implementation, which returns
80    /// `Ok(None)`. TEE-backed clients override this to attach attestation
81    /// evidence to the challenge envelope.
82    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    /// Derives a 256-bit AES-GCM key from the given derivation path
92    fn a256gcm_key(&self, derivation_path: Vec<Vec<u8>>) -> BoxPinFut<Result<[u8; 32], BoxError>>;
93
94    /// Signs a message using Ed25519 signature scheme from the given derivation path
95    fn ed25519_sign_message(
96        &self,
97        derivation_path: Vec<Vec<u8>>,
98        message: &[u8],
99    ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
100
101    /// Verifies an Ed25519 signature from the given derivation path
102    fn ed25519_verify(
103        &self,
104        derivation_path: Vec<Vec<u8>>,
105        message: &[u8],
106        signature: &[u8],
107    ) -> BoxPinFut<Result<(), BoxError>>;
108
109    /// Gets the public key for Ed25519 from the given derivation path
110    fn ed25519_public_key(
111        &self,
112        derivation_path: Vec<Vec<u8>>,
113    ) -> BoxPinFut<Result<[u8; 32], BoxError>>;
114
115    /// Signs a message using Secp256k1 BIP340 Schnorr signature from the given derivation path
116    fn secp256k1_sign_message_bip340(
117        &self,
118        derivation_path: Vec<Vec<u8>>,
119        message: &[u8],
120    ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
121
122    /// Verifies a Secp256k1 BIP340 Schnorr signature from the given derivation path
123    fn secp256k1_verify_bip340(
124        &self,
125        derivation_path: Vec<Vec<u8>>,
126        message: &[u8],
127        signature: &[u8],
128    ) -> BoxPinFut<Result<(), BoxError>>;
129
130    /// Signs a message using Secp256k1 ECDSA signature from the given derivation path
131    /// The message will be hashed with SHA-256 before signing
132    fn secp256k1_sign_message_ecdsa(
133        &self,
134        derivation_path: Vec<Vec<u8>>,
135        message: &[u8],
136    ) -> BoxPinFut<Result<[u8; 64], BoxError>>;
137
138    /// Signs a message hash using Secp256k1 ECDSA signature from the given derivation path
139    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    /// Verifies a Secp256k1 ECDSA signature from the given derivation path
146    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    /// Gets the compressed SEC1-encoded public key for Secp256k1 from the given derivation path
154    fn secp256k1_public_key(
155        &self,
156        derivation_path: Vec<Vec<u8>>,
157    ) -> BoxPinFut<Result<[u8; 33], BoxError>>;
158
159    /// Makes an HTTPs request
160    ///
161    /// # Arguments
162    /// * `url` - Target URL, should start with `https://`
163    /// * `method` - HTTP method (GET, POST, etc.)
164    /// * `headers` - Optional HTTP headers
165    /// * `body` - Optional request body (default empty)
166    fn https_call(
167        &self,
168        url: String,
169        method: http::Method,
170        headers: Option<http::HeaderMap>,
171        body: Option<Vec<u8>>, // default is empty
172    ) -> BoxPinFut<Result<reqwest::Response, BoxError>>;
173
174    /// Makes a signed HTTPs request with message authentication
175    ///
176    /// # Arguments
177    /// * `url` - Target URL
178    /// * `method` - HTTP method (GET, POST, etc.)
179    /// * `message_digest` - 32-byte message digest for signing
180    /// * `headers` - Optional HTTP headers
181    /// * `body` - Optional request body (default empty)
182    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>>, // default is empty
189    ) -> BoxPinFut<Result<reqwest::Response, BoxError>>;
190
191    /// Makes a signed CBOR-encoded RPC call
192    ///
193    /// # Arguments
194    /// * `endpoint` - URL endpoint to send the request to
195    /// * `method` - RPC method name to call
196    /// * `args` - Arguments to serialize as CBOR and send with the request
197    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>>, // default is empty
302    ) -> 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>>, // default is empty
313    ) -> 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    /// Makes an HTTPs request
329    ///
330    /// # Arguments
331    /// * `url` - Target URL, should start with `https://`
332    /// * `method` - HTTP method (GET, POST, etc.)
333    /// * `headers` - Optional HTTP headers
334    /// * `body` - Optional request body (default empty)
335    async fn https_call(
336        &self,
337        url: &str,
338        method: http::Method,
339        headers: Option<http::HeaderMap>,
340        body: Option<Vec<u8>>, // default is empty
341    ) -> Result<reqwest::Response, BoxError> {
342        self.client
343            .https_call(url.to_string(), method, headers, body)
344            .await
345    }
346
347    /// Makes a signed HTTPs request with message authentication
348    ///
349    /// # Arguments
350    /// * `url` - Target URL
351    /// * `method` - HTTP method (GET, POST, etc.)
352    /// * `message_digest` - 32-byte message digest for signing
353    /// * `headers` - Optional HTTP headers
354    /// * `body` - Optional request body (default empty)
355    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>>, // default is empty
362    ) -> Result<reqwest::Response, BoxError> {
363        self.client
364            .https_signed_call(url.to_string(), method, message_digest, headers, body)
365            .await
366    }
367
368    /// Makes a signed CBOR-encoded RPC call
369    ///
370    /// # Arguments
371    /// * `endpoint` - URL endpoint to send the request to
372    /// * `method` - RPC method name to call
373    /// * `args` - Arguments to serialize as CBOR and send with the request
374    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}