فهرست منبع

refactor: Allow multiple active keysets

Only one active keyset per unit
Rotate keyset was taken out to be put back in with mnemomic
thesimplekid 1 سال پیش
والد
کامیت
78f35eda8e

+ 3 - 4
bindings/cashu-sdk-ffi/src/cashu_sdk.udl

@@ -271,7 +271,7 @@ interface KeySetInfo {
 // Cashu Sdk
 
 interface MintKeySetInfo {
-	constructor(Id id, string unit, u64 valid_from, u64? valid_to, string derivation_path, u8 max_order);
+	constructor(Id id, boolean active, string unit, u64 valid_from, u64? valid_to, string derivation_path, u8 max_order);
 };
 
 
@@ -319,10 +319,9 @@ interface Wallet {
 
 interface Mint {
     [Throws=CashuSdkError]
-	constructor(string secret, MintKeySetInfo active_keyset_info, sequence<MintKeySetInfo> inactive_keysets, sequence<Secret> spent_secrets, Amount min_fee_reserve, f32 percent_fee_reserve);
-	KeysResponse active_keyset_pubkeys();
+	constructor(string secret, sequence<MintKeySetInfo> keysets_info, sequence<Secret> spent_secrets, Amount min_fee_reserve, f32 percent_fee_reserve);
+	KeysResponse? keyset_pubkeys(Id keyset_id);
 	KeySetResponse keysets();
-	MintKeySet active_keyset();
 	KeySet? keyset(Id id);
     [Throws=CashuSdkError]
 	PostMintResponse process_mint_request(MintRequest mint_request);

+ 11 - 13
bindings/cashu-sdk-ffi/src/mint.rs

@@ -3,8 +3,8 @@ use std::sync::{Arc, RwLock};
 
 use cashu_ffi::{
     Amount, CheckSpendableRequest, CheckSpendableResponse, Id, KeySet, KeySetResponse,
-    KeysResponse, MeltRequest, MeltResponse, MintKeySet, MintRequest, PostMintResponse, Secret,
-    SplitRequest, SplitResponse,
+    KeysResponse, MeltRequest, MeltResponse, MintRequest, PostMintResponse, Secret, SplitRequest,
+    SplitResponse,
 };
 use cashu_sdk::mint::Mint as MintSdk;
 
@@ -18,8 +18,7 @@ pub struct Mint {
 impl Mint {
     pub fn new(
         secret: String,
-        active_keyset_info: Arc<MintKeySetInfo>,
-        inactive_keysets: Vec<Arc<MintKeySetInfo>>,
+        keysets_info: Vec<Arc<MintKeySetInfo>>,
         spent_secrets: Vec<Arc<Secret>>,
         min_fee_reserve: Arc<Amount>,
         percent_fee_reserve: f32,
@@ -29,7 +28,7 @@ impl Mint {
             .map(|s| s.as_ref().deref().clone())
             .collect();
 
-        let inactive_keysets = inactive_keysets
+        let keysets = keysets_info
             .into_iter()
             .map(|ik| ik.as_ref().deref().clone())
             .collect();
@@ -37,8 +36,7 @@ impl Mint {
         Ok(Self {
             inner: MintSdk::new(
                 &secret,
-                active_keyset_info.as_ref().deref().clone(),
-                inactive_keysets,
+                keysets,
                 spent_secrets,
                 *min_fee_reserve.as_ref().deref(),
                 percent_fee_reserve,
@@ -47,18 +45,18 @@ impl Mint {
         })
     }
 
-    pub fn active_keyset_pubkeys(&self) -> Arc<KeysResponse> {
-        Arc::new(self.inner.read().unwrap().active_keyset_pubkeys().into())
+    pub fn keyset_pubkeys(&self, keyset_id: Arc<Id>) -> Option<Arc<KeysResponse>> {
+        self.inner
+            .read()
+            .unwrap()
+            .keyset_pubkeys(&keyset_id)
+            .map(|keyset| Arc::new(keyset.into()))
     }
 
     pub fn keysets(&self) -> Arc<KeySetResponse> {
         Arc::new(self.inner.read().unwrap().keysets().into())
     }
 
-    pub fn active_keyset(&self) -> Arc<MintKeySet> {
-        Arc::new(self.inner.read().unwrap().active_keyset.clone().into())
-    }
-
     pub fn keyset(&self, id: Arc<Id>) -> Option<Arc<KeySet>> {
         self.inner
             .read()

+ 2 - 0
bindings/cashu-sdk-ffi/src/types/keyset_info.rs

@@ -25,6 +25,7 @@ impl From<KeySetInfoSdk> for KeySetInfo {
 impl KeySetInfo {
     pub fn new(
         id: Arc<Id>,
+        active: bool,
         unit: String,
         valid_from: u64,
         valid_to: Option<u64>,
@@ -34,6 +35,7 @@ impl KeySetInfo {
         Self {
             inner: KeySetInfoSdk {
                 id: *id.as_ref().deref(),
+                active,
                 unit,
                 valid_from,
                 valid_to,

+ 11 - 22
bindings/cashu-sdk-js/src/mint.rs

@@ -3,7 +3,7 @@ use std::ops::Deref;
 #[cfg(feature = "nut07")]
 use cashu_js::nuts::{JsCheckSpendableRequest, JsCheckSpendableResponse};
 use cashu_js::nuts::{
-    JsId, JsKeySet, JsKeySetsResponse, JsKeysResponse, JsMeltRequest, JsMeltResponse, JsMintKeySet,
+    JsId, JsKeySet, JsKeySetsResponse, JsKeysResponse, JsMeltRequest, JsMeltResponse,
     JsMintRequest, JsPostMintResponse, JsSplitRequest, JsSplitResponse,
 };
 use cashu_js::JsAmount;
@@ -36,21 +36,17 @@ impl JsMint {
     #[wasm_bindgen(constructor)]
     pub fn new(
         secret: String,
-        active_keyset_info: JsValue,
-        inactive_keyset: JsValue,
+        keyset_info: JsValue,
         spent_secrets: JsValue,
         min_fee_reserve: JsAmount,
         percent_fee_reserve: f32,
     ) -> Result<JsMint> {
-        let active_keyset_info =
-            serde_wasm_bindgen::from_value(active_keyset_info).map_err(into_err)?;
-        let inactive_keyset = serde_wasm_bindgen::from_value(inactive_keyset).map_err(into_err)?;
+        let keyset_info = serde_wasm_bindgen::from_value(keyset_info).map_err(into_err)?;
         let spent_secrets = serde_wasm_bindgen::from_value(spent_secrets).map_err(into_err)?;
         Ok(JsMint {
             inner: Mint::new(
                 &secret,
-                active_keyset_info,
-                inactive_keyset,
+                keyset_info,
                 spent_secrets,
                 *min_fee_reserve.deref(),
                 percent_fee_reserve,
@@ -60,8 +56,13 @@ impl JsMint {
 
     /// Get Active Keyset Pubkeys
     #[wasm_bindgen(getter)]
-    pub fn active_keyset_pubkeys(&self) -> Result<JsKeysResponse> {
-        let keyset: KeySet = self.inner.active_keyset.clone().into();
+    pub fn keyset_pubkeys(&self, keyset_id: JsId) -> Result<JsKeysResponse> {
+        let keyset: KeySet = self
+            .inner
+            .keyset(&keyset_id)
+            .ok_or(JsError::new("Unknown Keyset"))?
+            .clone()
+            .into();
 
         Ok(KeysResponse { keys: keyset.keys }.into())
     }
@@ -72,24 +73,12 @@ impl JsMint {
         self.inner.keysets().into()
     }
 
-    /// Get Active Keyset
-    #[wasm_bindgen(getter)]
-    pub fn active_keyset(&self) -> JsMintKeySet {
-        self.inner.active_keyset.clone().into()
-    }
-
     /// Keyset
     #[wasm_bindgen(js_name = KeySet)]
     pub fn keyset(&self, id: JsId) -> Option<JsKeySet> {
         self.inner.keyset(id.deref()).map(|ks| ks.into())
     }
 
-    /// Rotate Keyset
-    #[wasm_bindgen(js_name = RotateKeyset)]
-    pub fn rotate_keyset(&mut self, secret: String, derivation_path: String, max_order: u8) {
-        self.inner.rotate_keyset(secret, derivation_path, max_order);
-    }
-
     /// Process Mint Request
     #[wasm_bindgen(js_name = ProcessMintRequest)]
     pub fn process_mint_request(

+ 58 - 63
crates/cashu-sdk/src/mint.rs

@@ -16,9 +16,8 @@ use tracing::{debug, info};
 pub struct Mint {
     //    pub pubkey: PublicKey
     secret: String,
-    pub active_keyset: nut02::mint::KeySet,
-    pub active_keyset_info: KeysetInfo,
-    pub inactive_keysets: HashMap<Id, KeysetInfo>,
+    pub keysets: HashMap<Id, nut02::mint::KeySet>,
+    pub keysets_info: HashMap<Id, KeysetInfo>,
     pub spent_secrets: HashSet<Secret>,
     pub pending_secrets: HashSet<Secret>,
     pub fee_reserve: FeeReserve,
@@ -27,24 +26,41 @@ pub struct Mint {
 impl Mint {
     pub fn new(
         secret: &str,
-        active_keyset_info: KeysetInfo,
-        inactive_keysets: HashSet<KeysetInfo>,
+        keysets_info: HashSet<KeysetInfo>,
         spent_secrets: HashSet<Secret>,
         min_fee_reserve: Amount,
         percent_fee_reserve: f32,
     ) -> Self {
-        let active_keyset = nut02::mint::KeySet::generate(
-            secret,
-            active_keyset_info.unit.clone(),
-            active_keyset_info.derivation_path.clone(),
-            active_keyset_info.max_order,
-        );
+        let mut keysets = HashMap::default();
+        let mut info = HashMap::default();
+
+        let mut active_units: HashSet<String> = HashSet::default();
+
+        // Check that there is only one active keyset per unit
+        for keyset_info in keysets_info {
+            if keyset_info.active {
+                if !active_units.insert(keyset_info.unit.clone()) {
+                    // TODO: Handle Error
+                    todo!()
+                }
+            }
+
+            let keyset = nut02::mint::KeySet::generate(
+                secret,
+                keyset_info.unit.clone(),
+                keyset_info.derivation_path.clone(),
+                keyset_info.max_order,
+            );
+
+            keysets.insert(keyset.id, keyset);
+
+            info.insert(keyset_info.id, keyset_info);
+        }
 
         Self {
             secret: secret.to_string(),
-            active_keyset,
-            inactive_keysets: inactive_keysets.into_iter().map(|ks| (ks.id, ks)).collect(),
-            active_keyset_info,
+            keysets,
+            keysets_info: info,
             spent_secrets,
             pending_secrets: HashSet::new(),
             fee_reserve: FeeReserve {
@@ -56,51 +72,31 @@ impl Mint {
 
     /// Retrieve the public keys of the active keyset for distribution to
     /// wallet clients
-    pub fn active_keyset_pubkeys(&self) -> KeysResponse {
-        KeysResponse {
-            keys: KeySet::from(self.active_keyset.clone()).keys,
-        }
+    pub fn keyset_pubkeys(&self, keyset_id: &Id) -> Option<KeysResponse> {
+        let keys: Keys = match self.keysets.get(keyset_id) {
+            Some(keyset) => keyset.keys.clone().into(),
+            None => {
+                return None;
+            }
+        };
+
+        Some(KeysResponse { keys })
     }
 
     /// Return a list of all supported keysets
     pub fn keysets(&self) -> KeysetResponse {
-        let mut keysets: HashSet<_> = self.inactive_keysets.values().cloned().collect();
-
-        keysets.insert(self.active_keyset_info.clone());
-
-        let keysets = keysets.into_iter().map(|k| k.into()).collect();
+        let keysets = self
+            .keysets_info
+            .values()
+            .into_iter()
+            .map(|k| k.clone().into())
+            .collect();
 
         KeysetResponse { keysets }
     }
 
-    pub fn active_keyset(&self) -> MintKeySet {
-        self.active_keyset.clone()
-    }
-
     pub fn keyset(&self, id: &Id) -> Option<KeySet> {
-        if self.active_keyset.id.eq(id) {
-            return Some(self.active_keyset.clone().into());
-        }
-
-        self.inactive_keysets.get(id).map(|k| {
-            nut02::mint::KeySet::generate(&self.secret, &k.unit, &k.derivation_path, k.max_order)
-                .into()
-        })
-    }
-
-    /// Add current keyset to inactive keysets
-    /// Generate new keyset
-    pub fn rotate_keyset(
-        &mut self,
-        unit: impl Into<String>,
-        derivation_path: impl Into<String>,
-        max_order: u8,
-    ) {
-        // Add current set to inactive keysets
-        self.inactive_keysets
-            .insert(self.active_keyset.id, self.active_keyset_info.clone());
-
-        self.active_keyset = MintKeySet::generate(&self.secret, unit, derivation_path, max_order);
+        self.keysets.get(id).map(|ks| ks.clone().into())
     }
 
     pub fn process_mint_request(
@@ -125,11 +121,19 @@ impl Mint {
             keyset_id,
         } = blinded_message;
 
-        if self.active_keyset.id.ne(keyset_id) {
+        let keyset = self.keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?;
+
+        // Check that the keyset is active and should be used to sign
+        if !self
+            .keysets_info
+            .get(keyset_id)
+            .ok_or(Error::UnknownKeySet)?
+            .active
+        {
             return Err(Error::InactiveKeyset);
         }
 
-        let Some(key_pair) = self.active_keyset.keys.0.get(amount) else {
+        let Some(key_pair) = keyset.keys.0.get(amount) else {
             // No key for amount
             return Err(Error::AmountKey);
         };
@@ -139,7 +143,7 @@ impl Mint {
         Ok(BlindedSignature {
             amount: *amount,
             c: c.into(),
-            id: self.active_keyset.id,
+            id: keyset.id,
         })
     }
 
@@ -190,16 +194,7 @@ impl Mint {
             return Err(Error::TokenSpent);
         }
 
-        let keyset = if let Some(keyset) = self.inactive_keysets.get(&proof.id) {
-            nut02::mint::KeySet::generate(
-                &self.secret,
-                &keyset.unit,
-                &keyset.derivation_path,
-                keyset.max_order,
-            )
-        } else {
-            self.active_keyset.clone()
-        };
+        let keyset = self.keysets.get(&proof.id).ok_or(Error::UnknownKeySet)?;
 
         let Some(keypair) = keyset.keys.0.get(&proof.amount) else {
             return Err(Error::AmountKey);

+ 3 - 0
crates/cashu/src/error.rs

@@ -104,6 +104,9 @@ pub mod mint {
         /// Keyset id not active
         #[error("Keyset id is not active")]
         InactiveKeyset,
+        /// Keyset is not known
+        #[error("Unknown Keyset")]
+        UnknownKeySet,
         #[error("`{0}`")]
         CustomError(String),
     }

+ 1 - 0
crates/cashu/src/types.rs

@@ -37,6 +37,7 @@ pub enum InvoiceStatus {
 pub struct KeysetInfo {
     pub id: Id,
     pub unit: String,
+    pub active: bool,
     pub valid_from: u64,
     pub valid_to: Option<u64>,
     pub derivation_path: String,