1use crate::auth::{AuthConfig, AuthSig, LitAbility, SessionKeyPair};
2use crate::error::LitSdkError;
3use chrono::Timelike;
4use ed25519_dalek::{Signer, SigningKey};
5use ethers::types::U256;
6use serde_json::json;
7use std::collections::HashMap;
8
9pub fn issue_session_sigs(
11 session_key_pair: &SessionKeyPair,
12 auth_config: &AuthConfig,
13 delegation_auth_sig: &AuthSig,
14 node_urls: &[String],
15) -> Result<HashMap<String, AuthSig>, LitSdkError> {
16 issue_session_sigs_with_max_price(
17 session_key_pair,
18 auth_config,
19 delegation_auth_sig,
20 node_urls,
21 None,
22 )
23}
24
25pub fn issue_session_sigs_with_max_price(
31 session_key_pair: &SessionKeyPair,
32 auth_config: &AuthConfig,
33 delegation_auth_sig: &AuthSig,
34 node_urls: &[String],
35 max_price_per_node_wei: Option<U256>,
36) -> Result<HashMap<String, AuthSig>, LitSdkError> {
37 let mut capabilities = auth_config.capability_auth_sigs.clone();
38 capabilities.push(delegation_auth_sig.clone());
39
40 let now = chrono::Utc::now();
42 let issued_at = now
43 .with_nanosecond((now.nanosecond() / 1_000_000) * 1_000_000)
44 .expect("valid nanosecond")
45 .format("%Y-%m-%dT%H:%M:%S%.3fZ")
46 .to_string();
47
48 let template = json!({
49 "sessionKey": session_key_pair.public_key,
50 "resourceAbilityRequests": auth_config.resources.iter().filter(|r| r.ability != LitAbility::ResolvedAuthContext).map(|r| {
51 json!({
52 "resource": {
53 "resourcePrefix": r.ability.resource_prefix(),
54 "resource": r.resource_id,
55 },
56 "ability": r.ability.as_str(),
57 "data": r.data.clone().unwrap_or_else(|| json!({})),
58 })
59 }).collect::<Vec<_>>(),
60 "capabilities": capabilities,
61 "issuedAt": issued_at,
62 "expiration": auth_config.expiration,
63 });
64
65 let secret_bytes = hex::decode(session_key_pair.secret_key.trim_start_matches("0x"))
66 .map_err(|e| LitSdkError::Crypto(e.to_string()))?;
67 let signing_key = SigningKey::from_bytes(
68 &secret_bytes
69 .try_into()
70 .map_err(|_| LitSdkError::Crypto("invalid session secret key length".into()))?,
71 );
72
73 let mut out = HashMap::new();
74 let max_price_hex = if let Some(p) = max_price_per_node_wei {
76 format!("0x{:x}", p)
77 } else {
78 format!("0x{:x}", u128::MAX)
79 };
80
81 for url in node_urls {
82 let mut to_sign = template.clone();
83 if let Some(obj) = to_sign.as_object_mut() {
84 obj.insert("nodeAddress".into(), json!(url));
85 obj.insert("maxPrice".into(), json!(max_price_hex.clone()));
86 }
87 let signed_message =
88 serde_json::to_string(&to_sign).map_err(|e| LitSdkError::Crypto(e.to_string()))?;
89 let sig = signing_key.sign(signed_message.as_bytes());
90
91 out.insert(
92 url.clone(),
93 AuthSig {
94 sig: hex::encode(sig.to_bytes()),
95 derived_via: "litSessionSignViaNacl".into(),
96 signed_message,
97 address: session_key_pair.public_key.clone(),
98 algo: Some("ed25519".into()),
99 },
100 );
101 }
102
103 Ok(out)
104}