Fix chanmon_consistency for real hashes
What changed, and why it matters
This change fixes a fuzz-testing harness so it generates and stores real payment preimages instead of reusing payment hash bytes as fake preimages. It only affects internal test code, not the production Lightning library, so it has no direct security impact on real users.
No security action required. Treat as a normal test-quality fix. If fuzzing infrastructure is part of CI, ensure the real-hashes runner is exercised to confirm the fix.
Security signals we found
No production code changed
Change is confined to fuzz/src/chanmon_consistency.rs
Fixes test-only preimage handling; previously used payment hash bytes as stand-in preimage
No cryptographic weakness introduced; improves test fidelity
Evidence from the diff
The commit modifies fuzz/src/chanmon_consistency.rs. Previously, the fuzz target derived a payment_hash by hashing a single counter byte, and later claimed funds by passing PaymentPreimage(payment_hash.0) — i.e., using the hash bytes as the preimage. The patch stores an actual PaymentPreimage, derives payment_hash = Sha256(preimage), and retrieves the correct preimage when claim_funds is called. This makes the real-hashes fuzz runner behave correctly. It is a test-only correctness fix with no production code changes.
Changed components
fuzz/src/chanmon_consistency.rsInspect captured patch +23 / −14
diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs
index d4a0e56..73b3f22 100644
--- a/fuzz/src/chanmon_consistency.rs
+++ b/fuzz/src/chanmon_consistency.rs
@@ -568,12 +568,18 @@ type ChanMan<'a> = ChannelManager<
>;
#[inline]
-fn get_payment_secret_hash(dest: &ChanMan, payment_ctr: &mut u64) -> (PaymentSecret, PaymentHash) {
+fn get_payment_secret_hash(
+ dest: &ChanMan, payment_ctr: &mut u64,
+ payment_preimages: &RefCell<HashMap<PaymentHash, PaymentPreimage>>,
+) -> (PaymentSecret, PaymentHash) {
*payment_ctr += 1;
- let payment_hash = PaymentHash(Sha256::hash(&[*payment_ctr as u8]).to_byte_array());
+ let mut payment_preimage = PaymentPreimage([0; 32]);
+ payment_preimage.0[0..8].copy_from_slice(&payment_ctr.to_be_bytes());
+ let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array());
let payment_secret = dest
.create_inbound_payment_for_hash(payment_hash, None, 3600, None)
.expect("create_inbound_payment_for_hash failed");
+ assert!(payment_preimages.borrow_mut().insert(payment_hash, payment_preimage).is_none());
(payment_secret, payment_hash)
}
@@ -1344,10 +1350,8 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
// Create 3 channels between A-B and 3 channels between B-C (6 total).
//
- // Use version numbers 1-6 to avoid txid collisions under fuzz hashing.
- // Fuzz mode uses XOR-based hashing (all bytes XOR to one byte), and
- // versions 0-5 cause collisions between A-B and B-C channel pairs
- // (e.g., A-B with Version(1) collides with B-C with Version(3)).
+ // Use distinct version numbers for each funding transaction so each test channel gets its own
+ // txid and funding outpoint.
// A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept),
// channel 3 A has 0-reserve (trusted accept)
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1, false, false);
@@ -1424,6 +1428,8 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
let resolved_payments: RefCell<[HashMap<PaymentId, Option<PaymentHash>>; 3]> =
RefCell::new([new_hash_map(), new_hash_map(), new_hash_map()]);
let claimed_payment_hashes: RefCell<HashSet<PaymentHash>> = RefCell::new(HashSet::new());
+ let payment_preimages: RefCell<HashMap<PaymentHash, PaymentPreimage>> =
+ RefCell::new(new_hash_map());
macro_rules! test_return {
() => {{
@@ -1940,9 +1946,8 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
macro_rules! process_events {
($node: expr, $fail: expr) => {{
- // In case we get 256 payments we may have a hash collision, resulting in the
- // second claim/fail call not finding the duplicate-hash HTLC, so we have to
- // deduplicate the calls here.
+ // Multiple HTLCs can resolve for the same payment hash, so deduplicate
+ // claim/fail handling per event batch.
let mut claim_set = new_hash_map();
let mut events = nodes[$node].get_and_clear_pending_events();
let had_events = !events.is_empty();
@@ -1955,7 +1960,11 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
if $fail {
nodes[$node].fail_htlc_backwards(&payment_hash);
} else {
- nodes[$node].claim_funds(PaymentPreimage(payment_hash.0));
+ let payment_preimage = *payment_preimages
+ .borrow()
+ .get(&payment_hash)
+ .expect("PaymentClaimable for unknown payment hash");
+ nodes[$node].claim_funds(payment_preimage);
claimed_payment_hashes.borrow_mut().insert(payment_hash);
}
}
@@ -2095,7 +2104,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
|source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| {
let source = &nodes[source_idx];
let dest = &nodes[dest_idx];
- let (secret, hash) = get_payment_secret_hash(dest, payment_ctr);
+ let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages);
let mut id = PaymentId([0; 32]);
id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes());
let succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id);
@@ -2118,7 +2127,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
let source = &nodes[source_idx];
let middle = &nodes[middle_idx];
let dest = &nodes[dest_idx];
- let (secret, hash) = get_payment_secret_hash(dest, payment_ctr);
+ let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages);
let mut id = PaymentId([0; 32]);
id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes());
let succeeded = send_hop_payment(
@@ -2145,7 +2154,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
payment_ctr: &mut u64| {
let source = &nodes[source_idx];
let dest = &nodes[dest_idx];
- let (secret, hash) = get_payment_secret_hash(dest, payment_ctr);
+ let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages);
let mut id = PaymentId([0; 32]);
id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes());
let succeeded = send_mpp_payment(source, dest, dest_chan_ids, amt, secret, hash, id);
@@ -2165,7 +2174,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
let source = &nodes[source_idx];
let middle = &nodes[middle_idx];
let dest = &nodes[dest_idx];
- let (secret, hash) = get_payment_secret_hash(dest, payment_ctr);
+ let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages);
let mut id = PaymentId([0; 32]);
id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes());
let succeeded = send_mpp_hop_payment(
Why this scored 14/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.