Files
grin-wallet/libwallet/src/payment_proof.rs
T
2026-09-13 08:20:22 +02:00

714 lines
23 KiB
Rust

// Copyright 2026 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Early payment proof creation and verification.
use crate::blake2::blake2b::blake2b;
use crate::grin_core::libtx::aggsig;
use crate::grin_core::libtx::secp_ser;
use crate::grin_core::ser as grin_ser;
use crate::grin_core::ser::{Writeable, Writer};
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::{PaymentMemo, PaymentProofType, Slate};
use crate::slate_versions::ser::{dalek_pubkey_serde, option_dalek_sig_serde, option_pubkey_serde};
use crate::Error;
use ed25519_dalek::Signature as DalekSignature;
use ed25519_dalek::SigningKey as DalekSecretKey;
use ed25519_dalek::VerifyingKey as DalekPublicKey;
use ed25519_dalek::{Signer, Verifier};
use grin_util::secp::Message;
pub(crate) fn check_proof_type(proof_type: &PaymentProofType) -> Result<(), Error> {
match proof_type {
PaymentProofType::Invoice | PaymentProofType::SenderNonce => Ok(()),
_ => Err(Error::GenericError(
"Unsupported early payment proof type".to_string(),
)),
}
}
fn write_amount<W: Writer>(writer: &mut W, amount: u64) -> Result<(), grin_ser::Error> {
let amount_bytes = amount.to_be_bytes();
if amount_bytes[0] != 0 {
return Err(grin_ser::Error::UnexpectedData {
expected: vec![0],
received: vec![amount_bytes[0]],
});
}
writer.write_fixed_bytes(&amount_bytes[1..])
}
fn verify_receiver_sig(
secp: &Secp256k1,
sig: &Signature,
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(())
}
fn recover_receiver_sig(
secp: &Secp256k1,
excess_sig: &Signature,
sender_sig: &Signature,
receiver_nonce: &PublicKey,
) -> Result<Signature, Error> {
let mut scalar = SecretKey::from_slice(secp, &excess_sig[32..])?;
let mut sender_scalar = SecretKey::from_slice(secp, &sender_sig[32..])?;
sender_scalar.neg_assign(secp)?;
scalar.add_assign(secp, &sender_scalar)?;
let nonce = receiver_nonce.serialize_vec(secp, true);
let mut signature = [0; 64];
signature[..32].copy_from_slice(&nonce[1..]);
signature[32..].copy_from_slice(&scalar.0);
Signature::from_raw_data(&signature).map_err(Error::from)
}
/// All elements required to validate a proof within a single struct
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ProofWitness {
/// Kernel index, supplied so verifiers can look up kernel
/// without an expensive lookup operation
#[serde(with = "secp_ser::string_or_u64")]
pub kernel_index: u64,
/// Kernel commitment, supplied so prover can recompute index
/// if required after a reorg
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::commitment_from_hex"
)]
pub kernel_commitment: Commitment,
/// sender partial signature, used to recover receiver partial signature
#[serde(with = "secp_ser::sig_serde")]
pub sender_partial_sig: Signature,
/// Untweaked sender nonce used by sender-nonce proofs
#[serde(
default,
with = "option_pubkey_serde",
skip_serializing_if = "Option::is_none"
)]
pub sender_public_nonce: Option<PublicKey>,
}
/// Early payment proof extracted from a slate and stored transaction data
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct EarlyPaymentProof {
/// Proof type
pub proof_type: PaymentProofType,
/// amount
#[serde(with = "secp_ser::string_or_u64")]
pub amount: u64,
/// receiver's public nonce from signing
#[serde(with = "secp_ser::pubkey_serde")]
pub receiver_public_nonce: PublicKey,
/// receiver's public excess from signing
#[serde(with = "secp_ser::pubkey_serde")]
pub receiver_public_excess: PublicKey,
/// Sender's address
#[serde(with = "dalek_pubkey_serde")]
pub sender_address: DalekPublicKey,
/// Timestamp provided by recipient when signing
pub timestamp: i64,
/// Optional payment memo
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<PaymentMemo>,
/// Not serialized in binary format
#[serde(with = "option_dalek_sig_serde")]
pub promise_signature: Option<DalekSignature>,
/// Not serialized in binary format, just a convenient place to insert
/// the witness kernel commitment index
#[serde(skip_serializing_if = "Option::is_none")]
pub witness_data: Option<ProofWitness>,
}
struct PromiseBin<'a>(&'a EarlyPaymentProof);
impl Writeable for PromiseBin<'_> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
writer.write_u8(self.0.proof_type.as_u8())?;
match self.0.proof_type {
PaymentProofType::Invoice => write_amount(writer, self.0.amount)?,
PaymentProofType::SenderNonce => writer.write_fixed_bytes(&[0; 7])?,
PaymentProofType::Legacy => {
return Err(grin_ser::Error::CorruptedData);
}
}
{
let static_secp = static_secp_instance();
let static_secp = static_secp.lock();
writer.write_fixed_bytes(
self.0
.receiver_public_nonce
.serialize_vec(&static_secp, true),
)?;
writer.write_fixed_bytes(
self.0
.receiver_public_excess
.serialize_vec(&static_secp, true),
)?;
}
writer.write_fixed_bytes(self.0.sender_address.as_bytes())?;
if self.0.proof_type == PaymentProofType::Invoice {
writer.write_i64(self.0.timestamp)?;
let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or("");
writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes())?;
}
Ok(())
}
}
struct SenderNonceMessage<'a>(&'a EarlyPaymentProof);
impl Writeable for SenderNonceMessage<'_> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
writer.write_u8(PaymentProofType::SenderNonce.as_u8())?;
write_amount(writer, self.0.amount)?;
writer.write_i64(self.0.timestamp)?;
let memo = self.0.memo.as_ref().map(PaymentMemo::as_str).unwrap_or("");
writer.write_fixed_bytes(blake2b(32, &[], memo.as_bytes()).as_bytes())
}
}
impl EarlyPaymentProof {
pub(crate) fn new(
slate: &Slate,
participant_index: usize,
proof_type: PaymentProofType,
sender_address: DalekPublicKey,
timestamp: i64,
memo: Option<PaymentMemo>,
) -> Result<Self, Error> {
// Check before indexing participant_data.
if participant_index >= slate.participant_data.len() {
return Err(Error::GenericError(format!(
"Participant index {} out of range for slate with {} participant(s)",
participant_index,
slate.participant_data.len()
)));
}
Ok(Self {
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,
sender_address,
timestamp,
memo,
promise_signature: None,
witness_data: None,
})
}
/// Extract as much proof data as possible from a slate
pub fn from_slate(
slate: &Slate,
participant_index: usize,
sender_address: Option<DalekPublicKey>,
) -> Result<Self, Error> {
// Sender address is either provided or in slate (or error)
let sender_address = match sender_address {
Some(a) => a,
None => {
if let Some(ref p) = slate.payment_proof {
if let Some(a) = p.sender_address {
a
} else {
return Err(Error::NoSenderAddressProvided);
}
} else {
return Err(Error::NoSenderAddressProvided);
}
}
};
let (proof_type, timestamp, memo, promise_signature) = match slate.payment_proof.as_ref() {
Some(p) => (
p.proof_type,
p.timestamp
.ok_or_else(|| Error::PaymentProof("Missing proof timestamp".to_string()))?
.timestamp(),
p.memo.clone(),
p.promise_signature.clone(),
),
None => (PaymentProofType::Invoice, 0, None, None),
};
let mut proof = Self::new(
slate,
participant_index,
proof_type,
sender_address,
timestamp,
memo,
)?;
proof.promise_signature = promise_signature;
Ok(proof)
}
/// Commit the sender nonce before sharing its public value
/// Save the base nonce on the first call and verify it on retries
pub fn commit_sender_nonce(
&self,
secp: &Secp256k1,
secret_nonce: &mut SecretKey,
base_public_nonce: &mut Option<PublicKey>,
) -> Result<(), Error> {
if self.proof_type != PaymentProofType::SenderNonce {
return Err(Error::PaymentProofValidation(
"Sender nonce commitment requires a sender-nonce proof".into(),
));
}
if let Some(base_public) = base_public_nonce.as_ref() {
let mut expected = base_public.clone();
expected.add_exp_assign(secp, &sender_nonce_tweak(secp, base_public, self)?)?;
if PublicKey::from_secret_key(secp, secret_nonce)? != expected {
return Err(Error::PaymentProofValidation(
"Sender nonce proof details changed".into(),
));
}
} else {
let base_public = PublicKey::from_secret_key(secp, secret_nonce)?;
let mut committed_nonce = secret_nonce.clone();
committed_nonce.add_assign(secp, &sender_nonce_tweak(secp, &base_public, self)?)?;
*secret_nonce = committed_nonce;
*base_public_nonce = Some(base_public);
}
Ok(())
}
/// Sign the payment promise
pub fn sign(&self, sec_key: &SecretKey) -> Result<(DalekSignature, DalekPublicKey), Error> {
let d_skey = DalekSecretKey::from_bytes(&sec_key.0);
let pub_key = d_skey.verifying_key();
let mut sig_data_bin = Vec::new();
grin_ser::serialize_default(&mut sig_data_bin, &PromiseBin(self)).map_err(|e| {
Error::GenericError(format!("Payment proof serialization failed: {}", e))
})?;
Ok((d_skey.sign(&sig_data_bin), pub_key))
}
/// Verify the receiver's promise signature
/// Sender-nonce payment details are only checked by `verify_witness`
pub fn verify_promise_signature(
&self,
recipient_address: &DalekPublicKey,
) -> Result<(), Error> {
check_proof_type(&self.proof_type)?;
let promise_signature = self
.promise_signature
.as_ref()
.ok_or_else(|| Error::PaymentProofValidation("Missing promise signature".into()))?;
// Rebuild message
let mut sig_data_bin = Vec::new();
grin_ser::serialize_default(&mut sig_data_bin, &PromiseBin(self)).map_err(|e| {
Error::GenericError(format!("Payment proof serialization failed: {}", e))
})?;
if recipient_address
.verify(&sig_data_bin, promise_signature)
.is_err()
{
return Err(Error::PaymentProof(
"Invalid recipient signature".to_owned(),
));
};
Ok(())
}
/// Verify signature and proof against a given kernel message (kernel lookup is beyond the scope
/// of this module)
pub fn verify_witness(
&self,
recipient_address: &DalekPublicKey,
excess_sig: &Signature,
msg: &Message,
) -> Result<(), Error> {
let wd = self
.witness_data
.as_ref()
.ok_or_else(|| Error::PaymentProofValidation("Missing witness data".into()))?;
self.verify_promise_signature(recipient_address)?;
{
let static_secp = static_secp_instance();
let static_secp = static_secp.lock();
// Retrieve the public nonce sum from the kernel excess signature
let mut pub_nonce_sum_bytes = [3u8; 33];
pub_nonce_sum_bytes[1..33].copy_from_slice(&excess_sig[0..32]);
let pub_nonce_sum = PublicKey::from_slice(&static_secp, &pub_nonce_sum_bytes)?;
let receiver_part_sigs = if self.proof_type == PaymentProofType::SenderNonce {
let sender_nonce = wd.sender_public_nonce.as_ref().ok_or_else(|| {
Error::PaymentProofValidation("Missing sender nonce witness".into())
})?;
let mut expected_sender_nonce = sender_nonce.clone();
expected_sender_nonce.add_exp_assign(
&static_secp,
&sender_nonce_tweak(&static_secp, sender_nonce, self)?,
)?;
let expected_sum = PublicKey::from_combination(
&static_secp,
vec![&expected_sender_nonce, &self.receiver_public_nonce],
)?;
if expected_sum.serialize_vec(&static_secp, true)[1..] != excess_sig[0..32] {
return Err(Error::PaymentProofValidation(
"Sender nonce does not commit to the payment details".into(),
));
}
let sender_nonce = expected_sender_nonce.serialize_vec(&static_secp, true);
if wd.sender_partial_sig[0..32] != sender_nonce[1..] {
return Err(Error::PaymentProofValidation(
"Sender partial signature nonce does not match the proof".into(),
));
}
vec![recover_receiver_sig(
&static_secp,
excess_sig,
&wd.sender_partial_sig,
&self.receiver_public_nonce,
)?]
} else {
let (signature, alternative) =
aggsig::subtract_signature(&static_secp, excess_sig, &wd.sender_partial_sig)?;
alternative.into_iter().chain(Some(signature)).collect()
};
// Retrieve the public key sum from the kernel excess
let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?;
if !receiver_part_sigs.iter().any(|signature| {
verify_receiver_sig(
&static_secp,
signature,
&self.receiver_public_nonce,
&pub_nonce_sum,
&self.receiver_public_excess,
&pub_blind_sum,
&msg,
)
.is_ok()
}) {
return Err(Error::PaymentProofValidation(
"Receiver signature does not match the promise".into(),
));
}
}
Ok(())
}
}
fn sender_nonce_tweak(
secp: &Secp256k1,
sender_nonce: &PublicKey,
proof: &EarlyPaymentProof,
) -> Result<SecretKey, Error> {
let mut message = Vec::new();
grin_ser::serialize_default(&mut message, &SenderNonceMessage(proof)).map_err(|e| {
Error::GenericError(format!("Sender nonce message serialization failed: {}", e))
})?;
let nonce = sender_nonce.serialize_vec(secp, true);
let mut data = Vec::with_capacity(nonce.len() + message.len());
data.extend_from_slice(&nonce);
data.extend_from_slice(&message);
let hash = blake2b(32, &[], &data);
SecretKey::from_slice(secp, hash.as_bytes()).map_err(Error::from)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::slate_versions::tests::populate_test_slate;
use chrono::DateTime;
#[test]
fn rejects_unsupported_proofs() {
assert!(check_proof_type(&PaymentProofType::Invoice).is_ok());
assert!(check_proof_type(&PaymentProofType::Legacy).is_err());
assert!(check_proof_type(&PaymentProofType::SenderNonce).is_ok());
}
#[test]
fn slate_proof_data() -> Result<(), Error> {
let mut slate = populate_test_slate()?;
slate.num_participants = 2;
let mut legacy_proof = slate.payment_proof.take().unwrap();
let sender_address = legacy_proof.sender_address.unwrap();
assert!(slate.verify_payment_proof_sig(1, None).is_err());
let secp = Secp256k1::new();
let receiver_key = SecretKey::from_slice(&secp, &[7; 32])?;
assert!(slate
.add_payment_proof_data(
1,
PaymentProofType::SenderNonce,
sender_address,
&receiver_key,
None,
)
.is_err());
let mut invalid_sender_nonce = slate.clone();
invalid_sender_nonce.state = crate::slate::SlateState::Invoice1;
assert!(invalid_sender_nonce
.add_payment_proof_data(
0,
PaymentProofType::SenderNonce,
sender_address,
&receiver_key,
None,
)
.is_err());
invalid_sender_nonce.participant_data.truncate(1);
invalid_sender_nonce.num_participants = 1;
assert!(invalid_sender_nonce
.add_payment_proof_data(
0,
PaymentProofType::SenderNonce,
sender_address,
&receiver_key,
None,
)
.is_err());
legacy_proof.receiver_address = DalekSecretKey::from_bytes(&receiver_key.0).verifying_key();
legacy_proof.proof_type = PaymentProofType::Legacy;
legacy_proof.timestamp = None;
slate.payment_proof = Some(legacy_proof.clone());
assert!(slate
.add_payment_proof_data(
1,
PaymentProofType::Invoice,
sender_address,
&receiver_key,
None,
)
.is_err());
legacy_proof.promise_signature = None;
slate.payment_proof = Some(legacy_proof.clone());
let other_sender_key = SecretKey::from_slice(&secp, &[8; 32])?;
let other_sender_address = DalekSecretKey::from_bytes(&other_sender_key.0).verifying_key();
assert!(slate
.add_payment_proof_data(
1,
PaymentProofType::Invoice,
other_sender_address,
&receiver_key,
None,
)
.is_err());
assert_eq!(slate.payment_proof, Some(legacy_proof.clone()));
let other_receiver_key = SecretKey::from_slice(&secp, &[9; 32])?;
assert!(slate
.add_payment_proof_data(
1,
PaymentProofType::Invoice,
sender_address,
&other_receiver_key,
None,
)
.is_err());
assert_eq!(slate.payment_proof, Some(legacy_proof));
slate.add_payment_proof_data(
1,
PaymentProofType::Invoice,
sender_address,
&receiver_key,
Some(PaymentMemo::new("invoice 42".to_string())?),
)?;
slate.verify_payment_proof_sig(1, None)?;
assert!(slate
.add_payment_proof_data(
1,
PaymentProofType::Invoice,
sender_address,
&receiver_key,
None,
)
.is_err());
slate.payment_proof.as_mut().unwrap().memo =
Some(PaymentMemo::new("invoice 43".to_string())?);
assert!(slate.verify_payment_proof_sig(1, None).is_err());
Ok(())
}
#[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 proof_promise() -> Result<(), Error> {
use crate::grin_util::ToHex;
let mut slate = populate_test_slate()?;
slate.amount |= 0xFF00_0000_0000_0000;
// Bin serialization doesn't include promise sig as it's used to create signature data
slate.payment_proof.as_mut().unwrap().promise_signature = None;
// Should fail, amount too big
let proof = EarlyPaymentProof::from_slate(&slate, 1, None)?;
let mut vec = Vec::new();
assert!(grin_ser::serialize_default(&mut vec, &PromiseBin(&proof)).is_err());
// Should be okay now
slate.amount = 1234;
slate.payment_proof.as_mut().unwrap().timestamp = DateTime::from_timestamp(123456789, 0);
let mut proof = EarlyPaymentProof::from_slate(&slate, 1, None)?;
let secp = Secp256k1::new();
proof.receiver_public_nonce =
PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&secp, &[1; 32])?)?;
proof.receiver_public_excess =
PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&secp, &[2; 32])?)?;
let mut vec = Vec::new();
grin_ser::serialize_default(&mut vec, &PromiseBin(&proof)).expect("Serialization Failed");
assert_eq!(
vec.to_hex(),
"01000000000004d2031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb00000000075bcd15c60e4f851ed93b6641571e6810f608fe42ee566f7e199915061ea13b3b04e772"
);
let proof_key = SecretKey::from_slice(&Secp256k1::new(), &[7; 32])?;
let (signature, recipient) = proof.sign(&proof_key)?;
proof.promise_signature = Some(signature);
proof.verify_promise_signature(&recipient)?;
proof.memo = Some(PaymentMemo::new("changed details".to_string())?);
assert!(proof.verify_promise_signature(&recipient).is_err());
let mut 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 mut secret_nonce = SecretKey::from_slice(&secp, &[9; 32])?;
let mut public_nonce = None;
assert!(proof
.commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce)
.is_err());
sender_nonce.commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce)?;
assert_eq!(public_nonce, Some(base));
let committed_nonce = secret_nonce.clone();
sender_nonce.commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce)?;
assert_eq!(secret_nonce, committed_nonce);
let mut changed = sender_nonce.clone();
changed.memo = Some(PaymentMemo::new("changed details".into())?);
assert!(changed
.commit_sender_nonce(&secp, &mut secret_nonce, &mut public_nonce)
.is_err());
let (signature, recipient) = sender_nonce.sign(&proof_key)?;
sender_nonce.promise_signature = Some(signature);
sender_nonce.amount += 1;
sender_nonce.timestamp += 1;
sender_nonce.verify_promise_signature(&recipient)?;
let mut wrong_type = proof;
wrong_type.proof_type = PaymentProofType::Legacy;
assert!(wrong_type
.verify_promise_signature(&slate.payment_proof.unwrap().receiver_address)
.is_err());
Ok(())
}
#[test]
fn memo_limit() {
assert!(PaymentMemo::new("a".repeat(PaymentMemo::MAX_LEN)).is_ok());
assert!(PaymentMemo::new("a".repeat(PaymentMemo::MAX_LEN + 1)).is_err());
assert!(PaymentMemo::new("ä".repeat(PaymentMemo::MAX_LEN / 2)).is_ok());
assert!(PaymentMemo::new("ä".repeat(PaymentMemo::MAX_LEN / 2 + 1)).is_err());
}
}