Skip to content
Open
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
45 changes: 42 additions & 3 deletions examples/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use rustyline::error::ReadlineError;
use orange_sdk::bitcoin_payment_instructions::amount::Amount;
use orange_sdk::{
CashuConfig, ChainSource, CurrencyUnit, Event, ExtraConfig, LoggerType, Mnemonic, PaymentInfo,
Seed, SparkWalletConfig, StorageConfig, Tunables, Wallet, WalletConfig, bitcoin::Network,
Seed, SparkWalletConfig, StorageConfig, Tunables, VssAuth, VssConfig, Wallet, WalletConfig,
bitcoin::Network,
};
use rand::RngCore;
use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
Expand All @@ -32,10 +34,45 @@ struct Cli {
/// npub.cash URL for lightning address support (requires --cashu)
#[arg(long, requires = "cashu")]
npubcash_url: Option<String>,
/// VSS server URL (e.g. http://127.0.0.1:8080/vss). When set, VSS replaces
/// local SQLite for all wallet persistence.
#[arg(long)]
vss_url: Option<String>,
/// LNURL-auth server URL for VSS authentication. When omitted, fixed
/// headers (possibly empty) are used instead.
#[arg(long, requires = "vss_url")]
vss_lnurl_auth_url: Option<String>,
/// Fixed HTTP header to attach to every VSS request, in `Key:Value` form.
/// Repeat for multiple headers. Ignored when --vss-lnurl-auth-url is set.
#[arg(long = "vss-header", value_parser = parse_kv_header, requires = "vss_url")]
vss_headers: Vec<(String, String)>,
#[command(subcommand)]
command: Option<Commands>,
}

fn parse_kv_header(s: &str) -> Result<(String, String), String> {
let (k, v) = s.split_once(':').ok_or_else(|| format!("expected `Key:Value`, got `{s}`"))?;
Ok((k.trim().to_string(), v.trim().to_string()))
}

fn build_storage_config(cli: &Cli, storage_path: &str) -> StorageConfig {
let Some(vss_url) = cli.vss_url.clone() else {
return StorageConfig::LocalSQLite(storage_path.to_string());
};
let store_id = "orange-cli".to_string();
let headers = match cli.vss_lnurl_auth_url.clone() {
Some(url) => VssAuth::LNURLAuthServer(url),
None => VssAuth::FixedHeaders(cli.vss_headers.iter().cloned().collect::<HashMap<_, _>>()),
};
println!(
"{} VSS storage: {} (store_id={})",
"💾".bright_green(),
vss_url.bright_cyan(),
store_id.bright_cyan()
);
StorageConfig::Vss(VssConfig { vss_url, store_id, headers })
}

