Skip to content

Commit 21ce949

Browse files
committed
Improve fuzz test coverage by building a resolver
1 parent 1eb8621 commit 21ce949

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

fuzz/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ stdin_fuzz = []
1616

1717
[dependencies]
1818
bitcoin = { version = "0.32", default-features = false, features = ["std"] }
19+
lightning-invoice = { version = "0.33", default-features = false }
1920
bitcoin-payment-instructions = { path = "../", default-features = false }
2021

2122
afl = { version = "0.12", optional = true }

fuzz/src/parse.rs

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@
99

1010
use bitcoin::Network;
1111

12-
use bitcoin_payment_instructions::hrn_resolution::DummyHrnResolver;
12+
use bitcoin_payment_instructions::amount::Amount;
13+
use bitcoin_payment_instructions::hrn::HumanReadableName;
14+
use bitcoin_payment_instructions::hrn_resolution::{
15+
DummyHrnResolver, HrnResolution, HrnResolutionFuture, HrnResolver, LNURLResolutionFuture,
16+
};
1317
use bitcoin_payment_instructions::PaymentInstructions;
1418

19+
use lightning_invoice::Bolt11Invoice;
20+
1521
use std::future::Future;
1622
use std::pin::Pin;
23+
use std::str::FromStr;
24+
use std::sync::Mutex;
1725
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
1826

1927
// Emulate Waker::noop until we fuzz on 1.85
@@ -25,13 +33,89 @@ fn clone_fn(p: *const ()) -> RawWaker {
2533

2634
fn dummy_fn(_: *const ()) {}
2735

36+
struct Resolver<'a>(
37+
Mutex<(Option<Result<HrnResolution, &'static str>>, Option<&'a [u8]>)>,
38+
);
39+
40+
impl HrnResolver for Resolver<'_> {
41+
fn resolve_hrn<'a>(&'a self, _: &'a HumanReadableName) -> HrnResolutionFuture<'a> {
42+
Box::pin(async {
43+
let us = self.0.lock().unwrap();
44+
us.0.take().unwrap()
45+
})
46+
}
47+
48+
fn resolve_lnurl<'a>(&'a self, _: String, _: Amount, _: [u8; 32]) -> LNURLResolutionFuture<'a> {
49+
Box::pin(async {
50+
let us = self.0.lock().unwrap();
51+
if let Ok(s) = std::str::from_utf8(us.1.take().unwrap()) {
52+
Bolt11Invoice::from_str(s).map_err(|_| "Failed to parse invoice")
53+
} else {
54+
Err("Invalid utf8 for invoice")
55+
}
56+
})
57+
}
58+
}
59+
2860
#[inline]
29-
pub fn do_test(data: &[u8]) {
61+
pub fn do_test(mut data: &[u8]) {
62+
if data.len() < 2 {
63+
return;
64+
}
65+
66+
let mut bolt11 = None;
67+
68+
let resolution = if (data[0] & 0b1100_0000) == 0b1100_0000 {
69+
Err("HRN resolution failed in fuzzing")
70+
} else if (data[0] & 0b1100_0000) == 0b1000_0000 {
71+
let result_len = (((data[0] & 0b0011_1111) as usize) << 8) | (data[1] as usize);
72+
if data.len() <= result_len + 2 {
73+
return;
74+
}
75+
let result = if let Ok(s) = String::from_utf8(data[2..result_len + 2].to_vec()) {
76+
s
77+
} else {
78+
return;
79+
};
80+
81+
data = &data[result_len + 2..];
82+
Ok(HrnResolution::DNSSEC { result, proof: Some(vec![8; 32]) })
83+
} else {
84+
if data.len() <= 16 + 2 {
85+
return;
86+
}
87+
let min = Amount::from_milli_sats(u64::from_le_bytes((&data[..8]).try_into().unwrap()));
88+
data = &data[8..];
89+
let max = Amount::from_milli_sats(u64::from_le_bytes((&data[..8]).try_into().unwrap()));
90+
data = &data[8..];
91+
92+
let bolt11_len = ((data[0] as usize) << 8) | (data[1] as usize);
93+
if data.len() <= bolt11_len + 2 {
94+
return;
95+
}
96+
97+
bolt11 = Some(&data[2..bolt11_len + 2]);
98+
data = &data[bolt11_len + 2..];
99+
100+
let mut expected_description_hash = [0; 32];
101+
expected_description_hash[31] = 42;
102+
103+
Ok(HrnResolution::LNURLPay {
104+
min_value: if let Ok(min) = min { min } else { return },
105+
max_value: if let Ok(max) = max { max } else { return },
106+
expected_description_hash,
107+
recipient_description: Some("Payment in fuzzing".to_owned()),
108+
callback: "https://callback.uri/in/fuzzing".to_owned(),
109+
})
110+
};
111+
112+
let resolver = Resolver(Mutex::new((Some(resolution), bolt11)));
113+
30114
if let Ok(s) = std::str::from_utf8(data) {
31115
let waker = unsafe { Waker::from_raw(clone_fn(std::ptr::null())) };
32116

33-
let fut = PaymentInstructions::parse(s, Network::Bitcoin, &DummyHrnResolver, true);
34-
// With a DummyHrnResolver, all instructions should resolve on the first `poll`.
117+
let fut = PaymentInstructions::parse(s, Network::Bitcoin, &resolver, true);
118+
// With our resolver, all instructions should resolve on the first `poll`.
35119
let res = Future::poll(Pin::new(&mut Box::pin(fut)), &mut Context::from_waker(&waker));
36120
assert!(matches!(res, Poll::Ready(_)));
37121

0 commit comments

Comments
 (0)