Skip to content

Commit 14de1ac

Browse files
committed
Improve fuzz test coverage by building a resolver
1 parent 1eb8621 commit 14de1ac

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-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: 84 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,85 @@ 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+
Bolt11Invoice::from_str(us.1.take().unwrap()).map_err(|_| "Failed to parse invoice")
52+
})
53+
}
54+
}
55+
2856
#[inline]
29-
pub fn do_test(data: &[u8]) {
57+
pub fn do_test(mut data: &[u8]) {
58+
if data.len() < 2 {
59+
return;
60+
}
61+
62+
let mut bolt11 = None;
63+
64+
let resolution = if (data[0] & 0b1100_0000) == 0b1100_0000 {
65+
Err("HRN resolution failed in fuzzing")
66+
} else if (data[0] & 0b1100_0000) == 0b1000_0000 {
67+
let result_len = (((data[0] & 0b0011_1111) as usize) << 8) | (data[1] as usize);
68+
if data.len() <= result_len + 2 {
69+
return;
70+
}
71+
let result = if let Ok(s) = String::from_utf8(data[2..result_len + 2].to_vec()) {
72+
s
73+
} else {
74+
return;
75+
};
76+
77+
data = &data[result_len + 2..];
78+
Ok(HrnResolution::DNSSEC { result, proof: Some(vec![8; 32]) })
79+
} else {
80+
if data.len() <= 16 + 2 {
81+
return;
82+
}
83+
let min = Amount::from_milli_sats(u64::from_le_bytes((&data[..8]).try_into().unwrap()));
84+
data = &data[8..];
85+
let max = Amount::from_milli_sats(u64::from_le_bytes((&data[..8]).try_into().unwrap()));
86+
data = &data[8..];
87+
88+
let bolt11_len = ((data[0] as usize) << 8) | (data[1] as usize);
89+
if data.len() <= bolt11_len + 2 {
90+
return;
91+
}
92+
93+
bolt11 = Some(&data[2..bolt11_len + 2]);
94+
data = &data[bolt11_len + 2..];
95+
96+
let mut expected_description_hash = [0; 32];
97+
expected_description_hash[31] = 42;
98+
99+
Ok(HrnResolution::LNURLPay {
100+
min_value: if let Ok(min) = min { min } else { return },
101+
max_value: if let Ok(max) = max { max } else { return },
102+
expected_description_hash,
103+
recipient_description: Some("Payment in fuzzing".to_owned()),
104+
callback: "https://callback.uri/in/fuzzing".to_owned(),
105+
})
106+
};
107+
108+
let resolver = Resolver(Mutex::new((Some(resolution), bolt11)));
109+
30110
if let Ok(s) = std::str::from_utf8(data) {
31111
let waker = unsafe { Waker::from_raw(clone_fn(std::ptr::null())) };
32112

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

0 commit comments

Comments
 (0)