Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 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
58 changes: 58 additions & 0 deletions crates/cashu/src/nuts/nut06.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,20 @@ impl MintInfo {

units.into_iter().collect()
}

/// Check if mint supports OHTTP (NUT-26)
pub fn supports_ohttp(&self) -> bool {
self.nuts
.nut26
.as_ref()
.map(|s| s.enabled)
.unwrap_or_default()
}

/// Get OHTTP configuration if supported
pub fn ohttp_config(&self) -> Option<&OhttpSettings> {
self.nuts.nut26.as_ref()
}
}

/// Supported nuts and settings
Expand Down Expand Up @@ -336,6 +350,10 @@ pub struct Nuts {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(feature = "auth")]
pub nut22: Option<BlindAuthSettings>,
/// NUT26 Settings
#[serde(rename = "26")]
#[serde(skip_serializing_if = "Option::is_none")]
pub nut26: Option<OhttpSettings>,
}

impl Nuts {
Expand Down Expand Up @@ -453,6 +471,14 @@ impl Nuts {
}
}

/// Nut23 OHTTP settings
pub fn nut23(self, ohttp_settings: OhttpSettings) -> Self {
Self {
nut26: Some(ohttp_settings),
..self
}
}

/// Units where minting is supported
pub fn supported_mint_units(&self) -> Vec<&CurrencyUnit> {
self.nut04
Expand All @@ -476,6 +502,38 @@ impl Nuts {
}
}

/// NUT-23 OHTTP Settings
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct OhttpSettings {
/// Ohttp is enabled
pub enabled: bool,
/// OHTTP gateway URL (actual destination, typically same as mint URL)
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_url: Option<String>,
}

impl OhttpSettings {
/// Create new [`OhttpSettings`]
pub fn new(enabled: bool, gateway_url: Option<String>) -> Self {
Self {
enabled,
gateway_url,
}
}

/// Validate OHTTP settings URLs
pub fn validate(&self) -> Result<(), String> {
use url::Url;

if let Some(url) = self.gateway_url.as_ref() {
Url::parse(url).map_err(|_| format!("Invalid gateway URL: {}", url))?;
}

Ok(())
}
}

/// Check state Settings
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
Expand Down
3 changes: 2 additions & 1 deletion crates/cdk-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ rust-version.workspace = true
readme = "README.md"

[features]
default = []
default = ["ohttp"]
sqlcipher = ["cdk-sqlite/sqlcipher"]
# MSRV is not tracked with redb enabled
redb = ["dep:cdk-redb"]
ohttp = ["cdk/ohttp"]

[dependencies]
anyhow.workspace = true
Expand Down
113 changes: 105 additions & 8 deletions crates/cdk-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,111 @@
> **Warning**
> This project is in early development, it does however work with real sats! Always use amounts you don't mind losing.
# CDK-CLI

cdk-cli is a CLI wallet implementation using of CDK(../cdk)
Cashu CLI wallet built on CDK.

## License
## Installation

Code is under the [MIT](../../LICENSE)
Build with OHTTP support (default):
```bash
cargo build --release
```

## Contribution
Build without OHTTP support:
```bash
cargo build --release --no-default-features
```

All contributions welcome.
## Usage

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions.
### Basic Commands

Check wallet balance:
```bash
cdk-cli balance
```

Send tokens:
```bash
cdk-cli send --amount 100
```

Receive tokens:
```bash
cdk-cli receive <TOKEN>
```

### OHTTP Support

The CLI supports OHTTP (Oblivious HTTP) for enhanced privacy when communicating with mints. OHTTP is enabled by default and automatically used when:

1. The mint supports OHTTP (advertised in mint info)
2. You provide an OHTTP relay URL using `--ohttp-relay`

#### OHTTP Usage

To use OHTTP, simply provide a relay URL. The CLI will automatically detect if the mint supports OHTTP and configure the connection appropriately:

