fix early payment proof versioning and validation

This commit is contained in:
wiesche
2026-09-01 19:19:06 +02:00
parent 88af108b88
commit 1d342e59de
19 changed files with 510 additions and 246 deletions
+15 -14
View File
@@ -37,6 +37,17 @@ mod common;
use common::{clean_output_dir, create_wallets, setup};
use std::path::PathBuf;
fn roundtrip_slate(slate: &Slate, version: u16) -> Result<Slate, libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
});
let slate = packer.get_slate(&packer.create_slatepack(slate)?)?;
assert_eq!(slate.version_info.version, version);
Ok(slate)
}
/// Development + Tests of early payment proof functionality - RSR workflow
fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
@@ -86,19 +97,7 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib
assert_eq!(slate.state, SlateState::Invoice1);
println!("I1 State slate: {}", slate);
// Serialize slate into slatepack
let slatepacker_args = SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
};
let slate_packer = Slatepacker::new(slatepacker_args);
let slate_packed = slate_packer.create_slatepack(&slate).unwrap();
let slate_unpacked = slate_packer.get_slate(&slate_packed).unwrap();
println!("I2 Slate unpacked: {}", slate_unpacked);
slate = roundtrip_slate(&slate, 5)?;
wallet::controller::owner_single_use(
send_wallet.clone(),
@@ -110,13 +109,14 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib
net_change: Some(-5_000_000_000),
..Default::default()
};
slate = api.contract_sign(m, &slate_unpacked, args)?;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
},
)?;
println!("I2 State slate: {}", slate);
assert_eq!(slate.state, SlateState::Invoice2);
slate = roundtrip_slate(&slate, 5)?;
// Send wallet finalizes and posts
wallet::controller::owner_single_use(
@@ -132,6 +132,7 @@ fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), lib
},
)?;
assert_eq!(slate.state, SlateState::Invoice3);
slate = roundtrip_slate(&slate, 5)?;
wallet::controller::owner_single_use(
send_wallet.clone(),
+16 -2
View File
@@ -27,7 +27,7 @@ use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, TxLogEntryType};
use libwallet::{Slate, SlateState, Slatepacker, SlatepackerArgs, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
@@ -37,6 +37,17 @@ mod common;
use common::{clean_output_dir, create_wallets, setup};
use std::path::PathBuf;
fn roundtrip_slate(slate: &Slate, version: u16) -> Result<Slate, libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
});
let slate = packer.get_slate(&packer.create_slatepack(slate)?)?;
assert_eq!(slate.version_info.version, version);
Ok(slate)
}
/// Development + Tests of early payment proof functionality
fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
@@ -69,6 +80,7 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib
},
)?;
assert_eq!(slate.state, SlateState::Standard1);
slate = roundtrip_slate(&slate, 4)?;
let mut recipient_address = None;
wallet::controller::owner_single_use(
@@ -90,6 +102,7 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib
},
)?;
assert_eq!(slate.state, SlateState::Standard2);
slate = roundtrip_slate(&slate, 5)?;
// Send wallet finalizes and posts
//let mut sender_part_sig = None;
@@ -103,7 +116,7 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib
};
let mut tampered = slate.clone();
let proof = tampered.payment_proof.as_mut().unwrap();
proof.timestamp += Duration::from_secs(1);
*proof.timestamp.as_mut().unwrap() += Duration::from_secs(1);
assert!(api.contract_sign(m, &tampered, args).is_err());
slate = api.contract_sign(m, &slate, args)?;
@@ -111,6 +124,7 @@ fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), lib
},
)?;
assert_eq!(slate.state, SlateState::Standard3);
slate = roundtrip_slate(&slate, 5)?;
wallet::controller::owner_single_use(
send_wallet.clone(),
+17 -3
View File
@@ -54,13 +54,27 @@ account selected with `--account`.
* Only one or two participants are supported
* A returned slate is not yet checked against every input and output saved during setup
* Early invoice payment proofs are only available through the API and require the
experimental Slate V5 format. Other proof types are not supported
* 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)
* Custom fee rates and contract TTLs are not supported
* There are no contract-specific history, lookup or transport commands. Payment proof
memos cannot be set through the contract API or CLI
memos cannot be set through the contract API or CLI. The current proof stores a memo
type and 32 bytes instead of the text and hash described by the early payment proofs
proposal
* If writing the signed transaction file fails, the wallet state has already been saved.
Cancelling the transaction releases the locked inputs
Implementation notes and remaining work are kept in
[`libwallet/src/contract/actions/README.md`](../libwallet/src/contract/actions/README.md).
## References
* [Contract prototype discussion](https://forum.grin.mw/t/grin-wallet-contract-prototype/9745)
* [Manual confirmation proposal](https://github.com/mimblewimble/grin-rfcs/pull/84) (open)
* [Early payment proofs proposal](https://github.com/mimblewimble/grin-rfcs/pull/70) (open)
* [RFC 0006: Payment Proofs](https://github.com/mimblewimble/grin-rfcs/blob/master/text/0006-payment-proofs.md)
* [RFC 0012: Compact Slates](https://github.com/mimblewimble/grin-rfcs/blob/master/text/0012-compact-slates.md)
* [RFC 0015: Slatepack](https://github.com/mimblewimble/grin-rfcs/blob/master/text/0015-slatepack.md)
* [RFC 0017: Fix Fees](https://github.com/mimblewimble/grin-rfcs/blob/master/text/0017-fix-fees.md)
+4 -2
View File
@@ -236,7 +236,8 @@ where
sender_id: m.dest,
dest: m.sender_id,
method: m.method,
body: serde_json::to_string(&SlateV4::from(slate)).unwrap(),
body: serde_json::to_string(&SlateV4::try_from(slate)?)
.map_err(|e| libwallet::Error::ClientCallback(e.to_string()))?,
})
}
@@ -397,7 +398,8 @@ impl LocalWalletClient {
sender_id: self.id.clone(),
dest: dest.to_owned(),
method: "send_tx_slate".to_owned(),
body: serde_json::to_string(&SlateV4::from(slate)).unwrap(),
body: serde_json::to_string(&SlateV4::try_from(slate)?)
.map_err(|e| libwallet::Error::ClientCallback(e.to_string()))?,
};
{
let p = self.proxy_tx.lock();
+2
View File
@@ -28,6 +28,7 @@ use crate::grin_core::core::FeeFields;
use crate::grin_keychain::Keychain;
use crate::grin_util::secp::key::SecretKey;
use crate::internal::{selection, tx, updater};
use crate::slate::PaymentProofType;
use crate::slate_versions::SlateVersion;
use crate::{
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
@@ -118,6 +119,7 @@ where
let excess = ret_slate.calc_excess(keychain.secp())?;
if let Some(ref mut p) = ret_slate.payment_proof {
p.proof_type.validate(PaymentProofType::Legacy)?;
if let Some(saddr) = p.sender_address {
let sig = tx::create_payment_proof_signature(
ret_slate.amount,
+6 -4
View File
@@ -36,7 +36,7 @@ use crate::contract::types::{
use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
use crate::internal::{keys, scan, selection, tx, updater};
use crate::slate::{PaymentInfo, Slate, SlateState};
use crate::slate::{PaymentInfo, PaymentProofType, Slate, SlateState};
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletInfo};
use crate::{
address, contract,
@@ -46,7 +46,6 @@ use crate::{
SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletBackend,
WalletInitStatus, WalletInst, WalletLCProvider,
};
use chrono::prelude::DateTime;
use ed25519_dalek::SigningKey as DalekSecretKey;
use ed25519_dalek::Verifier;
use ed25519_dalek::VerifyingKey as DalekPublicKey;
@@ -602,7 +601,9 @@ where
(
InvoiceProof {
proof_type: if let Some(t) = p.proof_type { t } else { 1u8 },
proof_type: PaymentProofType::try_from(
p.proof_type.unwrap_or(PaymentProofType::Invoice.as_u8()),
)?,
amount,
receiver_public_nonce: p.receiver_public_nonce.unwrap(),
receiver_public_excess: p.receiver_public_excess.unwrap(),
@@ -765,10 +766,11 @@ where
let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?;
slate.payment_proof = Some(PaymentInfo {
proof_type: PaymentProofType::Legacy,
sender_address: Some(sender_address.to_ed25519()?),
receiver_address: a.pub_key,
promise_signature: None,
timestamp: DateTime::from_timestamp(0, 0).unwrap(),
timestamp: None,
memo: None,
});
+6 -5
View File
@@ -57,13 +57,14 @@ Separating side effects until the 'save_step' part would make these functions mu
- Raise the replacement fee when `revoke` races a transaction already in the mempool.
- Merge invoice-proof retrieval with the existing payment-proof API.
- Confirm that invoice-proof retrieval excludes the transaction fee from the amount.
- Move payment-proof creation and verification onto `Slate` so it is versioned with the
slate format.
- Bind and validate the contract proof type in the serialized proof.
- Add a Grin node API for kernel lookup by MMR index, then use the witness index instead
of searching by commitment.
- Align the proof memo encoding with the early payment proofs proposal.
- Implement sender-nonce proofs for the RSR flow.
- 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.
- Preserve kernel features and their arguments across V4 and V5; see #793.
- Preserve missing proof sender addresses and timestamps instead of replacing them with
zero values.
- Choose the sender derivation path explicitly and keep encrypted outgoing files
identifiable locally.
- Decode the sender and slate from an incoming Slatepack in one pass.
+110 -16
View File
@@ -25,9 +25,10 @@ use crate::grin_core::ser::{Readable, Reader, Writeable, Writer};
use crate::grin_keychain::Keychain;
use crate::grin_util::secp::key::{PublicKey, SecretKey};
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, Slate};
use crate::slate::{PaymentInfo, PaymentMemo, PaymentProofType, Slate};
use crate::slate_versions::ser as dalek_ser;
use crate::types::{Context, NodeClient};
use crate::{address, Error};
@@ -48,6 +49,32 @@ pub(super) fn check_proof_type(proof_type: &ProofType) -> Result<(), Error> {
}
}
fn verify_receiver_sig(
secp: &Secp256k1,
sig: &Signature,
receiver_nonce: &PublicKey,
pub_nonce_sum: &PublicKey,
receiver_excess: &PublicKey,
pub_blind_sum: &PublicKey,
msg: &Message,
) -> Result<(), Error> {
let receiver_nonce = receiver_nonce.serialize_vec(secp, true);
if sig[0..32] != receiver_nonce[1..33] {
return Err(Error::PaymentProofValidation(
"Receiver nonce does not match the promise".into(),
));
}
aggsig::verify_partial_sig(
secp,
sig,
pub_nonce_sum,
receiver_excess,
Some(pub_blind_sum),
msg,
)?;
Ok(())
}
/// All elements required to validate a proof within a single struct
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ProofWitness {
@@ -72,7 +99,8 @@ pub struct ProofWitness {
#[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 proof_type: u8,
#[serde(with = "crate::slate::payment_proof_type_serde")]
pub proof_type: PaymentProofType,
/// amount
#[serde(with = "secp_ser::string_or_u64")]
pub amount: u64,
@@ -103,7 +131,7 @@ struct InvoiceProofBin(InvoiceProof);
impl Writeable for InvoiceProofBin {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
writer.write_u8(1)?;
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];
@@ -154,7 +182,8 @@ impl Readable for InvoiceProofBin {
// first 8 bytes are proof type + 7 bytes worth of amount
let mut amount = reader.read_u64()?;
// The shift leaves a single byte, so this conversion cannot lose data
let proof_type = ((amount & 0xFF00_0000_0000_0000) >> 56) as u8;
let proof_type = PaymentProofType::try_from(((amount & 0xFF00_0000_0000_0000) >> 56) as u8)
.map_err(|_| grin_ser::Error::CorruptedData)?;
amount &= 0x00FF_FFFF_FFFF_FFFF;
let receiver_public_nonce;
@@ -237,9 +266,14 @@ impl InvoiceProof {
}
};
let timestamp = match slate.payment_proof.as_ref() {
Some(p) => p.timestamp.timestamp(),
None => 0,
let (proof_type, timestamp) = match slate.payment_proof.as_ref() {
Some(p) => (
p.proof_type,
p.timestamp
.ok_or_else(|| Error::PaymentProof("Missing proof timestamp".to_string()))?
.timestamp(),
),
None => (PaymentProofType::Invoice, 0),
};
let memo = match slate.payment_proof.as_ref() {
@@ -253,7 +287,7 @@ impl InvoiceProof {
};
Ok(Self {
proof_type: 1u8,
proof_type,
amount: slate.amount,
receiver_public_nonce: slate.participant_data[participant_index].public_nonce,
receiver_public_excess: slate.participant_data[participant_index].public_blind_excess,
@@ -282,6 +316,7 @@ impl InvoiceProof {
&self,
recipient_address: &DalekPublicKey,
) -> Result<(), Error> {
self.proof_type.validate(PaymentProofType::Invoice)?;
if self.promise_signature.is_none() {
return Err(Error::PaymentProofValidation(
"Missing promise signature".into(),
@@ -334,28 +369,31 @@ impl InvoiceProof {
// Retrieve the public key sum from the kernel excess
let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?;
if let Err(_) = aggsig::verify_partial_sig(
if verify_receiver_sig(
&static_secp,
&receiver_part_sig.0,
&self.receiver_public_nonce,
&pub_nonce_sum,
&self.receiver_public_excess,
Some(&pub_blind_sum),
&pub_blind_sum,
&msg,
) {
)
.is_err()
{
// Try other possibility
if let Some(s) = receiver_part_sig.1 {
aggsig::verify_partial_sig(
verify_receiver_sig(
&static_secp,
&s,
&self.receiver_public_nonce,
&pub_nonce_sum,
&self.receiver_public_excess,
Some(&pub_blind_sum),
&pub_blind_sum,
&msg,
)?;
} else {
return Err(Error::PaymentProofValidation(
"Signature subtraction failed".into(),
"Receiver signature does not match the promise".into(),
));
}
}
@@ -401,9 +439,10 @@ where
})?;
let proof = PaymentInfo {
proof_type: invoice_proof.proof_type,
sender_address: proof_args.sender_address.clone(),
receiver_address,
timestamp,
timestamp: Some(timestamp),
promise_signature: Some(promise_signature),
memo: invoice_proof.memo,
};
@@ -452,6 +491,55 @@ mod tests {
assert!(check_proof_type(&ProofType::SenderNonce).is_err());
}
#[test]
fn checks_receiver_nonce() {
let secp = Secp256k1::new();
let sender_key = SecretKey::from_slice(&secp, &[1; 32]).unwrap();
let receiver_key = SecretKey::from_slice(&secp, &[2; 32]).unwrap();
let sender_nonce = SecretKey::from_slice(&secp, &[3; 32]).unwrap();
let receiver_nonce = SecretKey::from_slice(&secp, &[4; 32]).unwrap();
let sender_pub_nonce = PublicKey::from_secret_key(&secp, &sender_nonce).unwrap();
let receiver_pub_nonce = PublicKey::from_secret_key(&secp, &receiver_nonce).unwrap();
let sender_excess = PublicKey::from_secret_key(&secp, &sender_key).unwrap();
let receiver_excess = PublicKey::from_secret_key(&secp, &receiver_key).unwrap();
let pub_nonce_sum =
PublicKey::from_combination(&secp, vec![&sender_pub_nonce, &receiver_pub_nonce])
.unwrap();
let pub_blind_sum =
PublicKey::from_combination(&secp, vec![&sender_excess, &receiver_excess]).unwrap();
let msg = Message::from_slice(&[5; 32]).unwrap();
let sig = aggsig::calculate_partial_sig(
&secp,
&receiver_key,
&receiver_nonce,
&pub_nonce_sum,
Some(&pub_blind_sum),
&msg,
)
.unwrap();
assert!(verify_receiver_sig(
&secp,
&sig,
&receiver_pub_nonce,
&pub_nonce_sum,
&receiver_excess,
&pub_blind_sum,
&msg,
)
.is_ok());
assert!(verify_receiver_sig(
&secp,
&sig,
&sender_pub_nonce,
&pub_nonce_sum,
&receiver_excess,
&pub_blind_sum,
&msg,
)
.is_err());
}
#[test]
fn ser_invoice_proof_bin() -> Result<(), Error> {
let mut slate = populate_test_slate()?;
@@ -473,6 +561,12 @@ mod tests {
let proof_deser: InvoiceProofBin = grin_ser::deserialize_default(&mut &vec[..]).unwrap();
assert_eq!(invoice_proof, proof_deser.0);
let mut wrong_type = invoice_proof;
wrong_type.proof_type = PaymentProofType::Legacy;
assert!(wrong_type
.verify_promise_signature(&slate.payment_proof.unwrap().receiver_address)
.is_err());
Ok(())
}
+4 -1
View File
@@ -19,7 +19,7 @@ use crate::grin_core::libtx::build;
use crate::grin_core::libtx::proof::ProofBuilder;
use crate::grin_keychain::Keychain;
use crate::grin_util::secp::key::{PublicKey, SecretKey};
use crate::slate::{Slate, SlateState};
use crate::slate::{PaymentProofType, Slate, SlateState};
use crate::types::{Context, NodeClient};
use crate::util::OnionV3Address;
use crate::Error;
@@ -73,6 +73,9 @@ where
Some(proof) => proof,
None => return Ok(()),
};
payment_proof
.proof_type
.validate(PaymentProofType::Invoice)?;
if slate.participant_data.len() != 2 {
return Err(Error::GenericError(format!(
"Expected 2 participants for an invoice promise, found {}",
+1 -14
View File
@@ -15,6 +15,7 @@
//! Types related to a contract
use crate::error::Error;
pub use crate::slate::PaymentProofType as ProofType;
use crate::slate_versions::ser as dalek_ser;
use ed25519_dalek::VerifyingKey as DalekPublicKey;
@@ -76,20 +77,6 @@ impl Default for OutputSelectionArgs {
}
}
/// Types of proof that can be generated
/// as per https://github.com/tromp/grin-rfcs/blob/early-payment-proofs/text/0000-early-payment-proofs.md
/// TODO: Update when RFC is merged
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum ProofType {
/// Legacy (0x00)
Legacy,
/// Invoice (0x01)
Invoice,
/// Sender Nonce (0x02)
SenderNonce,
}
/// Proof generation parameters that can be provided during new or sign phases
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ProofArgs {
+5 -2
View File
@@ -87,6 +87,9 @@ where
// If we're sending and there's payment proof info in the slate added by recipient, store as well
if let Some(ref p) = slate.payment_proof {
if tx_log_entry.amount_debited > 0 {
let timestamp = p
.timestamp
.ok_or_else(|| Error::PaymentProof("Missing proof timestamp".to_string()))?;
// note we only use a single path for now
let sender_address_path = 0u32;
let sender_key = address::address_from_derivation_path(
@@ -116,12 +119,12 @@ where
sender_signature: None,
// Filled as separate steps for now; could be merged into a general case
// once we know which nonces here belong to the recipient.
proof_type: Some(1u8),
proof_type: Some(p.proof_type.as_u8()),
receiver_public_nonce: Some(slate.participant_data[recipient_index].public_nonce),
receiver_public_excess: Some(
slate.participant_data[recipient_index].public_blind_excess,
),
timestamp: Some(p.timestamp),
timestamp: Some(timestamp),
memo: p.memo.clone(),
promise_signature: p.promise_signature,
sender_part_sig: slate.participant_data[sender_index].part_sig,
+6 -16
View File
@@ -205,23 +205,13 @@ where
)?;
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
t.payment_proof = Some(StoredProofInfo {
receiver_address: p.receiver_address,
receiver_signature: p.promise_signature,
sender_address: sender_address.to_ed25519()?,
t.payment_proof = Some(StoredProofInfo::new(
p.receiver_address,
p.promise_signature,
sender_address.to_ed25519()?,
sender_address_path,
sender_signature: None,
// TODO: Will fill these as separate steps for now, check whether this
// can be merged in a general case (which means knowing which nonces here belong to
// the recipient)
proof_type: None,
receiver_public_nonce: None,
receiver_public_excess: None,
timestamp: None,
memo: None,
promise_signature: None,
sender_part_sig: None,
});
None,
));
};
// write the output representing our change
+9 -16
View File
@@ -25,7 +25,7 @@ use crate::grin_util::secp::key::SecretKey;
use crate::grin_util::secp::pedersen;
use crate::grin_util::Mutex;
use crate::internal::{selection, updater};
use crate::slate::Slate;
use crate::slate::{PaymentProofType, Slate};
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType};
use crate::util::OnionV3Address;
use crate::{address, Error};
@@ -437,21 +437,13 @@ where
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
let sig = create_payment_proof_signature(slate.amount, &excess, saddr, sender_key)?;
tx.payment_proof = Some(StoredProofInfo {
receiver_address: p.receiver_address,
receiver_signature: p.promise_signature,
sender_address_path: derivation_index,
sender_address: sender_address.to_ed25519()?,
sender_signature: Some(sig),
// Filled in during the contract proof flow; unused on the legacy path.
proof_type: None,
receiver_public_nonce: None,
receiver_public_excess: None,
timestamp: None,
memo: None,
promise_signature: None,
sender_part_sig: None,
})
tx.payment_proof = Some(StoredProofInfo::new(
p.receiver_address,
p.promise_signature,
sender_address.to_ed25519()?,
derivation_index,
Some(sig),
))
}
}
@@ -543,6 +535,7 @@ where
}
if let Some(ref p) = slate.clone().payment_proof {
p.proof_type.validate(PaymentProofType::Legacy)?;
let orig_proof_info = match orig_proof_info {
Some(p) => p.clone(),
None => {
+122 -88
View File
@@ -48,6 +48,72 @@ use crate::slate_versions::VersionedSlate;
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
use crate::Context;
/// Payment proof type from https://github.com/mimblewimble/grin-rfcs/pull/70
#[repr(u8)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
pub enum PaymentProofType {
/// Legacy payment proof
Legacy = 0,
/// Invoice payment proof
Invoice = 1,
/// Sender nonce payment proof
SenderNonce = 2,
}
impl PaymentProofType {
/// Wire value
pub fn as_u8(self) -> u8 {
self as u8
}
pub(crate) fn validate(self, expected: Self) -> Result<(), Error> {
if self == expected {
Ok(())
} else {
Err(Error::PaymentProof(format!(
"Invalid payment proof type: expected {:?}, got {:?}",
expected, self
)))
}
}
}
impl TryFrom<u8> for PaymentProofType {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Legacy),
1 => Ok(Self::Invoice),
2 => Ok(Self::SenderNonce),
_ => Err(Error::PaymentProof(format!(
"Invalid payment proof type: {}",
value
))),
}
}
}
pub(crate) mod payment_proof_type_serde {
use super::PaymentProofType;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(proof_type: &PaymentProofType, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(proof_type.as_u8())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<PaymentProofType, D::Error>
where
D: Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
PaymentProofType::try_from(value).map_err(serde::de::Error::custom)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct PaymentMemo {
// The type of memo
@@ -61,18 +127,30 @@ pub struct PaymentMemo {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PaymentInfo {
/// Proof type
pub proof_type: PaymentProofType,
/// Sender address
pub sender_address: Option<DalekPublicKey>,
/// Receiver address
pub receiver_address: DalekPublicKey,
/// Promise signature
pub promise_signature: Option<DalekSignature>,
/// Timestamp (seconds)
pub timestamp: DateTime<Utc>,
/// Timestamp
pub timestamp: Option<DateTime<Utc>>,
/// Memo
pub memo: Option<PaymentMemo>,
}
impl PaymentInfo {
/// Whether this proof needs fields that are only available in V5
pub(crate) fn requires_v5(&self) -> bool {
self.proof_type != PaymentProofType::Legacy
|| self.sender_address.is_none()
|| self.timestamp.is_some()
|| self.memo.is_some()
}
}
/// Public data for each participant in the slate
#[derive(Debug, Clone)]
pub struct ParticipantData {
@@ -930,24 +1008,15 @@ impl From<&VersionCompatInfo> for VersionCompatInfoV5 {
impl From<&PaymentInfo> for PaymentInfoV5 {
fn from(data: &PaymentInfo) -> PaymentInfoV5 {
let PaymentInfo {
proof_type,
sender_address,
receiver_address,
promise_signature,
timestamp,
memo,
} = data;
let proof_type = *proof_type;
let sender_address = *sender_address;
// Downgrading a proof with no sender address emits an all-zero placeholder key
// (a valid small-order ed25519 point, so from_bytes never panics). It can never be
// accepted as a real sender: every verification path binds the sender address into
// the signed message (contract::proofs::InvoiceProof::verify_promise_signature, and
// the legacy api_impl::owner::verify_payment_proof via internal::tx::payment_proof_message),
// so a proof only validates against the address the verifier supplies, never one
// derived from the proof. From is infallible by design, so we do not fail here.
let saddr = match sender_address {
Some(a) => a,
None => DalekPublicKey::from_bytes(&[0u8; 32]).unwrap(),
};
let receiver_address = *receiver_address;
let promise_signature = *promise_signature;
let timestamp = *timestamp;
@@ -959,7 +1028,8 @@ impl From<&PaymentInfo> for PaymentInfoV5 {
None => None,
};
PaymentInfoV5 {
saddr,
ptype: proof_type,
saddr: sender_address,
raddr: receiver_address,
psig: promise_signature,
ts: timestamp,
@@ -1156,12 +1226,14 @@ impl From<&VersionCompatInfoV5> for VersionCompatInfo {
impl From<&PaymentInfoV5> for PaymentInfo {
fn from(data: &PaymentInfoV5) -> PaymentInfo {
let PaymentInfoV5 {
ptype: proof_type,
saddr: sender_address,
raddr: receiver_address,
psig: promise_signature,
ts: timestamp,
memo,
} = data;
let proof_type = *proof_type;
let sender_address = *sender_address;
let receiver_address = *receiver_address;
let promise_signature = *promise_signature;
@@ -1177,7 +1249,8 @@ impl From<&PaymentInfoV5> for PaymentInfo {
None => None,
};
PaymentInfo {
sender_address: Some(sender_address),
proof_type,
sender_address,
receiver_address,
promise_signature: promise_signature,
timestamp,
@@ -1196,54 +1269,18 @@ impl From<OutputFeaturesV5> for OutputFeatures {
}
///////// V4
impl From<Slate> for SlateV4 {
fn from(slate: Slate) -> SlateV4 {
let Slate {
num_participants: num_parts,
id,
state,
tx: _,
amount,
fee_fields,
kernel_features,
ttl_cutoff_height: ttl,
offset: off,
participant_data,
version_info,
payment_proof,
kernel_features_args,
} = slate.clone();
let participant_data = map_vec!(participant_data, |data| ParticipantDataV4::from(data));
let ver = VersionCompatInfoV4::from(&version_info);
let payment_proof = match payment_proof {
Some(p) => Some(PaymentInfoV4::from(&p)),
None => None,
};
let feat_args = match kernel_features_args {
Some(a) => Some(KernelFeaturesArgsV4::from(&a)),
None => None,
};
let sta = SlateStateV4::from(&state);
SlateV4 {
num_parts,
id,
sta,
coms: (&slate).into(),
amt: amount,
fee: fee_fields,
feat: kernel_features,
ttl,
off,
sigs: participant_data,
ver,
proof: payment_proof,
feat_args,
}
impl TryFrom<Slate> for SlateV4 {
type Error = Error;
fn try_from(slate: Slate) -> Result<SlateV4, Self::Error> {
Self::try_from(&slate)
}
}
impl From<&Slate> for SlateV4 {
fn from(slate: &Slate) -> SlateV4 {
impl TryFrom<&Slate> for SlateV4 {
type Error = Error;
fn try_from(slate: &Slate) -> Result<SlateV4, Self::Error> {
let Slate {
num_participants: num_parts,
id,
@@ -1268,16 +1305,16 @@ impl From<&Slate> for SlateV4 {
let off = offset.clone();
let participant_data = map_vec!(participant_data, |data| ParticipantDataV4::from(data));
let ver = VersionCompatInfoV4::from(version_info);
let payment_proof = match payment_proof {
Some(p) => Some(PaymentInfoV4::from(p)),
None => None,
};
let payment_proof = payment_proof
.as_ref()
.map(PaymentInfoV4::try_from)
.transpose()?;
let sta = SlateStateV4::from(state);
let feat_args = match kernel_features_args {
Some(a) => Some(KernelFeaturesArgsV4::from(a)),
None => None,
};
SlateV4 {
Ok(SlateV4 {
num_parts,
id,
sta,
@@ -1291,7 +1328,7 @@ impl From<&Slate> for SlateV4 {
ver,
proof: payment_proof,
feat_args,
}
})
}
}
@@ -1372,32 +1409,28 @@ impl From<&VersionCompatInfo> for VersionCompatInfoV4 {
}
}
impl From<&PaymentInfo> for PaymentInfoV4 {
fn from(data: &PaymentInfo) -> PaymentInfoV4 {
impl TryFrom<&PaymentInfo> for PaymentInfoV4 {
type Error = Error;
fn try_from(data: &PaymentInfo) -> Result<PaymentInfoV4, Self::Error> {
if data.requires_v5() {
return Err(Error::SlateInvalidDowngrade(
"Payment proof requires slate version 5".to_string(),
));
}
let PaymentInfo {
sender_address,
receiver_address,
promise_signature: receiver_signature,
timestamp: _,
memo: _,
..
} = data;
let sender_address = *sender_address;
// As in the V5 downgrade: an all-zero placeholder key for a missing sender address.
// It can never be accepted as a real sender because the legacy verify path binds the
// sender address into the signed message (internal::tx::payment_proof_message, checked
// in api_impl::owner::verify_payment_proof). V4 carries only the legacy receiver
// signature, so the placeholder is inert. From is infallible by design.
let saddr = match sender_address {
Some(a) => a,
None => DalekPublicKey::from_bytes(&[0u8; 32]).unwrap(),
};
let receiver_address = *receiver_address;
let receiver_signature = *receiver_signature;
PaymentInfoV4 {
saddr,
raddr: receiver_address,
rsig: receiver_signature,
}
Ok(PaymentInfoV4 {
saddr: sender_address.ok_or_else(|| {
Error::SlateInvalidDowngrade("Payment proof requires a sender address".to_string())
})?,
raddr: *receiver_address,
rsig: *receiver_signature,
})
}
}
@@ -1597,10 +1630,11 @@ impl From<&PaymentInfoV4> for PaymentInfo {
let receiver_address = *receiver_address;
let receiver_signature = *receiver_signature;
PaymentInfo {
proof_type: PaymentProofType::Legacy,
sender_address: Some(sender_address),
receiver_address,
promise_signature: receiver_signature,
timestamp: DateTime::from_timestamp(0, 0).unwrap(),
timestamp: None,
memo: None,
}
}
+72 -34
View File
@@ -55,11 +55,10 @@ pub enum SlateVersion {
impl SlateVersion {
/// The lowest version that can represent this slate without losing data, so a slate
/// is only sent as V5 when it has to be. V4 carries the payment proof's sender and
/// receiver addresses and its signature, but neither the timestamp nor the memo, so
/// only a proof using one of those needs V5. A legacy send proof sets neither.
/// receiver addresses and its signature, but not the proof type, timestamp or memo.
pub fn lowest_for(slate: &Slate) -> SlateVersion {
match &slate.payment_proof {
Some(p) if p.memo.is_some() || p.timestamp.timestamp() != 0 => SlateVersion::V5,
Some(p) if p.requires_v5() => SlateVersion::V5,
_ => SlateVersion::V4,
}
}
@@ -89,7 +88,7 @@ impl VersionedSlate {
pub fn into_version(slate: Slate, version: SlateVersion) -> Result<VersionedSlate, Error> {
match version {
SlateVersion::V5 => Ok(VersionedSlate::V5(slate.into())),
SlateVersion::V4 => Ok(VersionedSlate::V4(slate.into())),
SlateVersion::V4 => Ok(VersionedSlate::V4(SlateV4::try_from(slate)?)),
}
}
}
@@ -163,7 +162,10 @@ pub mod tests {
use crate::grin_util::secp::key::PublicKey;
use crate::grin_util::secp::pedersen::{Commitment, RangeProof};
use crate::grin_util::secp::Signature;
use crate::slate::{KernelFeaturesArgs, ParticipantData, PaymentInfo, PaymentMemo};
use crate::slate::{
KernelFeaturesArgs, ParticipantData, PaymentInfo, PaymentMemo, PaymentProofType,
};
use crate::slate_versions::v4::SlateV4;
use crate::slate_versions::v5::{CommitsV5, SlateV5};
use crate::{
slate, Error, Slate, SlateVersion, Slatepacker, SlatepackerArgs, VersionedBinSlate,
@@ -252,9 +254,10 @@ pub mod tests {
let psig = DalekSignature::from_bytes(&[0u8; 64]);
slate_internal.payment_proof = Some(PaymentInfo {
proof_type: PaymentProofType::Invoice,
sender_address: Some(d_pkey.clone()),
receiver_address: d_pkey.clone(),
timestamp: ts.clone(),
timestamp: Some(ts.clone()),
promise_signature: Some(psig),
memo: Some(pm),
});
@@ -262,6 +265,28 @@ pub mod tests {
Ok(slate_internal)
}
fn use_legacy_proof(slate: &mut Slate) {
if let Some(proof) = slate.payment_proof.as_mut() {
proof.proof_type = PaymentProofType::Legacy;
proof.timestamp = None;
proof.memo = None;
}
}
#[test]
fn v5_proof_requires_type() -> Result<(), Error> {
let v5 = SlateV5::from(populate_test_slate()?);
let mut value = serde_json::to_value(v5).unwrap();
let proof = value["proof"].as_object_mut().unwrap();
assert_eq!(proof["ptype"].as_u64(), Some(1));
proof.remove("ptype");
assert!(serde_json::from_value::<SlateV5>(value.clone()).is_err());
value["proof"]["ptype"] = serde_json::json!(3);
assert!(serde_json::from_value::<SlateV5>(value).is_err());
Ok(())
}
#[test]
fn ser_deser_current_slate() -> Result<(), Error> {
let slate_internal = populate_test_slate()?;
@@ -306,14 +331,11 @@ pub mod tests {
let recovered_v4 = packer.get_slate(&packer.create_slatepack(&slate_no_proof)?)?;
assert_eq!(recovered_v4.version_info.version, 4);
// A legacy send proof uses no V5 field: the timestamp is left at the epoch and
// there is no memo. It must stay V4 so V4-only wallets can still read the slate,
// A legacy send proof uses no V5 field. It must stay V4 so V4-only wallets can read it,
// and the proof has to survive the round trip intact.
let mut slate_legacy_proof = populate_test_slate()?;
let mut proof = slate_legacy_proof.payment_proof.clone().unwrap();
proof.timestamp = DateTime::from_timestamp(0, 0).unwrap();
proof.memo = None;
slate_legacy_proof.payment_proof = Some(proof.clone());
use_legacy_proof(&mut slate_legacy_proof);
let proof = slate_legacy_proof.payment_proof.clone().unwrap();
let recovered_legacy = packer.get_slate(&packer.create_slatepack(&slate_legacy_proof)?)?;
assert_eq!(recovered_legacy.version_info.version, 4);
let recovered_proof = recovered_legacy.payment_proof.unwrap();
@@ -321,6 +343,20 @@ pub mod tests {
assert_eq!(recovered_proof.receiver_address, proof.receiver_address);
assert_eq!(recovered_proof.promise_signature, proof.promise_signature);
let mut partial_proof = populate_test_slate()?;
let proof = partial_proof.payment_proof.as_mut().unwrap();
proof.sender_address = None;
proof.timestamp = None;
proof.memo = None;
proof.promise_signature = None;
let recovered = packer.get_slate(&packer.create_slatepack(&partial_proof)?)?;
assert_eq!(recovered.version_info.version, 5);
let proof = recovered.payment_proof.unwrap();
assert!(proof.sender_address.is_none());
assert!(proof.timestamp.is_none());
assert!(SlateV4::try_from(populate_test_slate()?).is_err());
Ok(())
}
@@ -331,24 +367,23 @@ pub mod tests {
// Convert V5 slate into V4 slate, check result
let slate_internal = populate_test_slate()?;
let v5 = VersionedSlate::V5(slate_internal.clone().into());
let v4 = VersionedSlate::V4(slate_internal.into());
let mut legacy_slate = slate_internal;
use_legacy_proof(&mut legacy_slate);
let v4 = VersionedSlate::V4(SlateV4::try_from(legacy_slate)?);
let v5_converted: Slate = v5.into();
let v4_converted: Slate = v4.into();
assert!(v5_converted.payment_proof.as_ref().unwrap().memo.is_some());
// Converted from v4 will not have memos and ts will be zeroed out
// V4 has no memo or timestamp
assert!(v4_converted.payment_proof.as_ref().unwrap().memo.is_none());
assert_eq!(
v4_converted
.payment_proof
.as_ref()
.unwrap()
.timestamp
.timestamp(),
0
);
assert!(v4_converted
.payment_proof
.as_ref()
.unwrap()
.timestamp
.is_none());
Ok(())
}
@@ -368,6 +403,8 @@ pub mod tests {
let mut slate = slate_internal.clone();
if !with_proof {
slate.payment_proof = None;
} else if version == SlateVersion::V4 {
use_legacy_proof(&mut slate);
}
slate.version_info.version = match version {
SlateVersion::V4 => 4,
@@ -393,6 +430,8 @@ pub mod tests {
let mut slate = slate_internal.clone();
if !with_proof {
slate.payment_proof = None;
} else if version == SlateVersion::V4 {
use_legacy_proof(&mut slate);
}
slate.version_info.version = match version {
SlateVersion::V4 => 4,
@@ -420,7 +459,9 @@ pub mod tests {
let v5 = VersionedSlate::V5(slate_internal.clone().into());
let v5_bin: VersionedBinSlate = v5.try_into().unwrap();
let v4 = VersionedSlate::V4(slate_internal.into());
let mut legacy_slate = slate_internal;
use_legacy_proof(&mut legacy_slate);
let v4 = VersionedSlate::V4(SlateV4::try_from(legacy_slate)?);
let v4_bin: VersionedBinSlate = v4.try_into().unwrap();
let v5_versioned: VersionedSlate = v5_bin.into();
@@ -430,17 +471,14 @@ pub mod tests {
let v4_converted: Slate = v4_versioned.into();
assert!(v5_converted.payment_proof.as_ref().unwrap().memo.is_some());
// Converted from v4 will not have memos and ts will be zeroed out
// V4 has no memo or timestamp
assert!(v4_converted.payment_proof.as_ref().unwrap().memo.is_none());
assert_eq!(
v4_converted
.payment_proof
.as_ref()
.unwrap()
.timestamp
.timestamp(),
0
);
assert!(v4_converted
.payment_proof
.as_ref()
.unwrap()
.timestamp
.is_none());
Ok(())
}
+1 -1
View File
@@ -520,7 +520,7 @@ fn slate_v4_serialize_deserialize() {
set_local_chain_type(ChainTypes::Mainnet);
let slate = Slate::blank(1, false);
let mut v4 = SlateV4::from(slate);
let mut v4 = SlateV4::try_from(slate).unwrap();
let keychain = ExtKeychain::from_random_seed(true).unwrap();
let switch = SwitchCommitmentType::Regular;
+13 -10
View File
@@ -24,11 +24,10 @@
//! * `saddr`, i.e. `sender_address` in main Slate becomes optional
//! * `rsig` is renamed to `psig`, corresponding to rename of `receiver_signature` to `promise_signature` in main Slate
//!
//! * `ts` added (`timestamp` in main slate). Serialized as i64 representing epoch time in seconds
//! * `ptype` identifies the payment proof type
//! * `ts` adds an optional timestamp, serialized as seconds since the epoch
//! * `memo` added as optional MemoV5 Struct, which contains:
//! * `memo_type`: u8
//! * 0x00 = payment details directly embedded
//! * 0x01 = Blake2b hash of an arbitrary invoice document
//! * `memo`: [u8;32] the memo data itself
use crate::grin_core::core::FeeFields;
@@ -39,6 +38,7 @@ use crate::grin_util::secp;
use crate::grin_util::secp::key::PublicKey;
use crate::grin_util::secp::pedersen::{Commitment, RangeProof};
use crate::grin_util::secp::Signature;
use crate::slate::PaymentProofType;
use crate::{slate_versions::ser, CbData};
use chrono::prelude::{DateTime, Utc};
use ed25519_dalek::Signature as DalekSignature;
@@ -198,9 +198,7 @@ fn default_part_sig_none() -> Option<Signature> {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct PaymentMemoV5 {
// The type of memo
// 0x00 is directly embedded additional payment details
// 0x01 represents the blake2b hash of an arbitrary invoice document
// Uses the values defined by PaymentMemo
pub memo_type: u8,
// memo data itself
pub memo: [u8; 32],
@@ -209,12 +207,17 @@ pub struct PaymentMemoV5 {
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct PaymentInfoV5 {
#[serde(with = "ser::dalek_pubkey_serde")]
pub saddr: DalekPublicKey,
#[serde(with = "crate::slate::payment_proof_type_serde")]
pub ptype: PaymentProofType,
#[serde(default)]
#[serde(with = "ser::option_dalek_pubkey_serde")]
#[serde(skip_serializing_if = "Option::is_none")]
pub saddr: Option<DalekPublicKey>,
#[serde(with = "ser::dalek_pubkey_serde")]
pub raddr: DalekPublicKey,
#[serde_as(as = "TimestampSeconds<i64>")]
pub ts: DateTime<Utc>,
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ts: Option<DateTime<Utc>>,
#[serde(default = "default_promise_signature_none")]
#[serde(with = "ser::option_dalek_sig_serde")]
#[serde(skip_serializing_if = "Option::is_none")]
+76 -18
View File
@@ -21,6 +21,7 @@ use crate::grin_keychain::BlindingFactor;
use crate::grin_util::secp::key::PublicKey;
use crate::grin_util::secp::pedersen::{Commitment, RangeProof};
use crate::grin_util::secp::Signature;
use crate::slate::PaymentProofType;
use chrono::DateTime;
use ed25519_dalek::Signature as DalekSignature;
use ed25519_dalek::VerifyingKey as DalekPublicKey;
@@ -220,9 +221,22 @@ struct ProofWrapRef<'a>(&'a PaymentInfoV5);
impl<'a> Writeable for ProofWrapRef<'a> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
writer.write_fixed_bytes(self.0.saddr.to_bytes())?;
writer.write_u8(self.0.ptype.as_u8())?;
match self.0.saddr {
Some(a) => {
writer.write_u8(1)?;
writer.write_fixed_bytes(a.to_bytes())?;
}
None => writer.write_u8(0)?,
}
writer.write_fixed_bytes(self.0.raddr.to_bytes())?;
writer.write_i64(self.0.ts.timestamp())?;
match self.0.ts {
Some(ts) => {
writer.write_u8(1)?;
writer.write_i64(ts.timestamp())?;
}
None => writer.write_u8(0)?,
}
match self.0.psig {
Some(s) => {
writer.write_u8(1)?;
@@ -244,40 +258,56 @@ impl<'a> Writeable for ProofWrapRef<'a> {
impl Readable for ProofWrap {
fn read<R: Reader>(reader: &mut R) -> Result<ProofWrap, grin_ser::Error> {
let saddr_bytes = reader.read_fixed_bytes(32)?;
let saddr_bytes = <&[u8; 32]>::try_from(saddr_bytes.as_slice())
let ptype = PaymentProofType::try_from(reader.read_u8()?)
.map_err(|_| grin_ser::Error::CorruptedData)?;
let saddr =
DalekPublicKey::from_bytes(saddr_bytes).map_err(|_| grin_ser::Error::CorruptedData)?;
let saddr = match reader.read_u8()? {
0 => None,
1 => {
let bytes = reader.read_fixed_bytes(32)?;
let bytes = <&[u8; 32]>::try_from(bytes.as_slice())
.map_err(|_| grin_ser::Error::CorruptedData)?;
Some(
DalekPublicKey::from_bytes(bytes)
.map_err(|_| grin_ser::Error::CorruptedData)?,
)
}
_ => return Err(grin_ser::Error::CorruptedData),
};
let raddr_bytes = reader.read_fixed_bytes(32)?;
let raddr_bytes = <&[u8; 32]>::try_from(raddr_bytes.as_slice())
.map_err(|_| grin_ser::Error::CorruptedData)?;
let raddr =
DalekPublicKey::from_bytes(raddr_bytes).map_err(|_| grin_ser::Error::CorruptedData)?;
let ts_raw: i64 = match reader.read_i64() {
Ok(v) => v,
Err(_) => return Err(grin_ser::Error::CorruptedData),
};
let ts = match DateTime::from_timestamp(ts_raw, 0) {
Some(ts) => ts,
None => return Err(grin_ser::Error::CorruptedData),
let ts = match reader.read_u8()? {
0 => None,
1 => Some(
DateTime::from_timestamp(reader.read_i64()?, 0)
.ok_or(grin_ser::Error::CorruptedData)?,
),
_ => return Err(grin_ser::Error::CorruptedData),
};
let psig = match reader.read_u8()? {
0 => None,
1 | _ => Some(
1 => Some(
DalekSignature::try_from(&reader.read_fixed_bytes(64)?[..])
.map_err(|_| grin_ser::Error::CorruptedData)?,
),
_ => return Err(grin_ser::Error::CorruptedData),
};
let memo = match reader.read_u8()? {
0 => None,
1 | _ => Some(PaymentMemoV5 {
1 => Some(PaymentMemoV5 {
memo_type: reader.read_u8()?,
memo: reader.read_fixed_bytes(32)?.try_into().unwrap_or_default(),
memo: reader
.read_fixed_bytes(32)?
.try_into()
.map_err(|_| grin_ser::Error::CorruptedData)?,
}),
_ => return Err(grin_ser::Error::CorruptedData),
};
Ok(ProofWrap(PaymentInfoV5 {
ptype,
saddr,
raddr,
ts,
@@ -287,6 +317,33 @@ impl Readable for ProofWrap {
}
}
#[test]
fn rejects_invalid_proof_fields() {
use crate::grin_util::from_hex;
let bytes =
from_hex("d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb").unwrap();
let key = DalekPublicKey::from_bytes(bytes.as_slice().try_into().unwrap()).unwrap();
let proof = PaymentInfoV5 {
ptype: PaymentProofType::Invoice,
saddr: None,
raddr: key,
ts: None,
psig: None,
memo: None,
};
let mut encoded = Vec::new();
grin_ser::serialize_default(&mut encoded, &ProofWrapRef(&proof)).unwrap();
// ptype followed by the four optional-field flags
for (offset, value) in [(0, 3), (1, 2), (34, 2), (35, 2), (36, 2)] {
let mut invalid = encoded.clone();
invalid[offset] = value;
let result: Result<ProofWrap, _> = grin_ser::deserialize_default(&mut invalid.as_slice());
assert!(result.is_err());
}
}
#[derive(Debug, Clone)]
pub struct SlateV5Bin(pub SlateV5);
@@ -521,9 +578,10 @@ fn slate_v5_serialize_deserialize() {
memo: [9; 32],
};
v5.proof = Some(PaymentInfoV5 {
ptype: PaymentProofType::Invoice,
raddr: d_pkey.clone(),
saddr: d_pkey.clone(),
ts: ts.clone(),
saddr: Some(d_pkey.clone()),
ts: Some(ts.clone()),
psig: None,
memo: Some(pm),
});
+25
View File
@@ -797,6 +797,31 @@ pub struct StoredProofInfo {
pub sender_part_sig: Option<Signature>,
}
impl StoredProofInfo {
pub(crate) fn new(
receiver_address: DalekPublicKey,
receiver_signature: Option<DalekSignature>,
sender_address: DalekPublicKey,
sender_address_path: u32,
sender_signature: Option<DalekSignature>,
) -> Self {
Self {
receiver_address,
receiver_signature,
sender_address_path,
sender_address,
sender_signature,
proof_type: None,
receiver_public_nonce: None,
receiver_public_excess: None,
timestamp: None,
memo: None,
promise_signature: None,
sender_part_sig: None,
}
}
}
impl ser::Writeable for StoredProofInfo {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)