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
101 changes: 101 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: CI

on:
push:
branches: [ main, develop ]
paths:
- 'lit-rust-sdk/**'
- '.github/workflows/**'
pull_request:
branches: [ main, develop ]
paths:
- 'lit-rust-sdk/**'
- '.github/workflows/**'

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
test:
name: Test Suite
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
lit-rust-sdk/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Check formatting
run: |
cd lit-rust-sdk
cargo fmt --all -- --check

- name: Run clippy
run: |
cd lit-rust-sdk
cargo clippy --all-targets --all-features -- -D warnings

- name: Run unit tests
run: |
cd lit-rust-sdk
cargo test --lib

- name: Run all integration tests
env:
ETHEREUM_PRIVATE_KEY: ${{ secrets.ETHEREUM_PRIVATE_KEY }}
PKP_PUBLIC_KEY: ${{ secrets.PKP_PUBLIC_KEY }}
PKP_TOKEN_ID: ${{ secrets.PKP_TOKEN_ID }}
PKP_ETH_ADDRESS: ${{ secrets.PKP_ETH_ADDRESS }}
ETHEREUM_RPC_URL: ${{ secrets.ETHEREUM_RPC_URL }}
run: |
cd lit-rust-sdk
# Run all tests with single thread to avoid conflicts
cargo test -- --nocapture --test-threads=1

security:
name: Security Audit
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Install cargo-audit
run: cargo install cargo-audit

- name: Run security audit
run: |
cd lit-rust-sdk
cargo audit

- name: Check for cargo-deny
run: |
cd lit-rust-sdk
# Install cargo-deny if not present
cargo install --locked cargo-deny || true
# Run cargo-deny if deny.toml exists (skip license checking)
if [ -f "deny.toml" ]; then
cargo deny check advisories bans sources
else
echo "No deny.toml found, skipping cargo-deny check"
fi
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Lit Protocol Rust SDK

