feat(wallet): premint secrets from seed

This commit is contained in:
thesimplekid
2024-03-06 20:31:17 +00:00
parent 6632c08b01
commit 4d4467df58
6 changed files with 174 additions and 42 deletions

View File

@@ -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"]

View File

@@ -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())
}
}

View File

@@ -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>;
}

View File

@@ -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()))
}
}

View File

@@ -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, &quote_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(&quote_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, &quote_info.unit).await?,
proofs_amount,
)?;
let mut counter = None;
let active_keyset_id = self.active_mint_keyset(mint_url, &quote_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, &quote_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?;

View File

@@ -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);