diff --git a/libwallet/src/contract/actions/README.md b/libwallet/src/contract/actions/README.md index 57b716a..24b3ba2 100644 --- a/libwallet/src/contract/actions/README.md +++ b/libwallet/src/contract/actions/README.md @@ -44,7 +44,6 @@ without a user confirmation step. - Add a Grin node API for kernel lookup by MMR index, then use the witness index instead of searching by commitment. - Expose the kernel feature constants from `grin_core` and reuse them here. - - Make sender-nonce commitment handling reusable outside contracts - Add compact Slatepack support for contracts - Keep Slate V5 experimental until the related RFCs are accepted - Add contract history, lookup and transport. @@ -52,6 +51,8 @@ without a user confirmation step. - Store transactions in LMDB so they can be committed with the wallet state. - Revisit automatic receive signing once there is a user confirmation step. - Revisit `remove_other_sigdata` before allowing more than two participants. + - Check all preserved slate fields in the round-trip tests + - Test a full MWixnet round with a received contract output ### Side-effects diff --git a/libwallet/src/contract/proofs.rs b/libwallet/src/contract/proofs.rs index 05faf4a..c5f8f6f 100644 --- a/libwallet/src/contract/proofs.rs +++ b/libwallet/src/contract/proofs.rs @@ -17,9 +17,9 @@ use crate::backend::WalletBackend; use crate::contract::types::ProofArgs; use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::{PublicKey, SecretKey}; +use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::Secp256k1; -use crate::payment_proof::{sender_nonce_tweak, EarlyPaymentProof}; +use crate::payment_proof::EarlyPaymentProof; use crate::slate::{PaymentProofType, Slate}; use crate::types::{Context, NodeClient}; use crate::{address, Error}; @@ -46,22 +46,11 @@ pub(super) fn commit_sender_nonce( .sender_address .ok_or(Error::NoSenderAddressProvided)?; let proof = EarlyPaymentProof::from_slate(slate, 0, Some(sender_address))?; - if let Some(base_public) = context.sender_public_nonce.as_ref() { - let mut expected = base_public.clone(); - expected.add_exp_assign(secp, &sender_nonce_tweak(secp, base_public, &proof)?)?; - if PublicKey::from_secret_key(secp, &context.sec_nonce)? != expected { - return Err(Error::PaymentProofValidation( - "Sender nonce proof details changed".into(), - )); - } - } else { - let base_public = PublicKey::from_secret_key(secp, &context.sec_nonce)?; - context - .sec_nonce - .add_assign(secp, &sender_nonce_tweak(secp, &base_public, &proof)?)?; - context.sender_public_nonce = Some(base_public); - } - Ok(()) + proof.commit_sender_nonce( + secp, + &mut context.sec_nonce, + &mut context.sender_public_nonce, + ) } /// Add the receiver's proof data to a contract slate. diff --git a/libwallet/src/payment_proof.rs b/libwallet/src/payment_proof.rs index c0a2522..2e3f6b1 100644 --- a/libwallet/src/payment_proof.rs +++ b/libwallet/src/payment_proof.rs @@ -279,6 +279,37 @@ impl EarlyPaymentProof { Ok(proof) } + /// Commit the sender nonce before sharing its public value + /// Save the base nonce on the first call and verify it on retries + pub fn commit_sender_nonce( + &self, + secp: &Secp256k1, + secret_nonce: &mut SecretKey, + base_public_nonce: &mut Option, + ) -> Result<(), Error> { + if self.proof_type != PaymentProofType::SenderNonce { + return Err(Error::PaymentProofValidation( + "Sender nonce commitment requires a sender-nonce proof".into(), + )); + } + if let Some(base_public) = base_public_nonce.as_ref() { + let mut expected = base_public.clone(); + expected.add_exp_assign(secp, &sender_nonce_tweak(secp, base_public, self)?)?; + if PublicKey::from_secret_key(secp, secret_nonce)? != expected { + return Err(Error::PaymentProofValidation( + "Sender nonce proof details changed".into(), + )); + } + } else { + let base_public = PublicKey::from_secret_key(secp, secret_nonce)?; + let mut committed_nonce = secret_nonce.clone(); + committed_nonce.add_assign(secp, &sender_nonce_tweak(secp, &base_public, self)?)?; + *secret_nonce = committed_nonce; + *base_public_nonce = Some(base_public); + } + Ok(()) + } + /// Sign the payment promise pub fn sign(&self, sec_key: &SecretKey) -> Result<(DalekSignature, DalekPublicKey), Error> { let d_skey = DalekSecretKey::from_bytes(&sec_key.0); @@ -403,7 +434,7 @@ impl EarlyPaymentProof { } } -pub(crate) fn sender_nonce_tweak( +fn sender_nonce_tweak( secp: &Secp256k1, sender_nonce: &PublicKey, proof: &EarlyPaymentProof, @@ -644,6 +675,21 @@ mod tests { sender_nonce_tweak(&secp, &base, &sender_nonce)?.0.to_hex(), "6bd7c4e86bd61999c26fdd4cc1af90cdcb0a79e39644f4d8045e2a6599bd4db0" ); + let mut secret_nonce = SecretKey::from_slice(&secp, &[9; 32])?; + let mut public_nonce = None; + assert!(proof + .commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce) + .is_err()); + sender_nonce.commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce)?; + assert_eq!(public_nonce, Some(base)); + let committed_nonce = secret_nonce.clone(); + sender_nonce.commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce)?; + assert_eq!(secret_nonce, committed_nonce); + let mut changed = sender_nonce.clone(); + changed.memo = Some(PaymentMemo::new("changed details".into())?); + assert!(changed + .commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce) + .is_err()); let (signature, recipient) = sender_nonce.sign(&proof_key)?; sender_nonce.promise_signature = Some(signature); sender_nonce.amount += 1;