fix early proof export and slatepack account errors

This commit is contained in:
wiesche
2026-09-15 20:28:20 +02:00
parent 049e73ac07
commit a1d683294b
10 changed files with 290 additions and 32 deletions
+103 -17
View File
@@ -27,8 +27,8 @@ use crate::libwallet::contract::types::{
};
use crate::libwallet::contract::{can_finalize, initial_net_change};
use crate::libwallet::{
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState,
SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
self, EarlyPaymentProof, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate,
SlateState, SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
};
use crate::util::secp::key::SecretKey;
use crate::util::{Mutex, ZeroingString};
@@ -774,7 +774,22 @@ where
let was_encrypted = slatepack.is_encrypted();
if was_encrypted {
let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?;
slatepack.try_decrypt_payload(Some(&dec_key))?;
if let Err(e) = slatepack.try_decrypt_payload(Some(&dec_key)) {
// tell the user which account can decrypt it
let account = libwallet::api_impl::owner::slatepack_account(
owner_api.wallet_inst.clone(),
keychain_mask,
&slatepack,
)?;
return Err(match account {
Some(label) => libwallet::Error::SlatepackDecryption(format!(
"it is encrypted for account '{}' of this wallet, select it with --account {}",
label, label
))
.into(),
None => e.into(),
});
}
}
let slate = packer.get_slate(&slatepack)?;
Ok((slate, slatepack.sender, was_encrypted))
@@ -1554,6 +1569,13 @@ pub struct ProofExportArgs {
pub tx_slate_id: Option<Uuid>,
}
/// Exported early proof, with the receiver address needed to verify it
#[derive(Serialize, Deserialize)]
struct EarlyProofFile {
recipient_address: SlatepackAddress,
proof: EarlyPaymentProof,
}
pub fn proof_export<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
@@ -1564,14 +1586,34 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let result = owner_api.retrieve_payment_proof(keychain_mask, true, args.id, args.tx_slate_id);
// early proofs have no sender signature
let (_, txs) = owner_api.retrieve_txs(keychain_mask, false, args.id, args.tx_slate_id, None)?;
let early_recipient = txs
.first()
.and_then(|tx| tx.payment_proof.as_ref())
.filter(|p| p.proof_type.is_some())
.map(|p| SlatepackAddress::new(&p.receiver_address));
let result = match early_recipient {
Some(recipient_address) => owner_api
.retrieve_payment_proof_early(keychain_mask, true, args.id, args.tx_slate_id)
.map(|proof| {
json::to_string_pretty(&EarlyProofFile {
recipient_address,
proof,
})
.unwrap()
}),
None => owner_api
.retrieve_payment_proof(keychain_mask, true, args.id, args.tx_slate_id)
.map(|p| json::to_string_pretty(&p).unwrap()),
};
match result {
Ok(p) => {
// actually export proof
let mut proof_file = File::create(args.output_file.clone())
.map_err(|e| Error::GenericError(format!("{}", e)))?;
proof_file
.write_all(json::to_string_pretty(&p).unwrap().as_bytes())
.write_all(p.as_bytes())
.map_err(|e| Error::GenericError(format!("{}", e)))?;
proof_file
.sync_all()
@@ -1616,6 +1658,13 @@ where
proof_f
.read_to_string(&mut proof)
.map_err(|e| Error::GenericError(format!("{}", e)))?;
// early proof file
let is_early = json::from_str::<json::Value>(&proof)
.map(|v| v.get("proof").is_some())
.unwrap_or(false);
if is_early {
return proof_verify_early(owner_api, keychain_mask, &proof);
}
// read
let proof: PaymentProof = match json::from_str(&proof) {
Ok(p) => p,
@@ -1628,18 +1677,7 @@ where
let result = owner_api.verify_payment_proof(keychain_mask, &proof);
match result {
Ok((iam_sender, iam_recipient)) => {
println!("Payment proof's signatures are valid.");
if iam_sender {
println!("The proof's sender address belongs to this wallet.");
}
if iam_recipient {
println!("The proof's recipient address belongs to this wallet.");
}
if !iam_recipient && !iam_sender {
println!(
"Neither the proof's sender nor recipient address belongs to this wallet."
);
}
print_proof_verified(iam_sender, iam_recipient);
Ok(())
}
Err(e) => {
@@ -1649,6 +1687,54 @@ where
}
}
fn print_proof_verified(iam_sender: bool, iam_recipient: bool) {
println!("Payment proof's signatures are valid.");
if iam_sender {
println!("The proof's sender address belongs to this wallet.");
}
if iam_recipient {
println!("The proof's recipient address belongs to this wallet.");
}
if !iam_recipient && !iam_sender {
println!("Neither the proof's sender nor recipient address belongs to this wallet.");
}
}
fn proof_verify_early<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
proof: &str,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let file: EarlyProofFile = json::from_str(proof).map_err(|e| {
error!("Unable to parse payment proof file: {}", e);
Error::from(libwallet::Error::PaymentProofParsing(format!("{}", e)))
})?;
let result = {
let mut w_lock = owner_api.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
libwallet::api_impl::foreign::verify_payment_proof_early(
w,
&file.recipient_address.pub_key,
&file.proof,
)
};
if let Err(e) = result {
error!("Proof not valid: {}", e);
return Err(Error::from(e));
}
let my_address = owner_api.get_slatepack_address(keychain_mask, 0)?.pub_key;
print_proof_verified(
file.proof.sender_address == my_address,
file.recipient_address.pub_key == my_address,
);
Ok(())
}
/// Create new contract command arguments
#[derive(Clone)]
pub struct ContractNewArgs {
+26 -10
View File
@@ -598,14 +598,7 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> {
Some(f) => f.fee(), // apply fee mask past HF4
None => 0,
};
let amount = if tx.amount_credited >= tx.amount_debited {
core::amount_to_hr_string(tx.amount_credited - tx.amount_debited, true)
} else {
format!(
"{}",
core::amount_to_hr_string(tx.amount_debited - tx.amount_credited - fee, true)
)
};
let amount = core::amount_to_hr_string(proof_amount(tx, pp.proof_type.is_some(), fee), true);
let sender_signature = match pp.sender_signature {
Some(s) => {
@@ -647,6 +640,15 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> {
Ok(())
}
/// Contract tx logs store the net amount, so early proofs don't subtract the fee
fn proof_amount(tx: &TxLogEntry, early_proof: bool, fee: u64) -> u64 {
if early_proof || tx.amount_credited >= tx.amount_debited {
tx.amount_credited.abs_diff(tx.amount_debited)
} else {
tx.amount_debited - tx.amount_credited - fee
}
}
/// Display a summary of a contract slate
pub fn contract_view(slate: &Slate, view: &ContractView, was_encrypted: bool) {
println!("\n____ Contract ____\n");
@@ -766,11 +768,12 @@ fn format_net_change(change: i64) -> String {
#[cfg(test)]
mod tests {
use super::{contract_view_table, format_net_change};
use super::{contract_view_table, format_net_change, proof_amount};
use crate::core::core::{FeeFields, Input, Inputs, Output, OutputFeatures, Transaction};
use crate::core::global;
use crate::keychain::Identifier;
use crate::libwallet::contract::types::ContractView;
use crate::libwallet::Slate;
use crate::libwallet::{Slate, TxLogEntry, TxLogEntryType};
use crate::util::secp::pedersen::{Commitment, RangeProof};
fn table_value(table: &prettytable::Table, label: &str) -> String {
@@ -794,6 +797,19 @@ mod tests {
assert_eq!(format_net_change(0), "+0.000000000");
}
#[test]
fn proof_amount_legacy_and_early() {
let mut tx = TxLogEntry::new(Identifier::zero(), TxLogEntryType::TxSent, 0);
// legacy send
tx.amount_debited = 1_500_000_000;
tx.amount_credited = 477_000_000;
assert_eq!(proof_amount(&tx, false, 23_000_000), 1_000_000_000);
// contract send
tx.amount_debited = 200_000_000;
tx.amount_credited = 0;
assert_eq!(proof_amount(&tx, true, 11_750_000), 200_000_000);
}
#[test]
fn contract_view_rows() {
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);