mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
add sender nonce proofs
This commit is contained in:
+8
-8
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -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<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<InvoiceProof, Error> {
|
||||
) -> Result<EarlyPaymentProof, Error> {
|
||||
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,
|
||||
|
||||
@@ -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<Slate, libwallet::Erro
|
||||
}
|
||||
|
||||
/// Development + Tests of early payment proof functionality - RSR workflow
|
||||
fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> 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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
},
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
@@ -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<C, K>(
|
||||
/// Verify an early payment proof
|
||||
pub fn verify_payment_proof_early<C, K>(
|
||||
w: &mut WalletBackend<C, K>,
|
||||
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(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<InvoiceProof, Error>
|
||||
) -> Result<EarlyPaymentProof, Error>
|
||||
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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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<W: Writer>(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<Signature, Error> {
|
||||
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<PublicKey>,
|
||||
}
|
||||
|
||||
/// 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<ProofWitness>,
|
||||
}
|
||||
|
||||
struct InvoiceProofBin<'a>(&'a InvoiceProof);
|
||||
struct PromiseBin<'a>(&'a EarlyPaymentProof);
|
||||
|
||||
impl Writeable for InvoiceProofBin<'_> {
|
||||
impl Writeable for PromiseBin<'_> {
|
||||
fn write<W: Writer>(&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<W: Writer>(&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<SecretKey, Error> {
|
||||
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<C, K>(
|
||||
wallet: &mut WalletBackend<C, K>,
|
||||
@@ -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<C, K>(
|
||||
fn generate_promise_signature<C, K>(
|
||||
wallet: &mut WalletBackend<C, K>,
|
||||
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)
|
||||
|
||||
@@ -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<C, K>(
|
||||
@@ -60,8 +60,8 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify the receiver's invoice promise before paying
|
||||
pub fn verify_invoice_promise<K>(
|
||||
/// Verify the receiver's early payment promise before paying
|
||||
pub fn verify_payment_promise<K>(
|
||||
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
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,47 @@ pub mod dalek_pubkey_base64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an Option<secp::PublicKey> 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<S>(key: &Option<PublicKey>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<Option<PublicKey>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Option::<String>::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<ed25519_dalek::PublicKey> to and from hex
|
||||
pub mod option_dalek_pubkey_base64 {
|
||||
use base64;
|
||||
|
||||
@@ -405,6 +405,8 @@ pub struct Context {
|
||||
pub log_id: Option<u32>,
|
||||
/// Contract signing deadline copied from the slate
|
||||
pub contract_ttl_cutoff_height: Option<u64>,
|
||||
/// Untweaked public nonce used for a sender-nonce payment proof
|
||||
pub sender_public_nonce: Option<PublicKey>,
|
||||
}
|
||||
|
||||
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<DalekSignature>,
|
||||
// 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<u8>,
|
||||
/// receiver's public nonce from signing
|
||||
@@ -803,6 +805,8 @@ pub struct StoredProofInfo {
|
||||
pub promise_signature: Option<DalekSignature>,
|
||||
/// Original Sender partial key
|
||||
pub sender_part_sig: Option<Signature>,
|
||||
/// Untweaked sender public nonce for a sender-nonce proof
|
||||
pub sender_public_nonce: Option<PublicKey>,
|
||||
}
|
||||
|
||||
impl StoredProofInfo {
|
||||
@@ -826,6 +830,7 @@ impl StoredProofInfo {
|
||||
memo: None,
|
||||
promise_signature: None,
|
||||
sender_part_sig: None,
|
||||
sender_public_nonce: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user