```bash
# Use OHTTP relay - CLI auto-detects OHTTP support and gateway URL from mint
cdk-cli --ohttp-relay https://relay.example.com balance
cdk-cli --ohttp-relay https://relay.example.com send --amount 100
cdk-cli --ohttp-relay https://relay.example.com receive <TOKEN>
```

#### How OHTTP Works in CDK-CLI

1. **Automatic Detection**: When `--ohttp-relay` is provided, the CLI checks if the mint supports OHTTP
2. **Gateway Discovery**: The gateway URL is automatically discovered from the mint's OHTTP configuration, or falls back to using the mint URL directly
3. **Transport Setup**: An OHTTP transport layer is created with the mint URL, relay, and gateway
4. **Privacy Protection**: Requests are routed through the relay, providing privacy from both the relay and the gateway

#### OHTTP Arguments

- `--ohttp-relay <URL>`: OHTTP relay URL for routing requests through a privacy relay

#### Example OHTTP Usage

```bash
# Standard OHTTP usage with relay
cdk-cli --ohttp-relay https://ohttp-relay.example.com balance

# All commands work with OHTTP
cdk-cli --ohttp-relay https://relay.example.com send --amount 100
cdk-cli --ohttp-relay https://relay.example.com receive <TOKEN>
cdk-cli --ohttp-relay https://relay.example.com mint --amount 1000
```

#### OHTTP vs Regular Proxy

OHTTP provides significantly better privacy compared to regular HTTP proxies:

- **Regular proxy:** `cdk-cli --proxy https://proxy.example.com balance`
- Proxy can see all request content and your IP
- **OHTTP relay:** `cdk-cli --ohttp-relay https://relay.example.com balance`
- Relay cannot see request content (cryptographically protected)
- Gateway cannot see your real IP address
- Provides true metadata protection

#### Important Notes

- OHTTP requires the mint to explicitly support it
- If you specify `--ohttp-relay` but the mint doesn't support OHTTP, you'll see a warning and fall back to regular HTTP
- Gateway URL is automatically determined from the mint's OHTTP configuration
- When OHTTP is used, WebSocket subscriptions are automatically disabled in favor of HTTP polling

## Building

### Features

- `ohttp`: Enables OHTTP support for enhanced privacy
- `sqlcipher`: Enables SQLCipher support for encrypted databases
- `redb`: Enables redb as an alternative database backend

### Examples

```bash
# Build with all features
cargo build --features "ohttp,sqlcipher,redb"

# Build with just OHTTP
cargo build --features ohttp
```
72 changes: 58 additions & 14 deletions crates/cdk-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
use bip39::rand::{thread_rng, Rng};
use bip39::Mnemonic;
use cdk::cdk_database;
use cdk::cdk_database::WalletDatabase;
use cdk::nuts::CurrencyUnit;
use cdk::wallet::{HttpClient, MultiMintWallet, Wallet, WalletBuilder};
#[cfg(feature = "ohttp")]
use cdk::wallet::{ohttp_transport::OhttpTransport, BaseHttpClient};
use cdk::wallet::{HttpClient, MintConnector, MultiMintWallet, Wallet, WalletBuilder};
#[cfg(feature = "redb")]
use cdk_redb::WalletRedbDatabase;
use cdk_sqlite::WalletSqliteDatabase;
Expand Down Expand Up @@ -49,6 +51,10 @@ struct Cli {
/// NWS Proxy
#[arg(short, long)]
proxy: Option<Url>,
/// OHTTP Relay URL for proxying requests (advanced usage)
#[cfg(feature = "ohttp")]
#[arg(long)]
ohttp_relay: Option<Url>,
#[command(subcommand)]
command: Commands,
}
Expand Down Expand Up @@ -179,10 +185,10 @@ async fn main() -> Result<()> {
vec![CurrencyUnit::Sat]
};

