Ver código fonte

refactor: Mint to use bip32 derivation and not store priv keys

David Caseria 11 meses atrás
pai
commit
a5a50f281c

+ 6 - 7
crates/cdk-redb/src/mint.rs

@@ -5,9 +5,8 @@ use std::sync::Arc;
 use async_trait::async_trait;
 use cdk::cdk_database::{self, MintDatabase};
 use cdk::dhke::hash_to_curve;
-use cdk::nuts::{
-    BlindSignature, CurrencyUnit, Id, MintInfo, MintKeySet as KeySet, Proof, PublicKey,
-};
+use cdk::mint::MintKeySetInfo;
+use cdk::nuts::{BlindSignature, CurrencyUnit, Id, MintInfo, Proof, PublicKey};
 use cdk::secret::Secret;
 use cdk::types::{MeltQuote, MintQuote};
 use redb::{Database, ReadableTable, TableDefinition};
@@ -167,7 +166,7 @@ impl MintDatabase for MintRedbDatabase {
         Ok(active_keysets)
     }
 
-    async fn add_keyset(&self, keyset: KeySet) -> Result<(), Self::Err> {
+    async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
 
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -176,7 +175,7 @@ impl MintDatabase for MintRedbDatabase {
             let mut table = write_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
             table
                 .insert(
-                    Id::from(keyset.clone()).to_string().as_str(),
+                    keyset.id.to_string().as_str(),
                     serde_json::to_string(&keyset)
                         .map_err(Error::from)?
                         .as_str(),
@@ -188,7 +187,7 @@ impl MintDatabase for MintRedbDatabase {
         Ok(())
     }
 
-    async fn get_keyset(&self, keyset_id: &Id) -> Result<Option<KeySet>, Self::Err> {
+    async fn get_keyset_info(&self, keyset_id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
         let table = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
@@ -202,7 +201,7 @@ impl MintDatabase for MintRedbDatabase {
         }
     }
 
-    async fn get_keysets(&self) -> Result<Vec<KeySet>, Self::Err> {
+    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
         let table = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;

+ 6 - 6
crates/cdk/src/cdk_database/mint_memory.rs

@@ -6,7 +6,7 @@ use tokio::sync::Mutex;
 
 use super::{Error, MintDatabase};
 use crate::dhke::hash_to_curve;
-use crate::nuts::nut02::MintKeySet;
+use crate::mint::MintKeySetInfo;
 use crate::nuts::{BlindSignature, CurrencyUnit, Id, MintInfo, Proof, Proofs, PublicKey};
 use crate::secret::Secret;
 use crate::types::{MeltQuote, MintQuote};
@@ -15,7 +15,7 @@ use crate::types::{MeltQuote, MintQuote};
 pub struct MintMemoryDatabase {
     mint_info: Arc<Mutex<MintInfo>>,
     active_keysets: Arc<Mutex<HashMap<CurrencyUnit, Id>>>,
-    keysets: Arc<Mutex<HashMap<Id, MintKeySet>>>,
+    keysets: Arc<Mutex<HashMap<Id, MintKeySetInfo>>>,
     mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
     melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
     pending_proofs: Arc<Mutex<HashMap<[u8; 33], Proof>>>,
@@ -28,7 +28,7 @@ impl MintMemoryDatabase {
     pub fn new(
         mint_info: MintInfo,
         active_keysets: HashMap<CurrencyUnit, Id>,
-        keysets: Vec<MintKeySet>,
+        keysets: Vec<MintKeySetInfo>,
         mint_quotes: Vec<MintQuote>,
         melt_quotes: Vec<MeltQuote>,
         pending_proofs: Proofs,
@@ -87,16 +87,16 @@ impl MintDatabase for MintMemoryDatabase {
         Ok(self.active_keysets.lock().await.clone())
     }
 
-    async fn add_keyset(&self, keyset: MintKeySet) -> Result<(), Error> {
+    async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Error> {
         self.keysets.lock().await.insert(keyset.id, keyset);
         Ok(())
     }
 
-    async fn get_keyset(&self, keyset_id: &Id) -> Result<Option<MintKeySet>, Error> {
+    async fn get_keyset_info(&self, keyset_id: &Id) -> Result<Option<MintKeySetInfo>, Error> {
         Ok(self.keysets.lock().await.get(keyset_id).cloned())
     }
 
-    async fn get_keysets(&self) -> Result<Vec<MintKeySet>, Error> {
+    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Error> {
         Ok(self.keysets.lock().await.values().cloned().collect())
     }
 

+ 18 - 7
crates/cdk/src/cdk_database/mod.rs

@@ -1,16 +1,25 @@
 //! CDK Database
 
+#[cfg(any(feature = "wallet", feature = "mint"))]
 use std::collections::HashMap;
 
+#[cfg(any(feature = "wallet", feature = "mint"))]
 use async_trait::async_trait;
 use thiserror::Error;
 
-use crate::nuts::{
-    BlindSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, MintKeySet, Proof, Proofs,
-    PublicKey,
-};
+#[cfg(feature = "mint")]
+use crate::mint::MintKeySetInfo;
+#[cfg(feature = "mint")]
+use crate::nuts::{BlindSignature, CurrencyUnit, Proof, PublicKey};
+#[cfg(any(feature = "wallet", feature = "mint"))]
+use crate::nuts::{Id, MintInfo};
+#[cfg(feature = "wallet")]
+use crate::nuts::{KeySetInfo, Keys, Proofs};
+#[cfg(feature = "mint")]
 use crate::secret::Secret;
+#[cfg(any(feature = "wallet", feature = "mint"))]
 use crate::types::{MeltQuote, MintQuote};
+#[cfg(feature = "wallet")]
 use crate::url::UncheckedUrl;
 
 #[cfg(feature = "mint")]
@@ -26,6 +35,7 @@ pub enum Error {
     Cdk(#[from] crate::error::Error),
 }
 
+#[cfg(feature = "wallet")]
 #[async_trait]
 pub trait WalletDatabase {
     type Err: Into<Error> + From<Error>;
@@ -81,6 +91,7 @@ pub trait WalletDatabase {
     async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Self::Err>;
 }
 
+#[cfg(feature = "mint")]
 #[async_trait]
 pub trait MintDatabase {
     type Err: Into<Error> + From<Error>;
@@ -102,9 +113,9 @@ pub trait MintDatabase {
     async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, Self::Err>;
     async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>;
 
-    async fn add_keyset(&self, keyset: MintKeySet) -> Result<(), Self::Err>;
-    async fn get_keyset(&self, id: &Id) -> Result<Option<MintKeySet>, Self::Err>;
-    async fn get_keysets(&self) -> Result<Vec<MintKeySet>, Self::Err>;
+    async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>;
+    async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
+    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
 
     async fn add_spent_proof(&self, proof: Proof) -> Result<(), Self::Err>;
     async fn get_spent_proof_by_secret(&self, secret: &Secret) -> Result<Option<Proof>, Self::Err>;

+ 114 - 73
crates/cdk/src/mint.rs

@@ -1,10 +1,12 @@
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 use std::sync::Arc;
 
-use bip39::Mnemonic;
+use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey};
+use bitcoin::secp256k1::{self, Secp256k1};
 use http::StatusCode;
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
+use tokio::sync::RwLock;
 use tracing::{debug, error, info};
 
 use crate::cdk_database::{self, MintDatabase};
@@ -12,6 +14,7 @@ use crate::dhke::{hash_to_curve, sign_message, verify_message};
 use crate::error::ErrorResponse;
 use crate::nuts::*;
 use crate::types::{MeltQuote, MintQuote};
+use crate::util::unix_time;
 use crate::Amount;
 
 #[derive(Debug, Error)]
@@ -83,52 +86,43 @@ impl From<Error> for (StatusCode, ErrorResponse) {
 
 #[derive(Clone)]
 pub struct Mint {
-    //    pub pubkey: PublicKey
-    mnemonic: Mnemonic,
+    keysets: Arc<RwLock<HashMap<Id, MintKeySet>>>,
+    secp_ctx: Secp256k1<secp256k1::All>,
+    xpriv: ExtendedPrivKey,
     pub fee_reserve: FeeReserve,
     pub localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>,
 }
 
 impl Mint {
     pub async fn new(
+        seed: &[u8],
         localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>,
-        mnemonic: Mnemonic,
-        keysets_info: HashSet<MintKeySetInfo>,
         min_fee_reserve: Amount,
         percent_fee_reserve: f32,
     ) -> Result<Self, Error> {
-        let mut active_units: HashSet<CurrencyUnit> = HashSet::default();
+        let secp_ctx = Secp256k1::new();
+        let xpriv =
+            ExtendedPrivKey::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
 
+        let mut keysets = HashMap::new();
+        let keysets_info = localstore.get_keyset_infos().await?;
         if keysets_info.is_empty() {
-            let keyset =
-                MintKeySet::generate(&mnemonic.to_seed_normalized(""), CurrencyUnit::Sat, "", 64);
-
-            localstore
-                .add_active_keyset(CurrencyUnit::Sat, keyset.id)
-                .await?;
-            localstore.add_keyset(keyset).await?;
-        } else {
-            // Check that there is only one active keyset per unit
-            for keyset_info in keysets_info {
-                if keyset_info.active && !active_units.insert(keyset_info.unit.clone()) {
-                    // TODO: Handle Error
-                    todo!()
-                }
-
-                let keyset = MintKeySet::generate(
-                    &mnemonic.to_seed_normalized(""),
-                    keyset_info.unit.clone(),
-                    &keyset_info.derivation_path.clone(),
-                    keyset_info.max_order,
-                );
-
-                localstore.add_keyset(keyset).await?;
-            }
+            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);
+            let id = keyset_info.id;
+            localstore.add_active_keyset(CurrencyUnit::Sat, id).await?;
+            localstore.add_keyset_info(keyset_info).await?;
+            keysets.insert(id, keyset);
         }
 
         Ok(Self {
+            keysets: Arc::new(RwLock::new(keysets)),
+            secp_ctx,
+            xpriv,
             localstore,
-            mnemonic,
             fee_reserve: FeeReserve {
                 min_fee_reserve,
                 percent_fee_reserve,
@@ -199,13 +193,9 @@ impl Mint {
     /// Retrieve the public keys of the active keyset for distribution to
     /// wallet clients
     pub async fn keyset_pubkeys(&self, keyset_id: &Id) -> Result<KeysResponse, Error> {
-        let keyset = match self.localstore.get_keyset(keyset_id).await? {
-            Some(keyset) => keyset.clone(),
-            None => {
-                return Err(Error::UnknownKeySet);
-            }
-        };
-
+        self.ensure_keyset_loaded(keyset_id).await?;
+        let keysets = self.keysets.read().await;
+        let keyset = keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?.clone();
         Ok(KeysResponse {
             keysets: vec![keyset.into()],
         })
@@ -214,16 +204,19 @@ impl Mint {
     /// Retrieve the public keys of the active keyset for distribution to
     /// wallet clients
     pub async fn pubkeys(&self) -> Result<KeysResponse, Error> {
-        let keysets = self.localstore.get_keysets().await?;
-
+        let keyset_infos = self.localstore.get_keyset_infos().await?;
+        for keyset_info in keyset_infos {
+            self.ensure_keyset_loaded(&keyset_info.id).await?;
+        }
+        let keysets = self.keysets.read().await;
         Ok(KeysResponse {
-            keysets: keysets.into_iter().map(|k| k.into()).collect(),
+            keysets: keysets.values().map(|k| k.clone().into()).collect(),
         })
     }
 
     /// Return a list of all supported keysets
     pub async fn keysets(&self) -> Result<KeysetResponse, Error> {
-        let keysets = self.localstore.get_keysets().await?;
+        let keysets = self.localstore.get_keyset_infos().await?;
         let active_keysets: HashSet<Id> = self
             .localstore
             .get_active_keysets()
@@ -245,11 +238,10 @@ impl Mint {
     }
 
     pub async fn keyset(&self, id: &Id) -> Result<Option<KeySet>, Error> {
-        Ok(self
-            .localstore
-            .get_keyset(id)
-            .await?
-            .map(|ks| ks.clone().into()))
+        self.ensure_keyset_loaded(id).await?;
+        let keysets = self.keysets.read().await;
+        let keyset = keysets.get(id).map(|k| k.clone().into());
+        Ok(keyset)
     }
 
     /// Add current keyset to inactive keysets
@@ -257,21 +249,22 @@ impl Mint {
     pub async fn rotate_keyset(
         &mut self,
         unit: CurrencyUnit,
-        derivation_path: &str,
+        derivation_path: DerivationPath,
         max_order: u8,
     ) -> Result<(), Error> {
-        let new_keyset = MintKeySet::generate(
-            &self.mnemonic.to_seed_normalized(""),
-            unit.clone(),
+        let (keyset, keyset_info) = create_new_keyset(
+            &self.secp_ctx,
+            self.xpriv,
             derivation_path,
+            unit.clone(),
             max_order,
         );
+        let id = keyset_info.id;
+        self.localstore.add_keyset_info(keyset_info).await?;
+        self.localstore.add_active_keyset(unit, id).await?;
 
-        self.localstore.add_keyset(new_keyset.clone()).await?;
-
-        self.localstore
-            .add_active_keyset(unit, new_keyset.id)
-            .await?;
+        let mut keysets = self.keysets.write().await;
+        keysets.insert(id, keyset);
 
         Ok(())
     }
@@ -327,24 +320,27 @@ impl Mint {
             keyset_id,
             ..
         } = blinded_message;
+        self.ensure_keyset_loaded(keyset_id).await?;
 
-        let keyset = self
+        let keyset_info = self
             .localstore
-            .get_keyset(keyset_id)
+            .get_keyset_info(keyset_id)
             .await?
             .ok_or(Error::UnknownKeySet)?;
 
         let active = self
             .localstore
-            .get_active_keyset_id(&keyset.unit)
+            .get_active_keyset_id(&keyset_info.unit)
             .await?
             .ok_or(Error::InactiveKeyset)?;
 
         // Check that the keyset is active and should be used to sign
-        if keyset.id.ne(&active) {
+        if keyset_info.id.ne(&active) {
             return Err(Error::InactiveKeyset);
         }
 
+        let keysets = self.keysets.read().await;
+        let keyset = keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?;
         let Some(key_pair) = keyset.keys.get(amount) else {
             // No key for amount
             return Err(Error::AmountKey);
@@ -355,7 +351,7 @@ impl Mint {
         let blinded_signature = BlindSignature::new(
             *amount,
             c,
-            keyset.id,
+            keyset_info.id,
             &blinded_message.blinded_secret,
             key_pair.secret_key.clone(),
         )?;
@@ -416,7 +412,7 @@ impl Mint {
         for id in input_keyset_ids {
             let keyset = self
                 .localstore
-                .get_keyset(&id)
+                .get_keyset_info(&id)
                 .await?
                 .ok_or(Error::UnknownKeySet)?;
             keyset_units.insert(keyset.unit);
@@ -428,7 +424,7 @@ impl Mint {
         for id in &output_keyset_ids {
             let keyset = self
                 .localstore
-                .get_keyset(id)
+                .get_keyset_info(id)
                 .await?
                 .ok_or(Error::UnknownKeySet)?;
 
@@ -483,12 +479,9 @@ impl Mint {
             return Err(Error::TokenPending);
         }
 
-        let keyset = self
-            .localstore
-            .get_keyset(&proof.keyset_id)
-            .await?
-            .ok_or(Error::UnknownKeySet)?;
-
+        self.ensure_keyset_loaded(&proof.keyset_id).await?;
+        let keysets = self.keysets.read().await;
+        let keyset = keysets.get(&proof.keyset_id).ok_or(Error::UnknownKeySet)?;
         let Some(keypair) = keyset.keys.get(&proof.amount) else {
             return Err(Error::AmountKey);
         };
@@ -552,7 +545,7 @@ impl Mint {
         for id in input_keyset_ids {
             let keyset = self
                 .localstore
-                .get_keyset(&id)
+                .get_keyset_info(&id)
                 .await?
                 .ok_or(Error::UnknownKeySet)?;
             keyset_units.insert(keyset.unit);
@@ -563,7 +556,7 @@ impl Mint {
             for id in output_keysets_ids {
                 let keyset = self
                     .localstore
-                    .get_keyset(&id)
+                    .get_keyset_info(&id)
                     .await?
                     .ok_or(Error::UnknownKeySet)?;
 
@@ -734,6 +727,27 @@ impl Mint {
             signatures,
         })
     }
+
+    async fn ensure_keyset_loaded(&self, id: &Id) -> Result<(), Error> {
+        let keysets = self.keysets.read().await;
+        if keysets.contains_key(id) {
+            return Ok(());
+        }
+
+        let mut keysets = self.keysets.write().await;
+        let keyset_info = self
+            .localstore
+            .get_keyset_info(id)
+            .await?
+            .ok_or(Error::UnknownKeySet)?;
+        let id = keyset_info.id;
+        keysets.insert(id, self.generate_keyset(keyset_info));
+        Ok(())
+    }
+
+    fn generate_keyset(&self, keyset_info: MintKeySetInfo) -> MintKeySet {
+        MintKeySet::generate_from_xpriv(&self.secp_ctx, self.xpriv, keyset_info)
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -749,7 +763,7 @@ pub struct MintKeySetInfo {
     pub active: bool,
     pub valid_from: u64,
     pub valid_to: Option<u64>,
-    pub derivation_path: String,
+    pub derivation_path: DerivationPath,
     pub max_order: u8,
 }
 
@@ -762,3 +776,30 @@ impl From<MintKeySetInfo> for KeySetInfo {
         }
     }
 }
+
+fn create_new_keyset<C: secp256k1::Signing>(
+    secp: &secp256k1::Secp256k1<C>,
+    xpriv: ExtendedPrivKey,
+    derivation_path: DerivationPath,
+    unit: CurrencyUnit,
+    max_order: u8,
+) -> (MintKeySet, MintKeySetInfo) {
+    let keyset = MintKeySet::generate(
+        secp,
+        xpriv
+            .derive_priv(secp, &derivation_path)
+            .expect("RNG busted"),
+        unit,
+        max_order,
+    );
+    let keyset_info = MintKeySetInfo {
+        id: keyset.id,
+        unit: keyset.unit.clone(),
+        active: true,
+        valid_from: unix_time(),
+        valid_to: None,
+        derivation_path,
+        max_order,
+    };
+    (keyset, keyset_info)
+}

+ 3 - 1
crates/cdk/src/nuts/mod.rs

@@ -19,7 +19,9 @@ pub use nut00::{
     Proofs, Token,
 };
 pub use nut01::{Keys, KeysResponse, PublicKey, SecretKey};
-pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse, MintKeySet};
+#[cfg(feature = "mint")]
+pub use nut02::MintKeySet;
+pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse};
 #[cfg(feature = "wallet")]
 pub use nut03::PreSwap;
 pub use nut03::{SwapRequest, SwapResponse};

+ 59 - 27
crates/cdk/src/nuts/nut02.rs

@@ -5,17 +5,29 @@
 use core::fmt;
 use core::str::FromStr;
 use std::array::TryFromSliceError;
+#[cfg(feature = "mint")]
 use std::collections::BTreeMap;
 
+#[cfg(feature = "mint")]
+use bitcoin::bip32::{ChildNumber, ExtendedPrivKey};
 use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::Hash;
+#[cfg(feature = "mint")]
+use bitcoin::key::Secp256k1;
+#[cfg(feature = "mint")]
+use bitcoin::secp256k1;
 use serde::{Deserialize, Deserializer, Serialize};
 use serde_with::{serde_as, VecSkipError};
 use thiserror::Error;
 
-use super::nut01::{Keys, MintKeyPair, MintKeys, SecretKey};
+use super::nut01::Keys;
+#[cfg(feature = "mint")]
+use super::nut01::{MintKeyPair, MintKeys};
+#[cfg(feature = "mint")]
+use crate::mint::MintKeySetInfo;
 use crate::nuts::nut00::CurrencyUnit;
 use crate::util::hex;
+#[cfg(feature = "mint")]
 use crate::Amount;
 
 #[derive(Debug, Error)]
@@ -220,6 +232,7 @@ pub struct KeySet {
     pub keys: Keys,
 }
 
+#[cfg(feature = "mint")]
 impl From<MintKeySet> for KeySet {
     fn from(keyset: MintKeySet) -> Self {
         Self {
@@ -247,6 +260,7 @@ impl From<KeySet> for KeySetInfo {
     }
 }
 
+#[cfg(feature = "mint")]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MintKeySet {
     pub id: Id,
@@ -254,49 +268,66 @@ pub struct MintKeySet {
     pub keys: MintKeys,
 }
 
+#[cfg(feature = "mint")]
 impl MintKeySet {
-    pub fn generate(
-        secret: &[u8],
+    pub fn generate<C: secp256k1::Signing>(
+        secp: &Secp256k1<C>,
+        xpriv: ExtendedPrivKey,
         unit: CurrencyUnit,
-        derivation_path: &str,
         max_order: u8,
     ) -> Self {
-        // Elliptic curve math context
-
-        /* NUT-02 § 2.1
-            for i in range(MAX_ORDER):
-                k_i = HASH_SHA256(s + D + i)[:32]
-        */
-
         let mut map = BTreeMap::new();
-
-        // SHA-256 midstate, for quicker hashing
-        let mut engine = Sha256::engine();
-        engine.input(secret);
-        engine.input(derivation_path.as_bytes());
-
         for i in 0..max_order {
             let amount = Amount::from(2_u64.pow(i as u32));
-
-            // Reuse midstate
-            let mut e = engine.clone();
-            e.input(i.to_string().as_bytes());
-            let hash = Sha256::from_engine(e);
-            let secret_key = SecretKey::from_slice(&hash.to_byte_array()).unwrap(); // TODO: remove unwrap
-            let keypair = MintKeyPair::from_secret_key(secret_key);
-            map.insert(amount, keypair);
+            let secret_key = xpriv
+                .derive_priv(
+                    secp,
+                    &[ChildNumber::from_hardened_idx(i as u32).expect("order is valid index")],
+                )
+                .expect("RNG busted")
+                .private_key;
+            let public_key = secret_key.public_key(secp);
+            map.insert(
+                amount,
+                MintKeyPair {
+                    secret_key: secret_key.into(),
+                    public_key: public_key.into(),
+                },
+            );
         }
 
         let keys = MintKeys::new(map);
-
         Self {
             id: (&keys).into(),
             unit,
             keys,
         }
     }
+
+    pub fn generate_from_seed<C: secp256k1::Signing>(
+        secp: &Secp256k1<C>,
+        seed: &[u8],
+        info: MintKeySetInfo,
+    ) -> Self {
+        let xpriv =
+            ExtendedPrivKey::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
+        let max_order = info.max_order;
+        let unit = info.unit;
+        Self::generate(secp, xpriv, unit, max_order)
+    }
+
+    pub fn generate_from_xpriv<C: secp256k1::Signing>(
+        secp: &Secp256k1<C>,
+        xpriv: ExtendedPrivKey,
+        info: MintKeySetInfo,
+    ) -> Self {
+        let max_order = info.max_order;
+        let unit = info.unit;
+        Self::generate(secp, xpriv, unit, max_order)
+    }
 }
 
+#[cfg(feature = "mint")]
 impl From<MintKeySet> for Id {
     fn from(keyset: MintKeySet) -> Id {
         let keys: super::KeySet = keyset.into();
@@ -305,6 +336,7 @@ impl From<MintKeySet> for Id {
     }
 }
 
+#[cfg(feature = "mint")]
 impl From<&MintKeys> for Id {
     fn from(map: &MintKeys) -> Self {
         let keys: super::Keys = map.clone().into();