浏览代码

Add CLI for cdk-signatory to spawn an external signatory

Add to the pipeline the external signatory
Cesar Rodas 1 月之前
父节点
当前提交
be6073256e

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

@@ -18,7 +18,7 @@ cln = ["dep:cdk-cln"]
 lnd = ["dep:cdk-lnd"]
 lnbits = ["dep:cdk-lnbits"]
 fakewallet = ["dep:cdk-fake-wallet"]
-grpc-processor = ["dep:cdk-payment-processor"]
+grpc-processor = ["dep:cdk-payment-processor", "cdk-signatory/grpc"]
 sqlcipher = ["cdk-sqlite/sqlcipher"]
 # MSRV is not committed to with redb enabled
 redb = ["dep:cdk-redb"]
@@ -45,6 +45,7 @@ cdk-lnbits = { workspace = true, optional = true }
 cdk-lnd = { workspace = true, optional = true }
 cdk-fake-wallet = { workspace = true, optional = true }
 cdk-axum.workspace = true
+cdk-signatory.workspace = true
 cdk-mint-rpc = { workspace = true, optional = true }
 cdk-payment-processor = { workspace = true, optional = true }
 config = { version = "0.13.3", features = ["toml"] }
@@ -68,4 +69,3 @@ utoipa-swagger-ui = { version = "9.0.0", features = ["axum"], optional = true }
 # Dep of utopia 2.5.0 breaks so keeping here for now
 zip = "=2.4.2"
 time = "=0.3.39"
-

+ 13 - 5
crates/cdk-mintd/src/config.rs

