feat: remove multimint support

This commit is contained in:
thesimplekid
2024-10-25 08:56:04 +01:00
parent c91297aac6
commit 4e6bf594be
5 changed files with 179 additions and 169 deletions

View File

@@ -137,7 +137,7 @@ async fn receive_token(
) -> Result<Amount> { ) -> Result<Amount> {
let token: Token = Token::from_str(token_str)?; let token: Token = Token::from_str(token_str)?;
let mint_url = token.proofs().into_keys().next().expect("Mint in token"); let mint_url = token.mint_url()?;
let wallet_key = WalletKey::new(mint_url.clone(), token.unit().unwrap_or_default()); let wallet_key = WalletKey::new(mint_url.clone(), token.unit().unwrap_or_default());

View File

@@ -66,7 +66,7 @@ impl Token {
} }
/// Proofs in [`Token`] /// Proofs in [`Token`]
pub fn proofs(&self) -> HashMap<MintUrl, Proofs> { pub fn proofs(&self) -> Proofs {
match self { match self {
Self::TokenV3(token) => token.proofs(), Self::TokenV3(token) => token.proofs(),
Self::TokenV4(token) => token.proofs(), Self::TokenV4(token) => token.proofs(),
@@ -92,11 +92,27 @@ impl Token {
/// Unit /// Unit
pub fn unit(&self) -> Option<CurrencyUnit> { pub fn unit(&self) -> Option<CurrencyUnit> {
match self { match self {
Self::TokenV3(token) => token.unit(), Self::TokenV3(token) => *token.unit(),
Self::TokenV4(token) => Some(token.unit()), Self::TokenV4(token) => Some(token.unit()),
} }
} }
/// Mint url
pub fn mint_url(&self) -> Result<MintUrl, Error> {
match self {
Self::TokenV3(token) => {
let mint_urls = token.mint_urls();
if mint_urls.len() != 1 {
return Err(Error::UnsupportedToken);
}
Ok(mint_urls.first().expect("Length is checked above").clone())
}
Self::TokenV4(token) => Ok(token.mint_url.clone()),
}
}
/// To v3 string /// To v3 string
pub fn to_v3_string(&self) -> String { pub fn to_v3_string(&self) -> String {
let v3_token = match self { let v3_token = match self {
@@ -187,24 +203,17 @@ impl TokenV3 {
}) })
} }
fn proofs(&self) -> HashMap<MintUrl, Proofs> { /// Proofs
let mut proofs: HashMap<MintUrl, Proofs> = HashMap::new(); pub fn proofs(&self) -> Proofs {
self.token
for token in self.token.clone() { .iter()
let mint_url = token.mint; .flat_map(|token| token.proofs.clone())
let mut mint_proofs = token.proofs; .collect()
proofs
.entry(mint_url)
.and_modify(|p| p.append(&mut mint_proofs))
.or_insert(mint_proofs);
}
proofs
} }
/// Value
#[inline] #[inline]
fn value(&self) -> Result<Amount, Error> { pub fn value(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum( Ok(Amount::try_sum(
self.token self.token
.iter() .iter()
@@ -213,14 +222,27 @@ impl TokenV3 {
)?) )?)
} }
/// Memo
#[inline] #[inline]
fn memo(&self) -> &Option<String> { pub fn memo(&self) -> &Option<String> {
&self.memo &self.memo
} }
/// Unit
#[inline] #[inline]
fn unit(&self) -> Option<CurrencyUnit> { pub fn unit(&self) -> &Option<CurrencyUnit> {
self.unit &self.unit
}
/// Mint Url
pub fn mint_urls(&self) -> Vec<MintUrl> {
let mut mint_urls = Vec::new();
for token in self.token.iter() {
mint_urls.push(token.mint.clone());
}
mint_urls
} }
} }
@@ -249,13 +271,10 @@ impl fmt::Display for TokenV3 {
impl From<TokenV4> for TokenV3 { impl From<TokenV4> for TokenV3 {
fn from(token: TokenV4) -> Self { fn from(token: TokenV4) -> Self {
let (mint_url, proofs) = token let proofs = token.proofs();
.proofs()
.into_iter()
.next()
.expect("Token has no proofs");
TokenV3 { TokenV3 {
token: vec![TokenV3Token::new(mint_url, proofs)], token: vec![TokenV3Token::new(token.mint_url, proofs)],
memo: token.memo, memo: token.memo,
unit: Some(token.unit), unit: Some(token.unit),
} }
@@ -281,28 +300,16 @@ pub struct TokenV4 {
impl TokenV4 { impl TokenV4 {
/// Proofs from token /// Proofs from token
pub fn proofs(&self) -> HashMap<MintUrl, Proofs> { pub fn proofs(&self) -> Proofs {
let mint_url = &self.mint_url; self.token
let mut proofs: HashMap<MintUrl, Proofs> = HashMap::new(); .iter()
.flat_map(|token| token.proofs.iter().map(|p| p.into_proof(&token.keyset_id)))
for token in self.token.clone() { .collect()
let mut mint_proofs = token
.proofs
.iter()
.map(|p| p.into_proof(&token.keyset_id))
.collect();
proofs
.entry(mint_url.clone())
.and_modify(|p| p.append(&mut mint_proofs))
.or_insert(mint_proofs);
}
proofs
} }
/// Value
#[inline] #[inline]
fn value(&self) -> Result<Amount, Error> { pub fn value(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum( Ok(Amount::try_sum(
self.token self.token
.iter() .iter()
@@ -311,13 +318,15 @@ impl TokenV4 {
)?) )?)
} }
/// Memo
#[inline] #[inline]
fn memo(&self) -> &Option<String> { pub fn memo(&self) -> &Option<String> {
&self.memo &self.memo
} }
/// Unit
#[inline] #[inline]
fn unit(&self) -> CurrencyUnit { pub fn unit(&self) -> CurrencyUnit {
self.unit self.unit
} }
} }
@@ -350,13 +359,15 @@ impl TryFrom<TokenV3> for TokenV4 {
type Error = Error; type Error = Error;
fn try_from(token: TokenV3) -> Result<Self, Self::Error> { fn try_from(token: TokenV3) -> Result<Self, Self::Error> {
let proofs = token.proofs(); let proofs = token.proofs();
if proofs.len() != 1 { let mint_urls = token.mint_urls();
if mint_urls.len() != 1 {
return Err(Error::UnsupportedToken); return Err(Error::UnsupportedToken);
} }
let (mint_url, mint_proofs) = proofs.iter().next().expect("No proofs"); let mint_url = mint_urls.first().expect("Len is checked");
let proofs = mint_proofs let proofs = proofs
.iter() .iter()
.fold(HashMap::new(), |mut acc, val| { .fold(HashMap::new(), |mut acc, val| {
acc.entry(val.keyset_id) acc.entry(val.keyset_id)
@@ -369,7 +380,7 @@ impl TryFrom<TokenV3> for TokenV4 {
.collect(); .collect();
Ok(TokenV4 { Ok(TokenV4 {
mint_url: mint_url.to_owned(), mint_url: mint_url.clone(),
token: proofs, token: proofs,
memo: token.memo, memo: token.memo,
unit: token.unit.ok_or(Error::UnsupportedUnit)?, unit: token.unit.ok_or(Error::UnsupportedUnit)?,

View File

@@ -393,87 +393,87 @@ impl Wallet {
"Must set locktime".to_string(), "Must set locktime".to_string(),
)); ));
} }
if token.mint_url()? != self.mint_url {
return Err(Error::IncorrectWallet(format!(
"Should be {} not {}",
self.mint_url,
token.mint_url()?
)));
}
for (mint_url, proofs) in &token.proofs() { let proofs = token.proofs();
if mint_url != &self.mint_url { for proof in proofs {
return Err(Error::IncorrectWallet(format!( let secret: nut10::Secret = (&proof.secret).try_into()?;
"Should be {} not {}",
self.mint_url, mint_url let proof_conditions: SpendingConditions = secret.try_into()?;
)));
if num_sigs.ne(&proof_conditions.num_sigs()) {
tracing::debug!(
"Spending condition requires: {:?} sigs proof secret specifies: {:?}",
num_sigs,
proof_conditions.num_sigs()
);
return Err(Error::P2PKConditionsNotMet(
"Num sigs did not match spending condition".to_string(),
));
} }
for proof in proofs {
let secret: nut10::Secret = (&proof.secret).try_into()?;
let proof_conditions: SpendingConditions = secret.try_into()?; let spending_condition_pubkeys = pubkeys.clone().unwrap_or_default();
let proof_pubkeys = proof_conditions.pubkeys().unwrap_or_default();
if num_sigs.ne(&proof_conditions.num_sigs()) { // Check the Proof has the required pubkeys
tracing::debug!( if proof_pubkeys.len().ne(&spending_condition_pubkeys.len())
"Spending condition requires: {:?} sigs proof secret specifies: {:?}", || !proof_pubkeys
num_sigs, .iter()
proof_conditions.num_sigs() .all(|pubkey| spending_condition_pubkeys.contains(pubkey))
); {
tracing::debug!("Proof did not included Publickeys meeting condition");
tracing::debug!("{:?}", proof_pubkeys);
tracing::debug!("{:?}", spending_condition_pubkeys);
return Err(Error::P2PKConditionsNotMet(
"Pubkeys in proof not allowed by spending condition".to_string(),
));
}
return Err(Error::P2PKConditionsNotMet( // If spending condition refund keys is allowed (Some(Empty Vec))
"Num sigs did not match spending condition".to_string(), // If spending conition refund keys is allowed to restricted set of keys check
)); // it is one of them Check that proof locktime is > condition
} // locktime
let spending_condition_pubkeys = pubkeys.clone().unwrap_or_default(); if let Some(proof_refund_keys) = proof_conditions.refund_keys() {
let proof_pubkeys = proof_conditions.pubkeys().unwrap_or_default(); let proof_locktime = proof_conditions
.locktime()
.ok_or(Error::LocktimeNotProvided)?;
// Check the Proof has the required pubkeys if let (Some(condition_refund_keys), Some(condition_locktime)) =
if proof_pubkeys.len().ne(&spending_condition_pubkeys.len()) (&refund_keys, locktime)
|| !proof_pubkeys
.iter()
.all(|pubkey| spending_condition_pubkeys.contains(pubkey))
{ {
tracing::debug!("Proof did not included Publickeys meeting condition"); // Proof locktime must be greater then condition locktime to ensure it
tracing::debug!("{:?}", proof_pubkeys); // cannot be claimed back
tracing::debug!("{:?}", spending_condition_pubkeys); if proof_locktime.lt(&condition_locktime) {
return Err(Error::P2PKConditionsNotMet(
"Pubkeys in proof not allowed by spending condition".to_string(),
));
}
// If spending condition refund keys is allowed (Some(Empty Vec))
// If spending conition refund keys is allowed to restricted set of keys check
// it is one of them Check that proof locktime is > condition
// locktime
if let Some(proof_refund_keys) = proof_conditions.refund_keys() {
let proof_locktime = proof_conditions
.locktime()
.ok_or(Error::LocktimeNotProvided)?;
if let (Some(condition_refund_keys), Some(condition_locktime)) =
(&refund_keys, locktime)
{
// Proof locktime must be greater then condition locktime to ensure it
// cannot be claimed back
if proof_locktime.lt(&condition_locktime) {
return Err(Error::P2PKConditionsNotMet(
"Proof locktime less then required".to_string(),
));
}
// A non empty condition refund key list is used as a restricted set of keys
// returns are allowed to An empty list means the
// proof can be refunded to anykey set in the secret
if !condition_refund_keys.is_empty()
&& !proof_refund_keys
.iter()
.all(|refund_key| condition_refund_keys.contains(refund_key))
{
return Err(Error::P2PKConditionsNotMet(
"Refund Key not allowed".to_string(),
));
}
} else {
// Spending conditions does not allow refund keys
return Err(Error::P2PKConditionsNotMet( return Err(Error::P2PKConditionsNotMet(
"Spending condition does not allow refund keys".to_string(), "Proof locktime less then required".to_string(),
)); ));
} }
// A non empty condition refund key list is used as a restricted set of keys
// returns are allowed to An empty list means the
// proof can be refunded to anykey set in the secret
if !condition_refund_keys.is_empty()
&& !proof_refund_keys
.iter()
.all(|refund_key| condition_refund_keys.contains(refund_key))
{
return Err(Error::P2PKConditionsNotMet(
"Refund Key not allowed".to_string(),
));
}
} else {
// Spending conditions does not allow refund keys
return Err(Error::P2PKConditionsNotMet(
"Spending condition does not allow refund keys".to_string(),
));
} }
} }
} }
@@ -486,31 +486,32 @@ impl Wallet {
pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> { pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
let mut keys_cache: HashMap<Id, Keys> = HashMap::new(); let mut keys_cache: HashMap<Id, Keys> = HashMap::new();
for (mint_url, proofs) in &token.proofs() { // TODO: Get mint url
if mint_url != &self.mint_url { // if mint_url != &self.mint_url {
return Err(Error::IncorrectWallet(format!( // return Err(Error::IncorrectWallet(format!(
"Should be {} not {}", // "Should be {} not {}",
self.mint_url, mint_url // self.mint_url, mint_url
))); // )));
} // }
for proof in proofs {
let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
Some(keys) => keys.amount_key(proof.amount),
None => {
let keys = self.get_keyset_keys(proof.keyset_id).await?;
let key = keys.amount_key(proof.amount); let proofs = token.proofs();
keys_cache.insert(proof.keyset_id, keys); for proof in proofs {
let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
Some(keys) => keys.amount_key(proof.amount),
None => {
let keys = self.get_keyset_keys(proof.keyset_id).await?;
key let key = keys.amount_key(proof.amount);
} keys_cache.insert(proof.keyset_id, keys);
key
} }
.ok_or(Error::AmountKey)?;
proof
.verify_dleq(mint_pubkey)
.map_err(|_| Error::CouldNotVerifyDleq)?;
} }
.ok_or(Error::AmountKey)?;
proof
.verify_dleq(mint_pubkey)
.map_err(|_| Error::CouldNotVerifyDleq)?;
} }
Ok(()) Ok(())

View File

@@ -237,36 +237,36 @@ impl MultiMintWallet {
let token_data = Token::from_str(encoded_token)?; let token_data = Token::from_str(encoded_token)?;
let unit = token_data.unit().unwrap_or_default(); let unit = token_data.unit().unwrap_or_default();
let mint_proofs = token_data.proofs(); let proofs = token_data.proofs();
let mut amount_received = Amount::ZERO; let mut amount_received = Amount::ZERO;
let mut mint_errors = None; let mut mint_errors = None;
let mint_url = token_data.mint_url()?;
// Check that all mints in tokes have wallets // Check that all mints in tokes have wallets
for (mint_url, proofs) in mint_proofs { let wallet_key = WalletKey::new(mint_url.clone(), unit);
let wallet_key = WalletKey::new(mint_url.clone(), unit); if !self.has(&wallet_key).await {
if !self.has(&wallet_key).await { return Err(Error::UnknownWallet(wallet_key.clone()));
return Err(Error::UnknownWallet(wallet_key.clone())); }
let wallet_key = WalletKey::new(mint_url.clone(), unit);
let wallet = self
.get_wallet(&wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
match wallet
.receive_proofs(proofs, SplitTarget::default(), p2pk_signing_keys, preimages)
.await
{
Ok(amount) => {
amount_received += amount;
} }
Err(err) => {
let wallet_key = WalletKey::new(mint_url.clone(), unit); tracing::error!("Could no receive proofs for mint: {}", err);
let wallet = self mint_errors = Some(err);
.get_wallet(&wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
match wallet
.receive_proofs(proofs, SplitTarget::default(), p2pk_signing_keys, preimages)
.await
{
Ok(amount) => {
amount_received += amount;
}
Err(err) => {
tracing::error!("Could no receive proofs for mint: {}", err);
mint_errors = Some(err);
}
} }
} }

View File

@@ -207,9 +207,7 @@ impl Wallet {
return Err(Error::MultiMintTokenNotSupported); return Err(Error::MultiMintTokenNotSupported);
} }
let (mint_url, proofs) = proofs.into_iter().next().expect("Token has proofs"); if self.mint_url != token_data.mint_url()? {
if self.mint_url != mint_url {
return Err(Error::IncorrectMint); return Err(Error::IncorrectMint);
} }