refactor: Ensure unique proofs when calculating token value

This commit is contained in:
thesimplekid (aider)
2025-04-10 23:02:43 +01:00
committed by thesimplekid
parent dafdf757af
commit 3b5c8b5c5e
2 changed files with 80 additions and 14 deletions

View File

@@ -150,6 +150,9 @@ pub enum Error {
/// Unsupported token
#[error("Unsupported payment method")]
UnsupportedPaymentMethod,
/// Duplicate proofs in token
#[error("Duplicate proofs in token")]
DuplicateProofs,
/// Serde Json error
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),

View File

@@ -233,15 +233,21 @@ impl TokenV3 {
.collect()
}
/// Value
/// Value - errors if duplicate proofs are found
#[inline]
pub fn value(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.token
.iter()
.map(|t| t.proofs.total_amount())
.collect::<Result<Vec<Amount>, _>>()?,
)?)
let proofs = self.proofs();
let unique_count = proofs
.iter()
.collect::<std::collections::HashSet<_>>()
.len();
// Check if there are any duplicate proofs
if unique_count != proofs.len() {
return Err(Error::DuplicateProofs);
}
proofs.total_amount()
}
/// Memo
@@ -336,15 +342,21 @@ impl TokenV4 {
.collect()
}
/// Value
/// Value - errors if duplicate proofs are found
#[inline]
pub fn value(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.token
.iter()
.map(|t| Amount::try_sum(t.proofs.iter().map(|p| p.amount)))
.collect::<Result<Vec<Amount>, _>>()?,
)?)
let proofs = self.proofs();
let unique_count = proofs
.iter()
.collect::<std::collections::HashSet<_>>()
.len();
// Check if there are any duplicate proofs
if unique_count != proofs.len() {
return Err(Error::DuplicateProofs);
}
proofs.total_amount()
}
/// Memo
@@ -483,6 +495,7 @@ mod tests {
use super::*;
use crate::mint_url::MintUrl;
use crate::secret::Secret;
use crate::util::hex;
#[test]
@@ -621,4 +634,54 @@ mod tests {
let tokenv4_bytes_ = tokenv4_.to_raw_bytes().expect("Serialization error");
assert!(tokenv4_bytes_ == tokenv4_bytes);
}
#[test]
fn test_token_with_duplicate_proofs() {
// Create a token with duplicate proofs
let mint_url = MintUrl::from_str("https://example.com").unwrap();
let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
let secret = Secret::generate();
// Create two identical proofs
let proof1 = Proof {
amount: Amount::from(10),
keyset_id,
secret: secret.clone(),
c: "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"
.parse()
.unwrap(),
witness: None,
dleq: None,
};
let proof2 = proof1.clone(); // Duplicate proof
// Create a token with the duplicate proofs
let proofs = vec![proof1.clone(), proof2].into_iter().collect();
let token = Token::new(mint_url.clone(), proofs, None, CurrencyUnit::Sat);
// Verify that value() returns an error
let result = token.value();
assert!(result.is_err());
// Create a token with unique proofs
let proof3 = Proof {
amount: Amount::from(10),
keyset_id,
secret: Secret::generate(),
c: "03bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"
.parse()
.unwrap(), // Different C value
witness: None,
dleq: None,
};
let proofs = vec![proof1, proof3].into_iter().collect();
let token = Token::new(mint_url, proofs, None, CurrencyUnit::Sat);
// Verify that value() succeeds with unique proofs
let result = token.value();
assert!(result.is_ok());
assert_eq!(result.unwrap(), Amount::from(20));
}
}