Skip to content

Commit c91eae1

Browse files
fixup! fix(offers): use DFS order for payer proof missing hashes
Co-Authored-By: OpenAI Codex <noreply@openai.com>
1 parent 564682f commit c91eae1

1 file changed

Lines changed: 61 additions & 104 deletions

File tree

lightning/src/offers/merkle.rs

Lines changed: 61 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -421,18 +421,9 @@ fn compute_omitted_markers<'a>(
421421
.flatten()
422422
}
423423

424-
/// Task for iterative post-order DFS over the implicit merkle tree.
425-
enum DfsTask {
426-
/// Descend into a subtree covering the range [start..start+len).
427-
Descend { start: usize, len: usize },
428-
/// Combine two child hashes after both subtrees have been processed.
429-
Combine,
430-
}
431-
432-
/// Build merkle tree iteratively (DFS, left-to-right) and collect missing_hashes.
424+
/// Build merkle tree recursively (DFS, left-to-right) and collect missing_hashes.
433425
///
434426
/// Per the spec, missing_hashes are in depth-first left-to-right order.
435-
/// Uses an explicit stack to simulate post-order DFS without recursion.
436427
///
437428
/// Note: a level-by-level approach (as used by `root_hash()`) cannot produce
438429
/// DFS-ordered missing_hashes because it processes all subtrees at each depth
@@ -443,43 +434,31 @@ fn build_tree_with_disclosure(
443434
debug_assert!(!tlv_data.is_empty(), "TLV stream must contain at least one record");
444435

445436
let mut missing_hashes = Vec::new();
446-
// Each entry: (hash, has_included_leaf_in_subtree)
447-
let mut hash_stack: Vec<(sha256::Hash, bool)> = Vec::new();
448-
let mut task_stack: Vec<DfsTask> = vec![DfsTask::Descend { start: 0, len: tlv_data.len() }];
449-
450-
while let Some(task) = task_stack.pop() {
451-
match task {
452-
DfsTask::Descend { start, len } => {
453-
if len == 1 {
454-
hash_stack.push((tlv_data[start].per_tlv_hash, tlv_data[start].is_included));
455-
} else {
456-
let mid = len.next_power_of_two() / 2;
457-
// Push combine first (processed after both children).
458-
task_stack.push(DfsTask::Combine);
459-
// Push right then left so left is processed first (LIFO).
460-
task_stack.push(DfsTask::Descend { start: start + mid, len: len - mid });
461-
task_stack.push(DfsTask::Descend { start, len: mid });
462-
}
463-
},
464-
DfsTask::Combine => {
465-
let (right_hash, right_incl) = hash_stack.pop().unwrap();
466-
let (left_hash, left_incl) = hash_stack.pop().unwrap();
467-
468-
if left_incl && !right_incl {
469-
missing_hashes.push(right_hash);
470-
} else if !left_incl && right_incl {
471-
missing_hashes.push(left_hash);
472-
}
473-
474-
let combined =
475-
tagged_branch_hash_from_engine(branch_tag.clone(), left_hash, right_hash);
476-
hash_stack.push((combined, left_incl || right_incl));
477-
},
478-
}
437+
let (root, _) = build_tree_dfs(tlv_data, branch_tag, &mut missing_hashes);
438+
(root, missing_hashes)
439+
}
440+
441+
fn build_tree_dfs(
442+
tlv_data: &[TlvMerkleData], branch_tag: &sha256::HashEngine,
443+
missing_hashes: &mut Vec<sha256::Hash>,
444+
) -> (sha256::Hash, bool) {
445+
if tlv_data.len() == 1 {
446+
return (tlv_data[0].per_tlv_hash, tlv_data[0].is_included);
479447
}
480448

481-
let (root, _) = hash_stack.pop().unwrap();
482-
(root, missing_hashes)
449+
let mid = tlv_data.len().next_power_of_two() / 2;
450+
let (left_data, right_data) = tlv_data.split_at(mid);
451+
let (left_hash, left_incl) = build_tree_dfs(left_data, branch_tag, missing_hashes);
452+
let (right_hash, right_incl) = build_tree_dfs(right_data, branch_tag, missing_hashes);
453+
454+
if left_incl && !right_incl {
455+
missing_hashes.push(right_hash);
456+
} else if !left_incl && right_incl {
457+
missing_hashes.push(left_hash);
458+
}
459+
460+
let combined = tagged_branch_hash_from_engine(branch_tag.clone(), left_hash, right_hash);
461+
(combined, left_incl || right_incl)
483462
}
484463

485464
/// Reconstruct merkle root from selective disclosure data.
@@ -546,71 +525,49 @@ pub(super) fn reconstruct_merkle_root(
546525
}
547526
}
548527

