Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ fn create_ldk_settings(
input_fee_ppk: None,
http_cache: cdk_axum::cache::Config::default(),
enable_swagger_ui: None,
expose_v1_keyset_ids: true,
logging: LoggingConfig::default(),
},
mint_info: cdk_mintd::config::MintInfo::default(),
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 @@ -214,6 +214,7 @@ pub fn create_fake_wallet_settings(
file_level: Some("debug".to_string()),
},
enable_swagger_ui: None,
expose_v1_keyset_ids: true,
},
mint_info: cdk_mintd::config::MintInfo::default(),
ln: cdk_mintd::config::Ln {
Expand Down Expand Up @@ -267,6 +268,7 @@ pub fn create_cln_settings(
file_level: Some("debug".to_string()),
},
enable_swagger_ui: None,
expose_v1_keyset_ids: true,
},
mint_info: cdk_mintd::config::MintInfo::default(),
ln: cdk_mintd::config::Ln {
Expand Down Expand Up @@ -315,6 +317,7 @@ pub fn create_lnd_settings(
file_level: Some("debug".to_string()),
},
enable_swagger_ui: None,
expose_v1_keyset_ids: true,
},
mint_info: cdk_mintd::config::MintInfo::default(),
ln: cdk_mintd::config::Ln {
Expand Down
11 changes: 11 additions & 0 deletions crates/cdk-mintd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ pub struct Info {
/// If not provided, defaults are used.
#[serde(skip_serializing_if = "Option::is_none")]
pub quote_ttl: Option<QuoteTTL>,

/// When true, expose V1 keyset IDs alongside V2 IDs (default: true)
/// When false, only expose V2 keyset IDs in API responses
/// Note: V1 IDs will still be accepted for proof verification
#[serde(default = "default_expose_v1_ids")]
pub expose_v1_keyset_ids: bool,
}

fn default_expose_v1_ids() -> bool {
true
}

impl Default for Info {
Expand All @@ -92,6 +102,7 @@ impl Default for Info {
enable_swagger_ui: None,
logging: LoggingConfig::default(),
quote_ttl: None,
expose_v1_keyset_ids: true,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/cdk-mintd/src/env_vars/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ pub const ENV_ENABLE_SWAGGER: &str = "CDK_MINTD_ENABLE_SWAGGER";
pub const ENV_LOGGING_OUTPUT: &str = "CDK_MINTD_LOGGING_OUTPUT";
pub const ENV_LOGGING_CONSOLE_LEVEL: &str = "CDK_MINTD_LOGGING_CONSOLE_LEVEL";
pub const ENV_LOGGING_FILE_LEVEL: &str = "CDK_MINTD_LOGGING_FILE_LEVEL";
pub const ENV_EXPOSE_V1_KEYSET_IDS: &str = "CDK_MINTD_EXPOSE_V1_KEYSET_IDS";
7 changes: 7 additions & 0 deletions crates/cdk-mintd/src/env_vars/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ impl Info {
});
}

// V1 keyset ID exposure
if let Ok(expose_str) = env::var(ENV_EXPOSE_V1_KEYSET_IDS) {
if let Ok(expose) = expose_str.parse() {
self.expose_v1_keyset_ids = expose;
}
}

self
}
}
3 changes: 2 additions & 1 deletion crates/cdk-mintd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder)
let mut builder = mint_builder
.with_name(settings.mint_info.name.clone())
.with_version(mint_version)
.with_description(settings.mint_info.description.clone());
.with_description(settings.mint_info.description.clone())
.with_v1_keyset_ids(settings.info.expose_v1_keyset_ids);

