Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lit-rust-sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lit-rust-sdk"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["Lit Protocol"]
description = "Native Rust SDK for Lit Protocol - distributed key management and programmable signing"
Expand Down Expand Up @@ -37,6 +37,7 @@ serde_bare = "0.5.0"
alloy = { version = "1.0.24", features = ["full"] }
eyre = "0.6.12"
sha2 = "0.10"
ethabi = "18.0.0"

[dev-dependencies]
tokio-test = "0.4"
Expand Down
65 changes: 40 additions & 25 deletions lit-rust-sdk/src/client/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::client::LitNodeClient;
use crate::types::{
DecryptRequest, DecryptResponse, EncryptionSignRequest, EncryptionSignResponse,
};
use crate::utils;
use alloy::providers::Provider as ProviderTrait;
use eyre::{eyre, Result};
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -62,8 +63,7 @@ where
.ok_or_else(|| eyre!("network_pub_key not set"))?;

// Get identity parameter for decryption
let hash_of_conditions = self.get_hashed_access_control_conditions_from_decrypt(&params)?;
let hash_of_conditions_str = hex::encode(&hash_of_conditions);
let hash_of_conditions_str = self.hash_access_control_conditions(&params)?;
let identity_param = self.get_identity_param_for_encryption(
&hash_of_conditions_str,
&params.data_to_encrypt_hash,
Expand Down Expand Up @@ -176,33 +176,48 @@ where
Ok(decrypted)
}

/// Hash the access control conditions from decrypt request
fn get_hashed_access_control_conditions_from_decrypt(
&self,
params: &DecryptRequest,
) -> Result<Vec<u8>> {
// Serialize the conditions to JSON exactly like lit-node does
let conditions_json = if let Some(ref conditions) = params.unified_access_control_conditions
pub fn hash_access_control_conditions(&self, req: &DecryptRequest) -> Result<String> {
// hash the access control condition and thing to decrypt
let mut hasher = Sha256::new();

// we need to check if we got passed an access control condition or an evm contract condition
if let Some(access_control_conditions) = &req.access_control_conditions {
let stringified_access_control_conditions =
serde_json::to_string(access_control_conditions)?;
debug!(
"stringified_access_control_conditions: {:?}",
stringified_access_control_conditions
);
hasher.update(stringified_access_control_conditions.as_bytes());
} else if let Some(evm_contract_conditions) = &req.evm_contract_conditions {
let stringified_access_control_conditions =
serde_json::to_string(evm_contract_conditions)?;
debug!(
"stringified_access_control_conditions: {:?}",
stringified_access_control_conditions
);
hasher.update(stringified_access_control_conditions.as_bytes());
} else if req.sol_rpc_conditions.is_some() {
return Err(eyre!("SolRpcConditions are not supported for decryption"));
} else if let Some(unified_access_control_conditions) =
&req.unified_access_control_conditions
{
serde_json::to_string(conditions)?
} else if let Some(ref conditions) = params.access_control_conditions {
serde_json::to_string(conditions)?
} else if let Some(ref conditions) = params.evm_contract_conditions {
serde_json::to_string(conditions)?
} else if let Some(ref conditions) = params.sol_rpc_conditions {
serde_json::to_string(conditions)?
let stringified_access_control_conditions =
serde_json::to_string(unified_access_control_conditions)?;
debug!(
"stringified_access_control_conditions: {:?}",
stringified_access_control_conditions
);
hasher.update(stringified_access_control_conditions.as_bytes());
} else {
return Err(eyre!("No access control conditions provided"));
};
return Err(eyre!("Missing access control conditions"));
}

let hashed_access_control_conditions = utils::bytes_to_hex(hasher.finalize());
debug!(
"stringified_access_control_conditions: {:?}",
conditions_json
"hashed access control conditions: {:?}",
hashed_access_control_conditions
);

// Hash the JSON string exactly like lit-node does
let mut hasher = Sha256::new();
hasher.update(conditions_json.as_bytes());
Ok(hasher.finalize().to_vec())
Ok(hashed_access_control_conditions)
}
}
1 change: 1 addition & 0 deletions lit-rust-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub mod bls;
pub mod client;
pub mod config;
pub mod types;
pub mod utils;