549-
// Iterative DFS reconstruction: consume missing_hashes in DFS order.
550-
// result_stack holds Option<hash>: Some = subtree has included leaves, None = all omitted.
551-
let mut result_stack: Vec<Option<sha256::Hash>> = Vec::new();
552-
let mut task_stack: Vec<DfsTask> = vec![DfsTask::Descend { start: 0, len: num_nodes }];
553528
let mut missing_idx: usize = 0;
554-
555-
while let Some(task) = task_stack.pop() {
556-
match task {
557-
DfsTask::Descend { start, len } => {
558-
if len == 1 {
559-
result_stack.push(hashes[start]);
560-
} else {
561-
let mid = len.next_power_of_two() / 2;
562-
task_stack.push(DfsTask::Combine);
563-
task_stack.push(DfsTask::Descend { start: start + mid, len: len - mid });
564-
task_stack.push(DfsTask::Descend { start, len: mid });
565-
}
566-
},
567-
DfsTask::Combine => {
568-
let right = result_stack.pop().unwrap();
569-
let left = result_stack.pop().unwrap();
570-
571-
match (left, right) {
572-
(None, None) => result_stack.push(None),
573-
(Some(l), None) => {
574-
if missing_idx >= missing_hashes.len() {
575-
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
576-
}
577-
let r = missing_hashes[missing_idx];
578-
missing_idx += 1;
579-
result_stack.push(Some(tagged_branch_hash_from_engine(
580-
branch_tag.clone(),
581-
l,
582-
r,
583-
)));
584-
},
585-
(None, Some(r)) => {
586-
if missing_idx >= missing_hashes.len() {
587-
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
588-
}
589-
let l = missing_hashes[missing_idx];
590-
missing_idx += 1;
591-
result_stack.push(Some(tagged_branch_hash_from_engine(
592-
branch_tag.clone(),
593-
l,
594-
r,
595-
)));
596-
},
597-
(Some(l), Some(r)) => {
598-
result_stack.push(Some(tagged_branch_hash_from_engine(
599-
branch_tag.clone(),
600-
l,
601-
r,
602-
)));
603-
},
604-
}
605-
},
606-
}
607-
}
529+
let root = reconstruct_merkle_root_dfs(&hashes, &branch_tag, missing_hashes, &mut missing_idx)?;
608530

609531
if missing_idx != missing_hashes.len() {
610532
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
611533
}
612534

613-
result_stack.pop().unwrap().ok_or(SelectiveDisclosureError::InsufficientMissingHashes)
535+
root.ok_or(SelectiveDisclosureError::InsufficientMissingHashes)
536+
}
537+
538+
fn reconstruct_merkle_root_dfs(
539+
hashes: &[Option<sha256::Hash>], branch_tag: &sha256::HashEngine,
540+
missing_hashes: &[sha256::Hash], missing_idx: &mut usize,
541+
) -> Result<Option<sha256::Hash>, SelectiveDisclosureError> {
542+
if hashes.len() == 1 {
543+
return Ok(hashes[0]);
544+
}
545+
546+
let mid = hashes.len().next_power_of_two() / 2;
547+
let (left_hashes, right_hashes) = hashes.split_at(mid);
548+
let left = reconstruct_merkle_root_dfs(left_hashes, branch_tag, missing_hashes, missing_idx)?;
549+
let right = reconstruct_merkle_root_dfs(right_hashes, branch_tag, missing_hashes, missing_idx)?;
550+
551+
match (left, right) {
552+
(None, None) => Ok(None),
553+
(Some(l), None) => {
554+
if *missing_idx >= missing_hashes.len() {
555+
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
556+
}
557+
let r = missing_hashes[*missing_idx];
558+
*missing_idx += 1;
559+
Ok(Some(tagged_branch_hash_from_engine(branch_tag.clone(), l, r)))
560+
},
561+
(None, Some(r)) => {
562+
if *missing_idx >= missing_hashes.len() {
563+
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
564+
}
565+
let l = missing_hashes[*missing_idx];
566+
*missing_idx += 1;
567+
Ok(Some(tagged_branch_hash_from_engine(branch_tag.clone(), l, r)))
568+
},
569+
(Some(l), Some(r)) => Ok(Some(tagged_branch_hash_from_engine(branch_tag.clone(), l, r))),
570+
}
614571
}
615572

616573
fn validate_omitted_markers(markers: &[u64]) -> Result<(), SelectiveDisclosureError> {

0 commit comments

Comments
 (0)