let proxy_client = if let Some(proxy_url) = args.proxy.as_ref() {
let proxy_client = if args.ohttp_relay.is_none() && args.proxy.is_some() {
Some(HttpClient::with_proxy(
mint_url.clone(),
proxy_url.clone(),
args.proxy.as_ref().unwrap().clone(),
None,
true,
)?)
Expand All @@ -194,17 +200,55 @@ async fn main() -> Result<()> {

for unit in units {
let mint_url_clone = mint_url.clone();
let mut builder = WalletBuilder::new()
.mint_url(mint_url_clone.clone())
.unit(unit)
.localstore(localstore.clone())
.seed(seed);

if let Some(http_client) = &proxy_client {
builder = builder.client(http_client.clone());
}
let client = HttpClient::new(mint_url.clone(), None);

let mint_info = client.get_mint_info().await?;

let wallet = {
#[allow(unused_mut)] // mut needed for ohttp feature
let mut builder = WalletBuilder::new()
.mint_url(mint_url_clone.clone())
.unit(unit)
.localstore(localstore.clone())
.seed(seed);

// Configure client based on arguments
#[cfg(feature = "ohttp")]
if args.ohttp_relay.is_some() && mint_info.supports_ohttp() {
let mint_ohttp_settings =
mint_info.ohttp_config().expect("Checked its enabled");

let ohttp_relay = args
.ohttp_relay
.as_ref()
.ok_or(anyhow!("Relay url is invalid"))?;

let gateway_url = mint_ohttp_settings
.gateway_url
.clone()
.unwrap_or(mint_url_clone.to_string());

let ohttp_transport = OhttpTransport::new(
mint_url_clone.to_string().parse()?,
ohttp_relay.clone(),
gateway_url.parse()?,
);

// Create HttpClient with OHTTP transport
let ohttp_client = BaseHttpClient::with_transport(
mint_url_clone.clone(),
ohttp_transport,
None,
);
builder = builder.client(ohttp_client).use_http_subscription();
} else if mint_info.supports_ohttp() {
tracing::warn!("This mint supports ohttp but you have not provided a relay");
} else if let Some(client) = &proxy_client {
builder = builder.client(client.clone());
}

let wallet = builder.build()?;
builder.build()?
};

let wallet_clone = wallet.clone();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ fn create_ldk_settings(
mint_management_rpc: None,
prometheus: None,
auth: None,
ohttp_gateway: None,
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-integration-tests/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub fn create_fake_wallet_settings(
mint_management_rpc: None,
auth: None,
prometheus: Some(Default::default()),
ohttp_gateway: None,
}
}

Expand Down Expand Up @@ -267,6 +268,7 @@ pub fn create_cln_settings(
mint_management_rpc: None,
auth: None,
prometheus: Some(Default::default()),
ohttp_gateway: None,
}
}

Expand Down Expand Up @@ -313,5 +315,6 @@ pub fn create_lnd_settings(
mint_management_rpc: None,
auth: None,
prometheus: Some(Default::default()),
ohttp_gateway: None,
}
}
2 changes: 2 additions & 0 deletions crates/cdk-mintd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ cdk-axum.workspace = true
cdk-signatory.workspace = true
cdk-mint-rpc = { workspace = true, optional = true }
cdk-payment-processor = { workspace = true, optional = true }
ohttp-gateway = { path = "../ohttp-gateway" }
config.workspace = true
cdk-prometheus = { workspace = true, optional = true , features = ["system-metrics"]}
clap.workspace = true
Expand All @@ -68,5 +69,6 @@ lightning-invoice.workspace = true
home.workspace = true
utoipa = { workspace = true, optional = true }
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"], optional = true }
url.workspace = true

[build-dependencies]
4 changes: 4 additions & 0 deletions crates/cdk-mintd/example.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ mnemonic = ""
enabled = false
# address = "127.0.0.1"
# port = 8086
#

# [ohttp_gateway]
# enabled = false

#[prometheus]
#enabled = true
Expand Down
Loading
Loading