lit_rust_sdk/client/
session_sigs.rs

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