Browse Source

feat: use derivation path by unit

thesimplekid 8 months ago
parent
commit
6d3200c72d

+ 5 - 3
crates/cdk-mintd/src/main.rs

@@ -213,14 +213,16 @@ async fn main() -> anyhow::Result<()> {
 
     let input_fee_ppk = settings.info.input_fee_ppk.unwrap_or(0);
 
+    let mut supported_units = HashMap::new();
+
+    supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64));
+
     let mint = Mint::new(
         &settings.info.url,
         &mnemonic.to_seed_normalized(""),
         mint_info,
         localstore,
-        absolute_ln_fee_reserve,
-        relative_ln_fee,
-        input_fee_ppk,
+        supported_units,
     )
     .await?;
 

+ 1 - 0
crates/cdk-sqlite/src/mint/migrations/20240711183109_derivation_path_index.sql

@@ -0,0 +1 @@
+ALTER TABLE keyset ADD derivation_path_index INTEGER;

+ 6 - 2
crates/cdk-sqlite/src/mint/mod.rs

@@ -408,8 +408,8 @@ WHERE id=?
         sqlx::query(
             r#"
 INSERT OR REPLACE INTO keyset
-(id, unit, active, valid_from, valid_to, derivation_path, max_order, input_fee_ppk)
-VALUES (?, ?, ?, ?, ?, ?, ?, ?);
+(id, unit, active, valid_from, valid_to, derivation_path, max_order, input_fee_ppk, derivation_path_index)
+VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
         "#,
         )
         .bind(keyset.id.to_string())
@@ -420,6 +420,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?);
         .bind(keyset.derivation_path.to_string())
         .bind(keyset.max_order)
         .bind(keyset.input_fee_ppk as i64)
+            .bind(keyset.derivation_path_index)
         .execute(&self.pool)
         .await
         .map_err(Error::from)?;
