fix(ecash): change proofs no longer ride along inside sent tokens — double-credit bug
send_token_at split the mint swap's results with partition(send_denoms.contains(amount)) — membership, not count. When a change denomination equaled a send denomination (guaranteed when spending from a proof worth exactly 2x the amount: send [n], change [n]), the change proofs were serialized INTO the token, and the receiver redeemed double. Both the wallet send flow and peer-files purchases mint through this path. The split now consumes exactly one proof per required send denomination (everything else returns to the wallet as change) and hard-fails if the mint returns an incomplete denomination set, so a token can never again be silently over- or under-packed. Note: tokens minted BEFORE this fix already contain the extra proofs and will still redeem at their inflated value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
df88d6d00a
commit
98e15b3af0
@ -632,11 +632,30 @@ pub async fn send_token_at(data_dir: &Path, mint_url: &str, amount_sats: u64) ->
|
||||
// Mark original proofs as spent
|
||||
wallet.mark_spent(&indices);
|
||||
|
||||
// Separate send proofs from change proofs
|
||||
let (send, change): (Vec<_>, Vec<_>) = swap_result
|
||||
.new_proofs
|
||||
.into_iter()
|
||||
.partition(|p| send_denoms.contains(&p.amount));
|
||||
// Separate send proofs from change proofs BY COUNT, not membership:
|
||||
// partition(contains) put EVERY proof whose denomination appeared in
|
||||
// send_denoms into the token — when change shared a denomination with
|
||||
// the send (worst case: spending from a proof worth exactly 2× the
|
||||
// amount, send [n] + change [n]), the change proofs rode along and the
|
||||
// receiver was credited double. Consume exactly one proof per needed
|
||||
// send denomination; everything else is change.
|
||||
let mut send_needed = send_denoms.clone();
|
||||
let mut send: Vec<Proof> = Vec::new();
|
||||
let mut change: Vec<Proof> = Vec::new();
|
||||
for p in swap_result.new_proofs {
|
||||
if let Some(pos) = send_needed.iter().position(|&d| d == p.amount) {
|
||||
send_needed.swap_remove(pos);
|
||||
send.push(p);
|
||||
} else {
|
||||
change.push(p);
|
||||
}
|
||||
}
|
||||
if !send_needed.is_empty() {
|
||||
anyhow::bail!(
|
||||
"Mint swap returned incomplete send denominations (missing {:?})",
|
||||
send_needed
|
||||
);
|
||||
}
|
||||
|
||||
// Add change proofs back to wallet
|
||||
if !change.is_empty() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user