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::keychain::Keychain;
|
||||||
use crate::libwallet::api_impl::foreign;
|
use crate::libwallet::api_impl::foreign;
|
||||||
use crate::libwallet::api_impl::types::update_tx_slate_state;
|
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::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst,
|
BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst,
|
||||||
@@ -49,8 +49,8 @@ pub enum ForeignCheckMiddlewareFn {
|
|||||||
ContractSign,
|
ContractSign,
|
||||||
/// finalize_tx
|
/// finalize_tx
|
||||||
FinalizeTx,
|
FinalizeTx,
|
||||||
/// verify_payment_proof_invoice
|
/// verify_payment_proof_early
|
||||||
VerifyPaymentProofInvoice,
|
VerifyPaymentProofEarly,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main interface into all wallet API functions.
|
/// Main interface into all wallet API functions.
|
||||||
@@ -506,22 +506,22 @@ where
|
|||||||
foreign::contract_sign(w, (&self.keychain_mask).as_ref(), args, slate)
|
foreign::contract_sign(w, (&self.keychain_mask).as_ref(), args, slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify an invoice payment proof against the chain.
|
/// Verify an early payment proof against the chain
|
||||||
pub fn verify_payment_proof_invoice(
|
pub fn verify_payment_proof_early(
|
||||||
&self,
|
&self,
|
||||||
recipient_address: &DalekPublicKey,
|
recipient_address: &DalekPublicKey,
|
||||||
proof: &InvoiceProof,
|
proof: &EarlyPaymentProof,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut w_lock = self.wallet_inst.lock();
|
let mut w_lock = self.wallet_inst.lock();
|
||||||
let w = w_lock.lc_provider()?.wallet_inst()?;
|
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||||
if let Some(m) = self.middleware.as_ref() {
|
if let Some(m) = self.middleware.as_ref() {
|
||||||
m(
|
m(
|
||||||
ForeignCheckMiddlewareFn::VerifyPaymentProofInvoice,
|
ForeignCheckMiddlewareFn::VerifyPaymentProofEarly,
|
||||||
w.w2n_client().get_version_info(),
|
w.w2n_client().get_version_info(),
|
||||||
None,
|
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::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||||
use crate::libwallet::api_impl::types::update_tx_slate_state;
|
use crate::libwallet::api_impl::types::update_tx_slate_state;
|
||||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
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::{
|
use crate::libwallet::contract::types::{
|
||||||
ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, ContractView,
|
ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, ContractView,
|
||||||
};
|
};
|
||||||
@@ -2576,15 +2576,14 @@ where
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the invoice payment proof for a stored transaction.
|
/// Retrieve an early payment proof for a stored transaction
|
||||||
/// FUTURE: likely merge with retrieve_payment_proof above.
|
pub fn retrieve_payment_proof_early(
|
||||||
pub fn retrieve_payment_proof_invoice(
|
|
||||||
&self,
|
&self,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
refresh_from_node: bool,
|
refresh_from_node: bool,
|
||||||
tx_id: Option<u32>,
|
tx_id: Option<u32>,
|
||||||
tx_slate_id: Option<Uuid>,
|
tx_slate_id: Option<Uuid>,
|
||||||
) -> Result<InvoiceProof, Error> {
|
) -> Result<EarlyPaymentProof, Error> {
|
||||||
let tx = {
|
let tx = {
|
||||||
let t = self.status_tx.lock();
|
let t = self.status_tx.lock();
|
||||||
t.clone()
|
t.clone()
|
||||||
@@ -2593,7 +2592,7 @@ where
|
|||||||
true => false,
|
true => false,
|
||||||
false => refresh_from_node,
|
false => refresh_from_node,
|
||||||
};
|
};
|
||||||
owner::retrieve_payment_proof_invoice(
|
owner::retrieve_payment_proof_early(
|
||||||
self.wallet_inst.clone(),
|
self.wallet_inst.clone(),
|
||||||
keychain_mask,
|
keychain_mask,
|
||||||
&tx,
|
&tx,
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ extern crate log;
|
|||||||
|
|
||||||
use grin_wallet_libwallet as libwallet;
|
use grin_wallet_libwallet as libwallet;
|
||||||
|
|
||||||
|
use grin_util::secp::{Secp256k1, Signature};
|
||||||
use impls::test_framework::{self};
|
use impls::test_framework::{self};
|
||||||
use libwallet::contract::my_fee_contribution;
|
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 libwallet::{Slate, SlateState, Slatepacker, SlatepackerArgs, TxLogEntryType};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::thread;
|
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
|
/// 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)
|
// 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) =
|
let (wallets, chain, stopper, mut bh) =
|
||||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
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.
|
// Proofs are opt-in; enable and supply the sender address.
|
||||||
args.setup_args.proof_args.suppress_proof = false;
|
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;
|
args.setup_args.proof_args.sender_address = sender_address;
|
||||||
slate = api.contract_new(m, args)?;
|
slate = api.contract_new(m, args)?;
|
||||||
recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key);
|
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
|
// Now some time has passed, sender retrieves and verify the payment proof
|
||||||
wallet::controller::owner_single_use(
|
wallet::controller::owner_single_use(
|
||||||
send_wallet.clone(),
|
send_wallet.clone(),
|
||||||
send_mask,
|
send_mask,
|
||||||
PathBuf::from(test_dir),
|
PathBuf::from(test_dir),
|
||||||
|api, _m| {
|
|api, _m| {
|
||||||
// Extract the stored data as an invoice proof
|
// Extract the stored early payment proof
|
||||||
invoice_proof =
|
early_proof =
|
||||||
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
|
Some(api.retrieve_payment_proof_early(send_mask, true, None, Some(slate.id))?);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let invoice_proof = invoice_proof.unwrap();
|
let early_proof = early_proof.unwrap();
|
||||||
assert_eq!(invoice_proof.amount, 5_000_000_000);
|
assert_eq!(early_proof.proof_type, proof_type);
|
||||||
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
|
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
|
// 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(
|
wallet::controller::foreign_single_use(
|
||||||
recv_wallet.clone(),
|
recv_wallet.clone(),
|
||||||
PathBuf::from(test_dir),
|
PathBuf::from(test_dir),
|
||||||
recv_mask.cloned(),
|
recv_mask.cloned(),
|
||||||
|api| {
|
|api| {
|
||||||
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
|
let mut proof = serde_json::from_str(&early_proof_json).unwrap();
|
||||||
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?;
|
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
|
// tweak something and it shouldn't verify
|
||||||
proof.amount = 400000;
|
proof.amount = 400000;
|
||||||
let retval =
|
let err = api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &proof);
|
||||||
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof);
|
if proof_type == ProofType::SenderNonce {
|
||||||
assert!(retval.is_err());
|
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(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@@ -247,9 +288,19 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn contract_early_proofs_rsr() -> Result<(), libwallet::Error> {
|
fn contract_early_proofs_rsr() -> Result<(), libwallet::Error> {
|
||||||
let test_dir = "test_output/contract_early_proofs_rsr";
|
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);
|
setup(test_dir);
|
||||||
contract_early_proofs_rsr_test_impl(test_dir)?;
|
contract_early_proofs_rsr_test_impl(test_dir, proof_type)?;
|
||||||
clean_output_dir(test_dir);
|
clean_output_dir(test_dir);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fn reject_proof_verification(
|
|||||||
) -> Result<(), libwallet::Error> {
|
) -> Result<(), libwallet::Error> {
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
method,
|
method,
|
||||||
grin_wallet_api::ForeignCheckMiddlewareFn::VerifyPaymentProofInvoice
|
grin_wallet_api::ForeignCheckMiddlewareFn::VerifyPaymentProofEarly
|
||||||
));
|
));
|
||||||
assert!(slate.is_none());
|
assert!(slate.is_none());
|
||||||
Err(libwallet::Error::GenericError(
|
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
|
// Now some time has passed, sender retrieves and verify the payment proof
|
||||||
wallet::controller::owner_single_use(
|
wallet::controller::owner_single_use(
|
||||||
send_wallet.clone(),
|
send_wallet.clone(),
|
||||||
send_mask,
|
send_mask,
|
||||||
PathBuf::from(test_dir),
|
PathBuf::from(test_dir),
|
||||||
|api, _m| {
|
|api, _m| {
|
||||||
// Extract the stored data as an invoice proof
|
// Extract the stored early payment proof
|
||||||
invoice_proof =
|
early_proof =
|
||||||
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
|
Some(api.retrieve_payment_proof_early(send_mask, true, None, Some(slate.id))?);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let invoice_proof = invoice_proof.unwrap();
|
let early_proof = early_proof.unwrap();
|
||||||
assert_eq!(invoice_proof.amount, 5_000_000_000);
|
assert_eq!(early_proof.amount, 5_000_000_000);
|
||||||
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
|
let early_proof_json = serde_json::to_string(&early_proof).unwrap();
|
||||||
|
|
||||||
// Should have all proof fields filled out
|
// 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(
|
let api = grin_wallet_api::Foreign::new(
|
||||||
recv_wallet.clone(),
|
recv_wallet.clone(),
|
||||||
@@ -237,7 +237,7 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
let err = api
|
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();
|
.unwrap_err();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
err,
|
err,
|
||||||
@@ -251,12 +251,12 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib
|
|||||||
PathBuf::from(test_dir),
|
PathBuf::from(test_dir),
|
||||||
recv_mask.cloned(),
|
recv_mask.cloned(),
|
||||||
|api| {
|
|api| {
|
||||||
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
|
let mut proof = serde_json::from_str(&early_proof_json).unwrap();
|
||||||
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?;
|
api.verify_payment_proof_early(recipient_address.as_ref().unwrap(), &proof)?;
|
||||||
// tweak something and it shouldn't verify
|
// tweak something and it shouldn't verify
|
||||||
proof.amount = 400000;
|
proof.amount = 400000;
|
||||||
let retval =
|
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());
|
assert!(retval.is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-3
@@ -79,9 +79,10 @@ account selected with `--account`.
|
|||||||
* Only one or two participants are supported. Larger contracts remain disabled because of
|
* 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)
|
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
|
* 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
|
the API. Invoice proofs support both flows; sender-nonce proofs support RSR. Both require
|
||||||
format. The full proposal is described in
|
the experimental Slate V5 format. The full proposal is described in
|
||||||
[Early Payment Proofs](https://github.com/mimblewimble/grin-rfcs/pull/70)
|
[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
|
* 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
|
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
|
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::owner::{check_ttl, post_tx};
|
||||||
use crate::api_impl::types::update_tx_slate_state;
|
use crate::api_impl::types::update_tx_slate_state;
|
||||||
use crate::backend::WalletBackend;
|
use crate::backend::WalletBackend;
|
||||||
use crate::contract::proofs::InvoiceProof;
|
use crate::contract::proofs::EarlyPaymentProof;
|
||||||
use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||||
use crate::grin_core::core::FeeFields;
|
use crate::grin_core::core::FeeFields;
|
||||||
use crate::grin_keychain::Keychain;
|
use crate::grin_keychain::Keychain;
|
||||||
@@ -291,11 +291,11 @@ where
|
|||||||
owner_contract_sign(w, keychain_mask, args, slate)
|
owner_contract_sign(w, keychain_mask, args, slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify an invoice payment proof
|
/// Verify an early payment proof
|
||||||
pub fn verify_payment_proof_invoice<C, K>(
|
pub fn verify_payment_proof_early<C, K>(
|
||||||
w: &mut WalletBackend<C, K>,
|
w: &mut WalletBackend<C, K>,
|
||||||
recipient_address: &DalekPublicKey,
|
recipient_address: &DalekPublicKey,
|
||||||
proof: &InvoiceProof,
|
proof: &EarlyPaymentProof,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
C: NodeClient,
|
C: NodeClient,
|
||||||
@@ -307,7 +307,7 @@ where
|
|||||||
Some(w) => w,
|
Some(w) => w,
|
||||||
None => {
|
None => {
|
||||||
return Err(Error::PaymentProof(
|
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 uuid::Uuid;
|
||||||
|
|
||||||
use crate::api_impl::foreign::finalize_tx as foreign_finalize;
|
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::amount_to_hr_string;
|
||||||
use crate::grin_core::core::hash::Hashed;
|
use crate::grin_core::core::hash::Hashed;
|
||||||
use crate::grin_core::core::{FeeFields, Output, OutputFeatures, Transaction};
|
use crate::grin_core::core::{FeeFields, Output, OutputFeatures, Transaction};
|
||||||
@@ -523,16 +523,15 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve invoice payment proof
|
/// Retrieve an early payment proof
|
||||||
/// TODO: Need to unify with legacy above
|
pub fn retrieve_payment_proof_early<'a, L, C, K>(
|
||||||
pub fn retrieve_payment_proof_invoice<'a, L, C, K>(
|
|
||||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
status_send_channel: &Option<Sender<StatusMessage>>,
|
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||||
refresh_from_node: bool,
|
refresh_from_node: bool,
|
||||||
tx_id: Option<u32>,
|
tx_id: Option<u32>,
|
||||||
tx_slate_id: Option<Uuid>,
|
tx_slate_id: Option<Uuid>,
|
||||||
) -> Result<InvoiceProof, Error>
|
) -> Result<EarlyPaymentProof, Error>
|
||||||
where
|
where
|
||||||
L: WalletLCProvider<'a, C, K>,
|
L: WalletLCProvider<'a, C, K>,
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
@@ -571,32 +570,36 @@ where
|
|||||||
let tx = txs.1[0].clone();
|
let tx = txs.1[0].clone();
|
||||||
// Contract tx logs store the agreed net change separately from the fee.
|
// Contract tx logs store the agreed net change separately from the fee.
|
||||||
let amount = tx.amount_credited.abs_diff(tx.amount_debited);
|
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 {
|
let (mut proof, sender_part_sig) = match tx.payment_proof {
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
if p.receiver_public_nonce.is_none() {
|
if p.receiver_public_nonce.is_none() {
|
||||||
return Err(Error::PaymentProofRetrieval(
|
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() {
|
if p.receiver_public_excess.is_none() {
|
||||||
return Err(Error::PaymentProofRetrieval(
|
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() {
|
if p.timestamp.is_none() {
|
||||||
return Err(Error::PaymentProofRetrieval(
|
return Err(Error::PaymentProofRetrieval(
|
||||||
"Invoice Proof requires stored timestamp".into(),
|
"Early payment proof requires stored timestamp".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
if p.sender_part_sig.is_none() {
|
if p.sender_part_sig.is_none() {
|
||||||
return Err(Error::PaymentProofRetrieval(
|
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(
|
proof_type: PaymentProofType::try_from(
|
||||||
p.proof_type.unwrap_or(PaymentProofType::Invoice.as_u8()),
|
p.proof_type.unwrap_or(PaymentProofType::Invoice.as_u8()),
|
||||||
)?,
|
)?,
|
||||||
@@ -630,7 +633,7 @@ where
|
|||||||
Some(k) => k,
|
Some(k) => k,
|
||||||
None => {
|
None => {
|
||||||
return Err(Error::PaymentProofRetrieval(format!(
|
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_index: index,
|
||||||
kernel_commitment: retrieved_kernel.excess,
|
kernel_commitment: retrieved_kernel.excess,
|
||||||
sender_partial_sig: sender_part_sig,
|
sender_partial_sig: sender_part_sig,
|
||||||
|
sender_public_nonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(proof)
|
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.
|
races a transaction already in the mempool.
|
||||||
- Add a Grin node API for kernel lookup by MMR index, then use the witness index instead
|
- Add a Grin node API for kernel lookup by MMR index, then use the witness index instead
|
||||||
of searching by commitment.
|
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
|
- Move payment-proof creation and verification onto `Slate` so it can also be used
|
||||||
outside contracts and is versioned with the slate format.
|
outside contracts and is versioned with the slate format.
|
||||||
- Keep V5 when decoding Slatepacks through Owner RPC.
|
- Keep V5 when decoding Slatepacks through Owner RPC.
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ where
|
|||||||
contract::utils::verify_setup_args_consistency(context_args, setup_args)?;
|
contract::utils::verify_setup_args_consistency(context_args, setup_args)?;
|
||||||
|
|
||||||
// Add keys and payment proof to slate (both are idempotent operations)
|
// 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(
|
contract::slate::add_payment_proof(
|
||||||
w,
|
w,
|
||||||
&mut sl,
|
&mut sl,
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ where
|
|||||||
// Add outputs to the slate, verify the payment proof and sign the slate
|
// 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::add_outputs(w, keychain_mask, &mut sl, &context)?;
|
||||||
contract::slate::verify_own_commitments(w, keychain_mask, &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::sign(w, keychain_mask, &mut sl, &mut context)?;
|
||||||
contract::slate::transition_state(&mut sl)?;
|
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::Secp256k1;
|
||||||
use crate::grin_util::secp::Signature;
|
use crate::grin_util::secp::Signature;
|
||||||
use crate::grin_util::static_secp_instance;
|
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::slate_versions::ser as dalek_ser;
|
||||||
use crate::types::{Context, NodeClient};
|
use crate::types::{Context, NodeClient};
|
||||||
use crate::{address, Error};
|
use crate::{address, Error};
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use ed25519_dalek::Signature as DalekSignature;
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
use ed25519_dalek::SigningKey as DalekSecretKey;
|
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> {
|
pub(super) fn check_proof_type(proof_type: &ProofType) -> Result<(), Error> {
|
||||||
match proof_type {
|
match proof_type {
|
||||||
ProofType::Invoice => Ok(()),
|
ProofType::Invoice | ProofType::SenderNonce => Ok(()),
|
||||||
_ => Err(Error::GenericError(
|
_ => 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(
|
fn verify_receiver_sig(
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
sig: &Signature,
|
sig: &Signature,
|
||||||
@@ -76,6 +86,24 @@ fn verify_receiver_sig(
|
|||||||
Ok(())
|
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
|
/// All elements required to validate a proof within a single struct
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ProofWitness {
|
pub struct ProofWitness {
|
||||||
@@ -93,13 +121,19 @@ pub struct ProofWitness {
|
|||||||
/// sender partial signature, used to recover receiver partial signature
|
/// sender partial signature, used to recover receiver partial signature
|
||||||
#[serde(with = "secp_ser::sig_serde")]
|
#[serde(with = "secp_ser::sig_serde")]
|
||||||
pub sender_partial_sig: Signature,
|
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
|
/// Early payment proof extracted from a slate and stored transaction data
|
||||||
/// signing (when wrapped as InvoiceProofBin) or json export from stored tx data
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct InvoiceProof {
|
pub struct EarlyPaymentProof {
|
||||||
/// Proof type, 0x00 legacy (though this will use StoredProofInfo above, 1 invoice, 2 Sender nonce)
|
/// Proof type
|
||||||
#[serde(with = "crate::slate::payment_proof_type_serde")]
|
#[serde(with = "crate::slate::payment_proof_type_serde")]
|
||||||
pub proof_type: PaymentProofType,
|
pub proof_type: PaymentProofType,
|
||||||
/// amount
|
/// amount
|
||||||
@@ -128,23 +162,18 @@ pub struct InvoiceProof {
|
|||||||
pub witness_data: Option<ProofWitness>,
|
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> {
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||||
writer.write_u8(self.0.proof_type.as_u8())?;
|
writer.write_u8(self.0.proof_type.as_u8())?;
|
||||||
|
match self.0.proof_type {
|
||||||
// Amount field is 7 bytes, throw error if value is greater
|
PaymentProofType::Invoice => write_amount(writer, self.0.amount)?,
|
||||||
let mut amount_bytes = [0; 8];
|
PaymentProofType::SenderNonce => writer.write_fixed_bytes(&[0; 7])?,
|
||||||
BigEndian::write_u64(&mut amount_bytes, self.0.amount);
|
PaymentProofType::Legacy => {
|
||||||
|
return Err(grin_ser::Error::CorruptedData);
|
||||||
if amount_bytes[0] > 0 {
|
}
|
||||||
return Err(grin_ser::Error::UnexpectedData {
|
|
||||||
expected: [0u8].to_vec(),
|
|
||||||
received: [amount_bytes[0]].to_vec(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
writer.write_fixed_bytes(amount_bytes[1..].to_vec())?;
|
|
||||||
{
|
{
|
||||||
let static_secp = static_secp_instance();
|
let static_secp = static_secp_instance();
|
||||||
let static_secp = static_secp.lock();
|
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_fixed_bytes(self.0.sender_address.as_bytes())?;
|
||||||
|
if self.0.proof_type == PaymentProofType::Invoice {
|
||||||
writer.write_i64(self.0.timestamp)?;
|
writer.write_i64(self.0.timestamp)?;
|
||||||
let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or("");
|
let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or("");
|
||||||
writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes())?;
|
writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes())?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InvoiceProof {
|
struct SenderNonceMessage<'a>(&'a EarlyPaymentProof);
|
||||||
/// Extracts as much data as possible from the slate to create an invoice proof
|
|
||||||
|
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(
|
pub fn from_slate(
|
||||||
slate: &Slate,
|
slate: &Slate,
|
||||||
participant_index: usize,
|
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> {
|
pub fn sign(&self, sec_key: &SecretKey) -> Result<(DalekSignature, DalekPublicKey), Error> {
|
||||||
let d_skey = DalekSecretKey::from_bytes(&sec_key.0);
|
let d_skey = DalekSecretKey::from_bytes(&sec_key.0);
|
||||||
let pub_key = d_skey.verifying_key();
|
let pub_key = d_skey.verifying_key();
|
||||||
let mut sig_data_bin = Vec::new();
|
let mut sig_data_bin = Vec::new();
|
||||||
grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self)).map_err(|e| {
|
grin_ser::serialize_default(&mut sig_data_bin, &PromiseBin(self)).map_err(|e| {
|
||||||
Error::GenericError(format!("InvoiceProof serialization failed: {}", e))
|
Error::GenericError(format!("Payment proof serialization failed: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok((d_skey.sign(&sig_data_bin), pub_key))
|
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(
|
pub fn verify_promise_signature(
|
||||||
&self,
|
&self,
|
||||||
recipient_address: &DalekPublicKey,
|
recipient_address: &DalekPublicKey,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.proof_type.validate(PaymentProofType::Invoice)?;
|
check_proof_type(&self.proof_type)?;
|
||||||
if self.promise_signature.is_none() {
|
if self.promise_signature.is_none() {
|
||||||
return Err(Error::PaymentProofValidation(
|
return Err(Error::PaymentProofValidation(
|
||||||
"Missing promise signature".into(),
|
"Missing promise signature".into(),
|
||||||
@@ -258,8 +302,8 @@ impl InvoiceProof {
|
|||||||
|
|
||||||
// Rebuild message
|
// Rebuild message
|
||||||
let mut sig_data_bin = Vec::new();
|
let mut sig_data_bin = Vec::new();
|
||||||
grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self)).map_err(|e| {
|
grin_ser::serialize_default(&mut sig_data_bin, &PromiseBin(self)).map_err(|e| {
|
||||||
Error::GenericError(format!("InvoiceProof serialization failed: {}", e))
|
Error::GenericError(format!("Payment proof serialization failed: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if recipient_address
|
if recipient_address
|
||||||
@@ -292,49 +336,126 @@ impl InvoiceProof {
|
|||||||
let static_secp = static_secp_instance();
|
let static_secp = static_secp_instance();
|
||||||
let static_secp = static_secp.lock();
|
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
|
// Retrieve the public nonce sum from the kernel excess signature
|
||||||
let mut pub_nonce_sum_bytes = [3u8; 33];
|
let mut pub_nonce_sum_bytes = [3u8; 33];
|
||||||
pub_nonce_sum_bytes[1..33].copy_from_slice(&excess_sig[0..32]);
|
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 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
|
// Retrieve the public key sum from the kernel excess
|
||||||
let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?;
|
let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?;
|
||||||
if verify_receiver_sig(
|
if !receiver_part_sigs.iter().any(|signature| {
|
||||||
|
verify_receiver_sig(
|
||||||
&static_secp,
|
&static_secp,
|
||||||
&receiver_part_sig.0,
|
signature,
|
||||||
&self.receiver_public_nonce,
|
&self.receiver_public_nonce,
|
||||||
&pub_nonce_sum,
|
&pub_nonce_sum,
|
||||||
&self.receiver_public_excess,
|
&self.receiver_public_excess,
|
||||||
&pub_blind_sum,
|
&pub_blind_sum,
|
||||||
&msg,
|
&msg,
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_ok()
|
||||||
{
|
}) {
|
||||||
// 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(
|
return Err(Error::PaymentProofValidation(
|
||||||
"Receiver signature does not match the promise".into(),
|
"Receiver signature does not match the promise".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
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
|
/// Adds all info needed for a payment proof to a slate, complete with signed recipient data
|
||||||
pub(super) fn add_payment_proof<C, K>(
|
pub(super) fn add_payment_proof<C, K>(
|
||||||
wallet: &mut WalletBackend<C, K>,
|
wallet: &mut WalletBackend<C, K>,
|
||||||
@@ -347,45 +468,51 @@ where
|
|||||||
C: NodeClient,
|
C: NodeClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
let (invoice_proof, promise_signature, receiver_address) =
|
if proof_args.proof_type == PaymentProofType::SenderNonce && slate.state != SlateState::Invoice1
|
||||||
generate_invoice_signature(wallet, keychain_mask, slate, context, proof_args)?;
|
{
|
||||||
|
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
|
// 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
|
// the clock a second time. The signature binds it, so a tick between the two reads
|
||||||
// would leave a proof that cannot verify.
|
// 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!(
|
Error::GenericError(format!(
|
||||||
"Invalid proof timestamp: {}",
|
"Invalid proof timestamp: {}",
|
||||||
invoice_proof.timestamp
|
early_proof.timestamp
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let proof = PaymentInfo {
|
let proof = PaymentInfo {
|
||||||
proof_type: invoice_proof.proof_type,
|
proof_type: early_proof.proof_type,
|
||||||
sender_address: proof_args.sender_address.clone(),
|
sender_address: proof_args.sender_address.clone(),
|
||||||
receiver_address,
|
receiver_address,
|
||||||
timestamp: Some(timestamp),
|
timestamp: Some(timestamp),
|
||||||
promise_signature: Some(promise_signature),
|
promise_signature: Some(promise_signature),
|
||||||
memo: invoice_proof.memo,
|
memo: early_proof.memo,
|
||||||
};
|
};
|
||||||
slate.payment_proof = Some(proof);
|
slate.payment_proof = Some(proof);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a signature for proof type 'Invoice'
|
fn generate_promise_signature<C, K>(
|
||||||
fn generate_invoice_signature<C, K>(
|
|
||||||
wallet: &mut WalletBackend<C, K>,
|
wallet: &mut WalletBackend<C, K>,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
slate: &mut Slate,
|
slate: &mut Slate,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
proof_args: &ProofArgs,
|
proof_args: &ProofArgs,
|
||||||
) -> Result<(InvoiceProof, DalekSignature, DalekPublicKey), Error>
|
) -> Result<(EarlyPaymentProof, DalekSignature, DalekPublicKey), Error>
|
||||||
where
|
where
|
||||||
C: NodeClient,
|
C: NodeClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
let keychain = wallet.keychain(keychain_mask)?;
|
let keychain = wallet.keychain(keychain_mask)?;
|
||||||
let index = slate.find_index_matching_context(&keychain, context)?;
|
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 {
|
let derivation_index = match context.payment_proof_derivation_index {
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => 0,
|
None => 0,
|
||||||
@@ -395,9 +522,9 @@ where
|
|||||||
let recp_key =
|
let recp_key =
|
||||||
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
||||||
|
|
||||||
invoice_proof.timestamp = Utc::now().timestamp();
|
early_proof.timestamp = Utc::now().timestamp();
|
||||||
let (sig, addr) = invoice_proof.sign(&recp_key)?;
|
let (sig, addr) = early_proof.sign(&recp_key)?;
|
||||||
Ok((invoice_proof, sig, addr))
|
Ok((early_proof, sig, addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -409,7 +536,7 @@ mod tests {
|
|||||||
fn rejects_unsupported_proofs() {
|
fn rejects_unsupported_proofs() {
|
||||||
assert!(check_proof_type(&ProofType::Invoice).is_ok());
|
assert!(check_proof_type(&ProofType::Invoice).is_ok());
|
||||||
assert!(check_proof_type(&ProofType::Legacy).is_err());
|
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]
|
#[test]
|
||||||
@@ -462,36 +589,70 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ser_invoice_proof_bin() -> Result<(), Error> {
|
fn proof_promise() -> Result<(), Error> {
|
||||||
|
use crate::grin_util::ToHex;
|
||||||
|
|
||||||
let mut slate = populate_test_slate()?;
|
let mut slate = populate_test_slate()?;
|
||||||
slate.amount |= 0xFF00_0000_0000_0000;
|
slate.amount |= 0xFF00_0000_0000_0000;
|
||||||
// Bin serialization doesn't include promise sig as it's used to create signature data
|
// Bin serialization doesn't include promise sig as it's used to create signature data
|
||||||
slate.payment_proof.as_mut().unwrap().promise_signature = None;
|
slate.payment_proof.as_mut().unwrap().promise_signature = None;
|
||||||
|
|
||||||
// Should fail, amount too big
|
// 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();
|
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
|
// Should be okay now
|
||||||
slate.amount = 1234;
|
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();
|
let mut vec = Vec::new();
|
||||||
grin_ser::serialize_default(&mut vec, &InvoiceProofBin(&invoice_proof))
|
grin_ser::serialize_default(&mut vec, &PromiseBin(&proof)).expect("Serialization Failed");
|
||||||
.expect("Serialization Failed");
|
|
||||||
let memo = invoice_proof.memo.as_ref().unwrap().as_str();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&vec[vec.len() - 32..],
|
vec.to_hex(),
|
||||||
blake2b(32, &[], memo.as_bytes()).as_bytes()
|
"01000000000004d2031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb00000000075bcd15c60e4f851ed93b6641571e6810f608fe42ee566f7e199915061ea13b3b04e772"
|
||||||
);
|
);
|
||||||
let proof_key = SecretKey::from_slice(&Secp256k1::new(), &[7; 32])?;
|
let proof_key = SecretKey::from_slice(&Secp256k1::new(), &[7; 32])?;
|
||||||
let (signature, recipient) = invoice_proof.sign(&proof_key)?;
|
let (signature, recipient) = proof.sign(&proof_key)?;
|
||||||
invoice_proof.promise_signature = Some(signature);
|
proof.promise_signature = Some(signature);
|
||||||
invoice_proof.verify_promise_signature(&recipient)?;
|
proof.verify_promise_signature(&recipient)?;
|
||||||
invoice_proof.memo = Some(PaymentMemo::new("changed details".to_string())?);
|
proof.memo = Some(PaymentMemo::new("changed details".to_string())?);
|
||||||
assert!(invoice_proof.verify_promise_signature(&recipient).is_err());
|
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;
|
wrong_type.proof_type = PaymentProofType::Legacy;
|
||||||
assert!(wrong_type
|
assert!(wrong_type
|
||||||
.verify_promise_signature(&slate.payment_proof.unwrap().receiver_address)
|
.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::constants::PEDERSEN_COMMITMENT_SIZE;
|
||||||
use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::grin_util::secp::pedersen::Commitment;
|
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::types::{Context, NodeClient, OutputData};
|
||||||
use crate::util::OnionV3Address;
|
use crate::util::OnionV3Address;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use super::types::{OwnCommitmentStatus, ProofArgs};
|
use super::types::{OwnCommitmentStatus, ProofArgs};
|
||||||
use crate::contract::proofs::InvoiceProof;
|
use crate::contract::proofs::EarlyPaymentProof;
|
||||||
|
|
||||||
/// Add payment proof data to slate, noop for sender
|
/// Add payment proof data to slate, noop for sender
|
||||||
pub fn add_payment_proof<C, K>(
|
pub fn add_payment_proof<C, K>(
|
||||||
@@ -60,8 +60,8 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the receiver's invoice promise before paying
|
/// Verify the receiver's early payment promise before paying
|
||||||
pub fn verify_invoice_promise<K>(
|
pub fn verify_payment_promise<K>(
|
||||||
slate: &Slate,
|
slate: &Slate,
|
||||||
keychain: &K,
|
keychain: &K,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
@@ -70,7 +70,7 @@ where
|
|||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
// FUTURE: move proof verification onto Slate itself so it can be versioned (slate.verify_payment_proof_sig()).
|
// 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 {
|
if context.get_net_change()? >= 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -78,12 +78,10 @@ where
|
|||||||
Some(proof) => proof,
|
Some(proof) => proof,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
payment_proof
|
super::proofs::check_proof_type(&payment_proof.proof_type)?;
|
||||||
.proof_type
|
|
||||||
.validate(PaymentProofType::Invoice)?;
|
|
||||||
if slate.participant_data.len() != 2 {
|
if slate.participant_data.len() != 2 {
|
||||||
return Err(Error::GenericError(format!(
|
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()
|
slate.participant_data.len()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -94,7 +92,7 @@ where
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find_map(|(index, _)| (index != payer_index).then_some(index))
|
.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 derivation_index = context.payment_proof_derivation_index.unwrap_or(0);
|
||||||
let sender_key = crate::address::address_from_derivation_path(
|
let sender_key = crate::address::address_from_derivation_path(
|
||||||
keychain,
|
keychain,
|
||||||
@@ -102,8 +100,8 @@ where
|
|||||||
derivation_index,
|
derivation_index,
|
||||||
)?;
|
)?;
|
||||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?.to_ed25519()?;
|
let sender_address = OnionV3Address::from_private(&sender_key.0)?.to_ed25519()?;
|
||||||
let invoice_proof = InvoiceProof::from_slate(slate, receiver_index, Some(sender_address))?;
|
let proof = EarlyPaymentProof::from_slate(slate, receiver_index, Some(sender_address))?;
|
||||||
invoice_proof.verify_promise_signature(&payment_proof.receiver_address)
|
proof.verify_promise_signature(&payment_proof.receiver_address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds inputs and outputs to slate
|
/// Adds inputs and outputs to slate
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ impl Default for OutputSelectionArgs {
|
|||||||
pub struct ProofArgs {
|
pub struct ProofArgs {
|
||||||
/// If net change is positive during this step, whether to suppress the creation of payment proof
|
/// If net change is positive during this step, whether to suppress the creation of payment proof
|
||||||
pub suppress_proof: bool,
|
pub suppress_proof: bool,
|
||||||
/// Requested proof type. Only invoice proofs are currently supported.
|
/// Requested early payment proof type
|
||||||
pub proof_type: ProofType,
|
pub proof_type: ProofType,
|
||||||
/// Sender address (required at some stage, may not necessarily be in slate so can be provided explicitly)
|
/// 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")]
|
#[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_core::libtx::tx_fee;
|
||||||
use crate::grin_keychain::{Identifier, Keychain};
|
use crate::grin_keychain::{Identifier, Keychain};
|
||||||
use crate::grin_util::secp::key::SecretKey;
|
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::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType};
|
||||||
use crate::util::OnionV3Address;
|
use crate::util::OnionV3Address;
|
||||||
use crate::{address, Error, OutputData, OutputStatus, TxLogEntry};
|
use crate::{address, Error, OutputData, OutputStatus, TxLogEntry};
|
||||||
@@ -117,6 +117,13 @@ where
|
|||||||
.participant_data
|
.participant_data
|
||||||
.get(participant_index ^ 1)
|
.get(participant_index ^ 1)
|
||||||
.ok_or(Error::ContextToIndex)?;
|
.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 {
|
tx_log_entry.payment_proof = Some(StoredProofInfo {
|
||||||
receiver_address: p.receiver_address,
|
receiver_address: p.receiver_address,
|
||||||
@@ -133,6 +140,7 @@ where
|
|||||||
memo: p.memo.clone(),
|
memo: p.memo.clone(),
|
||||||
promise_signature: p.promise_signature,
|
promise_signature: p.promise_signature,
|
||||||
sender_part_sig: sender.part_sig,
|
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
|
/// Serializes an Option<ed25519_dalek::PublicKey> to and from hex
|
||||||
pub mod option_dalek_pubkey_base64 {
|
pub mod option_dalek_pubkey_base64 {
|
||||||
use base64;
|
use base64;
|
||||||
|
|||||||
@@ -405,6 +405,8 @@ pub struct Context {
|
|||||||
pub log_id: Option<u32>,
|
pub log_id: Option<u32>,
|
||||||
/// Contract signing deadline copied from the slate
|
/// Contract signing deadline copied from the slate
|
||||||
pub contract_ttl_cutoff_height: Option<u64>,
|
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 {
|
impl Context {
|
||||||
@@ -457,6 +459,7 @@ impl Context {
|
|||||||
setup_args: None,
|
setup_args: None,
|
||||||
log_id: None,
|
log_id: None,
|
||||||
contract_ttl_cutoff_height: None,
|
contract_ttl_cutoff_height: None,
|
||||||
|
sender_public_nonce: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,8 +789,7 @@ pub struct StoredProofInfo {
|
|||||||
/// sender signature
|
/// sender signature
|
||||||
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||||
pub sender_signature: Option<DalekSignature>,
|
pub sender_signature: Option<DalekSignature>,
|
||||||
// Fields beyond here are specific to early payment proofs,
|
// Fields beyond here are specific to early payment proofs
|
||||||
// invoice and sender nonce
|
|
||||||
/// Assumed to be 0x00 (Legacy) if missing
|
/// Assumed to be 0x00 (Legacy) if missing
|
||||||
pub proof_type: Option<u8>,
|
pub proof_type: Option<u8>,
|
||||||
/// receiver's public nonce from signing
|
/// receiver's public nonce from signing
|
||||||
@@ -803,6 +805,8 @@ pub struct StoredProofInfo {
|
|||||||
pub promise_signature: Option<DalekSignature>,
|
pub promise_signature: Option<DalekSignature>,
|
||||||
/// Original Sender partial key
|
/// Original Sender partial key
|
||||||
pub sender_part_sig: Option<Signature>,
|
pub sender_part_sig: Option<Signature>,
|
||||||
|
/// Untweaked sender public nonce for a sender-nonce proof
|
||||||
|
pub sender_public_nonce: Option<PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoredProofInfo {
|
impl StoredProofInfo {
|
||||||
@@ -826,6 +830,7 @@ impl StoredProofInfo {
|
|||||||
memo: None,
|
memo: None,
|
||||||
promise_signature: None,
|
promise_signature: None,
|
||||||
sender_part_sig: None,
|
sender_part_sig: None,
|
||||||
|
sender_public_nonce: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user