lit_rust_sdk/
wrapped_keys.rs

1use crate::accs::hash_access_control_conditions;
2use crate::auth::{AuthContext, AuthSig};
3use crate::client::{ExecuteJsOptions, LitClient};
4use crate::error::LitSdkError;
5use crate::session::issue_session_sigs;
6use crate::types::EncryptParams;
7use base64ct::{Base64, Encoding};
8use ethers::types::U256;
9use rand::RngCore;
10use reqwest::Method;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14const LIT_PREFIX: &str = "lit_";
15const AUTH_HEADER_PREFIX: &str = "LitSessionSig:";
16
17const SERVICE_URL_TEST: &str = "https://test.wrapped.litprotocol.com/encrypted";
18const SERVICE_URL_PROD: &str = "https://wrapped.litprotocol.com/encrypted";
19
20const CID_SIGN_TRANSACTION_EVM: &str = "QmdSQqkdGF5EqPCBi4pidkjGQXLNoP5kp8gxGLtgzyiw7L";
21const CID_SIGN_TRANSACTION_SOLANA: &str = "QmVeR4rKuyN27gq1JrsoJqjN2GrwZD87Sm2vHzV5MmBxwb";
22const CID_SIGN_MESSAGE_EVM: &str = "QmQWwWjJXLiCKi7ZpfwXnGPeXAcjfG1VXjB6CLTb3Xh31X";
23const CID_SIGN_MESSAGE_SOLANA: &str = "QmawpLLPxL6GVKj7QW3PR8us4gNyVEijPpcDzD8Uo95jXR";
24const CID_GENERATE_KEY_EVM: &str = "QmSKi3kMRP7biW6HhNf79vcffMSXDSu7Yr1K653ZoGXsxw";
25const CID_GENERATE_KEY_SOLANA: &str = "QmZGd5gPcqTJmeM9Thdguz6DufFkQCa9Qq2oS6L3D6HD8o";
26const CID_EXPORT_PRIVATE_KEY: &str = "QmaCGGq6EqXezgBiwAAbwh2UTeeTZnLaHQxxDcXwRboFXM";
27const CID_BATCH_GENERATE_KEYS: &str = "QmUDB7jZfCMwh9CuQZZ4YDmrJnNdPq9NGdqHzmQE3RggSr";
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "lowercase")]
31pub enum WrappedKeysNetwork {
32    Evm,
33    Solana,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum WrappedKeysKeyType {
38    #[serde(rename = "K256")]
39    K256,
40    #[serde(rename = "ed25519")]
41    Ed25519,
42}
43
44impl WrappedKeysNetwork {
45    pub fn key_type(self) -> WrappedKeysKeyType {
46        match self {
47            WrappedKeysNetwork::Evm => WrappedKeysKeyType::K256,
48            WrappedKeysNetwork::Solana => WrappedKeysKeyType::Ed25519,
49        }
50    }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct StoredKeyMetadata {
56    pub public_key: String,
57    pub pkp_address: String,
58    pub key_type: WrappedKeysKeyType,
59    pub lit_network: String,
60    pub memo: String,
61    pub id: String,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct StoredKeyData {
67    #[serde(flatten)]
68    pub metadata: StoredKeyMetadata,
69    pub ciphertext: String,
70    pub data_to_encrypt_hash: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct StoreEncryptedKeyResult {
76    pub id: String,
77    pub pkp_address: String,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct StoreEncryptedKeyBatchResult {
83    pub pkp_address: String,
84    pub ids: Vec<String>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct GeneratePrivateKeyResult {
90    pub pkp_address: String,
91    pub generated_public_key: String,
92    pub id: String,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct ExportPrivateKeyResult {
98    pub pkp_address: String,
99    pub decrypted_private_key: String,
100    pub public_key: String,
101    pub lit_network: String,
102    pub key_type: WrappedKeysKeyType,
103    pub memo: String,
104    pub id: String,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct ImportPrivateKeyResult {
110    pub pkp_address: String,
111    pub id: String,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct StoreEncryptedKeyParams {
117    pub ciphertext: String,
118    pub data_to_encrypt_hash: String,
119    pub public_key: String,
120    pub key_type: WrappedKeysKeyType,
121    pub memo: String,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct GenerateKeyParams {
127    pub memo: String,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct SignMessageParams {
133    pub message_to_sign: String,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct GeneratePrivateKeyAction {
139    pub network: WrappedKeysNetwork,
140    pub generate_key_params: GenerateKeyParams,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub sign_message_params: Option<SignMessageParams>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct BatchGeneratePrivateKeysParams {
148    pub actions: Vec<GeneratePrivateKeyAction>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct BatchGeneratePrivateKeysActionResult {
154    pub generate_encrypted_private_key: GeneratePrivateKeyResultWithMemo,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub sign_message: Option<BatchSignedMessageResult>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct BatchGeneratePrivateKeysResult {
162    pub pkp_address: String,
163    pub results: Vec<BatchGeneratePrivateKeysActionResult>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct GeneratePrivateKeyResultWithMemo {
169    pub pkp_address: String,
170    pub generated_public_key: String,
171    pub id: String,
172    pub memo: String,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct BatchSignedMessageResult {
178    pub signature: String,
179}
180
181#[derive(Clone)]
182pub struct WrappedKeysClient {
183    lit_client: LitClient,
184    http: reqwest::Client,
185}
186
187impl WrappedKeysClient {
188    pub fn new(lit_client: LitClient) -> Result<Self, LitSdkError> {
189        let http = reqwest::Client::builder()
190            .user_agent("lit-sdk-rust-wrapped-keys/0.1.0")
191            .build()?;
192        Ok(Self { lit_client, http })
193    }
194
195    pub fn lit_client(&self) -> &LitClient {
196        &self.lit_client
197    }
198
199    pub async fn generate_private_key(
200        &self,
201        auth_context: &AuthContext,
202        network: WrappedKeysNetwork,
203        memo: impl Into<String>,
204        user_max_price_wei: Option<U256>,
205    ) -> Result<GeneratePrivateKeyResult, LitSdkError> {
206        let pkp_address = pkp_address_from_auth_context(auth_context)?;
207        let access_control_conditions = pkp_access_control_conditions(&pkp_address);
208
209        let cid = match network {
210            WrappedKeysNetwork::Evm => CID_GENERATE_KEY_EVM,
211            WrappedKeysNetwork::Solana => CID_GENERATE_KEY_SOLANA,
212        };
213
214        let js_params = serde_json::json!({
215            "pkpAddress": pkp_address,
216            "accessControlConditions": access_control_conditions,
217        });
218
219        let lit_action_res = self
220            .lit_client
221            .execute_js_with_options(
222                None,
223                Some(cid.to_string()),
224                Some(js_params),
225                auth_context,
226                ExecuteJsOptions {
227                    use_single_node: true,
228                    user_max_price_wei,
229                    ..Default::default()
230                },
231            )
232            .await?;
233
234        let generated: GenerateEncryptedKeyResponse = decode_lit_action_json(&lit_action_res)?;
235        let store_res = self
236            .store_encrypted_key(
237                auth_context,
238                StoreEncryptedKeyParams {
239                    ciphertext: generated.ciphertext,
240                    data_to_encrypt_hash: generated.data_to_encrypt_hash,
241                    public_key: generated.public_key.clone(),
242                    key_type: network.key_type(),
243                    memo: memo.into(),
244                },
245            )
246            .await?;
247
248        Ok(GeneratePrivateKeyResult {
249            pkp_address: store_res.pkp_address,
250            id: store_res.id,
251            generated_public_key: generated.public_key,
252        })
253    }
254
255    pub async fn export_private_key(
256        &self,
257        auth_context: &AuthContext,
258        network: WrappedKeysNetwork,
259        id: &str,
260        user_max_price_wei: Option<U256>,
261    ) -> Result<ExportPrivateKeyResult, LitSdkError> {
262        let stored = self.get_encrypted_key(auth_context, id).await?;
263        let access_control_conditions = pkp_access_control_conditions(&stored.metadata.pkp_address);
264
265        let js_params = serde_json::json!({
266            "pkpAddress": stored.metadata.pkp_address,
267            "ciphertext": stored.ciphertext,
268            "dataToEncryptHash": stored.data_to_encrypt_hash,
269            "accessControlConditions": access_control_conditions,
270        });
271
272        let lit_action_res = self
273            .lit_client
274            .execute_js_with_options(
275                None,
276                Some(lit_action_cid_for_export_private_key(network).to_string()),
277                Some(js_params),
278                auth_context,
279                ExecuteJsOptions {
280                    use_single_node: false,
281                    user_max_price_wei,
282                    ..Default::default()
283                },
284            )
285            .await?;
286
287        let decrypted_private_key = decode_lit_action_string(&lit_action_res)?;
288
289        Ok(ExportPrivateKeyResult {
290            pkp_address: stored.metadata.pkp_address,
291            decrypted_private_key,
292            public_key: stored.metadata.public_key,
293            lit_network: stored.metadata.lit_network,
294            key_type: stored.metadata.key_type,
295            memo: stored.metadata.memo,
296            id: stored.metadata.id,
297        })
298    }
299
300    pub async fn list_encrypted_key_metadata(
301        &self,
302        auth_context: &AuthContext,
303    ) -> Result<Vec<StoredKeyMetadata>, LitSdkError> {
304        let base_url = service_url_for_lit_network(self.lit_client.network_name())?;
305        let pkp_address = pkp_address_from_auth_context(auth_context)?;
306        let session_sig = session_sig_for_service(&self.lit_client, auth_context)?;
307        let request_id = random_request_id();
308
309        let url = format!("{base_url}/{pkp_address}");
310        self.service_request(Method::GET, &url, &session_sig, &request_id, None)
311            .await
312    }
313
314    pub async fn get_encrypted_key(
315        &self,
316        auth_context: &AuthContext,
317        id: &str,
318    ) -> Result<StoredKeyData, LitSdkError> {
319        let base_url = service_url_for_lit_network(self.lit_client.network_name())?;
320        let pkp_address = pkp_address_from_auth_context(auth_context)?;
321        let session_sig = session_sig_for_service(&self.lit_client, auth_context)?;
322        let request_id = random_request_id();
323
324        let url = format!("{base_url}/{pkp_address}/{id}");
325        self.service_request(Method::GET, &url, &session_sig, &request_id, None)
326            .await
327    }
328
329    pub async fn store_encrypted_key(
330        &self,
331        auth_context: &AuthContext,
332        params: StoreEncryptedKeyParams,
333    ) -> Result<StoreEncryptedKeyResult, LitSdkError> {
334        let base_url = service_url_for_lit_network(self.lit_client.network_name())?;
335        let session_sig = session_sig_for_service(&self.lit_client, auth_context)?;
336        let request_id = random_request_id();
337
338        let body = serde_json::to_value(params).map_err(|e| LitSdkError::Network(e.to_string()))?;
339        self.service_request(
340            Method::POST,
341            base_url,
342            &session_sig,
343            &request_id,
344            Some(body),
345        )
346        .await
347    }
348
349    pub async fn store_encrypted_key_batch(
350        &self,
351        auth_context: &AuthContext,
352        key_batch: Vec<StoreEncryptedKeyParams>,
353    ) -> Result<StoreEncryptedKeyBatchResult, LitSdkError> {
354        let base_url = service_url_for_lit_network(self.lit_client.network_name())?;
355        let session_sig = session_sig_for_service(&self.lit_client, auth_context)?;
356        let request_id = random_request_id();
357
358        let body = serde_json::json!({ "keyParamsBatch": key_batch });
359        let url = format!("{base_url}_batch");
360        self.service_request(Method::POST, &url, &session_sig, &request_id, Some(body))
361            .await
362    }
363
364    pub async fn import_private_key(
365        &self,
366        auth_context: &AuthContext,
367        private_key: &str,
368        public_key: &str,
369        key_type: WrappedKeysKeyType,
370        memo: impl Into<String>,
371    ) -> Result<ImportPrivateKeyResult, LitSdkError> {
372        let pkp_address = pkp_address_from_auth_context(auth_context)?;
373        let access_control_conditions = pkp_access_control_conditions(&pkp_address);
374
375        let salted_private_key = format!("{LIT_PREFIX}{private_key}");
376        let hashed_accs = hex::encode(hash_access_control_conditions(&access_control_conditions)?);
377        let encrypted = self
378            .lit_client
379            .encrypt(EncryptParams {
380                data_to_encrypt: salted_private_key.as_bytes().to_vec(),
381                unified_access_control_conditions: None,
382                hashed_access_control_conditions_hex: Some(hashed_accs),
383                metadata: None,
384            })
385            .await?;
386
387        let store_res = self
388            .store_encrypted_key(
389                auth_context,
390                StoreEncryptedKeyParams {
391                    ciphertext: encrypted.ciphertext_base64,
392                    data_to_encrypt_hash: encrypted.data_to_encrypt_hash_hex,
393                    public_key: public_key.to_string(),
394                    key_type,
395                    memo: memo.into(),
396                },
397            )
398            .await?;
399
400        Ok(ImportPrivateKeyResult {
401            pkp_address: store_res.pkp_address,
402            id: store_res.id,
403        })
404    }
405
406    pub async fn sign_message_with_encrypted_key(
407        &self,
408        auth_context: &AuthContext,
409        network: WrappedKeysNetwork,
410        id: &str,
411        message_to_sign: &str,
412        user_max_price_wei: Option<U256>,
413    ) -> Result<String, LitSdkError> {
414        let stored = self.get_encrypted_key(auth_context, id).await?;
415        let access_control_conditions = pkp_access_control_conditions(&stored.metadata.pkp_address);
416
417        let cid = match network {
418            WrappedKeysNetwork::Evm => CID_SIGN_MESSAGE_EVM,
419            WrappedKeysNetwork::Solana => CID_SIGN_MESSAGE_SOLANA,
420        };
421
422        let js_params = serde_json::json!({
423            "pkpAddress": stored.metadata.pkp_address,
424            "ciphertext": stored.ciphertext,
425            "dataToEncryptHash": stored.data_to_encrypt_hash,
426            "messageToSign": message_to_sign,
427            "accessControlConditions": access_control_conditions,
428        });
429
430        let lit_action_res = self
431            .lit_client
432            .execute_js_with_options(
433                None,
434                Some(cid.to_string()),
435                Some(js_params),
436                auth_context,
437                ExecuteJsOptions {
438                    use_single_node: false,
439                    user_max_price_wei,
440                    ..Default::default()
441                },
442            )
443            .await?;
444
445        decode_lit_action_string(&lit_action_res)
446    }
447
448    #[allow(clippy::too_many_arguments)]
449    pub async fn sign_transaction_with_encrypted_key<T: Serialize>(
450        &self,
451        auth_context: &AuthContext,
452        network: WrappedKeysNetwork,
453        id: &str,
454        unsigned_transaction: T,
455        broadcast: bool,
456        versioned_transaction: Option<bool>,
457        user_max_price_wei: Option<U256>,
458    ) -> Result<String, LitSdkError> {
459        let stored = self.get_encrypted_key(auth_context, id).await?;
460        let access_control_conditions = pkp_access_control_conditions(&stored.metadata.pkp_address);
461
462        let cid = match network {
463            WrappedKeysNetwork::Evm => CID_SIGN_TRANSACTION_EVM,
464            WrappedKeysNetwork::Solana => CID_SIGN_TRANSACTION_SOLANA,
465        };
466
467        let unsigned_tx_val = serde_json::to_value(unsigned_transaction)
468            .map_err(|e| LitSdkError::Network(e.to_string()))?;
469
470        let inner = if let Some(v) = versioned_transaction {
471            serde_json::json!({
472                "pkpAddress": stored.metadata.pkp_address,
473                "ciphertext": stored.ciphertext,
474                "dataToEncryptHash": stored.data_to_encrypt_hash,
475                "unsignedTransaction": unsigned_tx_val,
476                "broadcast": broadcast,
477                "accessControlConditions": access_control_conditions,
478                "versionedTransaction": v,
479            })
480        } else {
481            serde_json::json!({
482                "pkpAddress": stored.metadata.pkp_address,
483                "ciphertext": stored.ciphertext,
484                "dataToEncryptHash": stored.data_to_encrypt_hash,
485                "unsignedTransaction": unsigned_tx_val,
486                "broadcast": broadcast,
487                "accessControlConditions": access_control_conditions,
488            })
489        };
490        let mut js_params = inner.clone();
491        if let Some(obj) = js_params.as_object_mut() {
492            obj.insert("jsParams".into(), inner);
493        }
494
495        let lit_action_res = self
496            .lit_client
497            .execute_js_with_options(
498                None,
499                Some(cid.to_string()),
500                Some(js_params),
501                auth_context,
502                ExecuteJsOptions {
503                    use_single_node: false,
504                    user_max_price_wei,
505                    ..Default::default()
506                },
507            )
508            .await?;
509
510        decode_lit_action_string(&lit_action_res)
511    }
512
513    pub async fn batch_generate_private_keys(
514        &self,
515        auth_context: &AuthContext,
516        params: BatchGeneratePrivateKeysParams,
517        user_max_price_wei: Option<U256>,
518    ) -> Result<BatchGeneratePrivateKeysResult, LitSdkError> {
519        let pkp_address = pkp_address_from_auth_context(auth_context)?;
520        let access_control_conditions = pkp_access_control_conditions(&pkp_address);
521
522        let lit_action_res = self
523            .lit_client
524            .execute_js_with_options(
525                None,
526                Some(CID_BATCH_GENERATE_KEYS.to_string()),
527                Some(serde_json::json!({
528                    "actions": params.actions,
529                    "accessControlConditions": access_control_conditions,
530                })),
531                auth_context,
532                ExecuteJsOptions {
533                    use_single_node: true,
534                    user_max_price_wei,
535                    ..Default::default()
536                },
537            )
538            .await?;
539
540        let action_results: Vec<BatchGenerateLitActionResult> =
541            decode_lit_action_json(&lit_action_res)?;
542        let store_batch: Vec<StoreEncryptedKeyParams> = action_results
543            .iter()
544            .map(|r| StoreEncryptedKeyParams {
545                ciphertext: r.generate_encrypted_private_key.ciphertext.clone(),
546                data_to_encrypt_hash: r
547                    .generate_encrypted_private_key
548                    .data_to_encrypt_hash
549                    .clone(),
550                public_key: r.generate_encrypted_private_key.public_key.clone(),
551                key_type: r.network.key_type(),
552                memo: r.generate_encrypted_private_key.memo.clone(),
553            })
554            .collect();
555
556        let stored = self
557            .store_encrypted_key_batch(auth_context, store_batch)
558            .await?;
559
560        let mut results = vec![];
561        for (idx, r) in action_results.into_iter().enumerate() {
562            let id = stored.ids.get(idx).cloned().ok_or_else(|| {
563                LitSdkError::Network(
564                    "wrapped keys service returned fewer ids than requested".into(),
565                )
566            })?;
567
568            results.push(BatchGeneratePrivateKeysActionResult {
569                generate_encrypted_private_key: GeneratePrivateKeyResultWithMemo {
570                    pkp_address: pkp_address.clone(),
571                    generated_public_key: r.generate_encrypted_private_key.public_key,
572                    id,
573                    memo: r.generate_encrypted_private_key.memo,
574                },
575                sign_message: r.sign_message,
576            });
577        }
578
579        Ok(BatchGeneratePrivateKeysResult {
580            pkp_address,
581            results,
582        })
583    }
584
585    async fn service_request<T: serde::de::DeserializeOwned>(
586        &self,
587        method: Method,
588        url: &str,
589        session_sig: &AuthSig,
590        request_id: &str,
591        body: Option<Value>,
592    ) -> Result<T, LitSdkError> {
593        let lit_network = self.lit_client.network_name();
594        let auth_header = compose_service_auth_header(session_sig)?;
595
596        let mut req = self
597            .http
598            .request(method, url)
599            .header("x-correlation-id", request_id)
600            .header("Content-Type", "application/json")
601            .header("Lit-Network", lit_network)
602            .header("Authorization", auth_header);
603        if let Some(body_val) = body {
604            req = req.json(&body_val);
605        }
606
607        let res = req.send().await?;
608        let status = res.status();
609        if !status.is_success() {
610            let txt = res.text().await.unwrap_or_default();
611            return Err(LitSdkError::Network(format!(
612                "Request({request_id}) for wrapped key failed. HTTP({}): {txt}",
613                status.as_u16()
614            )));
615        }
616
617        res.json::<T>().await.map_err(|e| {
618            LitSdkError::Network(format!(
619                "Request({request_id}) for wrapped key failed to parse JSON: {e}"
620            ))
621        })
622    }
623}
624
625fn service_url_for_lit_network(lit_network: &str) -> Result<&'static str, LitSdkError> {
626    match lit_network {
627        "naga-dev" | "naga-test" => Ok(SERVICE_URL_TEST),
628        "naga" => Ok(SERVICE_URL_PROD),
629        other => Err(LitSdkError::Config(format!(
630            "wrapped keys service unsupported for network {other}"
631        ))),
632    }
633}
634
635fn pkp_address_from_auth_context(auth_context: &AuthContext) -> Result<String, LitSdkError> {
636    if auth_context.delegation_auth_sig.algo.as_deref() != Some("LIT_BLS") {
637        return Err(LitSdkError::Config(
638            "wrapped keys requires a PKP auth context (delegationAuthSig algo LIT_BLS)".into(),
639        ));
640    }
641    Ok(auth_context.delegation_auth_sig.address.clone())
642}
643
644fn pkp_access_control_conditions(pkp_address: &str) -> Value {
645    serde_json::json!([
646        {
647            "contractAddress": "",
648            "standardContractType": "",
649            "chain": "ethereum",
650            "method": "",
651            "parameters": [":userAddress"],
652            "returnValueTest": {
653                "comparator": "=",
654                "value": pkp_address,
655            }
656        }
657    ])
658}
659
660fn session_sig_for_service(
661    lit_client: &LitClient,
662    auth_context: &AuthContext,
663) -> Result<AuthSig, LitSdkError> {
664    let url = lit_client
665        .handshake_result()
666        .connected_nodes
667        .first()
668        .ok_or_else(|| LitSdkError::Network("no connected nodes available".into()))?
669        .clone();
670    let map = issue_session_sigs(
671        &auth_context.session_key_pair,
672        &auth_context.auth_config,
673        &auth_context.delegation_auth_sig,
674        &[url],
675    )?;
676    map.into_iter()
677        .next()
678        .map(|(_, sig)| sig)
679        .ok_or_else(|| LitSdkError::Network("failed to generate sessionSig".into()))
680}
681
682fn compose_service_auth_header(session_sig: &AuthSig) -> Result<String, LitSdkError> {
683    let session_sig_json =
684        serde_json::to_string(session_sig).map_err(|e| LitSdkError::Network(e.to_string()))?;
685    let encoded = Base64::encode_string(session_sig_json.as_bytes());
686    Ok(format!("{AUTH_HEADER_PREFIX}{encoded}"))
687}
688
689fn random_request_id() -> String {
690    let mut bytes = [0u8; 8];
691    rand::thread_rng().fill_bytes(&mut bytes);
692    hex::encode(bytes)
693}
694
695fn lit_action_cid_for_export_private_key(_network: WrappedKeysNetwork) -> &'static str {
696    CID_EXPORT_PRIVATE_KEY
697}
698
699fn decode_lit_action_string(res: &crate::types::ExecuteJsResponse) -> Result<String, LitSdkError> {
700    match &res.response {
701        Value::String(s) => {
702            if s.is_empty() {
703                return Err(LitSdkError::Network(
704                    "Lit Action returned an empty response".into(),
705                ));
706            }
707            if s.starts_with("Error:") {
708                return Err(LitSdkError::Network(format!(
709                    "error executing Lit Action: {s}"
710                )));
711            }
712            Ok(s.clone())
713        }
714        other => Err(LitSdkError::Network(format!(
715            "expected Lit Action response string, got: {other}"
716        ))),
717    }
718}
719
720fn decode_lit_action_json<T: serde::de::DeserializeOwned>(
721    res: &crate::types::ExecuteJsResponse,
722) -> Result<T, LitSdkError> {
723    let s = match &res.response {
724        Value::String(s) => s.clone(),
725        other => serde_json::to_string(other).map_err(|e| LitSdkError::Network(e.to_string()))?,
726    };
727    if s.is_empty() {
728        return Err(LitSdkError::Network(
729            "Lit Action returned an empty response".into(),
730        ));
731    }
732    if s.starts_with("Error:") {
733        return Err(LitSdkError::Network(format!(
734            "error executing Lit Action: {s}"
735        )));
736    }
737    serde_json::from_str(&s).map_err(|e| LitSdkError::Network(e.to_string()))
738}
739
740#[derive(Debug, Deserialize)]
741#[serde(rename_all = "camelCase")]
742struct GenerateEncryptedKeyResponse {
743    ciphertext: String,
744    data_to_encrypt_hash: String,
745    public_key: String,
746}
747
748#[derive(Debug, Clone, Deserialize)]
749#[serde(rename_all = "camelCase")]
750struct BatchGenerateEncryptedPrivateKeyResult {
751    ciphertext: String,
752    data_to_encrypt_hash: String,
753    public_key: String,
754    memo: String,
755}
756
757#[derive(Debug, Clone, Deserialize)]
758#[serde(rename_all = "camelCase")]
759struct BatchGenerateLitActionResult {
760    network: WrappedKeysNetwork,
761    #[serde(default)]
762    sign_message: Option<BatchSignedMessageResult>,
763    generate_encrypted_private_key: BatchGenerateEncryptedPrivateKeyResult,
764}