diff --git a/api/src/foreign.rs b/api/src/foreign.rs index f17dcec..84e52d9 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -17,7 +17,7 @@ use crate::keychain::Keychain; use crate::libwallet::api_impl::foreign; use crate::libwallet::api_impl::types::update_tx_slate_state; -use crate::libwallet::contract::proofs::InvoiceProof; +use crate::libwallet::contract::proofs::EarlyPaymentProof; use crate::libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; use crate::libwallet::{ BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst, @@ -49,8 +49,8 @@ pub enum ForeignCheckMiddlewareFn { ContractSign, /// finalize_tx FinalizeTx, - /// verify_payment_proof_invoice - VerifyPaymentProofInvoice, + /// verify_payment_proof_early + VerifyPaymentProofEarly, } /// Main interface into all wallet API functions. @@ -506,22 +506,22 @@ where foreign::contract_sign(w, (&self.keychain_mask).as_ref(), args, slate) } - /// Verify an invoice payment proof against the chain. - pub fn verify_payment_proof_invoice( + /// Verify an early payment proof against the chain + pub fn verify_payment_proof_early( &self, recipient_address: &DalekPublicKey, - proof: &InvoiceProof, + proof: &EarlyPaymentProof, ) -> Result<(), Error> { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { m( - ForeignCheckMiddlewareFn::VerifyPaymentProofInvoice, + ForeignCheckMiddlewareFn::VerifyPaymentProofEarly, w.w2n_client().get_version_info(), None, )?; } - foreign::verify_payment_proof_invoice(w, recipient_address, proof) + foreign::verify_payment_proof_early(w, recipient_address, proof) } } diff --git a/api/src/owner.rs b/api/src/owner.rs index d8d9db9..b132a6b 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -23,7 +23,7 @@ use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage}; use crate::libwallet::api_impl::types::update_tx_slate_state; use crate::libwallet::api_impl::{owner, owner_updater}; -use crate::libwallet::contract::proofs::InvoiceProof; +use crate::libwallet::contract::proofs::EarlyPaymentProof; use crate::libwallet::contract::types::{ ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, ContractView, }; @@ -2576,15 +2576,14 @@ where ) } - /// Retrieve the invoice payment proof for a stored transaction. - /// FUTURE: likely merge with retrieve_payment_proof above. - pub fn retrieve_payment_proof_invoice( + /// Retrieve an early payment proof for a stored transaction + pub fn retrieve_payment_proof_early( &self, keychain_mask: Option<&SecretKey>, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, - ) -> Result { + ) -> Result { let tx = { let t = self.status_tx.lock(); t.clone() @@ -2593,7 +2592,7 @@ where true => false, false => refresh_from_node, }; - owner::retrieve_payment_proof_invoice( + owner::retrieve_payment_proof_early( self.wallet_inst.clone(), keychain_mask, &tx, diff --git a/controller/tests/contract_early_proofs_rsr.rs b/controller/tests/contract_early_proofs_rsr.rs index b8523fc..c2b1bd1 100644 --- a/controller/tests/contract_early_proofs_rsr.rs +++ b/controller/tests/contract_early_proofs_rsr.rs @@ -24,9 +24,10 @@ extern crate log; use grin_wallet_libwallet as libwallet; +use grin_util::secp::{Secp256k1, Signature}; use impls::test_framework::{self}; use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; +use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI, ProofType}; use libwallet::{Slate, SlateState, Slatepacker, SlatepackerArgs, TxLogEntryType}; use std::sync::atomic::Ordering; use std::thread; @@ -49,7 +50,10 @@ fn roundtrip_slate(slate: &Slate, version: u16) -> Result Result<(), libwallet::Error> { +fn contract_early_proofs_rsr_test_impl( + test_dir: &'static str, + proof_type: ProofType, +) -> Result<(), libwallet::Error> { // create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin) let (wallets, chain, stopper, mut bh) = create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap(); @@ -89,6 +93,7 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib }; // Proofs are opt-in; enable and supply the sender address. args.setup_args.proof_args.suppress_proof = false; + args.setup_args.proof_args.proof_type = proof_type; args.setup_args.proof_args.sender_address = sender_address; slate = api.contract_new(m, args)?; recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key); @@ -201,39 +206,75 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib }, )?; - let mut invoice_proof = None; + let mut early_proof = None; // Now some time has passed, sender retrieves and verify the payment proof wallet::controller::owner_single_use( send_wallet.clone(), send_mask, PathBuf::from(test_dir), |api, _m| { - // Extract the stored data as an invoice proof - invoice_proof = - Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?); + // Extract the stored early payment proof + early_proof = + Some(api.retrieve_payment_proof_early(send_mask, true, None, Some(slate.id))?); Ok(()) }, )?; - let invoice_proof = invoice_proof.unwrap(); - assert_eq!(invoice_proof.amount, 5_000_000_000); - let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap(); + let early_proof = early_proof.unwrap(); + assert_eq!(early_proof.proof_type, proof_type); + assert_eq!(early_proof.amount, 5_000_000_000); + assert_eq!( + early_proof + .witness_data + .as_ref() + .unwrap() + .sender_public_nonce + .is_some(), + proof_type == ProofType::SenderNonce + ); + let early_proof_json = serde_json::to_string(&early_proof).unwrap(); // Should have all proof fields filled out - println!("INVOICE PROOF: {}", invoice_proof_json); + println!("EARLY PAYMENT PROOF: {}", early_proof_json); wallet::controller::foreign_single_use( recv_wallet.clone(), PathBuf::from(test_dir), recv_mask.cloned(), |api| { - let mut proof = serde_json::from_str(&invoice_proof_json).unwrap(); - api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?; + let mut proof = serde_json::from_str(&early_proof_json).unwrap(); + api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &proof)?; + if proof_type == ProofType::SenderNonce { + let mut invalid = proof.clone(); + let nonce = invalid + .receiver_public_nonce + .serialize_vec(&Secp256k1::new(), true); + let sender_sig = &invalid.witness_data.as_ref().unwrap().sender_partial_sig; + let mut raw_sig = [0; 64]; + raw_sig.copy_from_slice(&sender_sig[..]); + raw_sig[..32].copy_from_slice(&nonce[1..]); + invalid.witness_data.as_mut().unwrap().sender_partial_sig = + Signature::from_raw_data(&raw_sig)?; + let err = + api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &invalid); + assert!(matches!( + err, + Err(libwallet::Error::PaymentProofValidation(ref msg)) + if msg == "Sender partial signature nonce does not match the proof" + )); + } // tweak something and it shouldn't verify proof.amount = 400000; - let retval = - api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof); - assert!(retval.is_err()); + let err = api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &proof); + if proof_type == ProofType::SenderNonce { + assert!(matches!( + err, + Err(libwallet::Error::PaymentProofValidation(ref msg)) + if msg == "Sender nonce does not commit to the payment details" + )); + } else { + assert!(err.is_err()); + } Ok(()) }, )?; @@ -247,9 +288,19 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib #[test] fn contract_early_proofs_rsr() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_early_proofs_rsr"; - setup(test_dir); - contract_early_proofs_rsr_test_impl(test_dir)?; - clean_output_dir(test_dir); + for (test_dir, proof_type) in [ + ( + "test_output/contract_early_proofs_rsr_invoice", + ProofType::Invoice, + ), + ( + "test_output/contract_early_proofs_rsr_sender_nonce", + ProofType::SenderNonce, + ), + ] { + setup(test_dir); + contract_early_proofs_rsr_test_impl(test_dir, proof_type)?; + clean_output_dir(test_dir); + } Ok(()) } diff --git a/controller/tests/contract_early_proofs_srs.rs b/controller/tests/contract_early_proofs_srs.rs index efac025..be837ce 100644 --- a/controller/tests/contract_early_proofs_srs.rs +++ b/controller/tests/contract_early_proofs_srs.rs @@ -44,7 +44,7 @@ fn reject_proof_verification( ) -> Result<(), libwallet::Error> { assert!(matches!( method, - grin_wallet_api::ForeignCheckMiddlewareFn::VerifyPaymentProofInvoice + grin_wallet_api::ForeignCheckMiddlewareFn::VerifyPaymentProofEarly )); assert!(slate.is_none()); Err(libwallet::Error::GenericError( @@ -208,26 +208,26 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib }, )?; - let mut invoice_proof = None; + let mut early_proof = None; // Now some time has passed, sender retrieves and verify the payment proof wallet::controller::owner_single_use( send_wallet.clone(), send_mask, PathBuf::from(test_dir), |api, _m| { - // Extract the stored data as an invoice proof - invoice_proof = - Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?); + // Extract the stored early payment proof + early_proof = + Some(api.retrieve_payment_proof_early(send_mask, true, None, Some(slate.id))?); Ok(()) }, )?; - let invoice_proof = invoice_proof.unwrap(); - assert_eq!(invoice_proof.amount, 5_000_000_000); - let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap(); + let early_proof = early_proof.unwrap(); + assert_eq!(early_proof.amount, 5_000_000_000); + let early_proof_json = serde_json::to_string(&early_proof).unwrap(); // Should have all proof fields filled out - println!("INVOICE PROOF: {}", invoice_proof_json); + println!("EARLY PAYMENT PROOF: {}", early_proof_json); { let api = grin_wallet_api::Foreign::new( recv_wallet.clone(), @@ -237,7 +237,7 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib false, ); let err = api - .verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &invoice_proof) + .verify_payment_proof_early(recipient_address.as_ref().unwrap(), &early_proof) .unwrap_err(); assert!(matches!( err, @@ -251,12 +251,12 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib PathBuf::from(test_dir), recv_mask.cloned(), |api| { - let mut proof = serde_json::from_str(&invoice_proof_json).unwrap(); - api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?; + let mut proof = serde_json::from_str(&early_proof_json).unwrap(); + api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &proof)?; // tweak something and it shouldn't verify proof.amount = 400000; let retval = - api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof); + api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &proof); assert!(retval.is_err()); Ok(()) }, diff --git a/doc/contracts.md b/doc/contracts.md index 9771ac8..24ae308 100644 --- a/doc/contracts.md +++ b/doc/contracts.md @@ -79,9 +79,10 @@ account selected with `--account`. * Only one or two participants are supported. Larger contracts remain disabled because of the [known multi-party attack](https://forum.grin.mw/t/grin-wallet-contract-prototype/9745/18) * Early payment proofs are only implemented for contracts and are only available through - the API. Only invoice proofs are supported and they require the experimental Slate V5 - format. The full proposal is described in - [Early Payment Proofs](https://github.com/mimblewimble/grin-rfcs/pull/70) + the API. Invoice proofs support both flows; sender-nonce proofs support RSR. Both require + the experimental Slate V5 format. The full proposal is described in + [Early Payment Proofs](https://github.com/mimblewimble/grin-rfcs/pull/70). Proof data is + stored in the wallet database and cannot be recovered from the wallet seed alone * There are no contract-specific history, lookup or transport commands. Payment proof memos cannot be set through the contract API or CLI. Early proofs support UTF-8 memos up to 1024 bytes and bind their Blake2b hash diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index 7548100..d4f95a5 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -22,7 +22,7 @@ use crate::api_impl::owner::contract_sign as owner_contract_sign; use crate::api_impl::owner::{check_ttl, post_tx}; use crate::api_impl::types::update_tx_slate_state; use crate::backend::WalletBackend; -use crate::contract::proofs::InvoiceProof; +use crate::contract::proofs::EarlyPaymentProof; use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; use crate::grin_core::core::FeeFields; use crate::grin_keychain::Keychain; @@ -291,11 +291,11 @@ where owner_contract_sign(w, keychain_mask, args, slate) } -/// Verify an invoice payment proof -pub fn verify_payment_proof_invoice( +/// Verify an early payment proof +pub fn verify_payment_proof_early( w: &mut WalletBackend, recipient_address: &DalekPublicKey, - proof: &InvoiceProof, + proof: &EarlyPaymentProof, ) -> Result<(), Error> where C: NodeClient, @@ -307,7 +307,7 @@ where Some(w) => w, None => { return Err(Error::PaymentProof( - "Cannot verify invoice proof with no witness data".to_string(), + "Cannot verify early payment proof with no witness data".to_string(), )) } }; diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index d6a3dd0..f1df0a7 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -18,7 +18,7 @@ use std::cmp; use uuid::Uuid; use crate::api_impl::foreign::finalize_tx as foreign_finalize; -use crate::contract::proofs::{InvoiceProof, ProofWitness}; +use crate::contract::proofs::{EarlyPaymentProof, ProofWitness}; use crate::grin_core::core::amount_to_hr_string; use crate::grin_core::core::hash::Hashed; use crate::grin_core::core::{FeeFields, Output, OutputFeatures, Transaction}; @@ -523,16 +523,15 @@ where }) } -/// Retrieve invoice payment proof -/// TODO: Need to unify with legacy above -pub fn retrieve_payment_proof_invoice<'a, L, C, K>( +/// Retrieve an early payment proof +pub fn retrieve_payment_proof_early<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, -) -> Result +) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, @@ -571,32 +570,36 @@ where let tx = txs.1[0].clone(); // Contract tx logs store the agreed net change separately from the fee. let amount = tx.amount_credited.abs_diff(tx.amount_debited); + let sender_public_nonce = tx + .payment_proof + .as_ref() + .and_then(|proof| proof.sender_public_nonce); let (mut proof, sender_part_sig) = match tx.payment_proof { Some(p) => { if p.receiver_public_nonce.is_none() { return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored receiver public nonce".into(), + "Early payment proof requires stored receiver public nonce".into(), )); }; if p.receiver_public_excess.is_none() { return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored receiver public excess".into(), + "Early payment proof requires stored receiver public excess".into(), )); }; if p.timestamp.is_none() { return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored timestamp".into(), + "Early payment proof requires stored timestamp".into(), )); }; if p.sender_part_sig.is_none() { return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored sender partial signature".into(), + "Early payment proof requires stored sender partial signature".into(), )); }; ( - InvoiceProof { + EarlyPaymentProof { proof_type: PaymentProofType::try_from( p.proof_type.unwrap_or(PaymentProofType::Invoice.as_u8()), )?, @@ -630,7 +633,7 @@ where Some(k) => k, None => { return Err(Error::PaymentProofRetrieval(format!( - "Invoice proof transaction kernel excess missing", + "Early payment proof transaction kernel excess missing", ))) } }; @@ -655,6 +658,7 @@ where kernel_index: index, kernel_commitment: retrieved_kernel.excess, sender_partial_sig: sender_part_sig, + sender_public_nonce, }); Ok(proof) diff --git a/libwallet/src/contract/actions/README.md b/libwallet/src/contract/actions/README.md index 40dd09b..3e35530 100644 --- a/libwallet/src/contract/actions/README.md +++ b/libwallet/src/contract/actions/README.md @@ -41,8 +41,6 @@ Separating side effects until the 'save_step' part would make these functions mu races a transaction already in the mempool. - Add a Grin node API for kernel lookup by MMR index, then use the witness index instead of searching by commitment. - - Implement sender-nonce proofs for the RSR flow and expose both early-proof types - through one API. - Move payment-proof creation and verification onto `Slate` so it can also be used outside contracts and is versioned with the slate format. - Keep V5 when decoding Slatepacks through Owner RPC. diff --git a/libwallet/src/contract/actions/setup.rs b/libwallet/src/contract/actions/setup.rs index 421bf03..84a5936 100644 --- a/libwallet/src/contract/actions/setup.rs +++ b/libwallet/src/contract/actions/setup.rs @@ -89,7 +89,9 @@ where contract::utils::verify_setup_args_consistency(context_args, setup_args)?; // Add keys and payment proof to slate (both are idempotent operations) - contract::slate::add_keys(&mut sl, &w.keychain(keychain_mask)?, &mut context)?; + let keychain = w.keychain(keychain_mask)?; + contract::proofs::commit_sender_nonce(&sl, &mut context, keychain.secp())?; + contract::slate::add_keys(&mut sl, &keychain, &mut context)?; contract::slate::add_payment_proof( w, &mut sl, diff --git a/libwallet/src/contract/actions/sign.rs b/libwallet/src/contract/actions/sign.rs index c2bbc38..375a103 100644 --- a/libwallet/src/contract/actions/sign.rs +++ b/libwallet/src/contract/actions/sign.rs @@ -122,7 +122,7 @@ where // Add outputs to the slate, verify the payment proof and sign the slate contract::slate::add_outputs(w, keychain_mask, &mut sl, &context)?; contract::slate::verify_own_commitments(w, keychain_mask, &sl, &context)?; - contract::slate::verify_invoice_promise(&sl, &w.keychain(keychain_mask)?, &context)?; + contract::slate::verify_payment_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/proofs.rs b/libwallet/src/contract/proofs.rs index ed6701f..e138498 100644 --- a/libwallet/src/contract/proofs.rs +++ b/libwallet/src/contract/proofs.rs @@ -29,11 +29,10 @@ use crate::grin_util::secp::pedersen::Commitment; use crate::grin_util::secp::Secp256k1; use crate::grin_util::secp::Signature; use crate::grin_util::static_secp_instance; -use crate::slate::{PaymentInfo, PaymentMemo, PaymentProofType, Slate}; +use crate::slate::{PaymentInfo, PaymentMemo, PaymentProofType, Slate, SlateState}; use crate::slate_versions::ser as dalek_ser; use crate::types::{Context, NodeClient}; use crate::{address, Error}; -use byteorder::{BigEndian, ByteOrder}; use chrono::{DateTime, Utc}; use ed25519_dalek::Signature as DalekSignature; use ed25519_dalek::SigningKey as DalekSecretKey; @@ -43,13 +42,24 @@ use grin_util::secp::Message; pub(super) fn check_proof_type(proof_type: &ProofType) -> Result<(), Error> { match proof_type { - ProofType::Invoice => Ok(()), + ProofType::Invoice | ProofType::SenderNonce => Ok(()), _ => Err(Error::GenericError( - "Only invoice contract proofs are supported".to_string(), + "Unsupported contract proof type".to_string(), )), } } +fn write_amount(writer: &mut W, amount: u64) -> Result<(), grin_ser::Error> { + let amount_bytes = amount.to_be_bytes(); + if amount_bytes[0] != 0 { + return Err(grin_ser::Error::UnexpectedData { + expected: vec![0], + received: vec![amount_bytes[0]], + }); + } + writer.write_fixed_bytes(&amount_bytes[1..]) +} + fn verify_receiver_sig( secp: &Secp256k1, sig: &Signature, @@ -76,6 +86,24 @@ fn verify_receiver_sig( Ok(()) } +fn recover_receiver_sig( + secp: &Secp256k1, + excess_sig: &Signature, + sender_sig: &Signature, + receiver_nonce: &PublicKey, +) -> Result { + let mut scalar = SecretKey::from_slice(secp, &excess_sig[32..])?; + let mut sender_scalar = SecretKey::from_slice(secp, &sender_sig[32..])?; + sender_scalar.neg_assign(secp)?; + scalar.add_assign(secp, &sender_scalar)?; + + let nonce = receiver_nonce.serialize_vec(secp, true); + let mut signature = [0; 64]; + signature[..32].copy_from_slice(&nonce[1..]); + signature[32..].copy_from_slice(&scalar.0); + Signature::from_raw_data(&signature).map_err(Error::from) +} + /// All elements required to validate a proof within a single struct #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct ProofWitness { @@ -93,13 +121,19 @@ pub struct ProofWitness { /// sender partial signature, used to recover receiver partial signature #[serde(with = "secp_ser::sig_serde")] pub sender_partial_sig: Signature, + /// Untweaked sender nonce used by sender-nonce proofs + #[serde( + default, + with = "dalek_ser::option_pubkey_serde", + skip_serializing_if = "Option::is_none" + )] + pub sender_public_nonce: Option, } -/// Payment proof, to be extracted from slates for -/// signing (when wrapped as InvoiceProofBin) or json export from stored tx data +/// Early payment proof extracted from a slate and stored transaction data #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct InvoiceProof { - /// Proof type, 0x00 legacy (though this will use StoredProofInfo above, 1 invoice, 2 Sender nonce) +pub struct EarlyPaymentProof { + /// Proof type #[serde(with = "crate::slate::payment_proof_type_serde")] pub proof_type: PaymentProofType, /// amount @@ -128,23 +162,18 @@ pub struct InvoiceProof { pub witness_data: Option, } -struct InvoiceProofBin<'a>(&'a InvoiceProof); +struct PromiseBin<'a>(&'a EarlyPaymentProof); -impl Writeable for InvoiceProofBin<'_> { +impl Writeable for PromiseBin<'_> { fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { writer.write_u8(self.0.proof_type.as_u8())?; - - // Amount field is 7 bytes, throw error if value is greater - let mut amount_bytes = [0; 8]; - BigEndian::write_u64(&mut amount_bytes, self.0.amount); - - if amount_bytes[0] > 0 { - return Err(grin_ser::Error::UnexpectedData { - expected: [0u8].to_vec(), - received: [amount_bytes[0]].to_vec(), - }); + match self.0.proof_type { + PaymentProofType::Invoice => write_amount(writer, self.0.amount)?, + PaymentProofType::SenderNonce => writer.write_fixed_bytes(&[0; 7])?, + PaymentProofType::Legacy => { + return Err(grin_ser::Error::CorruptedData); + } } - writer.write_fixed_bytes(amount_bytes[1..].to_vec())?; { let static_secp = static_secp_instance(); let static_secp = static_secp.lock(); @@ -160,15 +189,29 @@ impl Writeable for InvoiceProofBin<'_> { )?; } writer.write_fixed_bytes(self.0.sender_address.as_bytes())?; - writer.write_i64(self.0.timestamp)?; - let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or(""); - writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes())?; + if self.0.proof_type == PaymentProofType::Invoice { + writer.write_i64(self.0.timestamp)?; + let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or(""); + writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes())?; + } Ok(()) } } -impl InvoiceProof { - /// Extracts as much data as possible from the slate to create an invoice proof +struct SenderNonceMessage<'a>(&'a EarlyPaymentProof); + +impl Writeable for SenderNonceMessage<'_> { + fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { + writer.write_u8(PaymentProofType::SenderNonce.as_u8())?; + write_amount(writer, self.0.amount)?; + writer.write_i64(self.0.timestamp)?; + let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or(""); + writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes()) + } +} + +impl EarlyPaymentProof { + /// Extract as much proof data as possible from a slate pub fn from_slate( slate: &Slate, participant_index: usize, @@ -232,24 +275,25 @@ impl InvoiceProof { }) } - /// Sign the invoice proof, provided all fields are populated + /// Sign the payment promise pub fn sign(&self, sec_key: &SecretKey) -> Result<(DalekSignature, DalekPublicKey), Error> { let d_skey = DalekSecretKey::from_bytes(&sec_key.0); let pub_key = d_skey.verifying_key(); let mut sig_data_bin = Vec::new(); - grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self)).map_err(|e| { - Error::GenericError(format!("InvoiceProof serialization failed: {}", e)) + grin_ser::serialize_default(&mut sig_data_bin, &PromiseBin(self)).map_err(|e| { + Error::GenericError(format!("Payment proof serialization failed: {}", e)) })?; Ok((d_skey.sign(&sig_data_bin), pub_key)) } - /// Verify the signature of the invoice proof + /// Verify the receiver's promise signature + /// Sender-nonce payment details are only checked by `verify_witness` pub fn verify_promise_signature( &self, recipient_address: &DalekPublicKey, ) -> Result<(), Error> { - self.proof_type.validate(PaymentProofType::Invoice)?; + check_proof_type(&self.proof_type)?; if self.promise_signature.is_none() { return Err(Error::PaymentProofValidation( "Missing promise signature".into(), @@ -258,8 +302,8 @@ impl InvoiceProof { // Rebuild message let mut sig_data_bin = Vec::new(); - grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self)).map_err(|e| { - Error::GenericError(format!("InvoiceProof serialization failed: {}", e)) + grin_ser::serialize_default(&mut sig_data_bin, &PromiseBin(self)).map_err(|e| { + Error::GenericError(format!("Payment proof serialization failed: {}", e)) })?; if recipient_address @@ -292,49 +336,126 @@ impl InvoiceProof { let static_secp = static_secp_instance(); let static_secp = static_secp.lock(); - let receiver_part_sig = - aggsig::subtract_signature(&static_secp, &excess_sig, &wd.sender_partial_sig)?; - // Retrieve the public nonce sum from the kernel excess signature let mut pub_nonce_sum_bytes = [3u8; 33]; pub_nonce_sum_bytes[1..33].copy_from_slice(&excess_sig[0..32]); let pub_nonce_sum = PublicKey::from_slice(&static_secp, &pub_nonce_sum_bytes)?; + let receiver_part_sigs = if self.proof_type == PaymentProofType::SenderNonce { + let sender_nonce = wd.sender_public_nonce.as_ref().ok_or_else(|| { + Error::PaymentProofValidation("Missing sender nonce witness".into()) + })?; + let mut expected_sender_nonce = sender_nonce.clone(); + expected_sender_nonce.add_exp_assign( + &static_secp, + &sender_nonce_tweak(&static_secp, sender_nonce, self)?, + )?; + let expected_sum = PublicKey::from_combination( + &static_secp, + vec![&expected_sender_nonce, &self.receiver_public_nonce], + )?; + if expected_sum.serialize_vec(&static_secp, true)[1..] != excess_sig[0..32] { + return Err(Error::PaymentProofValidation( + "Sender nonce does not commit to the payment details".into(), + )); + } + let sender_nonce = expected_sender_nonce.serialize_vec(&static_secp, true); + if wd.sender_partial_sig[0..32] != sender_nonce[1..] { + return Err(Error::PaymentProofValidation( + "Sender partial signature nonce does not match the proof".into(), + )); + } + vec![recover_receiver_sig( + &static_secp, + excess_sig, + &wd.sender_partial_sig, + &self.receiver_public_nonce, + )?] + } else { + let (signature, alternative) = + aggsig::subtract_signature(&static_secp, excess_sig, &wd.sender_partial_sig)?; + alternative.into_iter().chain(Some(signature)).collect() + }; // Retrieve the public key sum from the kernel excess let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?; - if verify_receiver_sig( - &static_secp, - &receiver_part_sig.0, - &self.receiver_public_nonce, - &pub_nonce_sum, - &self.receiver_public_excess, - &pub_blind_sum, - &msg, - ) - .is_err() - { - // Try other possibility - if let Some(s) = receiver_part_sig.1 { - verify_receiver_sig( - &static_secp, - &s, - &self.receiver_public_nonce, - &pub_nonce_sum, - &self.receiver_public_excess, - &pub_blind_sum, - &msg, - )?; - } else { - return Err(Error::PaymentProofValidation( - "Receiver signature does not match the promise".into(), - )); - } + if !receiver_part_sigs.iter().any(|signature| { + verify_receiver_sig( + &static_secp, + signature, + &self.receiver_public_nonce, + &pub_nonce_sum, + &self.receiver_public_excess, + &pub_blind_sum, + &msg, + ) + .is_ok() + }) { + return Err(Error::PaymentProofValidation( + "Receiver signature does not match the promise".into(), + )); } } Ok(()) } } +fn sender_nonce_tweak( + secp: &Secp256k1, + sender_nonce: &PublicKey, + proof: &EarlyPaymentProof, +) -> Result { + let mut message = Vec::new(); + grin_ser::serialize_default(&mut message, &SenderNonceMessage(proof)).map_err(|e| { + Error::GenericError(format!("Sender nonce message serialization failed: {}", e)) + })?; + let nonce = sender_nonce.serialize_vec(secp, true); + let mut data = Vec::with_capacity(nonce.len() + message.len()); + data.extend_from_slice(&nonce); + data.extend_from_slice(&message); + let hash = blake2b(32, &[], &data); + SecretKey::from_slice(secp, hash.as_bytes()).map_err(Error::from) +} + +/// Commit the payer nonce to the sender-nonce proof fields +pub(super) fn commit_sender_nonce( + slate: &Slate, + context: &mut Context, + secp: &Secp256k1, +) -> Result<(), Error> { + if context.get_net_change()? >= 0 { + return Ok(()); + } + let payment_proof = match slate.payment_proof.as_ref() { + Some(proof) if proof.proof_type == PaymentProofType::SenderNonce => proof, + _ => return Ok(()), + }; + if slate.state != SlateState::Invoice1 || slate.participant_data.len() != 1 { + return Err(Error::PaymentProofValidation( + "Sender nonce proofs require the first RSR signing round".into(), + )); + } + let sender_address = payment_proof + .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(()) +} + /// Adds all info needed for a payment proof to a slate, complete with signed recipient data pub(super) fn add_payment_proof( wallet: &mut WalletBackend, @@ -347,45 +468,51 @@ where C: NodeClient, K: Keychain, { - let (invoice_proof, promise_signature, receiver_address) = - generate_invoice_signature(wallet, keychain_mask, slate, context, proof_args)?; + if proof_args.proof_type == PaymentProofType::SenderNonce && slate.state != SlateState::Invoice1 + { + return Err(Error::PaymentProofValidation( + "Sender nonce proofs are only supported for RSR contracts".into(), + )); + } + let (early_proof, promise_signature, receiver_address) = + generate_promise_signature(wallet, keychain_mask, slate, context, proof_args)?; // 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(|| { + let timestamp = DateTime::from_timestamp(early_proof.timestamp, 0).ok_or_else(|| { Error::GenericError(format!( "Invalid proof timestamp: {}", - invoice_proof.timestamp + early_proof.timestamp )) })?; let proof = PaymentInfo { - proof_type: invoice_proof.proof_type, + proof_type: early_proof.proof_type, sender_address: proof_args.sender_address.clone(), receiver_address, timestamp: Some(timestamp), promise_signature: Some(promise_signature), - memo: invoice_proof.memo, + memo: early_proof.memo, }; slate.payment_proof = Some(proof); Ok(()) } -/// Generates a signature for proof type 'Invoice' -fn generate_invoice_signature( +fn generate_promise_signature( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, context: &Context, proof_args: &ProofArgs, -) -> Result<(InvoiceProof, DalekSignature, DalekPublicKey), Error> +) -> Result<(EarlyPaymentProof, DalekSignature, DalekPublicKey), Error> where C: NodeClient, K: Keychain, { let keychain = wallet.keychain(keychain_mask)?; let index = slate.find_index_matching_context(&keychain, context)?; - let mut invoice_proof = InvoiceProof::from_slate(&slate, index, proof_args.sender_address)?; + let mut early_proof = EarlyPaymentProof::from_slate(&slate, index, proof_args.sender_address)?; + early_proof.proof_type = proof_args.proof_type; let derivation_index = match context.payment_proof_derivation_index { Some(i) => i, None => 0, @@ -395,9 +522,9 @@ where let recp_key = address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?; - invoice_proof.timestamp = Utc::now().timestamp(); - let (sig, addr) = invoice_proof.sign(&recp_key)?; - Ok((invoice_proof, sig, addr)) + early_proof.timestamp = Utc::now().timestamp(); + let (sig, addr) = early_proof.sign(&recp_key)?; + Ok((early_proof, sig, addr)) } #[cfg(test)] @@ -409,7 +536,7 @@ mod tests { fn rejects_unsupported_proofs() { assert!(check_proof_type(&ProofType::Invoice).is_ok()); assert!(check_proof_type(&ProofType::Legacy).is_err()); - assert!(check_proof_type(&ProofType::SenderNonce).is_err()); + assert!(check_proof_type(&ProofType::SenderNonce).is_ok()); } #[test] @@ -462,36 +589,70 @@ mod tests { } #[test] - fn ser_invoice_proof_bin() -> Result<(), Error> { + fn proof_promise() -> Result<(), Error> { + use crate::grin_util::ToHex; + let mut slate = populate_test_slate()?; slate.amount |= 0xFF00_0000_0000_0000; // Bin serialization doesn't include promise sig as it's used to create signature data slate.payment_proof.as_mut().unwrap().promise_signature = None; // Should fail, amount too big - let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?; + let proof = EarlyPaymentProof::from_slate(&slate, 1, None)?; let mut vec = Vec::new(); - assert!(grin_ser::serialize_default(&mut vec, &InvoiceProofBin(&invoice_proof)).is_err()); + assert!(grin_ser::serialize_default(&mut vec, &PromiseBin(&proof)).is_err()); // Should be okay now slate.amount = 1234; - let mut invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?; + slate.payment_proof.as_mut().unwrap().timestamp = DateTime::from_timestamp(123456789, 0); + let mut proof = EarlyPaymentProof::from_slate(&slate, 1, None)?; + let secp = Secp256k1::new(); + proof.receiver_public_nonce = + PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&secp, &[1; 32])?)?; + proof.receiver_public_excess = + PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&secp, &[2; 32])?)?; let mut vec = Vec::new(); - grin_ser::serialize_default(&mut vec, &InvoiceProofBin(&invoice_proof)) - .expect("Serialization Failed"); - let memo = invoice_proof.memo.as_ref().unwrap().as_str(); + grin_ser::serialize_default(&mut vec, &PromiseBin(&proof)).expect("Serialization Failed"); assert_eq!( - &vec[vec.len() - 32..], - blake2b(32, &[], memo.as_bytes()).as_bytes() + vec.to_hex(), + "01000000000004d2031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb00000000075bcd15c60e4f851ed93b6641571e6810f608fe42ee566f7e199915061ea13b3b04e772" ); let proof_key = SecretKey::from_slice(&Secp256k1::new(), &[7; 32])?; - let (signature, recipient) = invoice_proof.sign(&proof_key)?; - invoice_proof.promise_signature = Some(signature); - invoice_proof.verify_promise_signature(&recipient)?; - invoice_proof.memo = Some(PaymentMemo::new("changed details".to_string())?); - assert!(invoice_proof.verify_promise_signature(&recipient).is_err()); + let (signature, recipient) = proof.sign(&proof_key)?; + proof.promise_signature = Some(signature); + proof.verify_promise_signature(&recipient)?; + proof.memo = Some(PaymentMemo::new("changed details".to_string())?); + assert!(proof.verify_promise_signature(&recipient).is_err()); - let mut wrong_type = invoice_proof; + let mut sender_nonce = proof.clone(); + sender_nonce.memo = slate.payment_proof.as_ref().unwrap().memo.clone(); + sender_nonce.proof_type = PaymentProofType::SenderNonce; + let mut promise = Vec::new(); + grin_ser::serialize_default(&mut promise, &PromiseBin(&sender_nonce)) + .expect("Serialization Failed"); + assert_eq!( + promise.to_hex(), + "0200000000000000031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb" + ); + let mut sender_message = Vec::new(); + grin_ser::serialize_default(&mut sender_message, &SenderNonceMessage(&sender_nonce)) + .expect("Serialization Failed"); + assert_eq!( + sender_message.to_hex(), + "02000000000004d200000000075bcd15c60e4f851ed93b6641571e6810f608fe42ee566f7e199915061ea13b3b04e772" + ); + let base = PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&secp, &[9; 32])?)?; + assert_eq!( + sender_nonce_tweak(&secp, &base, &sender_nonce)?.0.to_hex(), + "6bd7c4e86bd61999c26fdd4cc1af90cdcb0a79e39644f4d8045e2a6599bd4db0" + ); + let (signature, recipient) = sender_nonce.sign(&proof_key)?; + sender_nonce.promise_signature = Some(signature); + sender_nonce.amount += 1; + sender_nonce.timestamp += 1; + sender_nonce.verify_promise_signature(&recipient)?; + + let mut wrong_type = proof; wrong_type.proof_type = PaymentProofType::Legacy; assert!(wrong_type .verify_promise_signature(&slate.payment_proof.unwrap().receiver_address) diff --git a/libwallet/src/contract/slate.rs b/libwallet/src/contract/slate.rs index dd306c2..1b5417f 100644 --- a/libwallet/src/contract/slate.rs +++ b/libwallet/src/contract/slate.rs @@ -23,14 +23,14 @@ use crate::grin_util::from_hex; use crate::grin_util::secp::constants::PEDERSEN_COMMITMENT_SIZE; use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::pedersen::Commitment; -use crate::slate::{PaymentProofType, Slate, SlateState}; +use crate::slate::{Slate, SlateState}; use crate::types::{Context, NodeClient, OutputData}; use crate::util::OnionV3Address; use crate::Error; use std::collections::BTreeSet; use super::types::{OwnCommitmentStatus, ProofArgs}; -use crate::contract::proofs::InvoiceProof; +use crate::contract::proofs::EarlyPaymentProof; /// Add payment proof data to slate, noop for sender pub fn add_payment_proof( @@ -60,8 +60,8 @@ where Ok(()) } -/// Verify the receiver's invoice promise before paying -pub fn verify_invoice_promise( +/// Verify the receiver's early payment promise before paying +pub fn verify_payment_promise( slate: &Slate, keychain: &K, context: &Context, @@ -70,7 +70,7 @@ where K: Keychain, { // FUTURE: move proof verification onto Slate itself so it can be versioned (slate.verify_payment_proof_sig()). - debug!("contract::slate::verify_invoice_promise => called"); + debug!("contract::slate::verify_payment_promise => called"); if context.get_net_change()? >= 0 { return Ok(()); } @@ -78,12 +78,10 @@ where Some(proof) => proof, None => return Ok(()), }; - payment_proof - .proof_type - .validate(PaymentProofType::Invoice)?; + super::proofs::check_proof_type(&payment_proof.proof_type)?; if slate.participant_data.len() != 2 { return Err(Error::GenericError(format!( - "Expected 2 participants for an invoice promise, found {}", + "Expected 2 participants for a payment promise, found {}", slate.participant_data.len() ))); } @@ -94,7 +92,7 @@ where .iter() .enumerate() .find_map(|(index, _)| (index != payer_index).then_some(index)) - .ok_or_else(|| Error::GenericError("Invoice promise has no receiver".to_string()))?; + .ok_or_else(|| Error::GenericError("Payment 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, @@ -102,8 +100,8 @@ where 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) + let proof = EarlyPaymentProof::from_slate(slate, receiver_index, Some(sender_address))?; + proof.verify_promise_signature(&payment_proof.receiver_address) } /// Adds inputs and outputs to slate diff --git a/libwallet/src/contract/types.rs b/libwallet/src/contract/types.rs index ad7559b..4ec2e1c 100644 --- a/libwallet/src/contract/types.rs +++ b/libwallet/src/contract/types.rs @@ -93,7 +93,7 @@ impl Default for OutputSelectionArgs { pub struct ProofArgs { /// If net change is positive during this step, whether to suppress the creation of payment proof pub suppress_proof: bool, - /// Requested proof type. Only invoice proofs are currently supported. + /// Requested early payment proof type pub proof_type: ProofType, /// Sender address (required at some stage, may not necessarily be in slate so can be provided explicitly) #[serde(with = "dalek_ser::option_dalek_pubkey_serde")] diff --git a/libwallet/src/contract/utils.rs b/libwallet/src/contract/utils.rs index 9bdfbca..712d089 100644 --- a/libwallet/src/contract/utils.rs +++ b/libwallet/src/contract/utils.rs @@ -20,7 +20,7 @@ use crate::contract::types::ContractSetupArgsAPI; use crate::grin_core::libtx::tx_fee; use crate::grin_keychain::{Identifier, Keychain}; use crate::grin_util::secp::key::SecretKey; -use crate::slate::Slate; +use crate::slate::{PaymentProofType, Slate}; use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType}; use crate::util::OnionV3Address; use crate::{address, Error, OutputData, OutputStatus, TxLogEntry}; @@ -117,6 +117,13 @@ where .participant_data .get(participant_index ^ 1) .ok_or(Error::ContextToIndex)?; + let sender_public_nonce = if p.proof_type == PaymentProofType::SenderNonce { + Some(context.sender_public_nonce.ok_or_else(|| { + Error::PaymentProofValidation("Missing sender nonce context".into()) + })?) + } else { + None + }; tx_log_entry.payment_proof = Some(StoredProofInfo { receiver_address: p.receiver_address, @@ -133,6 +140,7 @@ where memo: p.memo.clone(), promise_signature: p.promise_signature, sender_part_sig: sender.part_sig, + sender_public_nonce, }); } } diff --git a/libwallet/src/slate_versions/ser.rs b/libwallet/src/slate_versions/ser.rs index 7a7a431..55aa381 100644 --- a/libwallet/src/slate_versions/ser.rs +++ b/libwallet/src/slate_versions/ser.rs @@ -259,6 +259,47 @@ pub mod dalek_pubkey_base64 { } } +/// Serializes an Option to and from hex +pub mod option_pubkey_serde { + use crate::grin_util::secp::key::PublicKey; + use crate::grin_util::{from_hex, static_secp_instance, ToHex}; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + + /// + pub fn serialize(key: &Option, serializer: S) -> Result + where + S: Serializer, + { + match key { + Some(key) => { + let secp = static_secp_instance(); + let secp = secp.lock(); + serializer.serialize_str(&key.serialize_vec(&secp, true).to_hex()) + } + None => serializer.serialize_none(), + } + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).and_then(|value| match value { + Some(value) => { + let bytes = from_hex(&value).map_err(Error::custom)?; + let secp = static_secp_instance(); + let secp = secp.lock(); + PublicKey::from_slice(&secp, &bytes) + .map(Some) + .map_err(Error::custom) + } + None => Ok(None), + }) + } +} + /// Serializes an Option to and from hex pub mod option_dalek_pubkey_base64 { use base64; diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 0ec0330..a4ef0a8 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -405,6 +405,8 @@ pub struct Context { pub log_id: Option, /// Contract signing deadline copied from the slate pub contract_ttl_cutoff_height: Option, + /// Untweaked public nonce used for a sender-nonce payment proof + pub sender_public_nonce: Option, } impl Context { @@ -457,6 +459,7 @@ impl Context { setup_args: None, log_id: None, contract_ttl_cutoff_height: None, + sender_public_nonce: None, } } } @@ -786,8 +789,7 @@ pub struct StoredProofInfo { /// sender signature #[serde(with = "dalek_ser::option_dalek_sig_serde")] pub sender_signature: Option, - // Fields beyond here are specific to early payment proofs, - // invoice and sender nonce + // Fields beyond here are specific to early payment proofs /// Assumed to be 0x00 (Legacy) if missing pub proof_type: Option, /// receiver's public nonce from signing @@ -803,6 +805,8 @@ pub struct StoredProofInfo { pub promise_signature: Option, /// Original Sender partial key pub sender_part_sig: Option, + /// Untweaked sender public nonce for a sender-nonce proof + pub sender_public_nonce: Option, } impl StoredProofInfo { @@ -826,6 +830,7 @@ impl StoredProofInfo { memo: None, promise_signature: None, sender_part_sig: None, + sender_public_nonce: None, } } }