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
Expand Up @@ -36,7 +36,8 @@ k256 = { version = "0.13", features = ["serde", "ecdsa", "arithmetic"] }
serde_bare = "0.5.0"
alloy = { version = "1.0.24", features = ["full"] }
eyre = "0.6.12"
sha2 = "0.10"

[dev-dependencies]
tokio-test = "0.4"
tracing-subscriber = "0.3"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
125 changes: 125 additions & 0 deletions lit-rust-sdk/src/client/encrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use crate::client::LitNodeClient;
use crate::types::{EncryptRequest, EncryptResponse};
use alloy::providers::Provider as ProviderTrait;
use eyre::{eyre, Result};
use sha2::{Digest, Sha256};
use tracing::debug;

impl<P> LitNodeClient<P>
where
P: ProviderTrait,
{
/// Encrypt data using BLS encryption with access control conditions
///
/// # Arguments
///
/// * `params` - The encryption request containing the data and access control conditions
///
/// # Returns
///
/// * `Result<EncryptResponse>` - The encrypted ciphertext and hash of the data
pub async fn encrypt(&self, params: EncryptRequest) -> Result<EncryptResponse> {
// Validate that client is ready
if !self.ready {
return Err(eyre!(
"LitNodeClient is not ready. Please call await client.connect() first."
));
}

// Validate that subnet_pub_key exists
let subnet_pub_key = self
.subnet_pub_key
.as_ref()
.ok_or_else(|| eyre!("subnet_pub_key cannot be null"))?;

// Validate that at least one type of access control condition is provided
let has_conditions = params.access_control_conditions.is_some()
|| params.evm_contract_conditions.is_some()
|| params.sol_rpc_conditions.is_some()
|| params.unified_access_control_conditions.is_some();

if !has_conditions {
return Err(eyre!(
"You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions"
));
}

// Hash the access control conditions
let hash_of_conditions = self.get_hashed_access_control_conditions(&params)?;
let hash_of_conditions_str = hex::encode(&hash_of_conditions);

debug!("hashOfConditionsStr: {}", hash_of_conditions_str);

// Hash the private data
let mut hasher = Sha256::new();
hasher.update(&params.data_to_encrypt);
let hash_of_private_data = hasher.finalize();
let hash_of_private_data_str = hex::encode(hash_of_private_data);

debug!("hashOfPrivateDataStr: {}", hash_of_private_data_str);

// Assemble identity parameter
let identity_param = self
.get_identity_param_for_encryption(&hash_of_conditions_str, &hash_of_private_data_str);

debug!("identityParam: {}", identity_param);

// Remove 0x prefix from subnet_pub_key if present
let clean_pub_key = subnet_pub_key.strip_prefix("0x").unwrap_or(subnet_pub_key);

// Encrypt using BLS
let ciphertext_bytes = crate::bls::encrypt(
&hex::decode(clean_pub_key)?,
&params.data_to_encrypt,
identity_param.as_bytes(),
)?;

// Convert to base64
use base64::{engine::general_purpose::STANDARD, Engine as _};
let ciphertext = STANDARD.encode(&ciphertext_bytes);

Ok(EncryptResponse {
ciphertext,
data_to_encrypt_hash: hash_of_private_data_str,
})
}

/// Hash the access control conditions to match lit-node implementation
fn get_hashed_access_control_conditions(&self, params: &EncryptRequest) -> 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
{
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)?
} else {
return Err(eyre!("No access control conditions provided"));
};

tracing::debug!(
"stringified_access_control_conditions: {:?}",
conditions_json
);

// 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())
}

/// Generate the identity parameter for encryption
fn get_identity_param_for_encryption(
&self,
hash_of_conditions: &str,
hash_of_private_data: &str,
) -> String {
format!(
"lit-accesscontrolcondition://{}/{}",
hash_of_conditions, hash_of_private_data
)
}
}
1 change: 1 addition & 0 deletions lit-rust-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use reqwest::Client;
use std::sync::Arc;

mod connect;
mod encrypt;
mod execute;
mod pkp;
mod session_sigs;
Expand Down
6 changes: 5 additions & 1 deletion lit-rust-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@ pub mod types;

pub use client::LitNodeClient;
pub use config::{LitNetwork, LitNodeClientConfig};
pub use types::{AuthMethod, AuthSig, ExecuteJsParams, ExecuteJsResponse, SessionSignatures, PKP};
pub use types::{
AccessControlCondition, AuthMethod, AuthSig, EncryptRequest, EncryptResponse,
EvmContractCondition, ExecuteJsParams, ExecuteJsResponse, ReturnValueTest, SessionSignatures,
SolRpcCondition, UnifiedAccessControlCondition, UnifiedAccessControlConditionItem, PKP,
};
109 changes: 109 additions & 0 deletions lit-rust-sdk/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,112 @@ pub struct JsonSignSessionKeyResponseV1 {
pub data_signed: String,
pub bls_root_pubkey: String,
}

// Access Control Condition types
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccessControlCondition {
pub contract_address: String,
pub chain: String,
pub standard_contract_type: String,
pub method: String,
pub parameters: Vec<String>,
pub return_value_test: ReturnValueTest,
}

// For unified access control conditions, we need a version with conditionType
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnifiedAccessControlConditionItem {
pub condition_type: String,
pub contract_address: String,
pub standard_contract_type: String,
pub chain: String,
pub method: String,
pub parameters: Vec<String>,
pub return_value_test: ReturnValueTest,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
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 chain: String,
pub return_value_test: ReturnValueTest,
}

// For unified access control conditions, we need a version with conditionType
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnifiedEvmContractConditionItem {
pub condition_type: String,
pub contract_address: String,
pub function_name: String,
pub function_params: Vec<serde_json::Value>,
pub function_abi: serde_json::Value,
pub chain: String,
pub return_value_test: ReturnValueTest,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SolRpcCondition {
pub method: String,
pub params: Vec<serde_json::Value>,
pub chain: String,
pub return_value_test: ReturnValueTest,
}

// For unified access control conditions, we need a version with conditionType
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnifiedSolRpcConditionItem {
pub condition_type: String,
pub method: String,
pub params: Vec<serde_json::Value>,
pub chain: String,
pub return_value_test: ReturnValueTest,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UnifiedAccessControlCondition {
AccessControl(UnifiedAccessControlConditionItem),
EvmContract(UnifiedEvmContractConditionItem),
SolRpc(UnifiedSolRpcConditionItem),
Operator(OperatorCondition),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperatorCondition {
pub operator: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReturnValueTest {
pub comparator: String,
pub value: serde_json::Value,
}

// Encryption related types
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptRequest {
pub data_to_encrypt: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_control_conditions: Option<Vec<AccessControlCondition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub evm_contract_conditions: Option<Vec<EvmContractCondition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sol_rpc_conditions: Option<Vec<SolRpcCondition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unified_access_control_conditions: Option<Vec<UnifiedAccessControlCondition>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptResponse {
pub ciphertext: String,
pub data_to_encrypt_hash: String,
}
Loading