diff --git a/controller/tests/contract_early_proofs_srs.rs b/controller/tests/contract_early_proofs_srs.rs index 37faf0d..8460032 100644 --- a/controller/tests/contract_early_proofs_srs.rs +++ b/controller/tests/contract_early_proofs_srs.rs @@ -101,6 +101,10 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib let args = &ContractSetupArgsAPI { ..Default::default() }; + let mut tampered = slate.clone(); + let proof = tampered.payment_proof.as_mut().unwrap(); + proof.timestamp += Duration::from_secs(1); + assert!(api.contract_sign(m, &tampered, args).is_err()); slate = api.contract_sign(m, &slate, args)?; Ok(()) diff --git a/libwallet/src/contract/actions/README.md b/libwallet/src/contract/actions/README.md index 5c29dd2..5df88f3 100644 --- a/libwallet/src/contract/actions/README.md +++ b/libwallet/src/contract/actions/README.md @@ -59,7 +59,6 @@ Separating side effects until the 'save_step' part would make these functions mu - Confirm that invoice-proof retrieval excludes the transaction fee from the amount. - Move payment-proof creation and verification onto `Slate` so it is versioned with the slate format. - - Verify an invoice promise when paying and derive the receiver from the participants. - Bind and validate the contract proof type in the serialized proof. - Keep V5 when decoding Slatepacks through Owner RPC. - Preserve kernel features and their arguments across V4 and V5; see #793. diff --git a/libwallet/src/contract/actions/sign.rs b/libwallet/src/contract/actions/sign.rs index 5d90654..17d2c9c 100644 --- a/libwallet/src/contract/actions/sign.rs +++ b/libwallet/src/contract/actions/sign.rs @@ -79,10 +79,7 @@ where let (mut sl, mut context) = setup::compute(w, keychain_mask, &mut sl, &setup_args)?; // Add outputs to the slate, verify the payment proof and sign the slate contract::slate::add_outputs(w, keychain_mask, &mut sl, &context)?; - if let Some(ref p) = sl.payment_proof { - contract::slate::verify_payment_proof(&sl, expected_net_change, &p.receiver_address)?; - // noop for the receiver - } + contract::slate::verify_invoice_promise(&sl, &w.keychain(keychain_mask)?, &context)?; contract::slate::sign(w, keychain_mask, &mut sl, &mut context)?; contract::slate::transition_state(&mut sl)?; diff --git a/libwallet/src/contract/slate.rs b/libwallet/src/contract/slate.rs index b734fce..9ed23cc 100644 --- a/libwallet/src/contract/slate.rs +++ b/libwallet/src/contract/slate.rs @@ -21,11 +21,11 @@ use crate::grin_keychain::Keychain; use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::slate::{Slate, SlateState}; use crate::types::{Context, NodeClient}; +use crate::util::OnionV3Address; use crate::Error; use super::types::ProofArgs; use crate::contract::proofs::InvoiceProof; -use ed25519_dalek::VerifyingKey as DalekPublicKey; /// Add payment proof data to slate, noop for sender pub fn add_payment_proof( @@ -55,19 +55,47 @@ where Ok(()) } -/// Verify payment proof signature -pub fn verify_payment_proof( +/// Verify the receiver's invoice promise before paying +pub fn verify_invoice_promise( slate: &Slate, - net_change: i64, - recipient_address: &DalekPublicKey, -) -> Result<(), Error> { + keychain: &K, + context: &Context, +) -> Result<(), Error> +where + K: Keychain, +{ // FUTURE: move proof verification onto Slate itself so it can be versioned (slate.verify_payment_proof_sig()). - debug!("contract::slate::verify_payment_proof => called"); - if net_change > 0 && slate.payment_proof.is_some() { - let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?; - invoice_proof.verify_promise_signature(&recipient_address)?; + debug!("contract::slate::verify_invoice_promise => called"); + if context.get_net_change()? >= 0 { + return Ok(()); } - Ok(()) + let payment_proof = match slate.payment_proof.as_ref() { + Some(proof) => proof, + None => return Ok(()), + }; + if slate.participant_data.len() != 2 { + return Err(Error::GenericError(format!( + "Expected 2 participants for an invoice promise, found {}", + slate.participant_data.len() + ))); + } + + let payer_index = slate.find_index_matching_context(keychain, context)?; + let receiver_index = slate + .participant_data + .iter() + .enumerate() + .find_map(|(index, _)| (index != payer_index).then_some(index)) + .ok_or_else(|| Error::GenericError("Invoice promise has no receiver".to_string()))?; + let derivation_index = context.payment_proof_derivation_index.unwrap_or(0); + let sender_key = crate::address::address_from_derivation_path( + keychain, + &context.parent_key_id, + derivation_index, + )?; + let sender_address = OnionV3Address::from_private(&sender_key.0)?.to_ed25519()?; + let invoice_proof = InvoiceProof::from_slate(slate, receiver_index, Some(sender_address))?; + invoice_proof.verify_promise_signature(&payment_proof.receiver_address) } /// Adds inputs and outputs to slate