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
27 changes: 18 additions & 9 deletions crates/breez-sdk/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::command::CliHelper;
use crate::persist::CliPersistence;
use anyhow::{Result, anyhow};
use breez_sdk_spark::{
ConnectRequest, EventListener, Network, SdkEvent, Seed, connect, default_config,
EventListener, KeySetType, Network, SdkBuilder, SdkEvent, Seed, default_config,
};
use clap::Parser;
use command::{Command, execute_command};
Expand All @@ -26,6 +26,10 @@ struct Cli {
/// Network to use (mainnet, regtest)
#[arg(long, default_value = "regtest")]
network: String,

/// Account number to use for the Spark signer
#[arg(long)]
account_number: Option<u32>,
}

fn expand_path(path: &str) -> PathBuf {
Expand Down Expand Up @@ -72,7 +76,11 @@ impl EventListener for CliEventListener {
}
}

async fn run_interactive_mode(data_dir: PathBuf, network: Network) -> Result<()> {
async fn run_interactive_mode(
data_dir: PathBuf,
network: Network,
account_number: Option<u32>,
) -> Result<()> {
breez_sdk_spark::init_logging(Some(data_dir.to_string_lossy().into()), None, None)?;
let persistence = CliPersistence {
data_dir: data_dir.clone(),
Expand Down Expand Up @@ -101,12 +109,13 @@ async fn run_interactive_mode(data_dir: PathBuf, network: Network) -> Result<()>
passphrase: None,
};

let sdk = connect(ConnectRequest {
config,
seed,
storage_dir: data_dir.to_string_lossy().to_string(),
})
.await?;
let mut sdk_builder =
SdkBuilder::new(config, seed).with_default_storage(data_dir.to_string_lossy().to_string());
if let Some(account_number) = account_number {
sdk_builder = sdk_builder.with_key_set(KeySetType::Default, false, Some(account_number));
}

let sdk = sdk_builder.build().await?;

let listener = Box::new(CliEventListener {});
sdk.add_event_listener(listener).await;
Expand Down Expand Up @@ -187,7 +196,7 @@ async fn main() -> Result<(), anyhow::Error> {
_ => return Err(anyhow!("Invalid network. Use 'regtest' or 'mainnet'")),
};

Box::pin(run_interactive_mode(data_dir, network)).await?;
Box::pin(run_interactive_mode(data_dir, network, cli.account_number)).await?;

Ok(())
}
1 change: 1 addition & 0 deletions crates/breez-sdk/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub use persist::{
};
pub use sdk::{BreezSdk, default_config, init_logging, parse_input};
pub use sdk_builder::SdkBuilder;
pub use spark_wallet::KeySet;

#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
pub use {persist::sqlite::SqliteStorage, sdk::connect};
Expand Down
23 changes: 21 additions & 2 deletions crates/breez-sdk/core/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use std::{collections::HashMap, fmt::Display, str::FromStr};

use crate::{
BitcoinAddressDetails, BitcoinNetwork, Bolt11InvoiceDetails, ExternalInputParser, FiatCurrency,
LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate, SparkInvoiceDetails, SuccessAction,
SuccessActionProcessed, error::DepositClaimError,
LnurlPayRequestDetails, LnurlWithdrawRequestDetails, Rate, SdkError, SparkInvoiceDetails,
SuccessAction, SuccessActionProcessed, error::DepositClaimError,
};

/// A list of external input parsers that are used by default.
Expand Down Expand Up @@ -45,6 +45,25 @@ pub enum Seed {
Entropy(Vec<u8>),
}

impl Seed {
pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
match self {
Seed::Mnemonic {
mnemonic,
passphrase,
} => {
let mnemonic = bip39::Mnemonic::parse(mnemonic)
.map_err(|e| SdkError::Generic(e.to_string()))?;

Ok(mnemonic
.to_seed(passphrase.as_deref().unwrap_or(""))
.to_vec())
}
Seed::Entropy(entropy) => Ok(entropy.clone()),
}
}
}

#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct ConnectRequest {
pub config: Config,
Expand Down
31 changes: 8 additions & 23 deletions crates/breez-sdk/core/src/persist/path.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
use std::{path::PathBuf, str::FromStr};

use bitcoin::hashes::{Hash, sha256};
use spark_wallet::PublicKey;

use crate::{Network, SdkError, Seed};
use crate::{Network, SdkError};

