Skip to content

Commit

Permalink
feat: Add support for sqlcipher
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Mar 9, 2025
1 parent b787951 commit 40c53e8
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ jobs:
-p cdk --no-default-features --features "mint swagger",
-p cdk-redb,
-p cdk-sqlite,
-p cdk-sqlite --features sqlcipher,
-p cdk-axum --no-default-features,
-p cdk-axum --no-default-features --features swagger,
-p cdk-axum --no-default-features --features redis,
Expand All @@ -104,10 +105,12 @@ jobs:
-p cdk-lnbits,
-p cdk-fake-wallet,
--bin cdk-cli,
--bin cdk-cli --features sqlcipher,
--bin cdk-mintd,
--bin cdk-mintd --features redis,
--bin cdk-mintd --features redb,
--bin cdk-mintd --features "redis swagger redb",
--bin cdk-mintd --features sqlcipher,
--bin cdk-mintd --no-default-features --features lnd,
--bin cdk-mintd --no-default-features --features cln,
--bin cdk-mintd --no-default-features --features lnbits,
Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ repository.workspace = true
edition.workspace = true
rust-version.workspace = true

[features]
sqlcipher = ["cdk-sqlite/sqlcipher"]

[dependencies]
anyhow.workspace = true
bip39.workspace = true
Expand Down
12 changes: 12 additions & 0 deletions crates/cdk-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ struct Cli {
/// Database engine to use (sqlite/redb)
#[arg(short, long, default_value = "sqlite")]
engine: String,
/// Database password for sqlcipher
#[cfg(feature = "sqlcipher")]
#[arg(long)]
password: Option<String>,
/// Path to working dir
#[arg(short, long)]
work_dir: Option<PathBuf>,
Expand Down Expand Up @@ -106,7 +110,15 @@ async fn main() -> Result<()> {
match args.engine.as_str() {
"sqlite" => {
let sql_path = work_dir.join("cdk-cli.sqlite");
#[cfg(not(feature = "sqlcipher"))]
let sql = WalletSqliteDatabase::new(&sql_path).await?;
#[cfg(feature = "sqlcipher")]
let sql = {
match args.password {
Some(pass) => WalletSqliteDatabase::new(&sql_path, pass).await?,
None => bail!("Missing database password"),
}
};

sql.migrate().await;

Expand Down
1 change: 1 addition & 0 deletions crates/cdk-mintd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ redis = ["cdk-axum/redis"]
management-rpc = ["cdk-mint-rpc"]
# MSRV is not commited to with redb enabled
redb = ["dep:cdk-redb"]
sqlcipher = ["cdk-sqlite/sqlcipher"]
cln = ["dep:cdk-cln"]
lnd = ["dep:cdk-lnd"]
lnbits = ["dep:cdk-lnbits"]
Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-mintd/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub struct CLIArgs {
required = false
)]
pub work_dir: Option<PathBuf>,
#[cfg(feature = "sqlcipher")]
#[arg(short, long, help = "Database password for sqlcipher", required = true)]
pub password: String,
#[arg(
short,
long,
Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-mintd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ async fn main() -> anyhow::Result<()> {
match settings.database.engine {
DatabaseEngine::Sqlite => {
let sql_db_path = work_dir.join("cdk-mintd.sqlite");
#[cfg(not(feature = "sqlcipher"))]
let sqlite_db = MintSqliteDatabase::new(&sql_db_path).await?;
#[cfg(feature = "sqlcipher")]
let sqlite_db = MintSqliteDatabase::new(&sql_db_path, args.password).await?;

sqlite_db.migrate().await;

Expand Down
2 changes: 2 additions & 0 deletions crates/cdk-sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rust-version = "1.75.0" # MSRV
default = ["mint", "wallet"]
mint = ["cdk-common/mint"]
wallet = ["cdk-common/wallet"]
sqlcipher = ["libsqlite3-sys"]

[dependencies]
async-trait.workspace = true
Expand All @@ -26,6 +27,7 @@ sqlx = { version = "0.6.3", default-features = false, features = [
"migrate",
"uuid",
] }
libsqlite3-sys = { version = "0.24.1", features = ["bundled-sqlcipher"], optional = true }
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/cdk-sqlite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The following crate feature flags are available:
|-------------|:-------:|------------------------------------|
| `wallet` | Yes | Enable cashu wallet features |
| `mint` | Yes | Enable cashu mint wallet features |
| `sqlcipher` | No | Enable encrypted database |

## Implemented [NUTs](https://github.com/cashubtc/nuts/):

Expand Down
8 changes: 7 additions & 1 deletion crates/cdk-sqlite/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use sqlx::{Error, Pool, Sqlite};

#[inline(always)]
pub async fn create_sqlite_pool(path: &str) -> Result<Pool<Sqlite>, Error> {
pub async fn create_sqlite_pool(
path: &str,
#[cfg(feature = "sqlcipher")] password: String,
) -> Result<Pool<Sqlite>, Error> {
let db_options = SqliteConnectOptions::from_str(path)?
.busy_timeout(Duration::from_secs(10))
.read_only(false)
Expand All @@ -17,6 +20,9 @@ pub async fn create_sqlite_pool(path: &str) -> Result<Pool<Sqlite>, Error> {
.shared_cache(true)
.create_if_missing(true);

#[cfg(feature = "sqlcipher")]
let db_options = db_options.pragma("key", password);

let pool = SqlitePoolOptions::new()
.min_connections(1)
.max_connections(1)
Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-sqlite/src/mint/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use super::MintSqliteDatabase;

/// Creates a new in-memory [`MintSqliteDatabase`] instance
pub async fn empty() -> Result<MintSqliteDatabase, database::Error> {
#[cfg(not(feature = "sqlcipher"))]
let db = MintSqliteDatabase::new(":memory:").await?;
#[cfg(feature = "sqlcipher")]
let db = MintSqliteDatabase::new(":memory:", "memory".to_string()).await?;
db.migrate().await;
Ok(db)
}
Expand Down
13 changes: 13 additions & 0 deletions crates/cdk-sqlite/src/mint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,25 @@ impl MintSqliteDatabase {
}

/// Create new [`MintSqliteDatabase`]
#[cfg(not(feature = "sqlcipher"))]
pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
Ok(Self {
pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
})
}

/// Create new [`MintSqliteDatabase`]
#[cfg(feature = "sqlcipher")]
pub async fn new<P: AsRef<Path>>(path: P, password: String) -> Result<Self, Error> {
Ok(Self {
pool: create_sqlite_pool(
path.as_ref().to_str().ok_or(Error::InvalidDbPath)?,
password,
)
.await?,
})
}

/// Migrate [`MintSqliteDatabase`]
pub async fn migrate(&self) {
sqlx::migrate!("./src/mint/migrations")
Expand Down
42 changes: 42 additions & 0 deletions crates/cdk-sqlite/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,25 @@ pub struct WalletSqliteDatabase {

impl WalletSqliteDatabase {
/// Create new [`WalletSqliteDatabase`]
#[cfg(not(feature = "sqlcipher"))]
pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
Ok(Self {
pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
})
}

/// Create new [`WalletSqliteDatabase`]
#[cfg(feature = "sqlcipher")]
pub async fn new<P: AsRef<Path>>(path: P, password: String) -> Result<Self, Error> {
Ok(Self {
pool: create_sqlite_pool(
path.as_ref().to_str().ok_or(Error::InvalidDbPath)?,
password,
)
.await?,
})
}

/// Migrate [`WalletSqliteDatabase`]
pub async fn migrate(&self) {
sqlx::migrate!("./src/wallet/migrations")
Expand Down Expand Up @@ -954,3 +967,32 @@ fn sqlite_row_to_proof_info(row: &SqliteRow) -> Result<ProofInfo, Error> {
unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
})
}

#[cfg(test)]
mod tests {
use std::env::temp_dir;

#[tokio::test]
#[cfg(feature = "sqlcipher")]
async fn test_sqlcipher() {
use super::*;
let path = std::env::temp_dir()
.to_path_buf()
.join(format!("cdk-test-{}.sqlite", uuid::Uuid::new_v4()));
let db = WalletSqliteDatabase::new(path, "password".to_string())
.await
.unwrap();

db.migrate().await;

// do something simple to test the database
let pk = PublicKey::from_hex(
"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104",
)
.unwrap();
let last_checked = 6969;
db.add_nostr_last_checked(pk, last_checked).await.unwrap();
let res = db.get_nostr_last_checked(&pk).await.unwrap();
assert_eq!(res, Some(last_checked));
}
}

0 comments on commit 40c53e8

Please sign in to comment.