pub use client::LitNodeClient;
pub use config::{LitNetwork, LitNodeClientConfig};
Expand Down
17 changes: 12 additions & 5 deletions lit-rust-sdk/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ pub struct UnifiedAccessControlConditionItem {
pub struct EvmContractCondition {
pub contract_address: String,
pub function_name: String,
pub function_params: Vec<serde_json::Value>,
pub function_abi: serde_json::Value,
pub function_params: Vec<String>,
pub function_abi: ethabi::Function,
pub chain: String,
pub return_value_test: ReturnValueTest,
pub return_value_test: ReturnValueTestV2,
}

// For unified access control conditions, we need a version with conditionType
Expand All @@ -274,7 +274,7 @@ pub struct UnifiedEvmContractConditionItem {
pub condition_type: String,
pub contract_address: String,
pub function_name: String,
pub function_params: Vec<serde_json::Value>,
pub function_params: Vec<String>,
pub function_abi: serde_json::Value,
pub chain: String,
pub return_value_test: ReturnValueTest,
Expand Down Expand Up @@ -317,7 +317,14 @@ pub struct OperatorCondition {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReturnValueTest {
pub comparator: String,
pub value: serde_json::Value,
pub value: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReturnValueTestV2 {
pub key: String,
pub comparator: String,
pub value: String,
}

// Encryption related types
Expand Down
9 changes: 9 additions & 0 deletions lit-rust-sdk/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub fn bytes_to_hex<B>(vec: B) -> String
where
B: AsRef<[u8]>,
{
vec.as_ref()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>()
}
168 changes: 166 additions & 2 deletions lit-rust-sdk/tests/decrypt_client_side.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use lit_rust_sdk::{
types::{
AccessControlCondition, DecryptRequest, EncryptRequest, LitAbility,
LitResourceAbilityRequest, LitResourceAbilityRequestResource, ReturnValueTest,
ReturnValueTestV2,
},
LitNetwork, LitNodeClient, LitNodeClientConfig,
EvmContractCondition, LitNetwork, LitNodeClient, LitNodeClientConfig,
};
use std::time::Duration;

Expand Down Expand Up @@ -62,7 +63,7 @@ async fn test_client_side_decryption_with_session_sigs() {
parameters: vec![":userAddress".to_string(), "latest".to_string()],
return_value_test: ReturnValueTest {
comparator: ">=".to_string(),
value: serde_json::json!("0"),
value: "0".to_string(),
},
};

Expand Down Expand Up @@ -154,3 +155,166 @@ async fn test_client_side_decryption_with_session_sigs() {

println!("✅ Full encrypt/decrypt test with client-side decryption completed successfully!");
}

#[tokio::test]
async fn test_client_side_decryption_with_session_sigs_and_evm_contract_conditions() {
// Initialize tracing for debugging (honors RUST_LOG)
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init();

// Load wallet from environment
let wallet = match load_wallet_from_env() {
Ok(w) => w,
Err(e) => {
println!(
"❌ Failed to load wallet from environment: {}. Make sure ETHEREUM_PRIVATE_KEY is set in .env",
e
);
println!("Skipping test - required environment variables not set");
return;
}
};

println!("🔑 Using wallet address: {}", wallet.address());

// Create client configuration
let config = LitNodeClientConfig {
lit_network: LitNetwork::DatilDev,
alert_when_unauthorized: true,
debug: true,
connect_timeout: Duration::from_secs(30),
check_node_attestation: false,
};

// Create and connect client
let mut client = LitNodeClient::new(config)
.await
.expect("Failed to create client");

match client.connect().await {
Ok(()) => {
println!("✅ Connected to Lit Network");
}
Err(e) => {
panic!("❌ Failed to connect to Lit Network: {}", e);
}
}

let usdc_address_on_eth = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
// Create EVM contract condition with simple whitelist check
let evm_contract_condition = EvmContractCondition {
contract_address: usdc_address_on_eth.to_string(),
function_name: "balanceOf".to_string(),
function_params: vec![":userAddress".to_string()],
function_abi: ethabi::Function {
inputs: vec![ethabi::Param {
internal_type: None,
name: "account".to_string(),
kind: ethabi::ParamType::Address,
}],
name: "balanceOf".to_string(),
outputs: vec![ethabi::Param {
internal_type: None,
name: "".to_string(),
kind: ethabi::ParamType::Uint(256),
}],
state_mutability: ethabi::StateMutability::View,
#[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
constant: Some(false),
},
chain: "ethereum".to_string(),
return_value_test: ReturnValueTestV2 {
key: "".to_string(),
comparator: ">=".to_string(),
value: "0".to_string(),
},
};
let evm_contract_conditions = vec![evm_contract_condition];

// Create test data to encrypt
let test_data =
b"Secret message that requires wallet ownership to decrypt using client-side decryption!";

// Create encrypt request using basic access control conditions
let encrypt_request = EncryptRequest {
data_to_encrypt: test_data.to_vec(),
access_control_conditions: None,
evm_contract_conditions: Some(evm_contract_conditions.clone()),
sol_rpc_conditions: None,
unified_access_control_conditions: None,
};

// Encrypt the data
println!("🔒 Encrypting data with access control conditions...");
let encrypt_response = match client.encrypt(encrypt_request).await {
Ok(response) => {
println!("✅ Data encrypted successfully!");
println!("📦 Ciphertext length: {} bytes", response.ciphertext.len());
println!("🔗 Data hash: {}", response.data_to_encrypt_hash);
response
}
Err(e) => {
panic!("❌ Encryption failed: {}", e);
}
};

// Now let's prepare to decrypt by getting session signatures
let resource_ability_requests = vec![LitResourceAbilityRequest {
resource: LitResourceAbilityRequestResource {
resource: "*".to_string(),
resource_prefix: "lit-accesscontrolcondition".to_string(),
},
ability: LitAbility::AccessControlConditionDecryption.to_string(),
}];

let expiration = (chrono::Utc::now() + chrono::Duration::minutes(10)).to_rfc3339();

println!("🔄 Getting session signatures for decryption...");
let session_sigs = client
.get_local_session_sigs(&wallet, resource_ability_requests, &expiration)
.await
.expect("Failed to create local session signatures");

println!(
"✅ Got session signatures from {} nodes",
session_sigs.len()
);

// Now decrypt using client-side decryption
println!("🔓 Decrypting data using client-side decryption...");

let decrypt_request = DecryptRequest {
ciphertext: encrypt_response.ciphertext,
data_to_encrypt_hash: encrypt_response.data_to_encrypt_hash,
access_control_conditions: None,
evm_contract_conditions: Some(evm_contract_conditions),
sol_rpc_conditions: None,
unified_access_control_conditions: None,
chain: Some("ethereum".to_string()),
session_sigs,
};

match client.decrypt(decrypt_request).await {
Ok(response) => {
println!("✅ Client-side decryption successful!");

// Convert decrypted bytes to string
let decrypted_str = String::from_utf8_lossy(&response.decrypted_data);
println!("🔓 Decrypted content: {}", decrypted_str);

// Verify it matches our original data
let original = String::from_utf8_lossy(test_data);
assert_eq!(
decrypted_str, original,
"Decrypted data should match original data"
);
println!("✅ Decryption successful - data matches original!");
}
Err(e) => {
panic!("❌ Failed to decrypt using client-side decryption: {}", e);
}
}

println!("✅ Full encrypt/decrypt test with client-side decryption completed successfully!");
}
2 changes: 1 addition & 1 deletion lit-rust-sdk/tests/encrypt_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async fn test_encrypt_and_decrypt_with_session_sigs() {
parameters: vec![":userAddress".to_string(), "latest".to_string()],
return_value_test: ReturnValueTest {
comparator: ">=".to_string(),
value: serde_json::json!("0"),
value: "0".to_string(),
},
};

Expand Down