Skip to content

Commit 05f1356

Browse files
authored
fix evm access control conditions (#12)
* fix evm access control conditions * fix clippy error * add deprecated flag
1 parent c20d8ad commit 05f1356

File tree

7 files changed

+231
-34
lines changed

7 files changed

+231
-34
lines changed

lit-rust-sdk/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "lit-rust-sdk"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2021"
55
authors = ["Lit Protocol"]
66
description = "Native Rust SDK for Lit Protocol - distributed key management and programmable signing"
@@ -37,6 +37,7 @@ serde_bare = "0.5.0"
3737
alloy = { version = "1.0.24", features = ["full"] }
3838
eyre = "0.6.12"
3939
sha2 = "0.10"
40+
ethabi = "18.0.0"
4041

4142
[dev-dependencies]
4243
tokio-test = "0.4"

lit-rust-sdk/src/client/decrypt.rs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::client::LitNodeClient;
22
use crate::types::{
33
DecryptRequest, DecryptResponse, EncryptionSignRequest, EncryptionSignResponse,
44
};
5+
use crate::utils;
56
use alloy::providers::Provider as ProviderTrait;
67
use eyre::{eyre, Result};
78
use sha2::{Digest, Sha256};
@@ -62,8 +63,7 @@ where
6263
.ok_or_else(|| eyre!("network_pub_key not set"))?;
6364

6465
// Get identity parameter for decryption
65-
let hash_of_conditions = self.get_hashed_access_control_conditions_from_decrypt(&params)?;
66-
let hash_of_conditions_str = hex::encode(&hash_of_conditions);
66+
let hash_of_conditions_str = self.hash_access_control_conditions(&params)?;
6767
let identity_param = self.get_identity_param_for_encryption(
6868
&hash_of_conditions_str,
6969
&params.data_to_encrypt_hash,
@@ -176,33 +176,48 @@ where
176176
Ok(decrypted)
177177
}
178178

179-
/// Hash the access control conditions from decrypt request
180-
fn get_hashed_access_control_conditions_from_decrypt(
181-
&self,
182-
params: &DecryptRequest,
183-
) -> Result<Vec<u8>> {
184-
// Serialize the conditions to JSON exactly like lit-node does
185-
let conditions_json = if let Some(ref conditions) = params.unified_access_control_conditions
179+
pub fn hash_access_control_conditions(&self, req: &DecryptRequest) -> Result<String> {
180+
// hash the access control condition and thing to decrypt
181+
let mut hasher = Sha256::new();
182+
183+
// we need to check if we got passed an access control condition or an evm contract condition
184+
if let Some(access_control_conditions) = &req.access_control_conditions {
185+
let stringified_access_control_conditions =
186+
serde_json::to_string(access_control_conditions)?;
187+
debug!(
188+
"stringified_access_control_conditions: {:?}",
189+
stringified_access_control_conditions
190+
);
191+
hasher.update(stringified_access_control_conditions.as_bytes());
192+
} else if let Some(evm_contract_conditions) = &req.evm_contract_conditions {
193+
let stringified_access_control_conditions =
194+
serde_json::to_string(evm_contract_conditions)?;
195+
debug!(
196+
"stringified_access_control_conditions: {:?}",
197+
stringified_access_control_conditions
198+
);
199+
hasher.update(stringified_access_control_conditions.as_bytes());
200+
} else if req.sol_rpc_conditions.is_some() {
201+
return Err(eyre!("SolRpcConditions are not supported for decryption"));
202+
} else if let Some(unified_access_control_conditions) =
203+
&req.unified_access_control_conditions
186204
{
187-
serde_json::to_string(conditions)?
188-
} else if let Some(ref conditions) = params.access_control_conditions {
189-
serde_json::to_string(conditions)?
190-
} else if let Some(ref conditions) = params.evm_contract_conditions {
191-
serde_json::to_string(conditions)?
192-
} else if let Some(ref conditions) = params.sol_rpc_conditions {
193-
serde_json::to_string(conditions)?
205+
let stringified_access_control_conditions =
206+
serde_json::to_string(unified_access_control_conditions)?;
207+
debug!(
208+
"stringified_access_control_conditions: {:?}",
209+
stringified_access_control_conditions
210+
);
211+
hasher.update(stringified_access_control_conditions.as_bytes());
194212
} else {
195-
return Err(eyre!("No access control conditions provided"));
196-
};
213+
return Err(eyre!("Missing access control conditions"));
214+
}
197215

216+
let hashed_access_control_conditions = utils::bytes_to_hex(hasher.finalize());
198217
debug!(
199-
"stringified_access_control_conditions: {:?}",
200-
conditions_json
218+
"hashed access control conditions: {:?}",
219+
hashed_access_control_conditions
201220
);
202-
203-
// Hash the JSON string exactly like lit-node does
204-
let mut hasher = Sha256::new();
205-
hasher.update(conditions_json.as_bytes());
206-
Ok(hasher.finalize().to_vec())
221+
Ok(hashed_access_control_conditions)
207222
}
208223
}