// Add optional information
if let Some(long_description) = &settings.mint_info.description_long {
Expand Down
9 changes: 6 additions & 3 deletions crates/cdk-signatory/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@ pub async fn init_keysets(
{
tracing::debug!("Current highest index keyset matches expect fee and max order. Setting active");
let id = highest_index_keyset.id;
// Regenerate keyset using the version from the stored ID
// to maintain consistency with the database
let keyset = MintKeySet::generate_from_xpriv(
secp_ctx,
xpriv,
&highest_index_keyset.amounts,
highest_index_keyset.unit.clone(),
highest_index_keyset.derivation_path.clone(),
highest_index_keyset.final_expiry,
cdk_common::nut02::KeySetVersion::Version00,
highest_index_keyset.id.get_version(),
);
active_keysets.insert(id, keyset);
let mut keyset_info = highest_index_keyset;
Expand Down Expand Up @@ -140,8 +142,9 @@ pub fn create_new_keyset<C: secp256k1::Signing>(
unit,
amounts,
final_expiry,
// TODO: change this to Version01 to generate keysets v2
cdk_common::nut02::KeySetVersion::Version00,
// Generate V2 keysets - V1 IDs are exposed for backward compatibility
// via the expose_v1_keyset_ids configuration option
cdk_common::nut02::KeySetVersion::Version01,
);
let keyset_info = MintKeySetInfo {
id: keyset.id,
Expand Down
115 changes: 107 additions & 8 deletions crates/cdk-signatory/src/db_signatory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use crate::signatory::{RotateKeyArguments, Signatory, SignatoryKeySet, Signatory
pub struct DbSignatory {
keysets: RwLock<HashMap<Id, (MintKeySetInfo, MintKeySet)>>,
active_keysets: RwLock<HashMap<CurrencyUnit, Id>>,
/// Track which keyset IDs are native (from DB) vs computed alternates
native_keyset_ids: RwLock<std::collections::HashSet<Id>>,
localstore: Arc<dyn database::MintKeysDatabase<Err = database::Error> + Send + Sync>,
secp_ctx: Secp256k1<secp256k1::All>,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
Expand Down Expand Up @@ -93,6 +95,7 @@ impl DbSignatory {
let keys = Self {
keysets: Default::default(),
active_keysets: Default::default(),
native_keyset_ids: Default::default(),
localstore,
custom_paths,
xpub: xpriv.to_keypair(&secp_ctx).public_key().into(),
Expand All @@ -114,19 +117,45 @@ impl DbSignatory {
async fn reload_keys_from_db(&self) -> Result<(), Error> {
let mut keysets = self.keysets.write().await;
let mut active_keysets = self.active_keysets.write().await;
let mut native_ids = self.native_keyset_ids.write().await;
keysets.clear();
active_keysets.clear();
native_ids.clear();

let db_active_keysets = self.localstore.get_active_keysets().await?;

for mut info in self.localstore.get_keyset_infos().await? {
// First, collect all native keysets from the database
let native_infos: Vec<MintKeySetInfo> = self.localstore.get_keyset_infos().await?;

for mut info in native_infos {
let id = info.id;
let keyset = self.generate_keyset(&info);
info.active = db_active_keysets.get(&info.unit) == Some(&info.id);
if info.active {
active_keysets.insert(info.unit.clone(), id);
}
keysets.insert(id, (info, keyset));

// Track this as a native ID
native_ids.insert(id);

// Store with native ID
keysets.insert(id, (info.clone(), keyset.clone()));

// Also store with alternate ID for dual-ID support (for proof verification)
use cdk_common::nut02::KeySetVersion;
use cdk_common::Keys;
let keys: Keys = keyset.keys.clone().into();
let alternate_id = match info.id.get_version() {
KeySetVersion::Version00 => {
// Current is V1, compute V2
Id::v2_from_data(&keys, &info.unit, info.final_expiry)
}
KeySetVersion::Version01 => {
// Current is V2, compute V1
Id::v1_from_keys(&keys)
}
};
keysets.insert(alternate_id, (info, keyset));
}

Ok(())
Expand Down Expand Up @@ -203,14 +232,15 @@ impl Signatory for DbSignatory {

#[tracing::instrument(skip_all)]
async fn keysets(&self) -> Result<SignatoryKeysets, Error> {
let keysets_map = self.keysets.read().await;
let native_ids = self.native_keyset_ids.read().await;

Ok(SignatoryKeysets {
pubkey: self.xpub,
keysets: self
.keysets
.read()
.await
.values()
.map(|k| k.into())
keysets: keysets_map
.iter()
.filter(|(id, _)| native_ids.contains(id))
.map(|(_, k)| k.into())
.collect::<Vec<_>>(),
})
}
Expand Down Expand Up @@ -268,10 +298,79 @@ mod test {

use bitcoin::key::Secp256k1;
use bitcoin::Network;
use cdk_common::nut02::KeySetVersion;
use cdk_common::{Amount, MintKeySet, PublicKey};

use super::*;

#[tokio::test]
async fn test_dual_id_lookup_in_signatory() {
// Create a signatory with a V2 keyset
let seed = b"test_seed_for_dual_id";
let localstore = Arc::new(cdk_sqlite::mint::memory::empty().await.expect("create db"));

let mut supported_units = HashMap::new();
supported_units.insert(CurrencyUnit::Sat, (0u64, 4u8)); // Small keyset for test

let signatory = DbSignatory::new(localstore, seed, supported_units, HashMap::new())
.await
.expect("create signatory");

// Get the keysets
let keysets_response = signatory.keysets().await.expect("get keysets");
assert_eq!(keysets_response.keysets.len(), 2); // Sat + Auth

// Find the Sat keyset (should be V2 native)
let sat_keyset = keysets_response
.keysets
.iter()
.find(|k| k.unit == CurrencyUnit::Sat)
.expect("find sat keyset");

assert_eq!(
sat_keyset.id.get_version(),
KeySetVersion::Version01,
"Keyset should be V2 native"
);

// Compute the V1 ID from the keys
let v1_id = Id::v1_from_keys(&sat_keyset.keys);
assert_ne!(v1_id, sat_keyset.id, "V1 and V2 IDs should be different");

// Verify the signatory has both IDs in its keysets HashMap
{
let keysets = signatory.keysets.read().await;
assert!(
keysets.contains_key(&sat_keyset.id),
"Should have native V2 ID"
);
assert!(keysets.contains_key(&v1_id), "Should have alternate V1 ID");
}

// Now test that blind_sign works with the V1 ID
use cdk_common::dhke::blind_message;
use cdk_common::secret::Secret;

let secret = Secret::generate();
let (blinded_message, _blinding_factor) =
blind_message(secret.as_bytes(), None).expect("blind message");

let blinded_msg = cdk_common::BlindedMessage {
amount: Amount::from(1),
blinded_secret: blinded_message,
keyset_id: v1_id, // Use V1 ID
witness: None,
};

// This should succeed because the signatory stores with both IDs
let result = signatory.blind_sign(vec![blinded_msg]).await;
assert!(
result.is_ok(),
"blind_sign should work with V1 ID: {:?}",
result.err()
);
}

#[test]
fn mint_mod_generate_keyset_from_seed() {
let seed = "test_seed".as_bytes();
Expand Down
12 changes: 12 additions & 0 deletions crates/cdk/src/mint/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct MintBuilder {
payment_processors: HashMap<PaymentProcessorKey, DynMintPayment>,
supported_units: HashMap<CurrencyUnit, (u64, u8)>,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
expose_v1_keyset_ids: bool,
}

impl MintBuilder {
Expand All @@ -62,6 +63,7 @@ impl MintBuilder {
payment_processors: HashMap::new(),
supported_units: HashMap::new(),
custom_paths: HashMap::new(),
expose_v1_keyset_ids: true,
}
}

Expand Down Expand Up @@ -235,6 +237,14 @@ impl MintBuilder {
self
}

/// Configure V1 keyset ID exposure
/// - true (default): Expose both V1 and V2 IDs
/// - false: Only expose V2 IDs (sunset V1)
pub fn with_v1_keyset_ids(mut self, expose: bool) -> Self {
self.expose_v1_keyset_ids = expose;
self
}

/// Add payment processor
pub async fn add_payment_processor(
&mut self,
Expand Down Expand Up @@ -325,6 +335,7 @@ impl MintBuilder {
self.localstore,
auth_localstore,
self.payment_processors,
self.expose_v1_keyset_ids,
)
.await;
}
Expand All @@ -333,6 +344,7 @@ impl MintBuilder {
signatory,
self.localstore,
self.payment_processors,
self.expose_v1_keyset_ids,
)
.await
}
Expand Down
Loading