[![CI](https://github.com/LIT-Protocol/rust-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/LIT-Protocol/rust-sdk/actions/workflows/ci.yml)
[![Documentation](https://github.com/LIT-Protocol/rust-sdk/actions/workflows/docs.yml/badge.svg)](https://github.com/LIT-Protocol/rust-sdk/actions/workflows/docs.yml)

A native Rust implementation of the Lit Protocol SDK, providing programmatic access to the Lit Network for distributed key management, conditional access control, and programmable signing.

Currently in Beta and only supports Datil, DatilDev, and DatilTest networks.
Expand Down Expand Up @@ -560,6 +563,30 @@ Common issues and solutions:
- **"Invalid signature"**: Verify PKP public key format (should include 0x prefix)
- **"Rate limit exceeded"**: Ensure Rate Limit NFT has sufficient capacity

## CI/Development

The repository includes comprehensive GitHub Actions workflows for testing and validation:

### CI Pipeline

- **Basic CI** (`ci.yml`): Runs on every push/PR with formatting, clippy, unit tests, and all integration tests
- **Documentation** (`docs.yml`): Validates README files and builds documentation
- **Release Tests** (`release.yml`): Full test suite that can be run manually for release testing

### Required GitHub Secrets

For CI to work properly, the following secrets must be configured in the repository:

```bash
ETHEREUM_PRIVATE_KEY # Private key for test wallet (should have test ETH)
PKP_PUBLIC_KEY # Existing PKP public key for tests (optional)
PKP_TOKEN_ID # Existing PKP token ID for tests (optional)
PKP_ETH_ADDRESS # Existing PKP Ethereum address for tests (optional)
ETHEREUM_RPC_URL # RPC URL for Ethereum/L2 network interactions
```

**Note**: The CI will work with just `ETHEREUM_PRIVATE_KEY` and `ETHEREUM_RPC_URL` for basic tests. PKP-related secrets are only needed for advanced tests.

## Contributing

Contributions are welcome! Please ensure all tests pass before submitting a PR:
Expand All @@ -570,6 +597,19 @@ cargo fmt
cargo clippy
```

### Running Tests Locally

```bash
# Run all tests (requires environment variables)
cargo test -- --nocapture

# Run only local session signature tests (simpler setup)
cargo test local_session_sigs -- --nocapture

# Run specific test
cargo test test_connect_to_lit_network -- --nocapture
```

## License

See LICENSE file in the repository root.
Expand Down
21 changes: 21 additions & 0 deletions lit-rust-sdk/deny.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Configuration for cargo-deny - security-focused with permissive license checking

[advisories]
# Check for critical security vulnerabilities only
version = 2
yanked = "allow" # Many transitive deps in web3 are yanked but safe
unmaintained = "all" # Allow all unmaintained crates
ignore = [
# Allow these specific advisories for now
"RUSTSEC-2021-0141", # dotenv unmaintained but safe
"RUSTSEC-2024-0370", # proc-macro-error unmaintained but safe
"RUSTSEC-2024-0436", # paste unmaintained but safe
]

# Skip license checking entirely - focus only on security
# [licenses]

[bans]
# Warn about multiple versions but don't fail
multiple-versions = "warn"
wildcards = "allow"
2 changes: 1 addition & 1 deletion lit-rust-sdk/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[toolchain]
channel = "1.86.0"
channel = "stable"
37 changes: 24 additions & 13 deletions lit-rust-sdk/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl EthWalletProvider {
let address = wallet.address();

// Create nonce
let nonce = format!("0x{}", hex::encode(&rand::random::<[u8; 32]>()));
let nonce = format!("0x{}", hex::encode(rand::random::<[u8; 32]>()));

// Create SIWE message for authentication
let issued_at = chrono::Utc::now().to_rfc3339();
Expand All @@ -41,7 +41,7 @@ impl EthWalletProvider {
info!("Parsed message: {:?}", parsed_message);

// Sign the SIWE message
let signature = wallet.sign_message(&siwe_message.as_bytes()).await?;
let signature = wallet.sign_message(siwe_message.as_bytes()).await?;

// Convert signature to hex string
let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
Expand All @@ -68,15 +68,18 @@ impl EthWalletProvider {
delegatee_addresses: &[String],
uses: &str,
) -> Result<AuthSig> {
use serde_json::Value;
use siwe_recap::Capability;
use std::collections::BTreeMap;
use serde_json::Value;


let address = wallet.address();

// Create the nota bene data for the capability
let mut notabene = BTreeMap::new();
notabene.insert("nft_id".to_string(), Value::from(vec![Value::from(capacity_token_id)]));
notabene.insert(
"nft_id".to_string(),
Value::from(vec![Value::from(capacity_token_id)]),
);
notabene.insert("uses".to_string(), Value::from(uses.to_string()));
notabene.insert(
"delegate_to".to_string(),
Expand All @@ -85,18 +88,18 @@ impl EthWalletProvider {
.iter()
.map(|addr| {
// Remove 0x prefix if present for the delegate_to field
Value::from(if addr.starts_with("0x") {
addr[2..].to_string()
Value::from(if let Some(stripped) = addr.strip_prefix("0x") {
stripped.to_string()
} else {
addr.to_string()
})
})
.collect::<Vec<_>>()
.collect::<Vec<_>>(),
),
);

// Create nonce - use a random hex string
let nonce = format!("{}", hex::encode(&rand::random::<[u8; 16]>()));
let nonce = hex::encode(rand::random::<[u8; 16]>());

// Create SIWE message for capacity delegation
let issued_at = chrono::Utc::now();
Expand All @@ -106,7 +109,7 @@ impl EthWalletProvider {
let mut capabilities = Capability::<Value>::default();
let resource = "Auth/Auth".to_string();
let resource_prefix = format!("lit-ratelimitincrease://{}", capacity_token_id);

let capabilities = capabilities
.with_actions_convert(resource_prefix, [(resource, [notabene])])
.map_err(|e| eyre::eyre!("Failed to create capability: {}", e))?;
Expand All @@ -121,8 +124,16 @@ impl EthWalletProvider {
version: "1".parse().unwrap(),
chain_id: 1,
nonce: nonce.clone(),
issued_at: issued_at.to_rfc3339_opts(chrono::SecondsFormat::Millis, true).parse().unwrap(),
expiration_time: Some(expiration.to_rfc3339_opts(chrono::SecondsFormat::Millis, true).parse().unwrap()),
issued_at: issued_at
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
.parse()
.unwrap(),
expiration_time: Some(
expiration
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
.parse()
.unwrap(),
),
not_before: None,
request_id: None,
resources: vec![],
Expand All @@ -133,7 +144,7 @@ impl EthWalletProvider {
let message_str = siwe_message.to_string();

// Sign the SIWE message
let signature = wallet.sign_message(&message_str.as_bytes()).await?;
let signature = wallet.sign_message(message_str.as_bytes()).await?;

let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));

Expand Down
2 changes: 2 additions & 0 deletions lit-rust-sdk/src/blockchain/staking.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::too_many_arguments)]

use alloy::sol;

sol!(// `all_derives` - derives standard Rust traits.
Expand Down
2 changes: 1 addition & 1 deletion lit-rust-sdk/src/bls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ where
T: core::borrow::Borrow<JsonSignSessionKeyResponseV1>,
{
let shares = signature_shares
.map(|s| s.borrow().signature_share.clone())
.map(|s| s.borrow().signature_share)
.collect::<Vec<_>>();
let sig = Signature::from_shares(&shares)?;
Ok(sig)
Expand Down
2 changes: 1 addition & 1 deletion lit-rust-sdk/src/client/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl<P: alloy::providers::Provider> super::LitNodeClient<P> {
async fn handshake_with_nodes(&mut self, urls: &[String]) -> Result<()> {
let mut successful_connections = 0;
for url in urls {
match self.handshake_with_node(&url).await {
match self.handshake_with_node(url).await {
Ok(response) => {
info!("Successfully connected to node: {}", url);
self.connection_state.insert(
Expand Down
2 changes: 1 addition & 1 deletion lit-rust-sdk/src/client/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl<P: alloy::providers::Provider> super::LitNodeClient<P> {
if !response.success {
continue;
}
for (_key, signed_data) in &response.signed_data {
for signed_data in response.signed_data.values() {
let sig_name = signed_data.sig_name.clone();
signatures_by_name
.entry(sig_name)
Expand Down
2 changes: 1 addition & 1 deletion lit-rust-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl LitNodeClient<DynProvider> {
let http_client = Client::builder().timeout(config.connect_timeout).build()?;

let rpc_url = config.lit_network.rpc_url();
let provider = ProviderBuilder::new().connect(&rpc_url).await?;
let provider = ProviderBuilder::new().connect(rpc_url).await?;
let staking_address = config.lit_network.staking_contract_address()?;

let staking = Staking::new(staking_address, provider.erased());
Expand Down
2 changes: 1 addition & 1 deletion lit-rust-sdk/src/client/pkp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ impl<P: alloy::providers::Provider> super::LitNodeClient<P> {
uri: session_key_uri.parse().unwrap(),
version: siwe::Version::V1,
chain_id: 1,
nonce: nonce,
nonce,
issued_at: siwe_issued_at
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
.parse()
Expand Down
Loading