use alloc::collections::BTreeSet;
use bitcoin::hashes::{sha256, Hash};
use crate::offers::invoice::INVOICE_TYPES;
use crate::offers::merkle::{
merkle_tlv_data, tagged_branch_hash_from_engine, tagged_hash_engine, tagged_hash_from_engine,
TlvHashData, TlvRecord,
};
use crate::offers::offer::EXPERIMENTAL_OFFER_TYPES;
use crate::offers::payer::PAYER_METADATA_TYPE;
#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelectiveDisclosureError {
InvalidOmittedMarkersOrder,
InvalidOmittedMarker,
LeafHashCountMismatch,
InsufficientMissingHashes,
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct SelectiveDisclosure {
pub(super) nonce_hashes: Vec<sha256::Hash>,
pub(super) omitted_markers: Vec<u64>,
pub(super) missing_hashes: Vec<sha256::Hash>,
pub(super) merkle_root: sha256::Hash,
}
pub(super) fn compute_selective_disclosure<'a>(
records: impl Iterator<Item = TlvRecord<'a>> + 'a, included_types: &'a BTreeSet<u64>,
) -> SelectiveDisclosure {
debug_assert!(!included_types.contains(&PAYER_METADATA_TYPE));
let (tlv_data, branch_tag) = merkle_tlv_data(records);
let tlv_data: Vec<TlvHashData> = tlv_data.collect();
assert!(!tlv_data.is_empty(), "TLV stream must contain at least one non-signature record");
let omitted_markers: Vec<u64> =
compute_omitted_markers(tlv_data.iter(), included_types).collect();
let nonce_hashes = tlv_data
.iter()
.filter(|data| included_types.contains(&data.tlv_type))
.map(|data| data.nonce_hash)
.collect();
let (merkle_root, missing_hashes) =
build_tree_with_disclosure(&tlv_data, included_types, &branch_tag);
SelectiveDisclosure { nonce_hashes, omitted_markers, missing_hashes, merkle_root }
}
pub(super) fn next_marker(prev: u64) -> u64 {
let next = prev.saturating_add(1);
if (INVOICE_TYPES.end..EXPERIMENTAL_OFFER_TYPES.start).contains(&next) {
EXPERIMENTAL_OFFER_TYPES.start
} else {
next
}
}
fn compute_omitted_markers<'a>(
tlv_data: impl Iterator<Item = &'a TlvHashData> + 'a, included_types: &'a BTreeSet<u64>,
) -> impl Iterator<Item = u64> + 'a {
tlv_data
.filter(|data| data.tlv_type != PAYER_METADATA_TYPE)
.scan(PAYER_METADATA_TYPE, |prev_value, data| {
if included_types.contains(&data.tlv_type) {
*prev_value = data.tlv_type;
Some(None)
} else {
let marker = next_marker(*prev_value);
*prev_value = marker;
Some(Some(marker))
}
})
.flatten()
}
fn build_tree_with_disclosure(
tlv_data: &[TlvHashData], included_types: &BTreeSet<u64>, branch_tag: &sha256::HashEngine,
) -> (sha256::Hash, Vec<sha256::Hash>) {
let mut missing_hashes = Vec::new();
let (root, _) = build_tree_dfs(tlv_data, included_types, branch_tag, &mut missing_hashes);
(root, missing_hashes)
}
fn build_tree_dfs(
tlv_data: &[TlvHashData], included_types: &BTreeSet<u64>, branch_tag: &sha256::HashEngine,
missing_hashes: &mut Vec<sha256::Hash>,
) -> (sha256::Hash, bool) {
if tlv_data.len() == 1 {
return (tlv_data[0].per_tlv_hash, included_types.contains(&tlv_data[0].tlv_type));
}
let mid = tlv_data.len().next_power_of_two() / 2;
let (left_data, right_data) = tlv_data.split_at(mid);
let (left_hash, left_incl) =
build_tree_dfs(left_data, included_types, branch_tag, missing_hashes);
let (right_hash, right_incl) =
build_tree_dfs(right_data, included_types, branch_tag, missing_hashes);
if left_incl && !right_incl {
missing_hashes.push(right_hash);
} else if !left_incl && right_incl {
missing_hashes.push(left_hash);
}
let combined = tagged_branch_hash_from_engine(branch_tag.clone(), left_hash, right_hash);
(combined, left_incl || right_incl)
}
fn decode_positions(
included_types: impl ExactSizeIterator<Item = u64>, omitted_markers: &[u64],
) -> Vec<bool> {
let mut positions = Vec::with_capacity(1 + included_types.len() + omitted_markers.len());
positions.push(false);
let mut included = included_types.peekable();
let mut markers = omitted_markers.iter().copied().peekable();
let mut prev_marker = PAYER_METADATA_TYPE;
loop {
match (included.peek().copied(), markers.peek().copied()) {
(None, None) => break,
(Some(_), None) => {
included.next();
positions.push(true);
},
(None, Some(marker)) => {
markers.next();
prev_marker = marker;
positions.push(false);
},
(Some(_), Some(marker)) if marker == next_marker(prev_marker) => {
markers.next();
prev_marker = marker;
positions.push(false);
},
(Some(inc_type), Some(_)) => {
included.next();
prev_marker = inc_type;
positions.push(true);
},
}
}
positions
}
pub(super) fn reconstruct_merkle_root(
included_records: &[TlvRecord<'_>], nonce_hashes: &[sha256::Hash], omitted_markers: &[u64],
missing_hashes: &[sha256::Hash],
) -> Result<sha256::Hash, SelectiveDisclosureError> {
debug_assert!({
let included_types: BTreeSet<u64> = included_records.iter().map(|r| r.r#type).collect();
validate_omitted_markers(omitted_markers, &included_types).is_ok()
});
if included_records.len() != nonce_hashes.len() {
return Err(SelectiveDisclosureError::LeafHashCountMismatch);
}
let leaf_tag = tagged_hash_engine(sha256::Hash::hash("LnLeaf".as_bytes()));
let branch_tag = tagged_hash_engine(sha256::Hash::hash("LnBranch".as_bytes()));
let positions = decode_positions(included_records.iter().map(|r| r.r#type), omitted_markers);
let mut hashes: Vec<Option<sha256::Hash>> = Vec::with_capacity(positions.len());
let mut inc_idx = 0;
for included in positions {
if included {
let record = &included_records[inc_idx];
let leaf_hash = tagged_hash_from_engine(leaf_tag.clone(), record.record_bytes);
let nonce_hash = nonce_hashes[inc_idx];
hashes.push(Some(tagged_branch_hash_from_engine(
branch_tag.clone(),
leaf_hash,
nonce_hash,
)));
inc_idx += 1;
} else {
hashes.push(None);
}
}
let mut missing_idx: usize = 0;
let root = reconstruct_merkle_root_dfs(&hashes, &branch_tag, missing_hashes, &mut missing_idx)?;
if missing_idx != missing_hashes.len() {
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
}
root.ok_or(SelectiveDisclosureError::InsufficientMissingHashes)
}
fn reconstruct_merkle_root_dfs(
hashes: &[Option<sha256::Hash>], branch_tag: &sha256::HashEngine,
missing_hashes: &[sha256::Hash], missing_idx: &mut usize,
) -> Result<Option<sha256::Hash>, SelectiveDisclosureError> {
if hashes.len() == 1 {
return Ok(hashes[0]);
}
let mid = hashes.len().next_power_of_two() / 2;
let (left_hashes, right_hashes) = hashes.split_at(mid);
let left = reconstruct_merkle_root_dfs(left_hashes, branch_tag, missing_hashes, missing_idx)?;
let right = reconstruct_merkle_root_dfs(right_hashes, branch_tag, missing_hashes, missing_idx)?;
match (left, right) {
(None, None) => Ok(None),
(Some(l), None) => {
if *missing_idx >= missing_hashes.len() {
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
}
let r = missing_hashes[*missing_idx];
*missing_idx += 1;
Ok(Some(tagged_branch_hash_from_engine(branch_tag.clone(), l, r)))
},
(None, Some(r)) => {
if *missing_idx >= missing_hashes.len() {
return Err(SelectiveDisclosureError::InsufficientMissingHashes);
}
let l = missing_hashes[*missing_idx];
*missing_idx += 1;
Ok(Some(tagged_branch_hash_from_engine(branch_tag.clone(), l, r)))
},
(Some(l), Some(r)) => Ok(Some(tagged_branch_hash_from_engine(branch_tag.clone(), l, r))),
}
}
pub(super) fn validate_omitted_markers(
markers: &[u64], included_types: &BTreeSet<u64>,
) -> Result<(), SelectiveDisclosureError> {
let mut inc_iter = included_types.iter().copied().peekable();
let mut expected_next: u64 = next_marker(PAYER_METADATA_TYPE);
let mut prev = PAYER_METADATA_TYPE;
for &marker in markers {
if marker == PAYER_METADATA_TYPE {
return Err(SelectiveDisclosureError::InvalidOmittedMarker);
}
if marker <= prev {
return Err(SelectiveDisclosureError::InvalidOmittedMarkersOrder);
}
if included_types.contains(&marker) {
return Err(SelectiveDisclosureError::InvalidOmittedMarker);
}
if marker != expected_next {
let mut found = false;
for inc_type in inc_iter.by_ref() {
if next_marker(inc_type) == marker {
found = true;
break;
}
if inc_type >= marker {
return Err(SelectiveDisclosureError::InvalidOmittedMarker);
}
}
if !found {
return Err(SelectiveDisclosureError::InvalidOmittedMarker);
}
}
expected_next = next_marker(marker);
prev = marker;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::compute_omitted_markers;
use crate::offers::merkle::{TlvHashData, TlvRecord, TlvStream};
use alloc::collections::BTreeSet;
use bitcoin::hashes::{sha256, Hash};
fn reconstruct_positions(included_types: &[u64], omitted_markers: &[u64]) -> Vec<bool> {
super::decode_positions(included_types.iter().copied(), omitted_markers)
}
fn synthetic_tlv_stream(types: &[u64]) -> Vec<u8> {
let mut bytes = Vec::new();
for &tlv_type in types {
assert!(tlv_type < 253, "helper only supports single-byte BigSize types");
bytes.extend_from_slice(&[tlv_type as u8, 0x02, 0x00, 0x00]);
}
bytes
}
fn assert_disclosure_round_trip(
tlv_bytes: &[u8], included: &[u64], expected_markers: &[u64], expected_positions: &[bool],
) {
let included_types: BTreeSet<u64> = included.iter().copied().collect();
let disclosure =
super::compute_selective_disclosure(TlvStream::new(tlv_bytes), &included_types);
assert_eq!(disclosure.omitted_markers.as_slice(), expected_markers);
assert_eq!(
reconstruct_positions(included, &disclosure.omitted_markers).as_slice(),
expected_positions,
);
let included_records: Vec<TlvRecord<'_>> =
TlvStream::new(tlv_bytes).filter(|r| included_types.contains(&r.r#type)).collect();
let reconstructed = super::reconstruct_merkle_root(
&included_records,
&disclosure.nonce_hashes,
&disclosure.omitted_markers,
&disclosure.missing_hashes,
)
.unwrap();
assert_eq!(reconstructed, disclosure.merkle_root);
}
#[test]
fn test_reconstruct_positions_spec_example() {
assert_disclosure_round_trip(
&synthetic_tlv_stream(&[0, 10, 20, 30, 40, 50, 60]),
&[10, 40],
&[11, 12, 41, 42],
&[false, true, false, false, true, false, false],
);
}
#[test]
fn test_reconstruct_positions_omitted_before_included() {
assert_disclosure_round_trip(
&synthetic_tlv_stream(&[0, 5, 10, 20]),
&[10],
&[1, 11],
&[false, false, true, false],
);
}
#[test]
fn test_reconstruct_positions_no_omitted() {
assert_disclosure_round_trip(
&synthetic_tlv_stream(&[0, 10, 20]),
&[10, 20],
&[],
&[false, true, true],
);
}
#[test]
fn test_reconstruct_positions_no_included() {
let tlv_bytes = synthetic_tlv_stream(&[0, 5, 10]);
let included_types = BTreeSet::new();
let disclosure =
super::compute_selective_disclosure(TlvStream::new(&tlv_bytes), &included_types);
assert_eq!(disclosure.omitted_markers, vec![1, 2]);
assert_eq!(
reconstruct_positions(&[], &disclosure.omitted_markers),
vec![false, false, false],
);
assert_eq!(
super::reconstruct_merkle_root(
&[],
&disclosure.nonce_hashes,
&disclosure.omitted_markers,
&disclosure.missing_hashes,
),
Err(super::SelectiveDisclosureError::InsufficientMissingHashes),
);
}
#[test]
fn test_validate_omitted_markers_edge_cases() {
let included_types = |types: &[u64]| -> BTreeSet<u64> { types.iter().copied().collect() };
assert!(super::validate_omitted_markers(&[1, 2, 3, 41, 42], &included_types(&[40])).is_ok());
assert!(super::validate_omitted_markers(&[11, 12], &included_types(&[10])).is_ok());
assert!(super::validate_omitted_markers(&[], &included_types(&[10, 20])).is_ok());
assert!(super::validate_omitted_markers(&[1_000_000_000], &included_types(&[239])).is_ok());
assert_eq!(
super::validate_omitted_markers(&[0], &included_types(&[])),
Err(super::SelectiveDisclosureError::InvalidOmittedMarker)
);
assert_eq!(
super::validate_omitted_markers(&[11, 11], &included_types(&[10])),
Err(super::SelectiveDisclosureError::InvalidOmittedMarkersOrder)
);
assert_eq!(
super::validate_omitted_markers(&[10], &included_types(&[10])),
Err(super::SelectiveDisclosureError::InvalidOmittedMarker)
);
assert_eq!(
super::validate_omitted_markers(&[11, 15, 41], &included_types(&[10, 40])),
Err(super::SelectiveDisclosureError::InvalidOmittedMarker)
);
assert_eq!(
super::validate_omitted_markers(&[11, 12, 45], &included_types(&[10, 40])),
Err(super::SelectiveDisclosureError::InvalidOmittedMarker)
);
}
#[test]
fn compute_selective_disclosure_skips_signature_tlv_records() {
let bytes_without_signature = vec![
0x00, 0x01, 0x00, 0x0a, 0x01, 0x01, 0x14, 0x01, 0x02, ];
let bytes_with_signature = vec![
0x00, 0x01, 0x00, 0x0a, 0x01, 0x01, 0xf0, 0x00, 0x14, 0x01, 0x02, ];
let included = [10, 20].into_iter().collect::<BTreeSet<_>>();
assert_eq!(
super::compute_selective_disclosure(TlvStream::new(&bytes_with_signature), &included),
super::compute_selective_disclosure(
TlvStream::new(&bytes_without_signature),
&included
)
);
}
#[test]
fn test_selective_disclosure_round_trip() {
let mut tlv_bytes = Vec::new();
tlv_bytes.extend_from_slice(&[0x00, 0x04, 0x00, 0x00, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x0a, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x14, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x1e, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x28, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x32, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x3c, 0x02, 0x00, 0x00]);
let mut included = BTreeSet::new();
included.insert(10);
included.insert(40);
let disclosure = super::compute_selective_disclosure(TlvStream::new(&tlv_bytes), &included);
assert_eq!(disclosure.omitted_markers, vec![11, 12, 41, 42]);
assert_eq!(disclosure.nonce_hashes.len(), 2);
let included_records: Vec<TlvRecord<'_>> =
TlvStream::new(&tlv_bytes).filter(|r| included.contains(&r.r#type)).collect();
let reconstructed = super::reconstruct_merkle_root(
&included_records,
&disclosure.nonce_hashes,
&disclosure.omitted_markers,
&disclosure.missing_hashes,
)
.unwrap();
assert_eq!(reconstructed, disclosure.merkle_root);
}
#[test]
fn test_missing_hashes_for_synthetic_tree() {
let mut tlv_bytes = Vec::new();
tlv_bytes.extend_from_slice(&[0x00, 0x04, 0x00, 0x00, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x0a, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x14, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x1e, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x28, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x32, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x3c, 0x02, 0x00, 0x00]);
let mut included = BTreeSet::new();
included.insert(10);
included.insert(40);
let disclosure = super::compute_selective_disclosure(TlvStream::new(&tlv_bytes), &included);
assert_eq!(
disclosure.missing_hashes.len(),
4,
"Expected 4 missing hashes for omitted types [0, 20+30, 50, 60]"
);
let included_records: Vec<TlvRecord<'_>> =
TlvStream::new(&tlv_bytes).filter(|r| included.contains(&r.r#type)).collect();
let reconstructed = super::reconstruct_merkle_root(
&included_records,
&disclosure.nonce_hashes,
&disclosure.omitted_markers,
&disclosure.missing_hashes,
)
.unwrap();
assert_eq!(reconstructed, disclosure.merkle_root);
}
#[test]
fn test_reconstruction_fails_with_wrong_missing_hashes() {
let mut tlv_bytes = Vec::new();
tlv_bytes.extend_from_slice(&[0x00, 0x04, 0x00, 0x00, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x0a, 0x02, 0x00, 0x00]); tlv_bytes.extend_from_slice(&[0x14, 0x02, 0x00, 0x00]);
let mut included = BTreeSet::new();
included.insert(10);
let disclosure = super::compute_selective_disclosure(TlvStream::new(&tlv_bytes), &included);
let included_records: Vec<TlvRecord<'_>> =
TlvStream::new(&tlv_bytes).filter(|r| included.contains(&r.r#type)).collect();
let result = super::reconstruct_merkle_root(
&included_records,
&disclosure.nonce_hashes,
&disclosure.omitted_markers,
&[], );
assert!(result.is_err());
}
#[test]
fn compute_omitted_markers_jumps_to_high_range_after_239() {
let dummy_hash = sha256::Hash::all_zeros();
let included = BTreeSet::new();
let tlv_data: Vec<TlvHashData> = (1u64..=240)
.map(|tlv_type| TlvHashData {
tlv_type,
nonce_hash: dummy_hash,
per_tlv_hash: dummy_hash,
})
.collect();
let markers: Vec<u64> = compute_omitted_markers(tlv_data.iter(), &included).collect();
let mut expected: Vec<u64> = (1..=239).collect();
expected.push(1_000_000_000);
assert_eq!(markers, expected);
}
#[test]
fn compute_omitted_markers_jumps_after_included_at_top_of_low_range() {
let dummy_hash = sha256::Hash::all_zeros();
let included = [239u64].into_iter().collect::<BTreeSet<_>>();
let tlv_data = [
TlvHashData { tlv_type: 239, nonce_hash: dummy_hash, per_tlv_hash: dummy_hash },
TlvHashData {
tlv_type: 1_500_000_000,
nonce_hash: dummy_hash,
per_tlv_hash: dummy_hash,
},
];
let markers: Vec<u64> = compute_omitted_markers(tlv_data.iter(), &included).collect();
assert_eq!(markers, vec![1_000_000_000]);
}
#[test]
fn compute_omitted_markers_continue_in_experimental_range_after_jump() {
let dummy_hash = sha256::Hash::all_zeros();
let included = [239u64].into_iter().collect::<BTreeSet<_>>();
let tlv_data = [
TlvHashData { tlv_type: 239, nonce_hash: dummy_hash, per_tlv_hash: dummy_hash },
TlvHashData {
tlv_type: 3_000_000_000,
nonce_hash: dummy_hash,
per_tlv_hash: dummy_hash,
},
TlvHashData {
tlv_type: 3_000_000_001,
nonce_hash: dummy_hash,
per_tlv_hash: dummy_hash,
},
];
let markers: Vec<u64> = compute_omitted_markers(tlv_data.iter(), &included).collect();
assert_eq!(markers, vec![1_000_000_000, 1_000_000_001]);
}
#[test]
fn next_marker_jumps_the_gap() {
assert_eq!(super::next_marker(super::PAYER_METADATA_TYPE), 1);
assert_eq!(super::next_marker(5), 6);
assert_eq!(super::next_marker(238), 239);
assert_eq!(super::next_marker(239), 1_000_000_000);
assert_eq!(super::next_marker(1_000_000_000), 1_000_000_001);
}
#[test]
fn validate_omitted_markers_direct() {
use alloc::collections::BTreeSet;
let none: BTreeSet<u64> = BTreeSet::new();
assert!(super::validate_omitted_markers(&[1, 2, 3], &none).is_ok());
assert!(super::validate_omitted_markers(&[], &none).is_ok());
assert!(super::validate_omitted_markers(&[0], &none).is_err());
assert!(super::validate_omitted_markers(&[2, 1], &none).is_err());
assert!(super::validate_omitted_markers(&[1, 3], &none).is_err());
let inc2: BTreeSet<u64> = [2u64].into_iter().collect();
assert!(super::validate_omitted_markers(&[1, 3], &inc2).is_ok());
assert!(super::validate_omitted_markers(&[2], &inc2).is_err());
let inc239: BTreeSet<u64> = [239u64].into_iter().collect();
assert!(super::validate_omitted_markers(&[1_000_000_000], &inc239).is_ok());
assert!(super::validate_omitted_markers(&[1_000_000_000], &none).is_err());
}
}