Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions fuzz/fuzz-fake-hashes/src/bin/payer_proof_deser_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// This file is Copyright its original authors, visible in version control
// history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.

// This file is auto-generated by gen_target.sh based on target_template.txt
// To modify it, modify target_template.txt and run gen_target.sh instead.

#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
#![cfg_attr(rustfmt, rustfmt_skip)]

#[cfg(not(fuzzing))]
compile_error!("Fuzz targets need cfg=fuzzing");

#[cfg(not(hashes_fuzz))]
compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))");

#[cfg(not(secp256k1_fuzz))]
compile_error!("Fuzz targets need cfg=secp256k1_fuzz");

extern crate lightning_fuzz;
use lightning_fuzz::payer_proof_deser::*;
use lightning_fuzz::utils::test_logger;

#[cfg(feature = "afl")]
#[macro_use] extern crate afl;
#[cfg(feature = "afl")]
fn main() {
fuzz!(|data| {
payer_proof_deser_test(&data, test_logger::DevNull {});
});
}

#[cfg(feature = "honggfuzz")]
#[macro_use] extern crate honggfuzz;
#[cfg(feature = "honggfuzz")]
fn main() {
loop {
fuzz!(|data| {
payer_proof_deser_test(&data, test_logger::DevNull {});
});
}
}

#[cfg(feature = "libfuzzer_fuzz")]
#[macro_use] extern crate libfuzzer_sys;
#[cfg(feature = "libfuzzer_fuzz")]
fuzz_target!(|data: &[u8]| {
payer_proof_deser_test(data, test_logger::DevNull {});
});

#[cfg(feature = "stdin_fuzz")]
fn main() {
use std::io::Read;

// On macOS, panic=abort causes the process to send SIGABRT which can leave it
// stuck in an uninterruptible state due to the ReportCrash daemon. Using
// process::exit in a panic hook avoids this by terminating cleanly.
#[cfg(target_os = "macos")]
std::panic::set_hook(Box::new(|panic_info| {
use std::io::Write;
let _ = std::io::stdout().flush();
eprintln!("{}\n{}", panic_info, std::backtrace::Backtrace::force_capture());
let _ = std::io::stderr().flush();
std::process::exit(1);
}));

let mut data = Vec::with_capacity(8192);
std::io::stdin().read_to_end(&mut data).unwrap();
payer_proof_deser_test(&data, test_logger::Stdout {});
}

#[test]
fn run_test_cases() {
use std::fs;
use std::io::Read;
use lightning_fuzz::utils::test_logger::StringBuffer;

use std::sync::{atomic, Arc};
{
let data: Vec<u8> = vec![0];
payer_proof_deser_test(&data, test_logger::DevNull {});
}
let mut threads = Vec::new();
let threads_running = Arc::new(atomic::AtomicUsize::new(0));
if let Ok(tests) = fs::read_dir("../test_cases/payer_proof_deser") {
for test in tests {
let mut data: Vec<u8> = Vec::new();
let path = test.unwrap().path();
fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
threads_running.fetch_add(1, atomic::Ordering::AcqRel);

let thread_count_ref = Arc::clone(&threads_running);
let main_thread_ref = std::thread::current();
threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
std::thread::spawn(move || {
let string_logger = StringBuffer::new();

let panic_logger = string_logger.clone();
let res = if ::std::panic::catch_unwind(move || {
payer_proof_deser_test(&data, panic_logger);
}).is_err() {
Some(string_logger.into_string())
} else { None };
thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
main_thread_ref.unpark();
res
})
));
while threads_running.load(atomic::Ordering::Acquire) > 32 {
std::thread::park();
}
}
}
let mut failed_outputs = Vec::new();
for (test, thread) in threads.drain(..) {
if let Some(output) = thread.join().unwrap() {
println!("\nOutput of {}:\n{}\n", test, output);
failed_outputs.push(test);
}
}
if !failed_outputs.is_empty() {
println!("Test cases which failed: ");
for case in failed_outputs {
println!("{}", case);
}
panic!();
}
}
1 change: 1 addition & 0 deletions fuzz/src/bin/gen_target.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ GEN_FAKE_HASHES_TEST onion_message
GEN_FAKE_HASHES_TEST peer_crypt
GEN_FAKE_HASHES_TEST process_network_graph
GEN_FAKE_HASHES_TEST process_onion_failure
GEN_FAKE_HASHES_TEST payer_proof_deser
GEN_FAKE_HASHES_TEST refund_deser
GEN_FAKE_HASHES_TEST router
GEN_FAKE_HASHES_TEST zbase32
Expand Down
1 change: 1 addition & 0 deletions fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub mod lsps_message;
pub mod offer_deser;
pub mod onion_hop_data;
pub mod onion_message;
pub mod payer_proof_deser;
pub mod peer_crypt;
pub mod process_network_graph;
pub mod process_onion_failure;
Expand Down
31 changes: 31 additions & 0 deletions fuzz/src/payer_proof_deser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is Copyright its original authors, visible in version control
// history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.

use crate::utils::test_logger;
use core::convert::TryFrom;
use lightning::offers::payer_proof::PayerProof;
use lightning::util::ser::Writeable;