pub fn default_storage_path(
data_dir: &str,
network: &Network,
seed: &Seed,
identity_pub_key: &PublicKey,
) -> Result<PathBuf, SdkError> {
let storage_dir = std::path::PathBuf::from_str(data_dir)?;
let path_suffix: String = match seed {
crate::Seed::Mnemonic {
mnemonic,
passphrase,
} => {
// Ensure mnemonic is valid before proceeding
bip39::Mnemonic::parse(mnemonic)
.map_err(|e| SdkError::InvalidInput(format!("Invalid mnemonic: {e}")))?;
let str = format!("{mnemonic}:{passphrase:?}");
sha256::Hash::hash(str.as_bytes())
.to_string()
.chars()
.take(8)
.collect()
}
crate::Seed::Entropy(vec) => sha256::Hash::hash(vec.as_slice())
.to_string()
.chars()
.take(8)
.collect(),
};
let path_suffix = sha256::Hash::hash(&identity_pub_key.serialize())
.to_string()
.chars()
.take(8)
.collect::<String>();

Ok(storage_dir
.join(network.to_string().to_lowercase())
Expand Down
5 changes: 3 additions & 2 deletions crates/breez-sdk/core/src/realtime_sync/init.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::Arc;

use bitcoin::bip32::Xpriv;
use breez_sdk_common::sync::{BreezSyncerClient, SigningClient, SyncProcessor, SyncService};
use tracing::debug;
use uuid::Uuid;
Expand All @@ -16,7 +17,7 @@ pub struct RealTimeSyncParams {
pub server_url: String,
pub api_key: Option<String>,
pub network: Network,
pub seed: Vec<u8>,
pub master_key: Xpriv,
pub storage: Arc<dyn Storage>,
pub sync_storage: Arc<dyn SyncStorage>,
pub shutdown_receiver: tokio::sync::watch::Receiver<()>,
Expand All @@ -42,7 +43,7 @@ pub async fn init_and_start_real_time_sync(
.map_err(|e| SdkError::Generic(e.to_string()))?;

let sync_signer = Arc::new(
DefaultSyncSigner::new(&params.seed, params.network)
DefaultSyncSigner::new(&params.master_key, params.network)
.map_err(|e| SdkError::Generic(e.to_string()))?,
);
let signing_sync_client = SigningClient::new(
Expand Down
11 changes: 6 additions & 5 deletions crates/breez-sdk/core/src/realtime_sync/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ pub struct DefaultSyncSigner {
}

impl DefaultSyncSigner {
pub fn new(seed: &[u8], network: Network) -> Result<Self, bitcoin::bip32::Error> {
let bitcoin_network: bitcoin::Network = network.into();
let xpriv = Xpriv::new_master(bitcoin_network, seed)?;
pub fn new(master_key: &Xpriv, network: Network) -> Result<Self, bitcoin::bip32::Error> {
let secp = Secp256k1::new();

let signing_derivation_path: DerivationPath = match network {
Network::Mainnet => SIGNING_DERIVATION_PATH,
Network::Regtest => SIGNING_DERIVATION_PATH_TEST,
Expand All @@ -35,12 +34,14 @@ impl DefaultSyncSigner {
Network::Regtest => ENCRYPTION_DERIVATION_PATH_TEST,
}
.parse()?;
let signing_key = xpriv

let signing_key = master_key
.derive_priv(&secp, &signing_derivation_path)?
.private_key;
let encryption_key = xpriv
let encryption_key = master_key
.derive_priv(&secp, &encryption_derivation_path)?
.private_key;

Ok(Self {
signing_key,
encryption_key,
Expand Down
119 changes: 61 additions & 58 deletions crates/breez-sdk/core/src/sdk_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
)]
use std::sync::Arc;

use bitcoin::bip32::Xpriv;
use breez_sdk_common::{
breez_server::{BreezServer, PRODUCTION_BREEZSERVER_URL},
rest::ReqwestRestClient as CommonRequestRestClient,
};
use spark_wallet::{DefaultSigner, Signer};
use spark_wallet::{DefaultSigner, KeySet, Signer};
use tokio::sync::watch;
use tracing::{debug, info};

Expand Down Expand Up @@ -187,60 +188,18 @@ impl SdkBuilder {
/// Builds the `BreezSdk` instance with the configured components.
#[allow(clippy::too_many_lines)]
pub async fn build(self) -> Result<BreezSdk, SdkError> {
let (storage, sync_storage) = match (self.storage, self.storage_dir) {
// Use provided storages directly
(Some(storage), _) => (storage, self.sync_storage),
// Initialize default storages based on provided directory
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
(None, Some(storage_dir)) => {
let storage = default_storage(&storage_dir, self.config.network, &self.seed)?;
let sync_storage = match (self.sync_storage, &self.config.real_time_sync_server_url)
{
// Use provided sync storage directly
(Some(sync_storage), _) => Some(sync_storage),
// Initialize default sync storage based on provided directory
// if real-time sync is enabled
(None, Some(_)) => Some(default_sync_storage(
&storage_dir,
self.config.network,
&self.seed,
)?),
_ => None,
};
(storage, sync_storage)
}
_ => {
return Err(SdkError::Generic(
"Either storage or storage_dir must be set before building the SDK".to_string(),
));
}
};
// Create the signer from seed
let seed = match self.seed {
Seed::Mnemonic {
mnemonic,
passphrase,
} => {
let mnemonic = bip39::Mnemonic::parse(&mnemonic)
.map_err(|e| SdkError::Generic(e.to_string()))?;
let seed_bytes = self.seed.to_bytes()?;
let key_set = KeySet::new(
&seed_bytes,
self.config.network.into(),
self.key_set_type.into(),
self.use_address_index,
self.account_number,
)
.map_err(|e| SdkError::Generic(e.to_string()))?;
let signer: Arc<dyn Signer> = Arc::new(DefaultSigner::from_key_set(key_set.clone()));

mnemonic
.to_seed(passphrase.as_deref().unwrap_or(""))
.to_vec()
}
Seed::Entropy(entropy) => entropy,
};

let signer: Arc<dyn Signer> = Arc::new(
DefaultSigner::with_keyset_type(
&seed,
self.config.network.into(),
self.key_set_type.into(),
self.use_address_index,
self.account_number,
)
.map_err(|e| SdkError::Generic(e.to_string()))?,
);
let chain_service = if let Some(service) = self.chain_service {
service
} else {
Expand Down Expand Up @@ -275,6 +234,39 @@ impl SdkBuilder {
}
};

let (storage, sync_storage) = match (self.storage, self.storage_dir) {
// Use provided storages directly
(Some(storage), _) => (storage, self.sync_storage),
// Initialize default storages based on provided directory
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
(None, Some(storage_dir)) => {
let identity_pub_key = signer
.get_identity_public_key()
.map_err(|e| SdkError::Generic(e.to_string()))?;
let storage =
default_storage(&storage_dir, self.config.network, &identity_pub_key)?;
let sync_storage = match (self.sync_storage, &self.config.real_time_sync_server_url)
{
// Use provided sync storage directly
(Some(sync_storage), _) => Some(sync_storage),
// Initialize default sync storage based on provided directory
// if real-time sync is enabled
(None, Some(_)) => Some(default_sync_storage(
&storage_dir,
self.config.network,
&identity_pub_key,
)?),
_ => None,
};
(storage, sync_storage)
}
_ => {
return Err(SdkError::Generic(
"Either storage or storage_dir must be set before building the SDK".to_string(),
));
}
};

let fiat_service: Arc<dyn breez_sdk_common::fiat::FiatService> = match self.fiat_service {
Some(service) => Arc::new(FiatServiceWrapper::new(service)),
None => Arc::new(
Expand Down Expand Up @@ -335,11 +327,22 @@ impl SdkBuilder {
"Real-time sync is enabled, but no sync storage is supplied".to_string(),
));
};

// Use legacy master key when using default key set and default derivation path for backwards compatibility
let master_key =
if self.key_set_type == KeySetType::Default && self.account_number.is_none() {
let bitcoin_network: bitcoin::Network = self.config.network.into();
Xpriv::new_master(bitcoin_network, &seed_bytes)
.map_err(|e| SdkError::Generic(e.to_string()))?
} else {
key_set.identity_master_key
};

init_and_start_real_time_sync(RealTimeSyncParams {
server_url: server_url.clone(),
api_key: self.config.api_key.clone(),
network: self.config.network,
seed,
master_key,
storage: Arc::clone(&storage),
sync_storage,
shutdown_receiver: shutdown_sender.subscribe(),
Expand Down Expand Up @@ -372,9 +375,9 @@ impl SdkBuilder {
fn default_storage(
data_dir: &str,
network: Network,
seed: &Seed,
identity_pub_key: &spark_wallet::PublicKey,
) -> Result<Arc<dyn Storage>, SdkError> {
let db_path = crate::default_storage_path(data_dir, &network, seed)?;
let db_path = crate::default_storage_path(data_dir, &network, identity_pub_key)?;
let storage = Arc::new(crate::SqliteStorage::new(&db_path)?);
Ok(storage)
}
Expand All @@ -383,9 +386,9 @@ fn default_storage(
fn default_sync_storage(
data_dir: &str,
network: Network,
seed: &Seed,
identity_pub_key: &spark_wallet::PublicKey,
) -> Result<Arc<dyn SyncStorage>, SdkError> {
let db_path = crate::default_storage_path(data_dir, &network, seed)?;
let db_path = crate::default_storage_path(data_dir, &network, identity_pub_key)?;
let storage = Arc::new(crate::SqliteStorage::new(&db_path)?);
Ok(storage)
}
1 change: 1 addition & 0 deletions crates/breez-sdk/wasm/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub enum SdkEvent {
},
}

#[derive(Clone)]
#[macros::extern_wasm_bindgen(breez_sdk_spark::KeySetType)]
pub enum KeySetType {
Default,
Expand Down
Loading
Loading