lit-rust-sdk/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub mod bls;
5454
pub mod client;
5555
pub mod config;
5656
pub mod types;
57+
pub mod utils;
5758

5859
pub use client::LitNodeClient;
5960
pub use config::{LitNetwork, LitNodeClientConfig};

lit-rust-sdk/src/types.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,10 @@ pub struct UnifiedAccessControlConditionItem {
261261
pub struct EvmContractCondition {
262262
pub contract_address: String,
263263
pub function_name: String,
264-
pub function_params: Vec<serde_json::Value>,
265-
pub function_abi: serde_json::Value,
264+
pub function_params: Vec<String>,
265+
pub function_abi: ethabi::Function,
266266
pub chain: String,
267-
pub return_value_test: ReturnValueTest,
267+
pub return_value_test: ReturnValueTestV2,
268268
}
269269

270270
// For unified access control conditions, we need a version with conditionType
@@ -274,7 +274,7 @@ pub struct UnifiedEvmContractConditionItem {
274274
pub condition_type: String,
275275
pub contract_address: String,
276276
pub function_name: String,
277-
pub function_params: Vec<serde_json::Value>,
277+
pub function_params: Vec<String>,
278278
pub function_abi: serde_json::Value,
279279
pub chain: String,
280280
pub return_value_test: ReturnValueTest,
@@ -317,7 +317,14 @@ pub struct OperatorCondition {
317317
#[derive(Debug, Clone, Serialize, Deserialize)]
318318
pub struct ReturnValueTest {
319319
pub comparator: String,
320-
pub value: serde_json::Value,
320+
pub value: String,
321+
}
322+
323+
#[derive(Debug, Clone, Serialize, Deserialize)]
324+
pub struct ReturnValueTestV2 {
325+
pub key: String,
326+
pub comparator: String,
327+
pub value: String,
321328
}
322329

323330
// Encryption related types

lit-rust-sdk/src/utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub fn bytes_to_hex<B>(vec: B) -> String
2+
where
3+
B: AsRef<[u8]>,
4+
{
5+
vec.as_ref()
6+
.iter()
7+
.map(|b| format!("{b:02x}"))
8+
.collect::<String>()
9+
}

lit-rust-sdk/tests/decrypt_client_side.rs

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use lit_rust_sdk::{
33
types::{
44
AccessControlCondition, DecryptRequest, EncryptRequest, LitAbility,
55
LitResourceAbilityRequest, LitResourceAbilityRequestResource, ReturnValueTest,
6+
ReturnValueTestV2,
67
},
7-
LitNetwork, LitNodeClient, LitNodeClientConfig,
8+
EvmContractCondition, LitNetwork, LitNodeClient, LitNodeClientConfig,
89
};
910
use std::time::Duration;
1011

@@ -62,7 +63,7 @@ async fn test_client_side_decryption_with_session_sigs() {
6263
parameters: vec![":userAddress".to_string(), "latest".to_string()],
6364
return_value_test: ReturnValueTest {
6465
comparator: ">=".to_string(),
65-
value: serde_json::json!("0"),
66+
value: "0".to_string(),
6667
},
6768
};
6869

@@ -154,3 +155,166 @@ async fn test_client_side_decryption_with_session_sigs() {
154155

155156
println!("✅ Full encrypt/decrypt test with client-side decryption completed successfully!");
156157
}
158+
159+
#[tokio::test]
160+
async fn test_client_side_decryption_with_session_sigs_and_evm_contract_conditions() {
161+
// Initialize tracing for debugging (honors RUST_LOG)
162+
let _ = tracing_subscriber::fmt()
163+
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
164+
.try_init();
165+
166+
// Load wallet from environment
167+
let wallet = match load_wallet_from_env() {
168+
Ok(w) => w,
169+
Err(e) => {
170+
println!(
171+
"❌ Failed to load wallet from environment: {}. Make sure ETHEREUM_PRIVATE_KEY is set in .env",
172+
e
173+
);
174+
println!("Skipping test - required environment variables not set");
175+
return;
176+
}
177+
};
178+
179+
println!("🔑 Using wallet address: {}", wallet.address());
180+
181+
// Create client configuration
182+
let config = LitNodeClientConfig {
183+
lit_network: LitNetwork::DatilDev,
184+
alert_when_unauthorized: true,
185+
debug: true,
186+
connect_timeout: Duration::from_secs(30),
187+
check_node_attestation: false,
188+
};
189+
190+
// Create and connect client
191+
let mut client = LitNodeClient::new(config)
192+
.await
193+
.expect("Failed to create client");
194+
195+
match client.connect().await {
196+
Ok(()) => {
197+
println!("✅ Connected to Lit Network");
198+
}
199+
Err(e) => {
200+
panic!("❌ Failed to connect to Lit Network: {}", e);
201+
}
202+
}
203+
204+
let usdc_address_on_eth = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
205+
// Create EVM contract condition with simple whitelist check
206+
let evm_contract_condition = EvmContractCondition {
207+
contract_address: usdc_address_on_eth.to_string(),
208+
function_name: "balanceOf".to_string(),
209+
function_params: vec![":userAddress".to_string()],
210+
function_abi: ethabi::Function {
211+
inputs: vec![ethabi::Param {
212+
internal_type: None,
213+
name: "account".to_string(),
214+
kind: ethabi::ParamType::Address,
215+
}],
216+
name: "balanceOf".to_string(),
217+
outputs: vec![ethabi::Param {
218+
internal_type: None,
219+
name: "".to_string(),
220+
kind: ethabi::ParamType::Uint(256),
221+
}],
222+
state_mutability: ethabi::StateMutability::View,
223+
#[allow(deprecated)] // this is needed for compatibility with the version of ethabi running on the lit nodes which expects this param, and is an older version where this is not deprecated
224+
constant: Some(false),
225+
},
226+
chain: "ethereum".to_string(),
227+
return_value_test: ReturnValueTestV2 {
228+
key: "".to_string(),
229+
comparator: ">=".to_string(),
230+
value: "0".to_string(),
231+
},
232+
};
233+
let evm_contract_conditions = vec![evm_contract_condition];
234+
235+
// Create test data to encrypt
236+
let test_data =
237+
b"Secret message that requires wallet ownership to decrypt using client-side decryption!";
238+
239+
// Create encrypt request using basic access control conditions
240+
let encrypt_request = EncryptRequest {
241+
data_to_encrypt: test_data.to_vec(),
242+
access_control_conditions: None,
243+
evm_contract_conditions: Some(evm_contract_conditions.clone()),
244+
sol_rpc_conditions: None,
245+
unified_access_control_conditions: None,
246+
};
247+
248+
// Encrypt the data
249+
println!("🔒 Encrypting data with access control conditions...");
250+
let encrypt_response = match client.encrypt(encrypt_request).await {
251+
Ok(response) => {
252+
println!("✅ Data encrypted successfully!");
253+
println!("📦 Ciphertext length: {} bytes", response.ciphertext.len());
254+
println!("🔗 Data hash: {}", response.data_to_encrypt_hash);
255+
response
256+
}
257+
Err(e) => {
258+
panic!("❌ Encryption failed: {}", e);
259+
}
260+
};
261+
262+
// Now let's prepare to decrypt by getting session signatures
263+
let resource_ability_requests = vec![LitResourceAbilityRequest {
264+
resource: LitResourceAbilityRequestResource {
265+
resource: "*".to_string(),
266+
resource_prefix: "lit-accesscontrolcondition".to_string(),
267+
},
268+
ability: LitAbility::AccessControlConditionDecryption.to_string(),
269+
}];
270+
271+
let expiration = (chrono::Utc::now() + chrono::Duration::minutes(10)).to_rfc3339();
272+
273+
println!("🔄 Getting session signatures for decryption...");
274+
let session_sigs = client
275+
.get_local_session_sigs(&wallet, resource_ability_requests, &expiration)
276+
.await
277+
.expect("Failed to create local session signatures");
278+
279+
println!(
280+
"✅ Got session signatures from {} nodes",
281+
session_sigs.len()
282+
);
283+
284+
// Now decrypt using client-side decryption
285+
println!("🔓 Decrypting data using client-side decryption...");
286+
287+
let decrypt_request = DecryptRequest {
288+
ciphertext: encrypt_response.ciphertext,
289+
data_to_encrypt_hash: encrypt_response.data_to_encrypt_hash,
290+
access_control_conditions: None,
291+
evm_contract_conditions: Some(evm_contract_conditions),
292+
sol_rpc_conditions: None,
293+
unified_access_control_conditions: None,
294+
chain: Some("ethereum".to_string()),
295+
session_sigs,
296+
};
297+
298+
match client.decrypt(decrypt_request).await {
299+
Ok(response) => {
300+
println!("✅ Client-side decryption successful!");
301+
302+
// Convert decrypted bytes to string
303+
let decrypted_str = String::from_utf8_lossy(&response.decrypted_data);
304+
println!("🔓 Decrypted content: {}", decrypted_str);
305+
306+
// Verify it matches our original data
307+
let original = String::from_utf8_lossy(test_data);
308+
assert_eq!(
309+
decrypted_str, original,
310+
"Decrypted data should match original data"
311+
);
312+
println!("✅ Decryption successful - data matches original!");
313+
}
314+
Err(e) => {
315+
panic!("❌ Failed to decrypt using client-side decryption: {}", e);
316+
}
317+
}
318+
319+
println!("✅ Full encrypt/decrypt test with client-side decryption completed successfully!");
320+
}

lit-rust-sdk/tests/encrypt_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async fn test_encrypt_and_decrypt_with_session_sigs() {
6363
parameters: vec![":userAddress".to_string(), "latest".to_string()],
6464
return_value_test: ReturnValueTest {
6565
comparator: ">=".to_string(),
66-
value: serde_json::json!("0"),
66+
value: "0".to_string(),
6767
},
6868
};
6969

0 commit comments

Comments
 (0)