#[derive(Subcommand)]
enum Commands {
/// Get wallet balance
Expand Down Expand Up @@ -89,6 +126,8 @@ fn get_config(network: Network, cli: &Cli) -> Result<WalletConfig> {
// Generate or load seed
let seed = generate_or_load_seed(&storage_path)?;

let storage_config = build_storage_config(cli, &storage_path);

let extra_config = if cli.cashu {
let mint_url = cli
.mint_url
Expand All @@ -113,7 +152,7 @@ fn get_config(network: Network, cli: &Cli) -> Result<WalletConfig> {
.context("Failed to parse LSP public key")?;

Ok(WalletConfig {
storage_config: StorageConfig::LocalSQLite(storage_path.to_string()),
storage_config,
logger_type: LoggerType::File {
path: PathBuf::from(format!("{storage_path}/wallet.log")),
},
Expand All @@ -140,7 +179,7 @@ fn get_config(network: Network, cli: &Cli) -> Result<WalletConfig> {
let lsp_token = Some("DeveloperTestingOnly".to_string());

Ok(WalletConfig {
storage_config: StorageConfig::LocalSQLite(storage_path.to_string()),
storage_config,
logger_type: LoggerType::File {
path: PathBuf::from(format!("{storage_path}/wallet.log")),
},
Expand Down
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ cli-cashu *args:
cli-logs:
tail -n 50 -f examples/cli/wallet_data/bitcoin/wallet.log

# Run the CLI against a local VSS server on http://127.0.0.1:8080/vss.
cli-vss:
cd examples/cli && cargo run -- --vss-url http://127.0.0.1:8080/vss

build-android:
./scripts/uniffi_bindgen_generate_kotlin_android.sh
cd bindings/kotlin/orange-sdk-android/ && ./gradlew build
Expand Down
45 changes: 36 additions & 9 deletions orange-sdk/src/ffi/orange/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,27 @@ impl TryInto<OrangeSeed> for Seed {
}
}

/// Represents the authentication method for a Versioned Storage Service (VSS).
/// Authentication method used on every request to the [VSS] server.
///
/// **Caution**: VSS support is in **alpha** and is considered experimental.
/// Using VSS (or any remote persistence) may cause LDK to panic if persistence
/// failures are unrecoverable, i.e., if they remain unresolved after internal
/// retries are exhausted.
///
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
#[derive(Debug, Clone, uniffi::Enum)]
pub enum VssAuth {
/// Authentication using an LNURL-auth server.
/// [LNURL-auth] based authentication scheme.
///
/// The LNURL challenge will be retrieved by making a request to the given
/// URL. The returned JWT token in response to the signed LNURL request
/// will be used for authentication/authorization of all the requests made
/// to VSS.
///
/// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md
LNURLAuthServer(String),
/// Authentication using a fixed set of HTTP headers.
/// A fixed set of HTTP headers included as-is on every request made to
/// VSS.
FixedHeaders(HashMap<String, String>),
}

Expand All @@ -80,14 +95,23 @@ impl From<OrangeVssAuth> for VssAuth {
}
}

/// Configuration for a Versioned Storage Service (VSS).
/// Configuration for a [Versioned Storage Service (VSS)] backend.
///
/// **Caution**: VSS support is in **alpha** and is considered experimental.
/// Using VSS (or any remote persistence) may cause LDK to panic if persistence
/// failures are unrecoverable, i.e., if they remain unresolved after internal
/// retries are exhausted.
///
/// [Versioned Storage Service (VSS)]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
#[derive(Debug, Clone, uniffi::Object)]
pub struct VssConfig {
/// The URL of the VSS.
/// Base URL of the VSS server (e.g. `https://vss.example.com/vss`).
vss_url: String,
/// The store ID for the VSS.
/// Segments storage from other storage accessed under the same seed (as
/// storage keyed by different seeds is already segmented to prevent
/// wallets from reading data for unrelated wallets). Can be any value.
store_id: String,
/// Authentication method for the VSS.
/// Authentication method attached to every VSS request.
headers: VssAuth,
}

Expand Down Expand Up @@ -124,14 +148,17 @@ impl From<OrangeVssConfig> for VssConfig {
pub enum StorageConfig {
/// Local SQLite database configuration.
LocalSQLite(String),
// todo VSS(VssConfig),
/// Versioned Storage Service configuration.
Vss(Arc<VssConfig>),
}

impl From<StorageConfig> for OrangeStorageConfig {
fn from(config: StorageConfig) -> Self {
match config {
StorageConfig::LocalSQLite(path) => OrangeStorageConfig::LocalSQLite(path),
// todo VSS(vss_config) => OrangeStorageConfig::VSS(vss_config.into()),
StorageConfig::Vss(vss_config) => {
OrangeStorageConfig::Vss(vss_config.deref().clone().into())
},
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions orange-sdk/src/ffi/orange/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub enum InitFailure {
LdkNodeStartFailure(String),
/// Failure in the trusted wallet implementation.
TrustedFailure(String),
/// Failure to build the VSS-backed store.
VssStoreBuildFailure(String),
}

impl Display for InitFailure {
Expand All @@ -47,6 +49,7 @@ impl Display for InitFailure {
InitFailure::LdkNodeBuildFailure(e) => write!(f, "Failed to build the LDK node: {e}"),
InitFailure::LdkNodeStartFailure(e) => write!(f, "Failed to start the LDK node: {e}"),
InitFailure::TrustedFailure(e) => write!(f, "Failed to create the trusted wallet: {e}"),
InitFailure::VssStoreBuildFailure(e) => write!(f, "Failed to build the VSS store: {e}"),
}
}
}
Expand All @@ -68,6 +71,9 @@ impl From<OrangeInitFailure> for InitFailure {
InitFailure::LdkNodeStartFailure(e.to_string())
},
OrangeInitFailure::TrustedFailure(e) => InitFailure::TrustedFailure(e.to_string()),
OrangeInitFailure::VssStoreBuildFailure(e) => {
InitFailure::VssStoreBuildFailure(e.to_string())
},
}
}
}
Expand Down
80 changes: 71 additions & 9 deletions orange-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use ldk_node::bitcoin::Network;
use ldk_node::bitcoin::hashes::Hash;
use ldk_node::bitcoin::io;
use ldk_node::bitcoin::secp256k1::PublicKey;
use ldk_node::entropy::NodeEntropy;
use ldk_node::io::sqlite_store::SqliteStore;
use ldk_node::lightning::ln::msgs::SocketAddress;
use ldk_node::lightning::util::logger::Logger as _;
Expand Down Expand Up @@ -63,6 +64,7 @@ pub use cdk::nuts::nut00::CurrencyUnit;
pub use event::{Event, EventQueue};
pub use ldk_node::bip39::Mnemonic;
pub use ldk_node::bitcoin;
use ldk_node::io::vss_store::VssStore;
pub use ldk_node::payment::ConfirmationStatus;
pub use store::{PaymentId, PaymentType, Transaction, TxStatus};
pub use trusted_wallet::ExtraConfig;
Expand Down Expand Up @@ -154,24 +156,58 @@ pub enum Seed {
Seed64([u8; 64]),
}

/// Represents the authentication method for a Versioned Storage Service (VSS).
impl Seed {
pub(crate) fn to_node_entropy(&self) -> NodeEntropy {
match self {
Seed::Seed64(s) => NodeEntropy::from_seed_bytes(*s),
Seed::Mnemonic { mnemonic, passphrase } => {
NodeEntropy::from_bip39_mnemonic(mnemonic.clone(), passphrase.clone())
},
}
}
}

/// Authentication method used on every request to the [VSS] server.
///
/// **Caution**: VSS support is in **alpha** and is considered experimental.
/// Using VSS (or any remote persistence) may cause LDK to panic if persistence
/// failures are unrecoverable, i.e., if they remain unresolved after internal
/// retries are exhausted.
///
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
#[derive(Debug, Clone)]
pub enum VssAuth {
/// Authentication using an LNURL-auth server.
/// [LNURL-auth] based authentication scheme.
///
/// The LNURL challenge will be retrieved by making a request to the given
/// URL. The returned JWT token in response to the signed LNURL request
/// will be used for authentication/authorization of all the requests made
/// to VSS.
///
/// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md
LNURLAuthServer(String),
/// Authentication using a fixed set of HTTP headers.
/// A fixed set of HTTP headers included as-is on every request made to
/// VSS.
FixedHeaders(HashMap<String, String>),
}

/// Configuration for a Versioned Storage Service (VSS).
/// Configuration for a [Versioned Storage Service (VSS)] backend.
///
/// **Caution**: VSS support is in **alpha** and is considered experimental.
/// Using VSS (or any remote persistence) may cause LDK to panic if persistence
/// failures are unrecoverable, i.e., if they remain unresolved after internal
/// retries are exhausted.
///
/// [Versioned Storage Service (VSS)]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct VssConfig {
/// The URL of the VSS.
/// Base URL of the VSS server (e.g. `https://vss.example.com/vss`).
pub vss_url: String,
/// The store ID for the VSS.
/// Segments storage from other storage accessed under the same seed (as
/// storage keyed by different seeds is already segmented to prevent
/// wallets from reading data for unrelated wallets). Can be any value.
pub store_id: String,
/// Authentication method for the VSS.
/// Authentication method attached to every VSS request.
pub headers: VssAuth,
}

Expand All @@ -180,7 +216,10 @@ pub struct VssConfig {
pub enum StorageConfig {
/// Local SQLite database configuration.
LocalSQLite(String),
// todo VSS(VssConfig),
/// Versioned Storage Service configuration. The same store backs LDK
/// channel state and orange-sdk metadata, so a seed-based recovery against
/// the configured VSS endpoint restores both.
Vss(VssConfig),
}

/// Configuration for the blockchain data source.
Expand Down Expand Up @@ -411,6 +450,8 @@ pub enum InitFailure {
LdkNodeStartFailure(NodeError),
/// Failure in the trusted wallet implementation.
TrustedFailure(TrustedError),
/// Failure to build the VSS-backed store.
VssStoreBuildFailure(ldk_node::io::vss_store::VssStoreBuildError),
}

impl From<io::Error> for InitFailure {
Expand All @@ -437,6 +478,12 @@ impl From<TrustedError> for InitFailure {
}
}

impl From<ldk_node::io::vss_store::VssStoreBuildError> for InitFailure {
fn from(e: ldk_node::io::vss_store::VssStoreBuildError) -> InitFailure {
InitFailure::VssStoreBuildFailure(e)
}
}

/// Represents possible errors during wallet operations.
#[derive(Debug)]
pub enum WalletError {
Expand Down Expand Up @@ -526,6 +573,21 @@ impl Wallet {
StorageConfig::LocalSQLite(path) => {
Arc::new(SqliteStore::new(path.into(), Some("orange.sqlite".to_owned()), None)?)
},
StorageConfig::Vss(vss_config) => {
let builder = VssStore::builder(
config.seed.to_node_entropy(),
vss_config.vss_url.clone(),
vss_config.store_id.clone(),
config.network,
);
let vss_store = match &vss_config.headers {
VssAuth::FixedHeaders(h) => builder.build_with_fixed_headers(h.clone())?,
VssAuth::LNURLAuthServer(url) => {
builder.build_with_lnurl(url.clone(), HashMap::new())?
},
};
Arc::new(vss_store)
},
};

let event_queue = Arc::new(EventQueue::new(Arc::clone(&store), Arc::clone(&logger)));
Expand Down
10 changes: 2 additions & 8 deletions orange-sdk/src/lightning_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::event::{EventQueue, LdkEventHandler};
use crate::logging::Logger;
use crate::runtime::Runtime;
use crate::store::{TxMetadataStore, TxStatus};
use crate::{ChainSource, InitFailure, PaymentType, Seed, WalletConfig, store};
use crate::{ChainSource, InitFailure, PaymentType, WalletConfig, store};

use bitcoin_payment_instructions::PaymentMethod;
use bitcoin_payment_instructions::amount::Amount;
Expand All @@ -15,7 +15,6 @@ use ldk_node::bitcoin::base64::prelude::BASE64_STANDARD;
use ldk_node::bitcoin::secp256k1::PublicKey;
use ldk_node::bitcoin::{Address, Network};
use ldk_node::config::{AsyncPaymentsRole, BackgroundSyncConfig, SyncTimeoutsConfig};
use ldk_node::entropy::NodeEntropy;
use ldk_node::lightning::ln::channelmanager::PaymentId;
use ldk_node::lightning::ln::msgs::SocketAddress;
use ldk_node::lightning::util::logger::Logger as _;
Expand Down Expand Up @@ -74,12 +73,7 @@ impl LightningWallet {
};
let mut builder = ldk_node::Builder::from_config(ldk_node_config);
builder.set_network(config.network);
let node_entropy = match config.seed {
Seed::Seed64(seed) => NodeEntropy::from_seed_bytes(seed),
Seed::Mnemonic { mnemonic, passphrase } => {
NodeEntropy::from_bip39_mnemonic(mnemonic, passphrase)
},
};
let node_entropy = config.seed.to_node_entropy();

match config.rgs_url {
Some(url) => {
Expand Down
Loading