mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
refactor: Mint to use bip32 derivation and not store priv keys
This commit is contained in:
committed by
thesimplekid
parent
d65a0fac73
commit
a5a50f281c
@@ -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,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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user