From 43d14ebbf9a3f87b740f990a30cf8f354d31a852 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 27 Jul 2026 08:12:25 -0400 Subject: [PATCH] Contracts: sign and store the same payment proof timestamp add_payment_proof read the clock again after generate_invoice_signature had already signed over its own reading. The promise signature binds the timestamp, so a tick between the two reads produced a proof that failed verification with 'Invalid recipient signature'. Carry over the timestamp that was signed. --- libwallet/src/contract/proofs.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libwallet/src/contract/proofs.rs b/libwallet/src/contract/proofs.rs index 11beb74..a60cdd1 100644 --- a/libwallet/src/contract/proofs.rs +++ b/libwallet/src/contract/proofs.rs @@ -378,8 +378,15 @@ where // work tied to the early-payment-proof RFC. let (invoice_proof, promise_signature, receiver_address) = generate_invoice_signature(wallet, keychain_mask, slate, context, proof_args)?; - // Whole seconds only; the sub-second component is not serialized. - let timestamp = DateTime::from_timestamp(Utc::now().timestamp(), 0).unwrap(); + // Carry over the timestamp the promise signature was made over rather than reading + // the clock a second time. The signature binds it, so a tick between the two reads + // would leave a proof that cannot verify. + let timestamp = DateTime::from_timestamp(invoice_proof.timestamp, 0).ok_or_else(|| { + Error::GenericError(format!( + "Invalid proof timestamp: {}", + invoice_proof.timestamp + )) + })?; let proof = PaymentInfo { sender_address: proof_args.sender_address.clone(),