lit_rust_sdk/client/
session_sigs.rs1use crate::types::{
2 AuthSig, LitResourceAbilityRequest, SessionKeySignedMessage, SessionSignature,
3 SessionSignatures,
4};
5use alloy::signers::local::PrivateKeySigner;
6use ed25519_dalek::Signer;
7use eyre::Result;
8use rand::Rng;
9use serde_json::Value;
10use std::collections::HashMap;
11use std::ops::{Add, Sub};
12use tracing::info;
13
14impl<P: alloy::providers::Provider> super::LitNodeClient<P> {
15 pub async fn get_local_session_sigs(
16 &self,
17 wallet: &PrivateKeySigner,
18 resource_ability_requests: Vec<LitResourceAbilityRequest>,
19 expiration: &str,
20 ) -> Result<SessionSignatures> {
21 if !self.ready {
22 return Err(eyre::eyre!("Lit Node Client not connected"));
23 }
24
25 let session_keypair = {
27 let mut secret_bytes = [0u8; 32];
28 rand::rngs::OsRng.fill(&mut secret_bytes);
29 ed25519_dalek::SigningKey::from_bytes(&secret_bytes)
30 };
31 let session_verifying_key = session_keypair.verifying_key();
32 let session_public_key = hex::encode(session_verifying_key.to_bytes());
33 info!("Generated session key: {}", session_public_key);
34
35 let auth_sig = self
37 .create_auth_sig_for_session_sig(
38 wallet,
39 &session_public_key,
40 &resource_ability_requests,
41 )
42 .await?;
43 info!("Created auth sig for session: {:?}", auth_sig);
44
45 let mut session_sigs = HashMap::new();
47 let now = chrono::Utc::now();
48 let issued_at = now
49 .sub(chrono::Duration::days(1))
50 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
51
52 let capabilities = vec![auth_sig];
53
54 for node_url in self.connected_nodes() {
55 let session_key_signed_message = SessionKeySignedMessage {
56 session_key: session_public_key.clone(),
57 resource_ability_requests: resource_ability_requests.clone(),
58 capabilities: capabilities.clone(),
59 issued_at: issued_at.clone(),
60 expiration: expiration.to_string(),
61 node_address: node_url.to_owned(),
62 };
63
64 let message = serde_json::to_string(&session_key_signed_message)?;
66
67 let signature = session_keypair.sign(message.as_bytes());
69
70 let session_sig = SessionSignature {
71 sig: signature.to_string(),
72 derived_via: "litSessionSignViaNacl".to_string(),
73 signed_message: message,
74 address: session_public_key.clone(),
75 algo: Some("ed25519".to_string()),
76 };
77
78 session_sigs.insert(node_url.clone(), session_sig);
79 }
80
81 if session_sigs.is_empty() {
82 return Err(eyre::eyre!(
83 "Failed to create session signatures for any node"
84 ));
85 }
86
87 Ok(session_sigs)
88 }
89
90 async fn create_auth_sig_for_session_sig(
91 &self,
92 wallet: &PrivateKeySigner,
93 session_public_key: &str,
94 resource_ability_requests: &[LitResourceAbilityRequest],
95 ) -> Result<AuthSig> {
96 use alloy::signers::Signer;
97 use siwe::Message;
98 use siwe_recap::Capability;
99
100 let wallet_address = wallet.address();
101
102 let mut resources = vec![];
104 let mut resource_prefixes = vec![];
105
106 for resource_ability_request in resource_ability_requests.iter() {
107 let (resource, resource_prefix) = (
108 "*/*".to_string(),
109 format!(
110 "{}://*",
111 resource_ability_request.resource.resource_prefix.clone()
112 ),
113 );
114 resources.push(resource);
115 resource_prefixes.push(resource_prefix);
116 }
117
118 let mut capabilities = Capability::<Value>::default();
119 for (resource, resource_prefix) in resources.iter().zip(resource_prefixes.iter()) {
120 let _ = capabilities
121 .with_actions_convert(resource_prefix.clone(), [(resource.clone(), [])]);
122 }
123
124 let nonce = self.get_latest_ethereum_blockhash().await?;
126
127 let now = chrono::Utc::now();
128 let siwe_issued_at = now.sub(chrono::Duration::days(1));
129 let siwe_expiration_time = now.add(chrono::Duration::days(7));
130
131 let siwe_message = capabilities
133 .build_message(Message {
134 domain: "localhost:3000".parse().unwrap(),
135 address: wallet_address.into_array(),
136 statement: Some(format!(
137 "I am creating a session for {}.",
138 session_public_key
139 )),
140 uri: format!("lit:session:{}", session_public_key)
141 .parse()
142 .unwrap(),
143 version: siwe::Version::V1,
144 chain_id: 1,
145 nonce,
146 issued_at: siwe_issued_at
147 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
148 .parse()
149 .unwrap(),
150 expiration_time: Some(
151 siwe_expiration_time
152 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
153 .parse()
154 .unwrap(),
155 ),
156 not_before: None,
157 request_id: None,
158 resources: vec![],
159 })
160 .map_err(|e| eyre::eyre!("Could not create SIWE message: {}", e))?;
161
162 let message_str = siwe_message.to_string();
163 info!("Created SIWE message for auth sig: {}", message_str);
164
165 let signature = wallet.sign_message(message_str.as_bytes()).await?;
167 let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
168
169 Ok(AuthSig {
170 sig: sig_hex,
171 derived_via: "web3.eth.personal.sign".to_string(),
172 signed_message: message_str,
173 address: wallet_address.to_checksum(None),
174 algo: None,
175 })
176 }
177}