@@ -12,7 +12,9 @@ pub struct Info {
     pub url: String,
     pub listen_host: String,
     pub listen_port: u16,
-    pub mnemonic: String,
+    pub mnemonic: Option<String>,
+    pub signatory_url: Option<String>,
+    pub signatory_certs: Option<String>,
     pub input_fee_ppk: Option<u64>,
 
     pub http_cache: cache::Config,
@@ -28,7 +30,13 @@ impl std::fmt::Debug for Info {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         // Use a fallback approach that won't panic
         let mnemonic_display = {
-            let hash = sha256::Hash::hash(self.mnemonic.clone().into_bytes().as_ref());
+            let hash = sha256::Hash::hash(
+                self.mnemonic
+                    .clone()
+                    .unwrap_or_default()
+                    .into_bytes()
+                    .as_ref(),
+            );
             format!("<hashed: {}>", hash)
         };
 
@@ -377,7 +385,7 @@ mod tests {
             url: "http://example.com".to_string(),
             listen_host: "127.0.0.1".to_string(),
             listen_port: 8080,
-            mnemonic: "test secret mnemonic phrase".to_string(),
+            mnemonic: Some("test secret mnemonic phrase".to_string()),
             input_fee_ppk: Some(100),
             ..Default::default()
         };
@@ -404,7 +412,7 @@ mod tests {
             url: "http://example.com".to_string(),
             listen_host: "127.0.0.1".to_string(),
             listen_port: 8080,
-            mnemonic: "".to_string(), // Empty mnemonic
+            mnemonic: Some("".to_string()), // Empty mnemonic
             enable_swagger_ui: Some(false),
             ..Default::default()
         };
@@ -423,7 +431,7 @@ mod tests {
             url: "http://example.com".to_string(),
             listen_host: "127.0.0.1".to_string(),
             listen_port: 8080,
-            mnemonic: "特殊字符 !@#$%^&*()".to_string(), // Special characters
+            mnemonic: Some("特殊字符 !@#$%^&*()".to_string()), // Special characters
             ..Default::default()
         };
 

+ 2 - 0
crates/cdk-mintd/src/env_vars/common.rs

@@ -6,6 +6,8 @@ pub const ENV_URL: &str = "CDK_MINTD_URL";
 pub const ENV_LISTEN_HOST: &str = "CDK_MINTD_LISTEN_HOST";
 pub const ENV_LISTEN_PORT: &str = "CDK_MINTD_LISTEN_PORT";
 pub const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
+pub const ENV_SIGNATORY_URL: &str = "CDK_MINTD_SIGNATORY_URL";
+pub const ENV_SIGNATORY_CERTS: &str = "CDK_MINTD_SIGNATORY_CERTS";
 pub const ENV_SECONDS_QUOTE_VALID: &str = "CDK_MINTD_SECONDS_QUOTE_VALID";
 pub const ENV_CACHE_SECONDS: &str = "CDK_MINTD_CACHE_SECONDS";
 pub const ENV_EXTEND_CACHE_SECONDS: &str = "CDK_MINTD_EXTEND_CACHE_SECONDS";

+ 9 - 1
crates/cdk-mintd/src/env_vars/info.rs

@@ -22,8 +22,16 @@ impl Info {
             }
         }
 
+        if let Ok(signatory_url) = env::var(ENV_SIGNATORY_URL) {
+            self.signatory_url = Some(signatory_url);
+        }
+
+        if let Ok(signatory_certs) = env::var(ENV_SIGNATORY_CERTS) {
+            self.signatory_certs = Some(signatory_certs);
+        }
+
         if let Ok(mnemonic) = env::var(ENV_MNEMONIC) {
-            self.mnemonic = mnemonic;
+            self.mnemonic = Some(mnemonic);
         }
 
         if let Ok(cache_seconds_str) = env::var(ENV_CACHE_SECONDS) {

+ 22 - 4
crates/cdk-mintd/src/main.rs

@@ -363,13 +363,31 @@ async fn main() -> anyhow::Result<()> {
         mint_builder = mint_builder.with_tos_url(tos_url.to_string());
     }
 
-    let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?;
-
     mint_builder = mint_builder
         .with_name(settings.mint_info.name)
         .with_version(mint_version)
-        .with_description(settings.mint_info.description)
-        .with_seed(mnemonic.to_seed_normalized("").to_vec());
+        .with_description(settings.mint_info.description);
+
+    mint_builder = if let Some(signatory_url) = settings.info.signatory_url {
+        tracing::info!(
+            "Connecting to remote signatory to {} with certs {:?}",
+            signatory_url,
+            settings.info.signatory_certs
+        );
+        mint_builder.with_signatory(Arc::new(
+            cdk_signatory::SignatoryRpcClient::new(signatory_url, settings.info.signatory_certs)
+                .await?,
+        ))
+    } else if let Some(mnemonic) = settings
+        .info
+        .mnemonic
+        .map(|s| Mnemonic::from_str(&s))
+        .transpose()?
+    {
+        mint_builder.with_seed(mnemonic.to_seed_normalized("").to_vec())
+    } else {
+        bail!("No seed nor remote signatory set");
+    };
 
     let cached_endpoints = vec![
         CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),

+ 9 - 4
crates/cdk-signatory/Cargo.toml

@@ -5,7 +5,9 @@ edition = "2021"
 description = "CDK signatory default implementation"
 
 [features]
-default = []
+default = ["grpc"]
+sqlcipher = ["cdk-sqlite/sqlcipher"]
+redb = ["dep:cdk-redb"]
 grpc = ["dep:tonic", "tokio/full", "dep:prost", "dep:tonic-build"]
 
 [dependencies]
@@ -15,17 +17,20 @@ bitcoin.workspace = true
 cdk-common = { workspace = true, default-features=false, features = [
     "mint", "auth",
 ] }
-tracing.workspace = true
 tokio = { workspace = true, features = ["full"] }
 tonic = { workspace = true, optional = true }
 prost = { workspace = true, optional = true }
 
 # main.rs dependencies
-cdk-sqlite = { workspace = true }
-cdk-redb = { workspace = true }
+anyhow.workspace = true
+cdk-sqlite = { workspace = true, features = ["mint", "auth"] }
+cdk-redb = { workspace = true, features = ["mint", "auth"], optional = true }
 clap = { workspace = true }
 bip39.workspace = true
 home.workspace = true
+tracing.workspace = true
+thiserror.workspace = true
+tracing-subscriber.workspace = true
 
 [build-dependencies]
 tonic-build = { workspace = true, features = ["prost"], optional = true }

+ 52 - 0
crates/cdk-signatory/certs/ca.key

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+UNWVh49KGZqfMsiWk1MIM33raDDDLw==
+-----END PRIVATE KEY-----

+ 33 - 0
crates/cdk-signatory/certs/ca.pem

@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFozCCA4ugAwIBAgIUB0XG8dnN0hiPUxVitLRPOnJ3M7UwDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
+MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xDTALBgNVBAsMBFVuaXQxDTALBgNVBAMM
+BE15Q0EwHhcNMjUwNTA4MDA1NzE1WhcNMjYwNTA4MDA1NzE1WjBhMQswCQYDVQQG
+EwJVUzEOMAwGA1UECAwFU3RhdGUxDTALBgNVBAcMBENpdHkxFTATBgNVBAoMDE9y
+Z2FuaXphdGlvbjENMAsGA1UECwwEVW5pdDENMAsGA1UEAwwETXlDQTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBALrKCqp9Z5ZkSZ7ssiUj2MWYX86RNdlK
+nGm8uOy0uuKcxTPoh+K6zVfBJafMpP9j6TN4pdsVs5JHeTxf5gG6LlpTk0uZpPxu
+qKaRfFd6l7VQ1Q/Suz4H0Yd8Z8UeeeRBTpkos5ynBuEwHDFXh+LijyRR8V5JvaQk
+9S5OA/yBG25BWbGT5mq+YMGtkitVceXJI6fZ4seFhU2zP37evGTk3DpqFz+jlcFX
+5ueJIhAHFQIUgDLbvaxdc8xnmipqzYQpP2kkzyskxZfTBZsep8FBc523wNw9MgE7
+OVJWzDtg+iRUsQyE6AcdRz/aajpe0U1/t7pGSlF88ZOVgxsWOFWxBH4IpIfRRmw8
+JNX3sXI9Fo/yOgleFXWhhWGE4xqYEeRD+eK4s6WxzKzy1MWbBwRiPDgeAYhoDHO7
+4kb4+T1o/qRqaQJhVpUCVNttxSvpFdqr5GDtqEnUjdlBURPJKk4XulQOY6cdlra+
+O7LsFtms+AaawmtAqhQDbEG27gZc4iuS46D5U7faf1hZrWxI5ap6HK7y7QaIBhLB
+006QAKnvb1hAELG8Pvu9zSiYlY0G5sR/icJvsTXZJs6E0H+WAyoWqpuTMJgvWQX2
+wreL+aPzguIMKkcFpCE0Fz8fJitvWyRISsH7QUg1PXxd+PnsoX7OH3DyNRL8Ioi7
+krOUFzP1Tk3/AgMBAAGjUzBRMB0GA1UdDgQWBBSe15UOY/6RYu4PUQ6AVAA+oMd5
+PjAfBgNVHSMEGDAWgBSe15UOY/6RYu4PUQ6AVAA+oMd5PjAPBgNVHRMBAf8EBTAD
+AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCWxo/xvUwxnoQOT+FEj37Low1R1SMSamZF
+OvcTeloNYYpuwgNBrgGILE8or3Q8Ye5UGMqIN5uWjh2yy2z+aob3cyxkckwJXy/S
+aIePQnjRvySi7jVmBPcI0Xi9lc+1P16yuKuu3HH5cjZSgPbI8Xa56K/rcbF78/2t
+GTPDgU6hPu3wEELLOX+7E6u6xi4aq8CyqXMcoBkGCFlh6Xc0yZOKlyL3KcJ5SVVm
+3xWz/uLwGsOJahK8QASB0qzyxcCHeQXiAtjAw5oyh/6Sjc5zrhIhMdyLYuDfxaJ6
+DjFnbXsXFMTbgsNT6+3VVZIhupha/hj1KJvWbUlpMgqTdy/aBe+DO7h65kj5Em5a
+SWaZZCAk8Hz5mVdUi1oOaxyoeDbcIkCDwniSr5hNpZ1c8BdCI9mFHD/HPhLfzSH/
+nlF8DEsEqEVHTFE2z1uOgd2S/wd2RWE/wmOe29QgPdYZHvVzmARNMVRdlzXD/tQH
+tsMPEDAMiuDk1Wb7DHo5KeFw3LmnnutTHwjcSVcXQLGhztH4oJ4KcQeZy5Ih2heh
+ISHCICc55LcrJxMXekvOM+UvxXOdCqsnLUpWr/Hf0gQ0qQzjTWOvOedwasLFLgTu
+Xn4qqBmHGr2/SOaVmtBDrPwL+3oRRjK1zpDa8IhTmaPC7hzGrxSNOS/8NdwiCmLk
+N69hBkyjiA==
+-----END CERTIFICATE-----

+ 1 - 0
crates/cdk-signatory/certs/ca.srl

@@ -0,0 +1 @@
+752E045AEA725936B5C345B11063D6F99000B5DE

+ 52 - 0
crates/cdk-signatory/certs/client.key

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+0rDXy7ly9RfwMbQXIjEvIFJVfChOng==
+-----END PRIVATE KEY-----

+ 32 - 0
crates/cdk-signatory/certs/client.pem

@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFlDCCA3ygAwIBAgIUdS4EWupyWTa1w0WxEGPW+ZAAtd4wDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
+MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xDTALBgNVBAsMBFVuaXQxDTALBgNVBAMM
+BE15Q0EwHhcNMjUwNTA4MDA1NzE1WhcNMjYwNTA4MDA1NzE1WjBjMQswCQYDVQQG
+EwJVUzEOMAwGA1UECAwFU3RhdGUxDTALBgNVBAcMBENpdHkxFTATBgNVBAoMDE9y
+Z2FuaXphdGlvbjENMAsGA1UECwwEVW5pdDEPMA0GA1UEAwwGY2xpZW50MIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3jbHiInk/OzW8BAkbUC7Y8P6inkD
+P9EnzzpUiN9StpjYp6ek78z8KeZeqdDElsG+/nKrbf1jK6GSR3msL6HjWhbi/cn2
+YPfM9RBinQx3FP4x8iN7TmUw0fcZfYPl++edY1ylpkJqcJWcGzUCrGXwL9Z3IVnZ
+IVGl/1M2JIaqs9bcjhyCDXNUQL+1pvoRDtvPAlbUWlAJ3F+Mdb8rOU0PqI92Is9y
+JZXmsiAi9dl8r7nNPoGw08whJQ6wew73SDsuio8c02WVV8XbSPlGDkSPPQtEgfgw
+/wCKtcsGUmqeeEz5WZIlQAGyobwva8Yhfl6HNmZzA8KAFPcuC36lRznPskUS8GO0
+T+ItqMGceN9Mblgavk+aBWIi10OYYe5kAEX83E9N5mR9Ezqt2o0pRmfjvWOYg2rA
+HookBFVx9S8veB30hqGkqN2hB8q1Jcw6exr1NojT6d5izVYsCGz29ti7a8g3lCBf
+AquJbk6M9KiJL/m6Mj60AJhmfFHAlLDO+ZLz7khuD6O9AVGkxXwRKgwTdozgBunI
+T6POILMEiK7MBlm4NSwKswEMvHjJ8fcAuiOKwGoBEv9y2zOXEXahbFRt5K4BpkWC
+wqMdcr9BjxtuP0ZyCAg7YUgOmcSKMQ0n1bbsMzGzmqW8C5UtZOBSVxYNs1U9manv
+HjgFAS1COBlEy10CAwEAAaNCMEAwHQYDVR0OBBYEFD/RZ0Ne2u64r5GZj+xKUx07
+li3+MB8GA1UdIwQYMBaAFJ7XlQ5j/pFi7g9RDoBUAD6gx3k+MA0GCSqGSIb3DQEB
+CwUAA4ICAQBQ3ik+AJUFvb0WEaND/4IdBX+eQW+LU7iqRx41CRMA2eR6OV38nqXS
+KQLzL8BOvgPGHInPTYuINoQ1DcXkuXOfhZFE6xOqDbkddgGLGUlgcdF5daZKYYfZ
+UpsTktaqFXrJ05lTmDcuSLsmj+wGAg3wYtUuCX3rchjPmjJkzTx8MlZ+YvkBtwBq
+jm7uv5D6GqlLdGcBwLa5/sL2vYjTx+bP4+bNHeOslcyIcIQzTDXfx7UJObr38lGv
+ulzFJUiOmppKs61B/FOIyBxAifuS6mcWXbPE0u+KY4ivfp9PCSFh9tZtjJWqW/y9
+NNhby/7waVteOiNKyWPQgGYb6GjCyeWIB1wSQPz6grvwn/LFTcNTyxeAaBXqX+TD
+4fMD3OjNQpKkDdbW6KuDO0TO4F9XhRjvbLqC7+9jEFL0VStxMkHpFt+tYSeAjJTa
+6hCsvwTca7QLOzbd5V/k3MsiwiTH80q1psumvbLmzjRat3+VuVEqAjz3NYElhhEs
+fMVi3kLhnUnQYSha4YI8LxtIGQtWQGGM659OvP2aIoRfyU5Al3PM9S3bL9Vc+kJ7
+V0RWmYtXwe/p5K3pANZb4fMfGnxbcVUfGMdBoEaZllNWf0paZHdxKx2vaY5keQh1
+5nQpovCS7SEEqybiQ2ZyZ4yitRLEX0mxr6NlKMdN+nb8kdjF9hXWew==
+-----END CERTIFICATE-----

+ 52 - 0
crates/cdk-signatory/certs/server.key

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+NElkmGg5YqmTP2k7lccl8V9SaO2nQWk=
+-----END PRIVATE KEY-----

+ 33 - 0
crates/cdk-signatory/certs/server.pem

@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFvjCCA6agAwIBAgIUdS4EWupyWTa1w0WxEGPW+ZAAtd0wDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
+MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xDTALBgNVBAsMBFVuaXQxDTALBgNVBAMM
+BE15Q0EwHhcNMjUwNTA4MDA1NzE1WhcNMjYwNTA4MDA1NzE1WjBmMQswCQYDVQQG
+EwJVUzEOMAwGA1UECAwFU3RhdGUxDTALBgNVBAcMBENpdHkxFTATBgNVBAoMDE9y
+Z2FuaXphdGlvbjENMAsGA1UECwwEVW5pdDESMBAGA1UEAwwJbG9jYWxob3N0MIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1jQfxY4iM6f7WtHHOrxoYUYB
+ZRKBEO+yLDGgLvDX+Cr9hMJW4FL39n/7Nx7mL7aIt7l9Xw5O/egwkiS9Tn0MGP+T
+OU/Nz4PwkZOMabHV5qomw/zcrxpbi/W5zwJYiu3wOBmarOlxQ0GOXBfaC5CGeqK/
+bZHyfRXNAskWSp6rZNoC0ZHjL+tjECeurCmtoI+zH7PC4bYhgO97Tm6OS7Tip9gG
+Yw+toME/X0sSgTmGOalGq97yMFCivyBljXMTQxIQOo60fcFuXB9L02sRfM1UcaYT
+mVAKWWaYSNPuxuLHG9OiYOxhleKZbLPmNDq0oxkrW09EsGXLCxzlRhI9qMxeobNq
+4mHtYG84FPh3uotWMsvO/WOW7LhyJPDms+76vH5T3GnwQFr+PCDxY7DrzKbAcdti
++F89AlckdC2wRGc2WFmCsslLJOQyKATLm2PJcYnIuZ61ZbgDWxnYXw9lOH65lmGO
+BTYEWGw6Y0MVzswxBwXU1phI1notA6UkNnRihN4iDLgpKPhw1gqrlYJ9CjqHT8Rh
+bcg1T87TjHjHT/mQepSH2m3aQHxy38LCt4QyHDV6o8o8hvVc76XGExEoRfpVlZ+2
+BrF8mX+x2vT6cLV1iCyrMz/GhgcDuiUXK1z663FIjggKIQYyN3HiHYZw16g2xN53
+dgTgOuhR9dtgCyAhxgMCAwEAAaNpMGcwJQYDVR0RBB4wHIIJbG9jYWxob3N0gglt
+eS1zZXJ2ZXKHBH8AAAEwHQYDVR0OBBYEFJjfu10f++rcYtp/r3UYg89tLb8SMB8G
+A1UdIwQYMBaAFJ7XlQ5j/pFi7g9RDoBUAD6gx3k+MA0GCSqGSIb3DQEBCwUAA4IC
+AQA64XVN9EMP44XOgtYrCLaUX6A/elglgrGtolhN+OGAiNwwYd6Lv8DfhRdIJjkN
+F+jBD9gQLWwOjMLFl03M222wn88ybyumWMFnOi6D9qqMip1ugeUgtlJxZ2QM1n1A
+WV+vlfJQqmPYwVrlgX0G929FRIA4tSuEsCOUXia3IGGARZ/DqGtUlpUuuZrz89Jz
+5hEWnPRAwB+OdpjmrLk+QWaL9LW8eBHP/VMcRm4A8ebhpkFHw35+xHobQVsbgL6A
+VMt4v6dOTEFAIY0U/0UlY4OGW7YbtnoZOI/65SayMj4Y8cukQUO3KiqFU3GQrCpp
+IRowUOH7kRx8CNai6pMHCnruOcgYV5Zo46Yc+HlagGuw7KrCVK7AYuMW/HQWgS4w
+uUCBo6IYAl9EcKGafS6LUhlGLAOGzzg4JQj8Kh4tQqkFnJEGAzXdN3/mKdvTv1op
+dNkId+M9TTSUiyZb0RKPwCCdgSpvVb+6sFiFbYiNN7pjWoHJIkb24oE8XkZG4JSa
+yjU+dLYBzPNM9M/v57OgdOnEIAmujhhRo+dzAgJ7VxXew/rEuUYDg7oFdhdC6nAs
+miANXZFJykZUdyY9d/axMKRvhC8NZ8AHAW00byXTXe2tkTyUy8oebAiMJfA7uwmi
+lqyL2hq4iTgZXpPkuyQXuYUrTxF+tgvkXlCOoVMO8HQwcA==
+-----END CERTIFICATE-----

+ 51 - 0
crates/cdk-signatory/generate_certs.sh

@@ -0,0 +1,51 @@
+if [ $# -eq 1 ]; then
+  cd "$1" || { echo "Failed to cd into '$1'"; exit 1; }
+fi
+
+# Generate private key for Certificate Authority (CA)
+openssl genrsa -out ca.key 4096
+
+# Generate CA certificate
+openssl req -new -x509 -days 365 -key ca.key -out ca.pem -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=MyCA"
+
+# Generate private key for Server
+openssl genrsa -out server.key 4096
+
+# Generate Certificate Signing Request (CSR) for Server
+openssl req -new -key server.key -out server.csr -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=localhost"
+
+# Generate Server certificate
+openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -extfile <(printf "subjectAltName=DNS:localhost,DNS:my-server,IP:127.0.0.1")
+
+# Generate private key for Client
+openssl genrsa -out client.key 4096
+
+# Generate CSR for Client
+openssl req -new -key client.key -out client.csr -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=client"
+
+# Generate Client certificate
+openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem
+
+# Verify the certificates
+echo "Verifying Server Certificate:"
+openssl verify -CAfile ca.pem server.pem
+
+echo "Verifying Client Certificate:"
+openssl verify -CAfile ca.pem client.pem
+
+# Clean up CSR files (optional)
+rm server.csr client.csr
+
+# Display certificate information
+echo "Server Certificate Info:"
+openssl x509 -in server.pem -text -noout | grep "Subject:\|Issuer:\|DNS:\|IP Address:"
+
+echo "Client Certificate Info:"
+openssl x509 -in client.pem -text -noout | grep "Subject:\|Issuer:"
+
+# Final files you'll need:
+# - ca.pem (Certificate Authority certificate)
+# - server.key (Server private key)
+# - server.pem (Server certificate)
+# - client.key (Client private key)
+# - client.pem (Client certificate)

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

@@ -178,6 +178,10 @@ impl DbSignatory {
 
 #[async_trait::async_trait]
 impl Signatory for DbSignatory {
+    fn name(&self) -> String {
+        format!("Signatory {}", env!("CARGO_PKG_VERSION"))
+    }
+
     async fn blind_sign(
         &self,
         blinded_messages: Vec<BlindedMessage>,

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

@@ -92,6 +92,10 @@ impl Service {
 
 #[async_trait::async_trait]
 impl Signatory for Service {
+    fn name(&self) -> String {
+        "Embedded".to_owned()
+    }
+
     async fn blind_sign(
         &self,
         blinded_messages: Vec<BlindedMessage>,

+ 89 - 62
crates/cdk-signatory/src/main.rs

@@ -1,12 +1,23 @@
+use std::collections::HashMap;
 use std::fs;
+use std::net::SocketAddr;
 use std::path::PathBuf;
+use std::str::FromStr;
 use std::sync::Arc;
 
+use anyhow::{bail, Result};
 use bip39::rand::{thread_rng, Rng};
 use bip39::Mnemonic;
-use cdk_sqlite::WalletSqliteDatabase;
+use cashu::CurrencyUnit;
+use cdk_common::database::{MintAuthDatabase, MintKeysDatabase};
+#[cfg(feature = "redb")]
+use cdk_redb::MintRedbDatabase;
+use cdk_signatory::{db_signatory, grpc_server};
+use cdk_sqlite::mint::MintSqliteAuthDatabase;
+use cdk_sqlite::MintSqliteDatabase;
 use clap::Parser;
 use tracing::Level;
+use tracing_subscriber::EnvFilter;
 
 const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
 
@@ -29,15 +40,36 @@ struct Cli {
     /// Logging level
     #[arg(short, long, default_value = "error")]
     log_level: Level,
-    /// NWS Proxy
-    #[arg(short, long)]
-    proxy: Option<Url>,
+    #[arg(long, default_value = "127.0.0.1")]
+    listen_addr: String,
+    #[arg(long, default_value = "15060")]
+    listen_port: u32,
+    #[arg(long, short)]
+    certs: Option<String>,
+    #[arg(long, short, default_value = "sat,0,32")]
+    units: Vec<String>,
 }
 
 #[tokio::main]
 async fn main() -> Result<()> {
     let args: Cli = Cli::parse();
     let default_filter = args.log_level;
+    let supported_units = args
+        .units
+        .into_iter()
+        .map(|unit| {
+            let mut parts = unit.split(",").collect::<Vec<_>>();
+            parts.reverse();
+            let unit: CurrencyUnit = parts.pop().unwrap_or_default().parse()?;
+            let fee = parts
+                .pop()
+                .map(|x| x.parse())
+                .transpose()?
+                .unwrap_or_default();
+            let max_order = parts.pop().map(|x| x.parse()).transpose()?.unwrap_or(32);
+            Ok::<(_, (_, _)), anyhow::Error>((unit, (fee, max_order)))
+        })
+        .collect::<Result<HashMap<_, _>, _>>()?;
 
     let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
 
@@ -54,37 +86,52 @@ async fn main() -> Result<()> {
         }
     };
 
+    let certs = Some(
+        args.certs
+            .map(|x| x.into())
+            .unwrap_or_else(|| work_dir.clone()),
+    );
+
     fs::create_dir_all(&work_dir)?;
 
-    let localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync> =
-        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"),
-                    }
-                };
-
-                Arc::new(sql)
-            }
-            "redb" => {
-                #[cfg(feature = "redb")]
-                {
-                    let redb_path = work_dir.join("cdk-cli.redb");
-                    Arc::new(WalletRedbDatabase::new(&redb_path)?)
-                }
-                #[cfg(not(feature = "redb"))]
-                {
-                    bail!("redb feature not enabled");
+    let (localstore, auth_localstore): (
+        Arc<dyn MintKeysDatabase<Err = cdk_common::database::Error> + Send + Sync>,
+        Arc<dyn MintAuthDatabase<Err = cdk_common::database::Error> + Send + Sync>,
+    ) = match args.engine.as_str() {
+        "sqlite" => {
+            let sql_path = work_dir.join("cdk-cli.sqlite");
+            #[cfg(not(feature = "sqlcipher"))]
+            let db = (
+                MintSqliteDatabase::new(&sql_path).await?,
+                MintSqliteAuthDatabase::new(&sql_path).await?,
+            );
+            #[cfg(feature = "sqlcipher")]
+            let db = {
+                match args.password {
+                    Some(pass) => (
+                        MintSqliteDatabase::new(&sql_path, pass).await?,
+                        MintSqliteAuthDatabase::new(&sql_path).await?,
+                    ),
+                    None => bail!("Missing database password"),
                 }
+            };
+
+            (Arc::new(db.0), Arc::new(db.1))
+        }
+        "redb" => {
+            #[cfg(feature = "redb")]
+            {
+                let redb_path = work_dir.join("cdk-cli.redb");
+                let db = Arc::new(MintRedbDatabase::new(&redb_path)?);
+                (db.clone(), db)
             }
-            _ => bail!("Unknown DB engine"),
-        };
+            #[cfg(not(feature = "redb"))]
+            {
+                bail!("redb feature not enabled");
+            }
+        }
+        _ => bail!("Unknown DB engine"),
+    };
 
     let seed_path = work_dir.join("seed");
 
@@ -107,38 +154,18 @@ async fn main() -> Result<()> {
     };
     let seed = mnemonic.to_seed_normalized("");
 
-    let mut wallets: Vec<Wallet> = Vec::new();
-
-    let mints = localstore.get_mints().await?;
-
-    for (mint_url, _) in mints {
-        let mut builder = WalletBuilder::new()
-            .mint_url(mint_url.clone())
-            .unit(cdk::nuts::CurrencyUnit::Sat)
-            .localstore(localstore.clone())
-            .seed(&mnemonic.to_seed_normalized(""));
-
-        if let Some(proxy_url) = args.proxy.as_ref() {
-            let http_client = HttpClient::with_proxy(mint_url, proxy_url.clone(), None, true)?;
-            builder = builder.client(http_client);
-        }
-
-        let wallet = builder.build()?;
+    let signatory = db_signatory::DbSignatory::new(
+        localstore,
+        Some(auth_localstore),
+        &seed,
+        supported_units,
+        Default::default(),
+    )
+    .await?;
 
-        let wallet_clone = wallet.clone();
-
-        tokio::spawn(async move {
-            if let Err(err) = wallet_clone.get_mint_info().await {
-                tracing::error!(
-                    "Could not get mint quote for {}, {}",
-                    wallet_clone.mint_url,
-                    err
-                );
-            }
-        });
+    let socket_addr = SocketAddr::from_str(&format!("{}:{}", args.listen_addr, args.listen_port))?;
 
-        wallets.push(wallet);
-    }
+    grpc_server(signatory, socket_addr, certs).await?;
 
-    let multi_mint_wallet = MultiMintWallet::new(localstore, Arc::new(seed), wallets);
+    Ok(())
 }

+ 54 - 2
crates/cdk-signatory/src/proto/client.rs

@@ -1,5 +1,8 @@
+use std::path::Path;
+
 use cdk_common::error::Error;
 use cdk_common::{BlindSignature, BlindedMessage, Proof};
+use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
 
 use super::{blind_sign_response, boolean_response, key_rotation_response, keys_response};
 use crate::proto::signatory_client::SignatoryClient;
@@ -8,19 +11,68 @@ use crate::signatory::{RotateKeyArguments, Signatory, SignatoryKeySet, Signatory
 /// A client for the Signatory service.
 pub struct SignatoryRpcClient {
     client: SignatoryClient<tonic::transport::Channel>,
+    url: String,
+}
+
+#[derive(thiserror::Error, Debug)]
+/// Client Signatory Error
+pub enum ClientError {
+    /// Transport error
+    #[error(transparent)]
+    Transport(#[from] tonic::transport::Error),
+
+    /// IO-related errors
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+
+    /// Signatory Error
+    #[error(transparent)]
+    Signatory(#[from] cdk_common::error::Error),
+
+    /// Invalid URL
+    #[error("Invalid URL")]
+    InvalidUrl,
 }
 
 impl SignatoryRpcClient {
     /// Create a new RemoteSigner from a tonic transport channel.
-    pub async fn new(url: String) -> Result<Self, tonic::transport::Error> {
+    pub async fn new<A: AsRef<Path>>(url: String, tls_dir: Option<A>) -> Result<Self, ClientError> {
+        let channel = if let Some(tls_dir) = tls_dir {
+            let tls_dir = tls_dir.as_ref();
+            let server_root_ca_cert = std::fs::read_to_string(tls_dir.join("ca.pem")).unwrap();
+            let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert);
+            let client_cert = std::fs::read_to_string(tls_dir.join("client.pem"))?;
+            let client_key = std::fs::read_to_string(tls_dir.join("client.key"))?;
+            let client_identity = Identity::from_pem(client_cert, client_key);
+            let tls = ClientTlsConfig::new()
+                .ca_certificate(server_root_ca_cert)
+                .identity(client_identity);
+
+            Channel::from_shared(url.clone())
+                .map_err(|_| ClientError::InvalidUrl)?
+                .tls_config(tls)?
+                .connect()
+                .await?
+        } else {
+            Channel::from_shared(url.clone())
+                .map_err(|_| ClientError::InvalidUrl)?
+                .connect()
+                .await?
+        };
+
         Ok(Self {
-            client: SignatoryClient::connect(url).await?,
+            client: SignatoryClient::new(channel),
+            url,
         })
     }
 }
 
 #[async_trait::async_trait]
 impl Signatory for SignatoryRpcClient {
+    fn name(&self) -> String {
+        format!("Rpc Signatory {}", self.url)
+    }
+
     async fn blind_sign(&self, request: Vec<BlindedMessage>) -> Result<Vec<BlindSignature>, Error> {
         let req = super::BlindedMessages {
             blinded_messages: request

+ 5 - 0
crates/cdk-signatory/src/proto/convert.rs

@@ -326,6 +326,11 @@ impl From<cashu::CurrencyUnit> for CurrencyUnit {
                     CurrencyUnitType::Eur.into(),
                 )),
             },
+            cashu::CurrencyUnit::Auth => CurrencyUnit {
+                currency_unit: Some(currency_unit::CurrencyUnit::Unit(
+                    CurrencyUnitType::Auth.into(),
+                )),
+            },
             cashu::CurrencyUnit::Custom(name) => CurrencyUnit {
                 currency_unit: Some(currency_unit::CurrencyUnit::CustomUnit(name)),
             },

+ 81 - 4
crates/cdk-signatory/src/proto/server.rs

@@ -1,6 +1,7 @@
 use std::net::SocketAddr;
+use std::path::Path;
 
-use tonic::transport::{Error, Server};
+use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig};
 use tonic::{Request, Response, Status};
 
 use super::{boolean_response, key_rotation_response, keys_response, BooleanResponse};
@@ -112,13 +113,89 @@ where
     }
 }
 
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    /// Transport error
+    #[error(transparent)]
+    Transport(#[from] tonic::transport::Error),
+    /// Io error
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+}
+
 /// Runs the signatory server
-pub async fn grpc_server<T>(signatory: T, addr: SocketAddr) -> Result<(), Error>
+pub async fn grpc_server<T, I: AsRef<Path>>(
+    signatory: T,
+    addr: SocketAddr,
+    tls_dir: Option<I>,
+) -> Result<(), Error>
 where
     T: Signatory + Send + Sync + 'static,
 {
-    tracing::info!("grpc_server listening on {}", addr);
-    Server::builder()
+    tracing::info!("Starting RPC server {}", addr);
+
+    let mut server = match tls_dir {
+        Some(tls_dir) => {
+            tracing::info!("TLS configuration found, starting secure server");
+            let tls_dir = tls_dir.as_ref();
+            let server_pem_path = tls_dir.join("server.pem");
+            let server_key_path = tls_dir.join("server.key");
+            let ca_pem_path = tls_dir.join("ca.pem");
+
+            if !server_pem_path.exists() {
+                tracing::error!(
+                    "Server certificate file does not exist: {}",
+                    server_pem_path.display()
+                );
+                return Err(Error::Io(std::io::Error::new(
+                    std::io::ErrorKind::NotFound,
+                    format!(
+                        "Server certificate file not found: {}",
+                        server_pem_path.display()
+                    ),
+                )));
+            }
+
+            if !server_key_path.exists() {
+                tracing::error!(
+                    "Server key file does not exist: {}",
+                    server_key_path.display()
+                );
+                return Err(Error::Io(std::io::Error::new(
+                    std::io::ErrorKind::NotFound,
+                    format!("Server key file not found: {}", server_key_path.display()),
+                )));
+            }
+
+            if !ca_pem_path.exists() {
+                tracing::error!(
+                    "CA certificate file does not exist: {}",
+                    ca_pem_path.display()
+                );
+                return Err(Error::Io(std::io::Error::new(
+                    std::io::ErrorKind::NotFound,
+                    format!("CA certificate file not found: {}", ca_pem_path.display()),
+                )));
+            }
+
+            let cert = std::fs::read_to_string(&server_pem_path)?;
+            let key = std::fs::read_to_string(&server_key_path)?;
+            let client_ca_cert = std::fs::read_to_string(&ca_pem_path)?;
+            let client_ca_cert = Certificate::from_pem(client_ca_cert);
+            let server_identity = Identity::from_pem(cert, key);
+            let tls_config = ServerTlsConfig::new()
+                .identity(server_identity)
+                .client_ca_root(client_ca_cert);
+
+            Server::builder().tls_config(tls_config)?
+        }
+        None => {
+            tracing::warn!("No valid TLS configuration found, starting insecure server");
+            Server::builder()
+        }
+    };
+
+    server
         .add_service(signatory_server::SignatoryServer::new(CdkSignatoryServer {
             inner: signatory,
         }))

+ 2 - 0
crates/cdk-signatory/src/signatory.rs

@@ -121,6 +121,8 @@ impl From<&(MintKeySetInfo, MintKeySet)> for SignatoryKeySet {
 #[async_trait::async_trait]
 /// Signatory trait
 pub trait Signatory {
+    fn name(&self) -> String;
+
     /// Blind sign a message.
     ///
     /// The message can be for a coin or an auth token.

+ 17 - 0
crates/cdk/src/mint/mod.rs

@@ -154,6 +154,23 @@ impl Mint {
             open_id_discovery.map(|openid_discovery| OidcClient::new(openid_discovery.clone()));
 
         let keysets = signatory.keysets().await?;
+        if !keysets
+            .keysets
+            .iter()
+            .any(|keyset| keyset.active && keyset.unit != CurrencyUnit::Auth)
+        {
+            return Err(Error::NoActiveKeyset);
+        }
+
+        tracing::info!(
+            "Using Signatory {} with {} active keys",
+            signatory.name(),
+            keysets
+                .keysets
+                .iter()
+                .filter(|keyset| keyset.active && keyset.unit != CurrencyUnit::Auth)
+                .count()
+        );
 
         Ok(Self {
             signatory,

+ 2 - 0
justfile

@@ -63,6 +63,7 @@ test-all db="memory":
     #!/usr/bin/env bash
     just test {{db}}
     ./misc/itests.sh "{{db}}"
+    ./misc/fake_itests.sh "{{db}}" external_signatory
     ./misc/fake_itests.sh "{{db}}"
     
 test-nutshell:
@@ -119,6 +120,7 @@ itest db:
   
 fake-mint-itest db:
   #!/usr/bin/env bash
+  ./misc/fake_itests.sh "{{db}}" external_signatory
   ./misc/fake_itests.sh "{{db}}"
 
   

+ 14 - 4
misc/fake_itests.sh

@@ -7,13 +7,15 @@ cleanup() {
     echo "Killing the cdk mintd"
     kill -2 $CDK_MINTD_PID
     wait $CDK_MINTD_PID
+    kill -9 $CDK_SIGNATORY_PID
+    wait $CDK_SIGNATORY_PID
 
     echo "Mint binary terminated"
-    
+
     # Remove the temporary directory
     rm -rf "$CDK_ITESTS_DIR"
     echo "Temp directory removed: $CDK_ITESTS_DIR"
-    
+
     # Unset all environment variables
     unset CDK_ITESTS_DIR
     unset CDK_ITESTS_MINT_ADDR
@@ -49,7 +51,7 @@ fi
 echo "Temp directory created: $CDK_ITESTS_DIR"
 export CDK_MINTD_DATABASE="$1"
 
-cargo build -p cdk-integration-tests 
+cargo build -p cdk-integration-tests
 
 
 export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT"
@@ -62,6 +64,14 @@ export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt lugg
 export CDK_MINTD_FAKE_WALLET_FEE_PERCENT="0"
 export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1"
 
+if [ "$2" = "external_signatory" ]; then
+    export CDK_MINTD_SIGNATORY_URL="https://127.0.0.1:15060"
+    export CDK_MINTD_SIGNATORY_CERTS="$CDK_ITESTS_DIR"
+    bash -x `dirname $0`/../crates/cdk-signatory/generate_certs.sh $CDK_ITESTS_DIR
+    cargo run --bin cdk-signatory -- -w $CDK_ITESTS_DIR -u "sat" -u "usd"  &
+    export CDK_SIGNATORY_PID=$!
+    sleep 5
+fi
 
 echo "Starting fake mintd"
 cargo run --bin cdk-mintd --features "redb" &
@@ -74,7 +84,7 @@ START_TIME=$(date +%s)
 while true; do
     # Get the current time
     CURRENT_TIME=$(date +%s)
-    
+
     # Calculate the elapsed time
     ELAPSED_TIME=$((CURRENT_TIME - START_TIME))