@@ -716,6 +717,8 @@ fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result<MintKeySetInfo, Error> {
     let row_derivation_path: String = row.try_get("derivation_path").map_err(Error::from)?;
     let row_max_order: u8 = row.try_get("max_order").map_err(Error::from)?;
     let row_keyset_ppk: Option<i64> = row.try_get("input_fee_ppk").map_err(Error::from)?;
+    let row_derivation_path_index: Option<i64> =
+        row.try_get("derivation_path_index").map_err(Error::from)?;
 
     Ok(MintKeySetInfo {
         id: Id::from_str(&row_id).map_err(Error::from)?,
@@ -724,6 +727,7 @@ fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result<MintKeySetInfo, Error> {
         valid_from: row_valid_from as u64,
         valid_to: row_valid_to.map(|v| v as u64),
         derivation_path: DerivationPath::from_str(&row_derivation_path).map_err(Error::from)?,
+        derivation_path_index: row_derivation_path_index.map(|d| d as u32),
         max_order: row_max_order,
         input_fee_ppk: row_keyset_ppk.unwrap_or(0) as u64,
     })

+ 107 - 38
crates/cdk/src/mint/mod.rs

@@ -29,14 +29,14 @@ pub use types::{MeltQuote, MintQuote};
 pub struct Mint {
     /// Mint Url
     pub mint_url: UncheckedUrl,
-    mint_info: MintInfo,
+    /// Mint Info
+    pub mint_info: MintInfo,
+    /// Mint Storage backend
+    pub localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>,
+    /// Active Mint Keysets
     keysets: Arc<RwLock<HashMap<Id, MintKeySet>>>,
     secp_ctx: Secp256k1<secp256k1::All>,
     xpriv: ExtendedPrivKey,
-    /// Mint Expected [`FeeReserve`]
-    pub fee_reserve: FeeReserve,
-    /// Mint Storage backend
-    pub localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>,
 }
 
 impl Mint {
@@ -46,46 +46,105 @@ impl Mint {
         seed: &[u8],
         mint_info: MintInfo,
         localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>,
-        min_fee_reserve: Amount,
-        percent_fee_reserve: f32,
-        input_fee_ppk: u64,
+        // Hashmap where the key is the unit and value is (input fee ppk, max_order)
+        supported_units: HashMap<CurrencyUnit, (u64, u8)>,
     ) -> Result<Self, Error> {
         let secp_ctx = Secp256k1::new();
         let xpriv =
             ExtendedPrivKey::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
 
-        let mut keysets = HashMap::new();
+        let mut active_keysets = HashMap::new();
         let keysets_infos = localstore.get_keyset_infos().await?;
 
         match keysets_infos.is_empty() {
             false => {
-                for keyset_info in keysets_infos {
-                    let mut keyset_info = keyset_info;
-                    keyset_info.input_fee_ppk = input_fee_ppk;
-                    localstore.add_keyset_info(keyset_info.clone()).await?;
-                    if keyset_info.active {
+                tracing::debug!("Setting all saved keysets to inactive");
+                for keyset in keysets_infos.clone() {
+                    // Set all to in active
+                    let mut keyset = keyset;
+                    keyset.active = false;
+                    localstore.add_keyset_info(keyset).await?;
+                }
+
+                let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
+                    keysets_infos.iter().fold(HashMap::new(), |mut acc, ks| {
+                        acc.entry(ks.unit).or_default().push(ks.clone());
+                        acc
+                    });
+
+                for (unit, keysets) in keysets_by_unit {
+                    let mut keysets = keysets;
+                    keysets.sort_by(|a, b| b.derivation_path_index.cmp(&a.derivation_path_index));
+                    let highest_index_keyset = keysets
+                        .first()
+                        .cloned()
+                        .expect("unit will not be added to hashmap if empty");
+
+                    let keysets: Vec<MintKeySetInfo> = keysets
+                        .into_iter()
+                        .filter(|ks| ks.derivation_path_index.is_some())
+                        .collect();
+
+                    if let Some((input_fee_ppk, max_order)) = supported_units.get(&unit) {
+                        let derivation_path_index = if keysets.is_empty() {
+                            1
+                        } else if &highest_index_keyset.input_fee_ppk == input_fee_ppk
+                            && &highest_index_keyset.max_order == max_order
+                        {
+                            let id = highest_index_keyset.id;
+                            let keyset = MintKeySet::generate_from_xpriv(
+                                &secp_ctx,
+                                xpriv,
+                                highest_index_keyset.clone(),
+                            );
+                            active_keysets.insert(id, keyset);
+                            let mut keyset_info = highest_index_keyset;
+                            keyset_info.active = true;
+                            localstore.add_keyset_info(keyset_info).await?;
+                            localstore.add_active_keyset(unit, id).await?;
+                            continue;
+                        } else {
+                            highest_index_keyset.derivation_path_index.unwrap_or(0) + 1
+                        };
+
+                        let derivation_path =
+                            derivation_path_from_unit(unit, derivation_path_index);
+
+                        let (keyset, keyset_info) = create_new_keyset(
+                            &secp_ctx,
+                            xpriv,
+                            derivation_path,
+                            Some(derivation_path_index),
+                            unit,
+                            *max_order,
+                            *input_fee_ppk,
+                        );
+
                         let id = keyset_info.id;
-                        let keyset = MintKeySet::generate_from_xpriv(&secp_ctx, xpriv, keyset_info);
-                        keysets.insert(id, keyset);
+                        localstore.add_keyset_info(keyset_info).await?;
+                        localstore.add_active_keyset(unit, id).await?;
+                        active_keysets.insert(id, keyset);
                     }
                 }
             }
             true => {
-                let derivation_path = DerivationPath::from(vec![
-                    ChildNumber::from_hardened_idx(0).expect("0 is a valid index")
-                ]);
-                let (keyset, keyset_info) = create_new_keyset(
-                    &secp_ctx,
-                    xpriv,
-                    derivation_path,
-                    CurrencyUnit::Sat,
-                    64,
-                    input_fee_ppk,
-                );
-                let id = keyset_info.id;
-                localstore.add_keyset_info(keyset_info).await?;
-                localstore.add_active_keyset(CurrencyUnit::Sat, id).await?;
-                keysets.insert(id, keyset);
+                for (unit, (input_fee_ppk, max_order)) in supported_units {
+                    let derivation_path = derivation_path_from_unit(unit, 0);
+                    tracing::debug!("Der: {}", derivation_path);
+                    let (keyset, keyset_info) = create_new_keyset(
+                        &secp_ctx,
+                        xpriv,
+                        derivation_path,
+                        Some(0),
+                        unit,
+                        max_order,
+                        input_fee_ppk,
+                    );
+                    let id = keyset_info.id;
+                    localstore.add_keyset_info(keyset_info).await?;
+                    localstore.add_active_keyset(CurrencyUnit::Sat, id).await?;
+                    active_keysets.insert(id, keyset);
+                }
             }
         }
 
@@ -93,14 +152,10 @@ impl Mint {
 
         Ok(Self {
             mint_url,
-            keysets: Arc::new(RwLock::new(keysets)),
+            keysets: Arc::new(RwLock::new(active_keysets)),
             secp_ctx,
             xpriv,
             localstore,
-            fee_reserve: FeeReserve {
-                min_fee_reserve,
-                percent_fee_reserve,
-            },
             mint_info,
         })
     }
@@ -366,14 +421,16 @@ impl Mint {
     pub async fn rotate_keyset(
         &self,
         unit: CurrencyUnit,
-        derivation_path: DerivationPath,
+        derivation_path_index: u32,
         max_order: u8,
         input_fee_ppk: u64,
     ) -> Result<(), Error> {
+        let derivation_path = derivation_path_from_unit(unit, derivation_path_index);
         let (keyset, keyset_info) = create_new_keyset(
             &self.secp_ctx,
             self.xpriv,
             derivation_path,
+            Some(derivation_path_index),
             unit,
             max_order,
             input_fee_ppk,
@@ -1019,8 +1076,10 @@ pub struct MintKeySetInfo {
     /// When the Keyset is valid to
     /// This is not shown to the wallet and can only be used internally
     pub valid_to: Option<u64>,
-    /// [`DerivationPath`] of Keyset
+    /// [`DerivationPath`] keyset
     pub derivation_path: DerivationPath,
+    /// DerivationPath index of Keyset
+    pub derivation_path_index: Option<u32>,
     /// Max order of keyset
     pub max_order: u8,
     /// Input Fee ppk
@@ -1049,6 +1108,7 @@ fn create_new_keyset<C: secp256k1::Signing>(
     secp: &secp256k1::Secp256k1<C>,
     xpriv: ExtendedPrivKey,
     derivation_path: DerivationPath,
+    derivation_path_index: Option<u32>,
     unit: CurrencyUnit,
     max_order: u8,
     input_fee_ppk: u64,
@@ -1068,8 +1128,17 @@ fn create_new_keyset<C: secp256k1::Signing>(
         valid_from: unix_time(),
         valid_to: None,
         derivation_path,
+        derivation_path_index,
         max_order,
         input_fee_ppk,
     };
     (keyset, keyset_info)
 }
+
+fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> DerivationPath {
+    DerivationPath::from(vec![
+        ChildNumber::from_hardened_idx(0).expect("0 is a valid index"),
+        ChildNumber::from_hardened_idx(unit.derivation_index()).expect("0 is a valid index"),
+        ChildNumber::from_hardened_idx(index).expect("0 is a valid index"),
+    ])
+}

+ 13 - 0
crates/cdk/src/nuts/nut00/mod.rs

@@ -325,6 +325,19 @@ pub enum CurrencyUnit {
     Eur,
 }
 
+#[cfg(feature = "mint")]
+impl CurrencyUnit {
+    /// Derivation index mint will use for unit
+    pub fn derivation_index(&self) -> u32 {
+        match self {
+            Self::Sat => 0,
+            Self::Msat => 1,
+            Self::Usd => 2,
+            Self::Eur => 3,
+        }
+    }
+}
+
 impl FromStr for CurrencyUnit {
     type Err = Error;
     fn from_str(value: &str) -> Result<Self, Self::Err> {