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    ) -> Result<SessionSignatures> {
21        if !self.ready {
22            return Err(eyre::eyre!("Lit Node Client not connected"));
23        }
24
25        // Generate ed25519 keypair for signing
26        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        // Create auth sig with local wallet
36        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        // Generate session signatures for each node
46        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            // Serialize to JSON string
65            let message = serde_json::to_string(&session_key_signed_message)?;
66
67            // Sign message with session key
68            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        // Create resource capabilities
103        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        // Get latest blockhash for nonce
125        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        // Build SIWE message with capabilities
132        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        // Sign the SIWE message with the wallet
166        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}