#[inline]
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
if let Ok(payer_proof) = PayerProof::try_from(data.to_vec()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also update the invoice fuzzer to build a payer proof, just like how the offer fuzzer builds an invoice request and how the invoice request fuzzer builds an invoice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preimage gate makes this a no-op if I put it in invoice_deser. Building a proof requires SHA256(preimage) == invoice.payment_hash(), and invoice_deser parses arbitrary invoices, so we never hold a matching preimage. prove_payer() returns PreimageMismatch before any of the interesting code runs (build_unsigned doesn't even re-check the invoice signature, the preimage is the only gate), so we'd just be exercising the early error return.

Claude help me find the place where we actually control the payment hash is invoice_request_deser, since it builds the invoice itself via respond_with(paths, payment_hash). If I set payment_hash = SHA256(known_preimage) there, I can chain a full payer proof build+sign right after the invoice is signed, which exercises the selective disclosure, merkle tree, and proof signing for real.

Want me to add it in invoice_request_deser that way? Or do you still want the call in invoice_deser even though it only ever hits PreimageMismatch?

let mut bytes = Vec::with_capacity(data.len());
payer_proof.write(&mut bytes).unwrap();
assert_eq!(data, bytes);
}
}

pub fn payer_proof_deser_test<Out: test_logger::Output>(data: &[u8], out: Out) {
do_test(data, out);
}

#[no_mangle]
pub extern "C" fn payer_proof_deser_run(data: *const u8, datalen: usize) {
do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {});
}
1 change: 1 addition & 0 deletions fuzz/targets.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ void onion_message_run(const unsigned char* data, size_t data_len);
void peer_crypt_run(const unsigned char* data, size_t data_len);
void process_network_graph_run(const unsigned char* data, size_t data_len);
void process_onion_failure_run(const unsigned char* data, size_t data_len);
void payer_proof_deser_run(const unsigned char* data, size_t data_len);
void refund_deser_run(const unsigned char* data, size_t data_len);
void router_run(const unsigned char* data, size_t data_len);
void zbase32_run(const unsigned char* data, size_t data_len);
Expand Down
31 changes: 6 additions & 25 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use crate::ln::outbound_payment::RecipientOnionFields;
use crate::ln::types::ChannelId;
use crate::offers::invoice::Bolt12Invoice;
use crate::offers::invoice_request::InvoiceRequest;
pub use crate::offers::payer_proof::PaidBolt12Invoice;
use crate::offers::static_invoice::StaticInvoice;
use crate::onion_message::messenger::Responder;
use crate::routing::gossip::NetworkUpdate;
Expand Down Expand Up @@ -1206,17 +1207,13 @@ pub enum Event {
///
/// [`Route::get_total_fees`]: crate::routing::router::Route::get_total_fees
fee_paid_msat: Option<u64>,
/// The BOLT 12 invoice that was paid. `None` if the payment was a non BOLT 12 payment.
/// The paid BOLT 12 invoice bundled with the data needed to construct a
/// [`PayerProof`], which selectively discloses invoice fields to prove payment to a
/// third party.
///
/// The BOLT 12 invoice is useful for proof of payment because it contains the
/// payment hash. A third party can verify that the payment was made by
/// showing the invoice and confirming that the payment hash matches
/// the hash of the payment preimage.
/// `None` for non-BOLT 12 payments.
///
/// However, the [`PaidBolt12Invoice`] can also be of type [`StaticInvoice`], which
/// is a special [`Bolt12Invoice`] where proof of payment is not possible.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
/// [`PayerProof`]: crate::offers::payer_proof::PayerProof
bolt12_invoice: Option<PaidBolt12Invoice>,
},
/// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events
Expand Down Expand Up @@ -3310,19 +3307,3 @@ impl<T: EventHandler> EventHandler for Arc<T> {
self.deref().handle_event(event)
}
}

/// The BOLT 12 invoice that was paid, surfaced in [`Event::PaymentSent::bolt12_invoice`].
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PaidBolt12Invoice {
/// The BOLT 12 invoice specified by the BOLT 12 specification,
/// allowing the user to perform proof of payment.
Bolt12Invoice(Bolt12Invoice),
/// The Static invoice, used in the async payment specification update proposal,
/// where the user cannot perform proof of payment.
StaticInvoice(StaticInvoice),
}

impl_ser_tlv_based_enum!(PaidBolt12Invoice,
{0, Bolt12Invoice} => (),
{2, StaticInvoice} => (),
);
4 changes: 3 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::events::bump_transaction::sync::BumpTransactionEventHandlerSync;
use crate::events::bump_transaction::BumpTransactionEvent;
use crate::events::{
ClaimedHTLC, ClosureReason, Event, FundingInfo, HTLCHandlingFailureType,
NegotiationFailureReason, PaidBolt12Invoice, PathFailure, PaymentFailureReason, PaymentPurpose,
NegotiationFailureReason, PathFailure, PaymentFailureReason, PaymentPurpose,
};
use crate::ln::chan_utils::{
commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, TRUC_MAX_WEIGHT,
Expand All @@ -39,6 +39,7 @@ use crate::ln::outbound_payment::RecipientOnionFields;
use crate::ln::outbound_payment::Retry;
use crate::ln::peer_handler::IgnoringMessageHandler;
use crate::ln::types::ChannelId;
use crate::offers::payer_proof::PaidBolt12Invoice;
use crate::onion_message::messenger::OnionMessenger;
use crate::routing::gossip::{NetworkGraph, NetworkUpdate, P2PGossipSync};
use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
Expand Down Expand Up @@ -3016,6 +3017,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
ref amount_msat,
ref fee_paid_msat,
ref bolt12_invoice,
..
} => {
assert_eq!(expected_payment_preimage, *payment_preimage);
assert_eq!(expected_payment_hash, *payment_hash);
Expand Down
Loading
Loading