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}