Browse Source

Add gRPC server

Cesar Rodas 2 months ago
parent
commit
8a3f544fdf

+ 222 - 26
Cargo.lock

@@ -311,6 +311,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
 name = "autocfg"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -323,7 +329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
 dependencies = [
  "async-trait",
- "axum-core",
+ "axum-core 0.3.4",
  "base64 0.21.7",
  "bitflags 1.3.2",
  "bytes",
@@ -352,6 +358,33 @@ dependencies = [
 ]
 
 [[package]]
+name = "axum"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
+dependencies = [
+ "async-trait",
+ "axum-core 0.4.5",
+ "bytes",
+ "futures-util",
+ "http 1.2.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper 1.0.2",
+ "tower 0.5.2",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
 name = "axum-core"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -369,6 +402,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "axum-core"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.2.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper 1.0.2",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
 name = "backtrace"
 version = "0.3.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -790,7 +843,7 @@ version = "0.6.0"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "cdk",
  "futures",
  "moka",
@@ -869,6 +922,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "cdk-database"
+version = "0.6.1"
+dependencies = [
+ "cdk-common",
+ "cdk-redb",
+ "cdk-sqlite",
+ "serde",
+]
+
+[[package]]
 name = "cdk-fake-wallet"
 version = "0.6.0"
 dependencies = [
@@ -893,7 +956,7 @@ version = "0.6.0"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "bip39",
  "cdk",
  "cdk-axum",
@@ -925,7 +988,7 @@ version = "0.6.0"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "bitcoin 0.32.5",
  "cdk",
  "futures",
@@ -956,17 +1019,17 @@ name = "cdk-mintd"
 version = "0.6.1"
 dependencies = [
  "anyhow",
- "axum",
+ "axum 0.6.20",
  "bip39",
  "cdk",
  "cdk-axum",
  "cdk-cln",
+ "cdk-database",
  "cdk-fake-wallet",
  "cdk-lnbits",
  "cdk-lnd",
  "cdk-phoenixd",
- "cdk-redb",
- "cdk-sqlite",
+ "cdk-signatory",
  "cdk-strike",
  "clap",
  "config",
@@ -990,7 +1053,7 @@ version = "0.6.0"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "bitcoin 0.32.5",
  "cdk",
  "futures",
@@ -1039,7 +1102,10 @@ dependencies = [
  "async-trait",
  "bitcoin 0.32.5",
  "cdk-common",
+ "prost 0.13.4",
  "tokio",
+ "tonic 0.12.3",
+ "tonic-build 0.12.3",
  "tracing",
 ]
 
@@ -1065,7 +1131,7 @@ version = "0.6.0"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "bitcoin 0.32.5",
  "cdk",
  "futures",
@@ -1621,13 +1687,13 @@ dependencies = [
  "http-body 0.4.6",
  "hyper 0.14.32",
  "hyper-rustls 0.24.2",
- "prost",
+ "prost 0.12.6",
  "rustls 0.21.12",
  "rustls-pemfile 1.0.4",
  "tokio",
  "tokio-stream",
- "tonic",
- "tonic-build",
+ "tonic 0.10.2",
+ "tonic-build 0.10.2",
  "tower 0.4.13",
 ]
 
@@ -1862,6 +1928,25 @@ dependencies = [
 ]
 
 [[package]]
+name = "h2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http 1.2.0",
+ "indexmap 2.7.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
 name = "half"
 version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2066,7 +2151,7 @@ dependencies = [
  "futures-channel",
  "futures-core",
  "futures-util",
- "h2",
+ "h2 0.3.26",
  "http 0.2.12",
  "http-body 0.4.6",
  "httparse",
@@ -2089,9 +2174,11 @@ dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
+ "h2 0.4.7",
  "http 1.2.0",
  "http-body 1.0.1",
  "httparse",
+ "httpdate",
  "itoa",
  "pin-project-lite",
  "smallvec",
@@ -2145,6 +2232,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "hyper-timeout"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
+dependencies = [
+ "hyper 1.5.2",
+ "hyper-util",
+ "pin-project-lite",
+ "tokio",
+ "tower-service",
+]
+
+[[package]]
 name = "hyper-util"
 version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2595,7 +2695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "de0781184bc1ef37a5096191ab2cec74e7b467876547e067a61c515192cabfe7"
 dependencies = [
  "anyhow",
- "axum",
+ "axum 0.6.20",
  "log",
  "reqwest",
  "serde",
@@ -3122,7 +3222,7 @@ checksum = "06f3fb00880034c8199fa10d4635e984ccb09bad0ad294b869d63d3f2d15cc24"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "log",
  "reqwest",
  "serde",
@@ -3289,7 +3389,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29"
 dependencies = [
  "bytes",
- "prost-derive",
+ "prost-derive 0.12.6",
+]
+
+[[package]]
+name = "prost"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
+dependencies = [
+ "bytes",
+ "prost-derive 0.13.4",
 ]
 
 [[package]]
@@ -3306,8 +3416,28 @@ dependencies = [
  "once_cell",
  "petgraph",
  "prettyplease",
- "prost",
- "prost-types",
+ "prost 0.12.6",
+ "prost-types 0.12.6",
+ "regex",
+ "syn 2.0.96",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
+dependencies = [
+ "heck 0.5.0",
+ "itertools 0.12.1",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost 0.13.4",
+ "prost-types 0.13.4",
  "regex",
  "syn 2.0.96",
  "tempfile",
@@ -3327,12 +3457,34 @@ dependencies = [
 ]
 
 [[package]]
+name = "prost-derive"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
+dependencies = [
+ "anyhow",
+ "itertools 0.12.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
 name = "prost-types"
 version = "0.12.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0"
 dependencies = [
- "prost",
+ "prost 0.12.6",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
+dependencies = [
+ "prost 0.13.4",
 ]
 
 [[package]]
@@ -4400,7 +4552,7 @@ checksum = "50bd122f8342d272c6818e216972295810297fb6681eae8d8f3357446860e928"
 dependencies = [
  "anyhow",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "http-body-util",
  "hyper 0.14.32",
  "log",
@@ -4866,17 +5018,17 @@ checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e"
 dependencies = [
  "async-stream",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "base64 0.21.7",
  "bytes",
- "h2",
+ "h2 0.3.26",
  "http 0.2.12",
  "http-body 0.4.6",
  "hyper 0.14.32",
- "hyper-timeout",
+ "hyper-timeout 0.4.1",
  "percent-encoding",
  "pin-project",
- "prost",
+ "prost 0.12.6",
  "rustls 0.21.12",
  "rustls-pemfile 1.0.4",
  "tokio",
@@ -4889,6 +5041,36 @@ dependencies = [
 ]
 
 [[package]]
+name = "tonic"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum 0.7.9",
+ "base64 0.22.1",
+ "bytes",
+ "h2 0.4.7",
+ "http 1.2.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "hyper 1.5.2",
+ "hyper-timeout 0.5.2",
+ "hyper-util",
+ "percent-encoding",
+ "pin-project",
+ "prost 0.13.4",
+ "socket2 0.5.8",
+ "tokio",
+ "tokio-stream",
+ "tower 0.4.13",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
 name = "tonic-build"
 version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4896,7 +5078,21 @@ checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889"
 dependencies = [
  "prettyplease",
  "proc-macro2",
- "prost-build",
+ "prost-build 0.12.6",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "tonic-build"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "prost-build 0.13.4",
+ "prost-types 0.13.4",
  "quote",
  "syn 2.0.96",
 ]
@@ -5255,7 +5451,7 @@ version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "154517adf0d0b6e22e8e1f385628f14fcaa3db43531dc74303d3edef89d6dfe5"
 dependencies = [
- "axum",
+ "axum 0.6.20",
  "mime_guess",
  "regex",
  "rust-embed",

+ 12 - 0
crates/cashu/src/nuts/nut00/mod.rs

@@ -200,6 +200,18 @@ pub enum Witness {
     HTLCWitness(HTLCWitness),
 }
 
+impl From<P2PKWitness> for Witness {
+    fn from(witness: P2PKWitness) -> Self {
+        Self::P2PKWitness(witness)
+    }
+}
+
+impl From<HTLCWitness> for Witness {
+    fn from(witness: HTLCWitness) -> Self {
+        Self::HTLCWitness(witness)
+    }
+}
+
 impl Witness {
     /// Add signatures to [`Witness`]
     pub fn add_signatures(&mut self, signatues: Vec<String>) {

+ 16 - 0
crates/cdk-database/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "cdk-database"
+version = "0.6.1"
+edition = "2021"
+description = "CDK database"
+
+[features]
+mint = ["cdk-sqlite/mint", "cdk-redb/mint"]
+wallet = ["cdk-sqlite/wallet", "cdk-redb/wallet"]
+default = ["mint", "wallet"]
+
+[dependencies]
+cdk-common = { path = "../cdk-common" }
+cdk-sqlite = { path = "../cdk-sqlite", default-features = false }
+cdk-redb = { path = "../cdk-redb", default-features = false }
+serde = { version = "1.0.217", features = ["derive"] }

+ 57 - 0
crates/cdk-database/src/lib.rs

@@ -0,0 +1,57 @@
+//! Common types for the CDK CLI
+use std::path::PathBuf;
+use std::sync::Arc;
+
+use cdk_redb::MintRedbDatabase;
+use cdk_sqlite::MintSqliteDatabase;
+use serde::{Deserialize, Serialize};
+
+/// Database engine definition
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
+#[serde(rename_all = "lowercase")]
+pub enum DatabaseEngine {
+    #[default]
+    Sqlite,
+    Redb,
+}
+
+impl std::str::FromStr for DatabaseEngine {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.to_lowercase().as_str() {
+            "sqlite" => Ok(DatabaseEngine::Sqlite),
+            "redb" => Ok(DatabaseEngine::Redb),
+            _ => Err(format!("Unknown database engine: {}", s)),
+        }
+    }
+}
+
+impl DatabaseEngine {
+    /// Convert the database instance into a mint database
+    pub async fn mint<P: Into<PathBuf>>(
+        self,
+        work_dir: P,
+    ) -> Result<
+        Arc<
+            dyn cdk_common::database::MintDatabase<Err = cdk_common::database::Error>
+                + Sync
+                + Send
+                + 'static,
+        >,
+        cdk_common::database::Error,
+    > {
+        match self {
+            DatabaseEngine::Sqlite => {
+                let sql_db_path = work_dir.into().join("cdk-mintd.sqlite");
+                let db = MintSqliteDatabase::new(&sql_db_path).await?;
+                db.migrate().await;
+                Ok(Arc::new(db))
+            }
+            DatabaseEngine::Redb => {
+                let redb_path = work_dir.into().join("cdk-mintd.redb");
+                Ok(Arc::new(MintRedbDatabase::new(&redb_path)?))
+            }
+        }
+    }
+}

+ 5 - 5
crates/cdk-mintd/Cargo.toml

@@ -15,17 +15,17 @@ axum = "0.6.20"
 cdk = { path = "../cdk", version = "0.6.0", default-features = false, features = [
     "mint",
 ] }
-cdk-redb = { path = "../cdk-redb", version = "0.6.0", default-features = false, features = [
-    "mint",
-] }
-cdk-sqlite = { path = "../cdk-sqlite", version = "0.6.0", default-features = false, features = [
+cdk-cln = { path = "../cdk-cln", version = "0.6.0", default-features = false }
+cdk-database = { path = "../cdk-database", version = "0.6.0", default-features = false, features = [
     "mint",
 ] }
-cdk-cln = { path = "../cdk-cln", version = "0.6.0", default-features = false }
 cdk-lnbits = { path = "../cdk-lnbits", version = "0.6.0", default-features = false }
 cdk-phoenixd = { path = "../cdk-phoenixd", version = "0.6.0", default-features = false }
 cdk-lnd = { path = "../cdk-lnd", version = "0.6.0", default-features = false }
 cdk-fake-wallet = { path = "../cdk-fake-wallet", version = "0.6.0", default-features = false }
+cdk-signatory = { path = "../cdk-signatory", default-features = false, features = [
+    "server",
+] }
 cdk-strike = { path = "../cdk-strike", version = "0.6.0" }
 cdk-axum = { path = "../cdk-axum", version = "0.6.0", default-features = false }
 config = { version = "0.13.3", features = ["toml"] }

+ 60 - 0
crates/cdk-mintd/src/bin/signatory.rs

@@ -0,0 +1,60 @@
+use std::collections::HashMap;
+use std::env;
+use std::str::FromStr;
+
+use bip39::Mnemonic;
+use cdk_mintd::cli::CLIArgs;
+use cdk_mintd::env_vars::ENV_WORK_DIR;
+use cdk_mintd::{config, work_dir};
+use cdk_signatory::proto::server::grpc_server;
+use cdk_signatory::MemorySignatory;
+use clap::Parser;
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args = CLIArgs::parse();
+    let work_dir = if let Some(work_dir) = args.work_dir {
+        tracing::info!("Using work dir from cmd arg");
+        work_dir
+    } else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
+        tracing::info!("Using work dir from env var");
+        env_work_dir.into()
+    } else {
+        work_dir()?
+    };
+
+    let config_file_arg = match args.config {
+        Some(c) => c,
+        None => work_dir.join("config.toml"),
+    };
+
+    let settings = if config_file_arg.exists() {
+        config::Settings::new(Some(config_file_arg), false)
+    } else {
+        tracing::info!("Config file does not exist. Attempting to read env vars");
+        config::Settings::default()
+    };
+
+    // This check for any settings defined in ENV VARs
+    // ENV VARS will take **priority** over those in the config
+    let mut settings = settings.from_env(false)?;
+    let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?;
+
+    let signatory = MemorySignatory::new(
+        settings.database.engine.clone().mint(&work_dir).await?,
+        &mnemonic.to_seed_normalized(""),
+        settings
+            .supported_units
+            .take()
+            .expect("No supported units defined")
+            .into_iter()
+            .map(|u| (u, (0, 32)))
+            .collect::<HashMap<_, _>>(),
+        HashMap::new(),
+    )
+    .await?;
+
+    grpc_server(signatory, "[::1]:50051".parse().unwrap()).await?;
+
+    Ok(())
+}

+ 35 - 49
crates/cdk-mintd/src/config.rs

@@ -3,6 +3,7 @@ use std::path::PathBuf;
 use cdk::nuts::{CurrencyUnit, PublicKey};
 use cdk::Amount;
 use cdk_axum::cache;
+use cdk_database::DatabaseEngine;
 use config::{Config, ConfigError, File};
 use serde::{Deserialize, Serialize};
 
@@ -149,26 +150,6 @@ fn default_max_delay_time() -> u64 {
     3
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
-#[serde(rename_all = "lowercase")]
-pub enum DatabaseEngine {
-    #[default]
-    Sqlite,
-    Redb,
-}
-
-impl std::str::FromStr for DatabaseEngine {
-    type Err = String;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s.to_lowercase().as_str() {
-            "sqlite" => Ok(DatabaseEngine::Sqlite),
-            "redb" => Ok(DatabaseEngine::Redb),
-            _ => Err(format!("Unknown database engine: {}", s)),
-        }
-    }
-}
-
 #[derive(Debug, Clone, Serialize, Deserialize, Default)]
 pub struct Database {
     pub engine: DatabaseEngine,
@@ -186,6 +167,8 @@ pub struct Settings {
     pub phoenixd: Option<Phoenixd>,
     pub lnd: Option<Lnd>,
     pub fake_wallet: Option<FakeWallet>,
+    pub supported_units: Option<Vec<CurrencyUnit>>,
+    pub remote_signatory: Option<String>,
     pub database: Database,
 }
 
@@ -211,13 +194,13 @@ pub struct MintInfo {
 
 impl Settings {
     #[must_use]
-    pub fn new<P>(config_file_name: Option<P>) -> Self
+    pub fn new<P>(config_file_name: Option<P>, check_ln: bool) -> Self
     where
         P: Into<PathBuf>,
     {
         let default_settings = Self::default();
         // attempt to construct settings with file
-        let from_file = Self::new_from_default(&default_settings, config_file_name);
+        let from_file = Self::new_from_default(&default_settings, config_file_name, check_ln);
         match from_file {
             Ok(f) => f,
             Err(e) => {
@@ -230,6 +213,7 @@ impl Settings {
     fn new_from_default<P>(
         default: &Settings,
         config_file_name: Option<P>,
+        check_ln: bool,
     ) -> Result<Self, ConfigError>
     where
         P: Into<PathBuf>,
@@ -252,34 +236,36 @@ impl Settings {
             .build()?;
         let settings: Settings = config.try_deserialize()?;
 
-        match settings.ln.ln_backend {
-            LnBackend::None => panic!("Ln backend must be set"),
-            LnBackend::Cln => assert!(
-                settings.cln.is_some(),
-                "CLN backend requires a valid config."
-            ),
-            LnBackend::Strike => assert!(
-                settings.strike.is_some(),
-                "Strike backend requires a valid config."
-            ),
-            LnBackend::LNbits => assert!(
-                settings.lnbits.is_some(),
-                "LNbits backend requires a valid config"
-            ),
-            LnBackend::Phoenixd => assert!(
-                settings.phoenixd.is_some(),
-                "Phoenixd backend requires a valid config"
-            ),
-            LnBackend::Lnd => {
-                assert!(
-                    settings.lnd.is_some(),
-                    "LND backend requires a valid config."
-                )
+        if check_ln {
+            match settings.ln.ln_backend {
+                LnBackend::None => panic!("Ln backend must be set"),
+                LnBackend::Cln => assert!(
+                    settings.cln.is_some(),
+                    "CLN backend requires a valid config."
+                ),
+                LnBackend::Strike => assert!(
+                    settings.strike.is_some(),
+                    "Strike backend requires a valid config."
+                ),
+                LnBackend::LNbits => assert!(
+                    settings.lnbits.is_some(),
+                    "LNbits backend requires a valid config"
+                ),
+                LnBackend::Phoenixd => assert!(
+                    settings.phoenixd.is_some(),
+                    "Phoenixd backend requires a valid config"
+                ),
+                LnBackend::Lnd => {
+                    assert!(
+                        settings.lnd.is_some(),
+                        "LND backend requires a valid config."
+                    )
+                }
+                LnBackend::FakeWallet => assert!(
+                    settings.fake_wallet.is_some(),
+                    "FakeWallet backend requires a valid config."
+                ),
             }
-            LnBackend::FakeWallet => assert!(
-                settings.fake_wallet.is_some(),
-                "FakeWallet backend requires a valid config."
-            ),
         }
 
         Ok(settings)

+ 30 - 26
crates/cdk-mintd/src/env_vars.rs

@@ -4,10 +4,11 @@ use std::str::FromStr;
 
 use anyhow::{anyhow, bail, Result};
 use cdk::nuts::CurrencyUnit;
+use cdk_database::DatabaseEngine;
 
 use crate::config::{
-    Cln, Database, DatabaseEngine, FakeWallet, Info, LNbits, Ln, LnBackend, Lnd, MintInfo,
-    Phoenixd, Settings, Strike,
+    Cln, Database, FakeWallet, Info, LNbits, Ln, LnBackend, Lnd, MintInfo, Phoenixd, Settings,
+    Strike,
 };
 
 pub const ENV_WORK_DIR: &str = "CDK_MINTD_WORK_DIR";
@@ -72,7 +73,7 @@ pub const ENV_FAKE_WALLET_MIN_DELAY: &str = "CDK_MINTD_FAKE_WALLET_MIN_DELAY";
 pub const ENV_FAKE_WALLET_MAX_DELAY: &str = "CDK_MINTD_FAKE_WALLET_MAX_DELAY";
 
 impl Settings {
-    pub fn from_env(&mut self) -> Result<Self> {
+    pub fn from_env(mut self, check_ln: bool) -> Result<Self> {
         if let Ok(database) = env::var(DATABASE_ENV_VAR) {
             let engine = DatabaseEngine::from_str(&database).map_err(|err| anyhow!(err))?;
             self.database = Database { engine };
@@ -82,29 +83,32 @@ impl Settings {
         self.mint_info = self.mint_info.clone().from_env();
         self.ln = self.ln.clone().from_env();
 
-        match self.ln.ln_backend {
-            LnBackend::Cln => {
-                self.cln = Some(self.cln.clone().unwrap_or_default().from_env());
-            }
-            LnBackend::Strike => {
-                self.strike = Some(self.strike.clone().unwrap_or_default().from_env());
-            }
-            LnBackend::LNbits => {
-                self.lnbits = Some(self.lnbits.clone().unwrap_or_default().from_env());
-            }
-            LnBackend::FakeWallet => {
-                self.fake_wallet = Some(self.fake_wallet.clone().unwrap_or_default().from_env());
-            }
-            LnBackend::Phoenixd => {
-                self.phoenixd = Some(self.phoenixd.clone().unwrap_or_default().from_env());
-            }
-            LnBackend::Lnd => {
-                self.lnd = Some(self.lnd.clone().unwrap_or_default().from_env());
-            }
-            LnBackend::None => bail!("Ln backend must be set"),
-        }
-
-        Ok(self.clone())
+        if check_ln {
+            match self.ln.ln_backend {
+                LnBackend::Cln => {
+                    self.cln = Some(self.cln.clone().unwrap_or_default().from_env());
+                }
+                LnBackend::Strike => {
+                    self.strike = Some(self.strike.clone().unwrap_or_default().from_env());
+                }
+                LnBackend::LNbits => {
+                    self.lnbits = Some(self.lnbits.clone().unwrap_or_default().from_env());
+                }
+                LnBackend::FakeWallet => {
+                    self.fake_wallet =
+                        Some(self.fake_wallet.clone().unwrap_or_default().from_env());
+                }
+                LnBackend::Phoenixd => {
+                    self.phoenixd = Some(self.phoenixd.clone().unwrap_or_default().from_env());
+                }
+                LnBackend::Lnd => {
+                    self.lnd = Some(self.lnd.clone().unwrap_or_default().from_env());
+                }
+                LnBackend::None => bail!("Ln backend must be set"),
+            }
+        }
+
+        Ok(self)
     }
 }
 

+ 19 - 4
crates/cdk-mintd/src/lib.rs

@@ -2,6 +2,8 @@
 
 use std::path::PathBuf;
 
+use anyhow::anyhow;
+
 pub mod cli;
 pub mod config;
 pub mod env_vars;
@@ -22,6 +24,16 @@ fn expand_path(path: &str) -> Option<PathBuf> {
     }
 }
 
+/// Work dir
+pub fn work_dir() -> anyhow::Result<PathBuf> {
+    let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
+    let dir = home_dir.join(".cdk-mintd");
+
+    std::fs::create_dir_all(&dir)?;
+
+    Ok(dir)
+}
+
 #[cfg(test)]
 mod test {
     use std::env::current_dir;
@@ -30,10 +42,13 @@ mod test {
 
     #[test]
     fn example_is_parsed() {
-        let config = config::Settings::new(Some(format!(
-            "{}/example.config.toml",
-            current_dir().expect("cwd").to_string_lossy()
-        )));
+        let config = config::Settings::new(
+            Some(format!(
+                "{}/example.config.toml",
+                current_dir().expect("cwd").to_string_lossy()
+            )),
+            true,
+        );
         let cache = config.info.http_cache;
 
         assert_eq!(cache.ttl, Some(60));

+ 13 - 34
crates/cdk-mintd/src/main.rs

@@ -5,17 +5,15 @@
 
 use std::collections::HashMap;
 use std::env;
-use std::path::PathBuf;
 use std::str::FromStr;
 use std::sync::Arc;
 
-use anyhow::{anyhow, bail, Result};
+use anyhow::bail;
 use axum::http::Request;
 use axum::middleware::Next;
 use axum::response::Response;
 use axum::{middleware, Router};
 use bip39::Mnemonic;
-use cdk::cdk_database::{self, MintDatabase};
 use cdk::cdk_lightning;
 use cdk::cdk_lightning::MintLightning;
 use cdk::mint::{MintBuilder, MintMeltLimits};
@@ -25,11 +23,10 @@ use cdk::nuts::{ContactInfo, CurrencyUnit, MintVersion, PaymentMethod};
 use cdk::types::LnKey;
 use cdk_axum::cache::HttpCache;
 use cdk_mintd::cli::CLIArgs;
-use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
+use cdk_mintd::config::{self, LnBackend};
 use cdk_mintd::env_vars::ENV_WORK_DIR;
 use cdk_mintd::setup::LnBackendSetup;
-use cdk_redb::MintRedbDatabase;
-use cdk_sqlite::MintSqliteDatabase;
+use cdk_mintd::work_dir;
 use clap::Parser;
 use tokio::sync::Notify;
 use tower_http::compression::CompressionLayer;
@@ -76,8 +73,8 @@ async fn main() -> anyhow::Result<()> {
 
     let mut mint_builder = MintBuilder::new();
 
-    let mut settings = if config_file_arg.exists() {
-        config::Settings::new(Some(config_file_arg))
+    let settings = if config_file_arg.exists() {
+        config::Settings::new(Some(config_file_arg), true)
     } else {
         tracing::info!("Config file does not exist. Attempting to read env vars");
         config::Settings::default()
@@ -85,23 +82,8 @@ async fn main() -> anyhow::Result<()> {
 
     // This check for any settings defined in ENV VARs
     // ENV VARS will take **priority** over those in the config
-    let settings = settings.from_env()?;
-
-    let localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync> =
-        match settings.database.engine {
-            DatabaseEngine::Sqlite => {
-                let sql_db_path = work_dir.join("cdk-mintd.sqlite");
-                let sqlite_db = MintSqliteDatabase::new(&sql_db_path).await?;
-
-                sqlite_db.migrate().await;
-
-                Arc::new(sqlite_db)
-            }
-            DatabaseEngine::Redb => {
-                let redb_path = work_dir.join("cdk-mintd.redb");
-                Arc::new(MintRedbDatabase::new(&redb_path)?)
-            }
-        };
+    let settings = settings.from_env(true)?;
+    let localstore = settings.database.engine.clone().mint(&work_dir).await?;
 
     mint_builder = mint_builder.with_localstore(localstore);
 
@@ -307,6 +289,12 @@ async fn main() -> anyhow::Result<()> {
         .with_quote_ttl(10000, 10000)
         .with_seed(mnemonic.to_seed_normalized("").to_vec());
 
+    mint_builder = if let Some(remote_signatory) = settings.remote_signatory.clone() {
+        mint_builder.with_remote_signatory(remote_signatory)
+    } else {
+        mint_builder
+    };
+
     let cached_endpoints = vec![
         CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
         CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
@@ -412,12 +400,3 @@ async fn logging_middleware<B>(req: Request<B>, next: Next<B>) -> Response {
 
     response
 }
-
-fn work_dir() -> Result<PathBuf> {
-    let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
-    let dir = home_dir.join(".cdk-mintd");
-
-    std::fs::create_dir_all(&dir)?;
-
-    Ok(dir)
-}

+ 10 - 1
crates/cdk-signatory/Cargo.toml

@@ -4,6 +4,10 @@ version = "0.6.0"
 edition = "2021"
 description = "CDK signatory default implementation"
 
+[features]
+default = []
+server = []
+
 [dependencies]
 async-trait = "0.1.83"
 bitcoin = { version = "0.32.2", features = [
@@ -16,4 +20,9 @@ cdk-common = { path = "../cdk-common", default-features = false, features = [
     "mint",
 ] }
 tracing = "0.1.41"
-tokio = { version = "1.21", features = ["rt", "macros", "sync", "time"] }
+tokio = { version = "1.21", features = ["full"] }
+tonic = "0.12.3"
+prost = "0.13.4"
+
+[build-dependencies]
+tonic-build = { version = "0.12.3", features = ["prost"] }

+ 3 - 0
crates/cdk-signatory/build.rs

@@ -0,0 +1,3 @@
+fn main() {
+    tonic_build::compile_protos("src/proto/signatory.proto").unwrap();
+}

+ 4 - 0
crates/cdk-signatory/src/lib.rs

@@ -25,6 +25,10 @@ use cdk_common::signatory::Signatory;
 use cdk_common::util::unix_time;
 use tokio::sync::RwLock;
 
+pub mod proto;
+
+pub use proto::client::RemoteSigner;
+
 /// Generate new [`MintKeySetInfo`] from path
 #[tracing::instrument(skip_all)]
 fn create_new_keyset<C: secp256k1::Signing>(

+ 0 - 3
crates/cdk-signatory/src/main.rs

@@ -1,3 +0,0 @@
-fn main() {
-    println!("Hello, world!");
-}

+ 67 - 0
crates/cdk-signatory/src/proto/client.rs

@@ -0,0 +1,67 @@
+use std::collections::HashMap;
+
+use bitcoin::bip32::DerivationPath;
+use cdk_common::error::Error;
+use cdk_common::signatory::Signatory;
+use cdk_common::{
+    BlindSignature, BlindedMessage, CurrencyUnit, Id, KeySet, KeysResponse, KeysetResponse, Proof,
+};
+
+use crate::proto::signatory_client::SignatoryClient;
+
+/// A client for the Signatory service.
+pub struct RemoteSigner {
+    client: SignatoryClient<tonic::transport::Channel>,
+}
+
+impl RemoteSigner {
+    /// Create a new RemoteSigner from a tonic transport channel.
+    pub async fn new(url: String) -> Result<Self, tonic::transport::Error> {
+        Ok(Self {
+            client: SignatoryClient::connect(url).await?,
+        })
+    }
+}
+
+#[async_trait::async_trait]
+impl Signatory for RemoteSigner {
+    async fn blind_sign(&self, request: BlindedMessage) -> Result<BlindSignature, Error> {
+        let req: super::BlindedMessage = request.into();
+        self.client
+            .clone()
+            .blind_sign(req)
+            .await
+            .map(|response| response.into_inner().try_into())
+            .map_err(|e| Error::Custom(e.to_string()))?
+    }
+
+    async fn verify_proof(&self, _proof: Proof) -> Result<(), Error> {
+        todo!()
+    }
+    async fn keyset(&self, _keyset_id: Id) -> Result<Option<KeySet>, Error> {
+        todo!()
+    }
+
+    async fn keyset_pubkeys(&self, _keyset_id: Id) -> Result<KeysResponse, Error> {
+        todo!()
+    }
+
+    async fn pubkeys(&self) -> Result<KeysResponse, Error> {
+        todo!()
+    }
+
+    async fn keysets(&self) -> Result<KeysetResponse, Error> {
+        todo!()
+    }
+
+    async fn rotate_keyset(
+        &self,
+        _unit: CurrencyUnit,
+        _derivation_path_index: u32,
+        _max_order: u8,
+        _input_fee_ppk: u64,
+        _custom_paths: HashMap<CurrencyUnit, DerivationPath>,
+    ) -> Result<(), Error> {
+        todo!()
+    }
+}

+ 120 - 0
crates/cdk-signatory/src/proto/mod.rs

@@ -0,0 +1,120 @@
+use cdk_common::{HTLCWitness, P2PKWitness};
+use tonic::Status;
+
+tonic::include_proto!("cdk_signatory");
+
+pub mod client;
+#[cfg(feature = "server")]
+pub mod server;
+
+impl From<cdk_common::BlindedMessage> for BlindedMessage {
+    fn from(value: cdk_common::BlindedMessage) -> Self {
+        BlindedMessage {
+            amount: value.amount.into(),
+            keyset_id: value.keyset_id.to_string(),
+            blinded_secret: value.blinded_secret.to_bytes().to_vec(),
+            witness: value.witness.map(|x| x.into()),
+        }
+    }
+}
+
+impl TryInto<cdk_common::BlindedMessage> for BlindedMessage {
+    type Error = Status;
+    fn try_into(self) -> Result<cdk_common::BlindedMessage, Self::Error> {
+        Ok(cdk_common::BlindedMessage {
+            amount: self.amount.into(),
+            keyset_id: self
+                .keyset_id
+                .parse()
+                .map_err(|e| Status::from_error(Box::new(e)))?,
+            blinded_secret: cdk_common::PublicKey::from_slice(&self.blinded_secret)
+                .map_err(|e| Status::from_error(Box::new(e)))?,
+            witness: self.witness.map(|x| x.try_into()).transpose()?,
+        })
+    }
+}
+
+impl From<cdk_common::BlindSignatureDleq> for BlindSignatureDleq {
+    fn from(value: cdk_common::BlindSignatureDleq) -> Self {
+        BlindSignatureDleq {
+            e: value.e.as_secret_bytes().to_vec(),
+            s: value.s.as_secret_bytes().to_vec(),
+        }
+    }
+}
+
+impl TryInto<cdk_common::BlindSignatureDleq> for BlindSignatureDleq {
+    type Error = cdk_common::error::Error;
+    fn try_into(self) -> Result<cdk_common::BlindSignatureDleq, Self::Error> {
+        Ok(cdk_common::BlindSignatureDleq {
+            e: cdk_common::SecretKey::from_slice(&self.e)?,
+            s: cdk_common::SecretKey::from_slice(&self.s)?,
+        })
+    }
+}
+
+impl From<cdk_common::BlindSignature> for BlindSignature {
+    fn from(value: cdk_common::BlindSignature) -> Self {
+        BlindSignature {
+            amount: value.amount.into(),
+            blinded_secret: value.c.to_bytes().to_vec(),
+            keyset_id: value.keyset_id.to_string(),
+            dleq: value.dleq.map(|x| x.into()),
+        }
+    }
+}
+
+impl TryInto<cdk_common::BlindSignature> for BlindSignature {
+    type Error = cdk_common::error::Error;
+
+    fn try_into(self) -> Result<cdk_common::BlindSignature, Self::Error> {
+        Ok(cdk_common::BlindSignature {
+            amount: self.amount.into(),
+            c: cdk_common::PublicKey::from_slice(&self.blinded_secret)?,
+            keyset_id: self.keyset_id.parse().expect("Invalid keyset id"),
+            dleq: self.dleq.map(|dleq| dleq.try_into()).transpose()?,
+        })
+    }
+}
+
+impl From<cdk_common::Witness> for Witness {
+    fn from(value: cdk_common::Witness) -> Self {
+        match value {
+            cdk_common::Witness::P2PKWitness(P2PKWitness { signatures }) => Witness {
+                witness_type: Some(witness::WitnessType::P2pkWitness(P2pkWitness {
+                    signatures,
+                })),
+            },
+            cdk_common::Witness::HTLCWitness(HTLCWitness {
+                preimage,
+                signatures,
+            }) => Witness {
+                witness_type: Some(witness::WitnessType::HtlcWitness(HtlcWitness {
+                    preimage,
+                    signatures: signatures.unwrap_or_default(),
+                })),
+            },
+        }
+    }
+}
+
+impl TryInto<cdk_common::Witness> for Witness {
+    type Error = Status;
+    fn try_into(self) -> Result<cdk_common::Witness, Self::Error> {
+        match self.witness_type {
+            Some(witness::WitnessType::P2pkWitness(P2pkWitness { signatures })) => {
+                Ok(P2PKWitness { signatures }.into())
+            }
+            Some(witness::WitnessType::HtlcWitness(hltc_witness)) => Ok(HTLCWitness {
+                preimage: hltc_witness.preimage,
+                signatures: if hltc_witness.signatures.is_empty() {
+                    None
+                } else {
+                    Some(hltc_witness.signatures)
+                },
+            }
+            .into()),
+            None => Err(Status::invalid_argument("Witness type not set")),
+        }
+    }
+}

+ 38 - 0
crates/cdk-signatory/src/proto/server.rs

@@ -0,0 +1,38 @@
+use std::net::SocketAddr;
+
+use cdk_common::signatory::Signatory as _;
+use tonic::transport::{Error, Server};
+use tonic::{Request, Response, Status};
+
+use crate::proto::{self, signatory_server};
+use crate::MemorySignatory;
+
+struct CdkSignatory(MemorySignatory);
+
+#[tonic::async_trait]
+impl signatory_server::Signatory for CdkSignatory {
+    async fn blind_sign(
+        &self,
+        request: Request<proto::BlindedMessage>,
+    ) -> Result<Response<proto::BlindSignature>, Status> {
+        println!("Got a request: {:?}", request);
+        let blind_signature = self
+            .0
+            .blind_sign(request.into_inner().try_into()?)
+            .await
+            .map_err(|e| Status::from_error(Box::new(e)))?;
+        Ok(Response::new(blind_signature.into()))
+    }
+}
+
+/// Runs the signatory server
+pub async fn grpc_server(signatory: MemorySignatory, addr: SocketAddr) -> Result<(), Error> {
+    tracing::info!("grpc_server listening on {}", addr);
+    Server::builder()
+        .add_service(signatory_server::SignatoryServer::new(CdkSignatory(
+            signatory,
+        )))
+        .serve(addr)
+        .await?;
+    Ok(())
+}

+ 52 - 0
crates/cdk-signatory/src/proto/signatory.proto

@@ -0,0 +1,52 @@
+syntax = "proto3";
+
+package cdk_signatory;
+
+service Signatory {
+  rpc BlindSign (BlindedMessage) returns (BlindSignature);
+}
+
+
+message BlindSignature {
+    uint64 amount = 1;
+    string keyset_id = 2;
+    bytes blinded_secret = 3;
+    optional BlindSignatureDLEQ dleq = 4;
+}
+
+message BlindSignatureDLEQ {
+    bytes e = 1;
+    bytes s = 2;
+}
+
+
+// Represents a blinded message
+message BlindedMessage {
+    uint64 amount = 1;
+    string keyset_id = 2;
+    bytes blinded_secret = 3;
+    optional Witness witness = 4; // This field is optional by default in proto3
+}
+
+// Witness type
+message Witness {
+    oneof witness_type {
+        P2PKWitness p2pk_witness = 1;
+        HTLCWitness htlc_witness = 2;
+    }
+}
+
+// P2PKWitness type
+message P2PKWitness {
+    // List of signatures
+    repeated string signatures = 1;
+}
+
+// HTLCWitness type
+message HTLCWitness {
+    // Preimage
+    string preimage = 1;
+
+    // List of signatures
+    repeated string signatures = 2;
+}

+ 2 - 2
crates/cdk/Cargo.toml

@@ -21,8 +21,8 @@ http_subscription = []
 
 
 [dependencies]
-cdk-common = { path = "../cdk-common", version = "0.6.0" }
-cdk-signatory = { path = "../cdk-signatory", version = "0.6.0" }
+cdk-common = { path = "../cdk-common" }
+cdk-signatory = { path = "../cdk-signatory" }
 cbor-diag = "0.1.12"
 arc-swap = "1.7.1"
 async-trait = "0.1"

+ 35 - 11
crates/cdk/src/mint/builder.rs

@@ -20,6 +20,12 @@ use crate::nuts::{
 };
 use crate::types::{LnKey, QuoteTTL};
 
+#[derive(Debug)]
+pub enum SignatoryInfo {
+    Seed(Vec<u8>),
+    Remote(String),
+}
+
 /// Cashu Mint
 #[derive(Default)]
 pub struct MintBuilder {
@@ -31,7 +37,7 @@ pub struct MintBuilder {
     localstore: Option<Arc<dyn MintDatabase<Err = database::Error> + Send + Sync>>,
     /// Ln backends for mint
     ln: Option<HashMap<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>>,
-    seed: Option<Vec<u8>>,
+    signatory_info: Option<SignatoryInfo>,
     quote_ttl: Option<QuoteTTL>,
     /// expose supported units
     pub supported_units: HashMap<CurrencyUnit, (u64, u8)>,
@@ -79,9 +85,15 @@ impl MintBuilder {
         self
     }
 
-    /// Set seed
+    /// Set seed to create a local signatory
     pub fn with_seed(mut self, seed: Vec<u8>) -> Self {
-        self.seed = Some(seed);
+        self.signatory_info = Some(SignatoryInfo::Seed(seed));
+        self
+    }
+
+    /// connect to a remote signatary instead of a creating a local one
+    pub fn with_remote_signatory(mut self, url: String) -> Self {
+        self.signatory_info = Some(SignatoryInfo::Remote(url));
         self
     }
 
@@ -240,15 +252,27 @@ impl MintBuilder {
         let signatory = if let Some(signatory) = self.signatory {
             signatory
         } else {
-            Arc::new(
-                cdk_signatory::MemorySignatory::new(
-                    localstore.clone(),
-                    self.seed.as_ref().ok_or(anyhow!("Mint seed not set"))?,
-                    self.supported_units,
-                    HashMap::new(),
+            match self.signatory_info {
+                Some(SignatoryInfo::Seed(seed)) => Arc::new(
+                    cdk_signatory::MemorySignatory::new(
+                        localstore.clone(),
+                        &seed,
+                        self.supported_units,
+                        HashMap::new(),
+                    )
+                    .await?,
+                )
+                    as Arc<dyn Signatory + Sync + Send + 'static>,
+                Some(SignatoryInfo::Remote(url)) => Arc::new(
+                    cdk_signatory::RemoteSigner::new(url)
+                        .await
+                        .map_err(|e| anyhow!("Remote signatory error: {}", e.to_string()))?,
                 )
-                .await?,
-            )
+                    as Arc<dyn Signatory + Sync + Send + 'static>,
+                None => {
+                    return Err(anyhow!("Signatory not set"));
+                }
+            }
         };
 
         let signatory_manager = Arc::new(SignatoryManager::new(signatory));

+ 1 - 1
crates/cdk/src/mint/mod.rs

@@ -38,7 +38,7 @@ mod swap;
 /// re-export types
 pub use builder::{MintBuilder, MintMeltLimits};
 pub use cdk_common::mint::{MeltQuote, MintQuote};
-pub use cdk_signatory::MemorySignatory;
+pub use cdk_signatory::{proto::client::RemoteSigner, MemorySignatory};
 
 /// Cashu Mint
 #[derive(Clone)]