mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
feat(wallet): premint secrets from seed
This commit is contained in:
@@ -14,11 +14,12 @@ default = ["mint", "wallet", "all-nuts", "redb"]
|
||||
mint = ["cashu/mint"]
|
||||
wallet = ["cashu/wallet", "dep:minreq", "dep:once_cell"]
|
||||
gloo = ["dep:gloo"]
|
||||
all-nuts = ["nut07", "nut08", "nut10", "nut11"]
|
||||
all-nuts = ["nut07", "nut08", "nut10", "nut11", "nut13"]
|
||||
nut07 = ["cashu/nut07"]
|
||||
nut08 = ["cashu/nut08"]
|
||||
nut10 = ["cashu/nut10"]
|
||||
nut11 = ["cashu/nut11"]
|
||||
nut13 = ["cashu/nut13"]
|
||||
redb = ["dep:redb"]
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ pub struct MemoryLocalStore {
|
||||
mint_keys: Arc<Mutex<HashMap<Id, Keys>>>,
|
||||
proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
|
||||
pending_proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
|
||||
#[cfg(feature = "nut13")]
|
||||
keyset_counter: Arc<Mutex<HashMap<Id, u64>>>,
|
||||
}
|
||||
|
||||
impl MemoryLocalStore {
|
||||
@@ -25,6 +27,7 @@ impl MemoryLocalStore {
|
||||
mint_quotes: Vec<MintQuote>,
|
||||
melt_quotes: Vec<MeltQuote>,
|
||||
mint_keys: Vec<Keys>,
|
||||
keyset_counter: HashMap<Id, u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mints: Arc::new(Mutex::new(HashMap::new())),
|
||||
@@ -40,6 +43,8 @@ impl MemoryLocalStore {
|
||||
)),
|
||||
proofs: Arc::new(Mutex::new(HashMap::new())),
|
||||
pending_proofs: Arc::new(Mutex::new(HashMap::new())),
|
||||
#[cfg(feature = "nut13")]
|
||||
keyset_counter: Arc::new(Mutex::new(keyset_counter)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,4 +210,16 @@ impl LocalStore for MemoryLocalStore {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
|
||||
self.keyset_counter
|
||||
.lock()
|
||||
.await
|
||||
.insert(keyset_id.clone(), count);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_keyset_counter(&self, id: &Id) -> Result<Option<u64>, Error> {
|
||||
Ok(self.keyset_counter.lock().await.get(id).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,9 @@ pub trait LocalStore {
|
||||
mint_url: UncheckedUrl,
|
||||
proofs: &Proofs,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
#[cfg(feature = "nut13")]
|
||||
async fn add_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error>;
|
||||
#[cfg(feature = "nut13")]
|
||||
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Error>;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ const MINT_KEYS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_
|
||||
const PROOFS_TABLE: MultimapTableDefinition<&str, &str> = MultimapTableDefinition::new("proofs");
|
||||
const PENDING_PROOFS_TABLE: MultimapTableDefinition<&str, &str> =
|
||||
MultimapTableDefinition::new("pending_proofs");
|
||||
#[cfg(feature = "nut13")]
|
||||
const KEYSET_COUNTER: TableDefinition<&str, u64> = TableDefinition::new("keyset_counter");
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RedbLocalStore {
|
||||
@@ -40,6 +42,8 @@ impl RedbLocalStore {
|
||||
let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
|
||||
let _ = write_txn.open_table(MINT_KEYS_TABLE)?;
|
||||
let _ = write_txn.open_multimap_table(PROOFS_TABLE)?;
|
||||
#[cfg(feature = "nut13")]
|
||||
let _ = write_txn.open_table(KEYSET_COUNTER)?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
|
||||
@@ -383,4 +387,31 @@ impl LocalStore for RedbLocalStore {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "nut13")]
|
||||
async fn add_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
|
||||
let db = self.db.lock().await;
|
||||
|
||||
let write_txn = db.begin_write()?;
|
||||
|
||||
{
|
||||
let mut table = write_txn.open_table(KEYSET_COUNTER)?;
|
||||
|
||||
table.insert(keyset_id.to_string().as_str(), count)?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "nut13")]
|
||||
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Error> {
|
||||
let db = self.db.lock().await;
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(KEYSET_COUNTER)?;
|
||||
|
||||
let counter = table.get(keyset_id.to_string().as_str())?;
|
||||
|
||||
Ok(counter.map(|c| c.value()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,23 +51,17 @@ pub enum Error {
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BackupInfo {
|
||||
mnemonic: Mnemonic,
|
||||
counter: HashMap<Id, u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Wallet<C: Client, L: LocalStore> {
|
||||
pub client: C,
|
||||
localstore: L,
|
||||
backup_info: Option<BackupInfo>,
|
||||
mnemonic: Option<Mnemonic>,
|
||||
}
|
||||
|
||||
impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
pub async fn new(client: C, localstore: L, backup_info: Option<BackupInfo>) -> Self {
|
||||
pub async fn new(client: C, localstore: L, mnemonic: Option<Mnemonic>) -> Self {
|
||||
Self {
|
||||
backup_info,
|
||||
mnemonic,
|
||||
client,
|
||||
localstore,
|
||||
}
|
||||
@@ -75,12 +69,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
|
||||
/// Back up seed
|
||||
pub fn mnemonic(&self) -> Option<Mnemonic> {
|
||||
self.backup_info.clone().map(|b| b.mnemonic)
|
||||
}
|
||||
|
||||
/// Back up keyset counters
|
||||
pub fn keyset_counters(&self) -> Option<HashMap<Id, u64>> {
|
||||
self.backup_info.clone().map(|b| b.counter)
|
||||
self.mnemonic.clone().map(|b| b)
|
||||
}
|
||||
|
||||
pub async fn mint_balances(&self) -> Result<HashMap<UncheckedUrl, Amount>, Error> {
|
||||
@@ -303,13 +292,25 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
|
||||
let active_keyset_id = self.active_mint_keyset(&mint_url, "e_info.unit).await?;
|
||||
|
||||
let premint_secrets = match &self.backup_info {
|
||||
Some(backup_info) => PreMintSecrets::from_seed(
|
||||
active_keyset_id,
|
||||
*backup_info.counter.get(&active_keyset_id).unwrap_or(&0),
|
||||
&backup_info.mnemonic,
|
||||
quote_info.amount,
|
||||
)?,
|
||||
let mut counter = None;
|
||||
|
||||
let premint_secrets = match &self.mnemonic {
|
||||
Some(mnemonic) => {
|
||||
let count = self
|
||||
.localstore
|
||||
.get_keyset_counter(&active_keyset_id)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
counter = Some(count);
|
||||
PreMintSecrets::from_seed(
|
||||
active_keyset_id,
|
||||
count,
|
||||
&mnemonic,
|
||||
quote_info.amount,
|
||||
false,
|
||||
)?
|
||||
}
|
||||
None => PreMintSecrets::random(active_keyset_id, quote_info.amount)?,
|
||||
};
|
||||
|
||||
@@ -336,6 +337,14 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
// Remove filled quote from store
|
||||
self.localstore.remove_mint_quote("e_info.id).await?;
|
||||
|
||||
// Update counter for keyset
|
||||
if let Some(counter) = counter {
|
||||
let count = counter + proofs.len() as u64;
|
||||
self.localstore
|
||||
.add_keyset_counter(&active_keyset_id, count)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Add new proofs to store
|
||||
self.localstore.add_proofs(mint_url, proofs).await?;
|
||||
|
||||
@@ -407,27 +416,52 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
) -> Result<PreSwap, Error> {
|
||||
let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?;
|
||||
|
||||
let pre_mint_secrets = if let Some(amount) = amount {
|
||||
let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
|
||||
// Desired amount is either amount passwed or value of all proof
|
||||
let proofs_total = proofs.iter().map(|p| p.amount).sum();
|
||||
|
||||
let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
|
||||
let desired_amount = amount.unwrap_or(proofs_total);
|
||||
|
||||
let change_messages = PreMintSecrets::random(active_keyset_id, change_amount)?;
|
||||
let mut counter = None;
|
||||
|
||||
let mut desired_messages = if let Some(mnemonic) = &self.mnemonic {
|
||||
let count = self
|
||||
.localstore
|
||||
.get_keyset_counter(&active_keyset_id)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
let premint_secrets = PreMintSecrets::from_seed(
|
||||
active_keyset_id,
|
||||
count,
|
||||
mnemonic,
|
||||
desired_amount,
|
||||
false,
|
||||
)?;
|
||||
|
||||
counter = Some(count + premint_secrets.len() as u64);
|
||||
|
||||
premint_secrets
|
||||
} else {
|
||||
PreMintSecrets::random(active_keyset_id, desired_amount)?
|
||||
};
|
||||
|
||||
if let (Some(amt), Some(mnemonic)) = (amount, &self.mnemonic) {
|
||||
let change_amount = proofs_total - amt;
|
||||
|
||||
let change_messages = if let Some(count) = counter {
|
||||
PreMintSecrets::from_seed(active_keyset_id, count, mnemonic, desired_amount, false)?
|
||||
} else {
|
||||
PreMintSecrets::random(active_keyset_id, change_amount)?
|
||||
};
|
||||
// Combine the BlindedMessages totoalling the desired amount with change
|
||||
desired_messages.combine(change_messages);
|
||||
// Sort the premint secrets to avoid finger printing
|
||||
desired_messages.sort_secrets();
|
||||
desired_messages
|
||||
} else {
|
||||
let amount = proofs.iter().map(|p| p.amount).sum();
|
||||
|
||||
PreMintSecrets::random(active_keyset_id, amount)?
|
||||
};
|
||||
|
||||
let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
|
||||
let swap_request = SwapRequest::new(proofs, desired_messages.blinded_messages());
|
||||
|
||||
Ok(PreSwap {
|
||||
pre_mint_secrets,
|
||||
pre_mint_secrets: desired_messages,
|
||||
swap_request,
|
||||
})
|
||||
}
|
||||
@@ -439,6 +473,8 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
) -> Result<Proofs, Error> {
|
||||
let mut proofs = vec![];
|
||||
|
||||
let mut proof_count: HashMap<Id, u64> = HashMap::new();
|
||||
|
||||
for (promise, premint) in promises.iter().zip(blinded_messages) {
|
||||
let a = self
|
||||
.localstore
|
||||
@@ -452,6 +488,10 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
let blinded_c = promise.c.clone();
|
||||
|
||||
let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap();
|
||||
|
||||
let count = proof_count.get(&promise.keyset_id).unwrap_or(&0);
|
||||
proof_count.insert(promise.keyset_id, count + 1);
|
||||
|
||||
let proof = Proof::new(
|
||||
promise.amount,
|
||||
promise.keyset_id,
|
||||
@@ -462,6 +502,20 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
proofs.push(proof);
|
||||
}
|
||||
|
||||
if self.mnemonic.is_some() {
|
||||
for (keyset_id, count) in proof_count {
|
||||
let counter = self
|
||||
.localstore
|
||||
.get_keyset_counter(&keyset_id)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
self.localstore
|
||||
.add_keyset_counter(&keyset_id, counter + count)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(proofs)
|
||||
}
|
||||
|
||||
@@ -637,10 +691,23 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
|
||||
let proofs_amount = proofs.iter().map(|p| p.amount).sum();
|
||||
|
||||
let blinded = PreMintSecrets::blank(
|
||||
self.active_mint_keyset(mint_url, "e_info.unit).await?,
|
||||
proofs_amount,
|
||||
)?;
|
||||
let mut counter = None;
|
||||
|
||||
let active_keyset_id = self.active_mint_keyset(mint_url, "e_info.unit).await?;
|
||||
|
||||
let premint_secrets = match &self.mnemonic {
|
||||
Some(mnemonic) => {
|
||||
let count = self
|
||||
.localstore
|
||||
.get_keyset_counter(&active_keyset_id)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
counter = Some(count);
|
||||
PreMintSecrets::from_seed(active_keyset_id, count, &mnemonic, proofs_amount, true)?
|
||||
}
|
||||
None => PreMintSecrets::blank(active_keyset_id, proofs_amount)?,
|
||||
};
|
||||
|
||||
let melt_response = self
|
||||
.client
|
||||
@@ -648,15 +715,15 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
mint_url.clone().try_into()?,
|
||||
quote_id.to_string(),
|
||||
proofs.clone(),
|
||||
Some(blinded.blinded_messages()),
|
||||
Some(premint_secrets.blinded_messages()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let change_proofs = match melt_response.change {
|
||||
Some(change) => Some(construct_proofs(
|
||||
change,
|
||||
blinded.rs(),
|
||||
blinded.secrets(),
|
||||
premint_secrets.rs(),
|
||||
premint_secrets.secrets(),
|
||||
&self.active_keys(mint_url, "e_info.unit).await?.unwrap(),
|
||||
)?),
|
||||
None => None,
|
||||
@@ -669,6 +736,14 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
|
||||
};
|
||||
|
||||
if let Some(change_proofs) = change_proofs {
|
||||
// Update counter for keyset
|
||||
if let Some(counter) = counter {
|
||||
let count = counter + change_proofs.len() as u64;
|
||||
self.localstore
|
||||
.add_keyset_counter(&active_keyset_id, count)
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.localstore
|
||||
.add_proofs(mint_url.clone(), change_proofs)
|
||||
.await?;
|
||||
|
||||
@@ -55,6 +55,7 @@ mod wallet {
|
||||
counter: u64,
|
||||
mnemonic: &Mnemonic,
|
||||
amount: Amount,
|
||||
zero_amount: bool,
|
||||
) -> Result<Self, wallet::Error> {
|
||||
let mut pre_mint_secrets = PreMintSecrets::default();
|
||||
|
||||
@@ -66,13 +67,15 @@ mod wallet {
|
||||
|
||||
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
|
||||
|
||||
let amount = if zero_amount { Amount::ZERO } else { amount };
|
||||
|
||||
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
|
||||
|
||||
let pre_mint = PreMint {
|
||||
blinded_message,
|
||||
secret: secret.clone(),
|
||||
r: r.into(),
|
||||
amount: Amount::ZERO,
|
||||
amount,
|
||||
};
|
||||
|
||||
pre_mint_secrets.secrets.push(pre_mint);
|
||||
|
||||
Reference in New Issue
Block a user