lit_rust_sdk/client/
pkp.rs

1use crate::auth::EthWalletProvider;
2use crate::types::{
3    AuthMethod, AuthSig, JsonSignSessionKeyResponseV1, LitResourceAbilityRequest,
4    SessionKeySignedMessage, SessionSignature, SessionSignatures, SignSessionKeyRequest,
5};
6use alloy::hex::FromHex;
7use alloy::primitives::Address;
8use alloy::signers::local::PrivateKeySigner;
9use ed25519_dalek::Signer;
10use eyre::Result;
11use rand::Rng;
12use serde_json::Value;
13use siwe::Message;
14use siwe_recap::Capability;
15use std::collections::HashMap;
16use std::ops::{Add, Sub};
17use tracing::{info, warn};
18
19impl<P: alloy::providers::Provider> super::LitNodeClient<P> {
20    pub async fn get_pkp_session_sigs(
21        &self,
22        pkp_public_key: &str,
23        pkp_eth_address: &str,
24        capability_auth_sigs: Vec<AuthSig>,
25        auth_methods: Vec<AuthMethod>,
26        resource_ability_requests: Vec<LitResourceAbilityRequest>,
27        expiration: &str,
28    ) -> Result<SessionSignatures> {
29        if !self.ready {
30            return Err(eyre::eyre!("Lit Node Client not connected"));
31        }
32
33        let session_keypair = {
34            let mut secret_bytes = [0u8; 32];
35            rand::rngs::OsRng.fill(&mut secret_bytes);
36            ed25519_dalek::SigningKey::from_bytes(&secret_bytes)
37        };
38        let session_verifying_key = session_keypair.verifying_key();
39        let session_public_key = hex::encode(session_verifying_key.to_bytes());
40        let session_key_uri = format!("lit:session:{}", session_public_key);
41        info!("Generated session key: {}", session_key_uri);
42
43        let siwe_message = self
44            .create_siwe_message(
45                &resource_ability_requests,
46                &capability_auth_sigs,
47                expiration,
48                pkp_eth_address,
49                &session_key_uri,
50            )
51            .await?;
52        info!("Created SIWE message: {}", siwe_message);
53
54        let delegation_auth_sig = self
55            .get_delegation_signature_from_pkp(
56                pkp_public_key,
57                pkp_eth_address,
58                &auth_methods,
59                &siwe_message,
60                &session_key_uri,
61            )
62            .await?;
63        info!("Delegation auth sig: {:?}", delegation_auth_sig);
64
65        let mut session_sigs = HashMap::new();
66        let now = chrono::Utc::now();
67        let issued_at = now.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
68
69        let mut capabilities = vec![delegation_auth_sig];
70        capabilities.extend(capability_auth_sigs);
71
72        for node_url in self.connected_nodes() {
73            let session_key_signed_message = SessionKeySignedMessage {
74                session_key: session_public_key.clone(),
75                resource_ability_requests: resource_ability_requests.clone(),
76                capabilities: capabilities.clone(),
77                issued_at: issued_at.clone(),
78                expiration: expiration.to_string(),
79                node_address: node_url.to_owned(),
80            };
81            let message = serde_json::to_string(&session_key_signed_message)?;
82            let signature = session_keypair.sign(message.as_bytes());
83            let session_sig = SessionSignature {
84                sig: signature.to_string(),
85                derived_via: "litSessionSignViaNacl".to_string(),
86                signed_message: message,
87                address: session_public_key.clone(),
88                algo: Some("ed25519".to_string()),
89            };
90            session_sigs.insert(node_url.clone(), session_sig);
91        }
92
93        if session_sigs.is_empty() {
94            return Err(eyre::eyre!(
95                "Failed to create session signatures for any node"
96            ));
97        }
98        Ok(session_sigs)
99    }
100
101    async fn create_siwe_message(
102        &self,
103        resource_ability_requests: &[LitResourceAbilityRequest],
104        _capability_auth_sigs: &[AuthSig],
105        _expiration: &str,
106        pkp_eth_address: &str,
107        session_key_uri: &str,
108    ) -> Result<String> {
109        let address = Address::from_hex(pkp_eth_address)?;
110        info!("Using PKP ETH address for SIWE message: {}", address);
111
112        let nonce = self.get_latest_ethereum_blockhash().await?;
113
114        let mut resources = vec![];
115        let mut resource_prefixes = vec![];
116        for resource_ability_request in resource_ability_requests.iter() {
117            let (resource, resource_prefix) = (
118                "*/*".to_string(),
119                format!(
120                    "{}://*",
121                    resource_ability_request.resource.resource_prefix.clone()
122                ),
123            );
124            resources.push(resource);
125            resource_prefixes.push(resource_prefix);
126        }
127
128        let mut capabilities = Capability::<Value>::default();
129        for (resource, resource_prefix) in resources.iter().zip(resource_prefixes.iter()) {
130            let _ = capabilities
131                .with_actions_convert(resource_prefix.clone(), [(resource.clone(), [])]);
132        }
133
134        let now = chrono::Utc::now();
135        let siwe_issued_at = now.sub(chrono::Duration::days(1));
136        let siwe_expiration_time = now.add(chrono::Duration::days(7));
137
138        let siwe_message = capabilities
139            .build_message(Message {
140                domain: "localhost:3000".parse().unwrap(),
141                address: address.into_array(),
142                statement: Some(r#"I am delegating to a session key"#.into()),
143                uri: session_key_uri.parse().unwrap(),
144                version: siwe::Version::V1,
145                chain_id: 1,
146                nonce,
147                issued_at: siwe_issued_at
148                    .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
149                    .parse()
150                    .unwrap(),
151                expiration_time: Some(
152                    siwe_expiration_time
153                        .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
154                        .parse()
155                        .unwrap(),
156                ),
157                not_before: None,
158                request_id: None,
159                resources: vec![],
160            })
161            .map_err(|e| eyre::eyre!("Could not create SIWE message: {}", e))?;
162
163        let message_str = siwe_message.to_string();
164        info!("Created SIWE message: {}", message_str);
165        Ok(message_str)
166    }
167
168    pub async fn create_capacity_delegation_auth_sig(
169        &self,
170        wallet: &PrivateKeySigner,
171        capacity_token_id: &str,
172        delegatee_addresses: &[String],
173        uses: &str,
174    ) -> Result<AuthSig> {
175        EthWalletProvider::create_capacity_delegation_auth_sig(
176            wallet,
177            capacity_token_id,
178            delegatee_addresses,
179            uses,
180        )
181        .await
182    }
183
184    pub async fn get_latest_ethereum_blockhash(&self) -> Result<String> {
185        let rpc_url = std::env::var("ETHEREUM_RPC_URL").map_err(|_| {
186            eyre::eyre!("ETHEREUM_RPC_URL environment variable not set".to_string())
187        })?;
188        let request = serde_json::json!({
189            "jsonrpc": "2.0",
190            "method": "eth_getBlockByNumber",
191            "params": ["latest", false],
192            "id": 1
193        });
194        let response = self
195            .http_client
196            .post(&rpc_url)
197            .json(&request)
198            .send()
199            .await?;
200        if !response.status().is_success() {
201            return Err(eyre::eyre!(
202                "Failed to fetch latest block: HTTP {}",
203                response.status()
204            ));
205        }
206        let response_json: serde_json::Value = response.json().await?;
207        let block_hash = response_json
208            .get("result")
209            .and_then(|result| result.get("hash"))
210            .and_then(|hash| hash.as_str())
211            .ok_or_else(|| eyre::eyre!("Failed to extract block hash from response"))?;
212        Ok(block_hash.to_string())
213    }
214
215    async fn get_delegation_signature_from_pkp(
216        &self,
217        pkp_public_key: &str,
218        pkp_eth_address: &str,
219        auth_methods: &[AuthMethod],
220        siwe_message: &str,
221        session_key_uri: &str,
222    ) -> Result<AuthSig> {
223        let mut node_responses = Vec::new();
224        let request_id = self.generate_request_id();
225        info!("auth methods: {:?}", auth_methods);
226
227        for node_url in self.connected_nodes() {
228            let endpoint = format!("{}/web/sign_session_key/v1", node_url);
229            let request = SignSessionKeyRequest {
230                session_key: session_key_uri.to_string(),
231                auth_methods: auth_methods.to_vec(),
232                pkp_public_key: pkp_public_key.to_string(),
233                siwe_message: siwe_message.to_string(),
234                curve_type: "BLS".to_string(),
235                epoch: None,
236            };
237            info!("Signing session key with node: {}", endpoint);
238            let response = self
239                .http_client
240                .post(&endpoint)
241                .header("X-Request-Id", request_id.clone())
242                .json(&request)
243                .send()
244                .await?;
245
246            if !response.status().is_success() {
247                let status = response.status();
248                let body = response.text().await?;
249                warn!(
250                    "Session key signing failed with status {}: {}",
251                    status, body
252                );
253                continue;
254            }
255
256            let response_body = response.text().await?;
257            info!("Session key signing response: {}", response_body);
258            node_responses.push(response_body);
259        }
260
261        if node_responses.is_empty() {
262            return Err(eyre::eyre!(
263                "Failed to get delegation signature from any node"
264            ));
265        }
266
267        let parsed_responses: Vec<JsonSignSessionKeyResponseV1> = node_responses
268            .iter()
269            .map(|response| serde_json::from_str(response).unwrap())
270            .collect();
271        let one_response_with_share = parsed_responses[0].clone();
272
273        let signature = crate::bls::combine(&parsed_responses)?;
274
275        let bls_root_key_bytes = hex::decode(&one_response_with_share.bls_root_pubkey)
276            .map_err(|e| eyre::eyre!("Failed to decode root key: {}", e))?;
277        let data_signed = hex::decode(&one_response_with_share.data_signed)
278            .map_err(|e| eyre::eyre!("Failed to decode data_signed: {}", e))?;
279
280        crate::bls::verify(&bls_root_key_bytes, &data_signed, &signature)
281            .map_err(|e| eyre::eyre!("Failed to verify signature when getting delegation signature from PKP and locally checking against the root key: {}", e))?;
282
283        let serialized_signature = serde_json::to_string(&signature)
284            .map_err(|e| eyre::eyre!("Failed to serialize signature: {}", e))?;
285
286        let address = pkp_eth_address.parse::<Address>()?;
287
288        Ok(AuthSig {
289            sig: serialized_signature,
290            derived_via: "lit.bls".to_string(),
291            signed_message: one_response_with_share.siwe_message.clone(),
292            address: address.to_checksum(None),
293            algo: Some("LIT_BLS".to_string()),
294        })
295    }
296}