lit_rust_sdk/
auth.rs

1use crate::{
2    types::{AuthMethod, AuthSig},
3    LitNodeClient,
4};
5use alloy::signers::local::PrivateKeySigner;
6use alloy::signers::Signer;
7use eyre::Result;
8use hex;
9use rand;
10use serde_json::json;
11use siwe::Message;
12use tracing::info;
13
14pub struct EthWalletProvider;
15
16impl EthWalletProvider {
17    pub async fn authenticate(
18        wallet: &PrivateKeySigner,
19        _lit_node_client: &LitNodeClient,
20    ) -> Result<AuthMethod> {
21        // Get the wallet address in EIP-55 checksum format
22        let address = wallet.address();
23
24        // Create nonce
25        let nonce = format!("0x{}", hex::encode(rand::random::<[u8; 32]>()));
26
27        // Create SIWE message for authentication
28        let issued_at = chrono::Utc::now().to_rfc3339();
29        let expiration = (chrono::Utc::now() + chrono::Duration::hours(24)).to_rfc3339();
30
31        let siwe_message = format!(
32            "localhost wants you to sign in with your Ethereum account:\n{}\n\n\nURI: http://localhost\nVersion: 1\nChain ID: 1\nNonce: {}\nIssued At: {}\nExpiration Time: {}",
33            address,
34            nonce,
35            issued_at,
36            expiration
37        );
38
39        // ensure the message will be parsed correctly
40        let parsed_message: Message = siwe_message.parse().unwrap();
41        info!("Parsed message: {:?}", parsed_message);
42
43        // Sign the SIWE message
44        let signature = wallet.sign_message(siwe_message.as_bytes()).await?;
45
46        // Convert signature to hex string
47        let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
48
49        // Create the auth method with proper Lit Protocol auth sig format
50        let auth_sig = json!({
51            "sig": sig_hex,
52            "derivedVia": "web3.eth.personal.sign",
53            "signedMessage": siwe_message,
54            "address": address.to_checksum(None)
55        });
56
57        let auth_method = AuthMethod {
58            auth_method_type: 1, // EthWallet auth method type
59            access_token: auth_sig.to_string(),
60        };
61
62        Ok(auth_method)
63    }
64
65    pub async fn create_capacity_delegation_auth_sig(
66        wallet: &PrivateKeySigner,
67        capacity_token_id: &str,
68        delegatee_addresses: &[String],
69        uses: &str,
70    ) -> Result<AuthSig> {
71        use serde_json::Value;
72        use siwe_recap::Capability;
73        use std::collections::BTreeMap;
74
75        let address = wallet.address();
76
77        // Create the nota bene data for the capability
78        let mut notabene = BTreeMap::new();
79        notabene.insert(
80            "nft_id".to_string(),
81            Value::from(vec![Value::from(capacity_token_id)]),
82        );
83        notabene.insert("uses".to_string(), Value::from(uses.to_string()));
84        notabene.insert(
85            "delegate_to".to_string(),
86            Value::from(
87                delegatee_addresses
88                    .iter()
89                    .map(|addr| {
90                        // Remove 0x prefix if present for the delegate_to field
91                        Value::from(if let Some(stripped) = addr.strip_prefix("0x") {
92                            stripped.to_string()
93                        } else {
94                            addr.to_string()
95                        })
96                    })
97                    .collect::<Vec<_>>(),
98            ),
99        );
100
101        // Create nonce - use a random hex string
102        let nonce = hex::encode(rand::random::<[u8; 16]>());
103
104        // Create SIWE message for capacity delegation
105        let issued_at = chrono::Utc::now();
106        let expiration = issued_at + chrono::Duration::hours(24);
107
108        // Build the capability
109        let mut capabilities = Capability::<Value>::default();
110        let resource = "Auth/Auth".to_string();
111        let resource_prefix = format!("lit-ratelimitincrease://{}", capacity_token_id);
112
113        let capabilities = capabilities
114            .with_actions_convert(resource_prefix, [(resource, [notabene])])
115            .map_err(|e| eyre::eyre!("Failed to create capability: {}", e))?;
116
117        // Build the SIWE message with the capability
118        let siwe_message = capabilities
119            .build_message(Message {
120                domain: "lit-protocol.com".parse().unwrap(),
121                address: address.0.into(),
122                statement: Some("Lit Protocol PKP sessionSig".to_string()),
123                uri: "lit:capability:delegation".parse().unwrap(),
124                version: "1".parse().unwrap(),
125                chain_id: 1,
126                nonce: nonce.clone(),
127                issued_at: issued_at
128                    .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
129                    .parse()
130                    .unwrap(),
131                expiration_time: Some(
132                    expiration
133                        .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
134                        .parse()
135                        .unwrap(),
136                ),
137                not_before: None,
138                request_id: None,
139                resources: vec![],
140            })
141            .map_err(|e| eyre::eyre!("Failed to build SIWE message: {}", e))?;
142
143        // Prepare the message string
144        let message_str = siwe_message.to_string();
145
146        // Sign the SIWE message
147        let signature = wallet.sign_message(message_str.as_bytes()).await?;
148
149        let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
150
151        Ok(AuthSig {
152            sig: sig_hex,
153            derived_via: "web3.eth.personal.sign".to_string(),
154            signed_message: message_str,
155            address: address.to_checksum(None),
156            algo: None,
157        })
158    }
159}
160
161pub fn load_wallet_from_env() -> Result<PrivateKeySigner> {
162    dotenv::dotenv().ok(); // Load .env file if it exists
163
164    let private_key = std::env::var("ETHEREUM_PRIVATE_KEY")
165        .map_err(|_| eyre::eyre!("ETHEREUM_PRIVATE_KEY environment variable not set"))?;
166
167    match private_key.parse() {
168        Ok(signer) => Ok(signer),
169        Err(e) => Err(eyre::eyre!("Invalid private key: {}", e)),
170    }
171}