Skip to content

Commit dbe7d8a

Browse files
committed
[WIP] Create POC for fuzzing payjoin
This commit adds fuzzing infrastructure for payjoin
1 parent b97ffb7 commit dbe7d8a

File tree

7 files changed

+235
-1
lines changed

7 files changed

+235
-1
lines changed

Cargo-minimal.lock

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ version = "1.0.99"
152152
source = "registry+https://github.com/rust-lang/crates.io-index"
153153
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
154154

155+
[[package]]
156+
name = "arbitrary"
157+
version = "1.4.2"
158+
source = "registry+https://github.com/rust-lang/crates.io-index"
159+
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
160+
155161
[[package]]
156162
name = "arraydeque"
157163
version = "0.5.1"
@@ -1920,6 +1926,16 @@ version = "0.2.174"
19201926
source = "registry+https://github.com/rust-lang/crates.io-index"
19211927
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
19221928

1929+
[[package]]
1930+
name = "libfuzzer-sys"
1931+
version = "0.4.10"
1932+
source = "registry+https://github.com/rust-lang/crates.io-index"
1933+
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
1934+
dependencies = [
1935+
"arbitrary",
1936+
"cc",
1937+
]
1938+
19231939
[[package]]
19241940
name = "libredox"
19251941
version = "0.1.3"
@@ -2343,6 +2359,19 @@ dependencies = [
23432359
"url",
23442360
]
23452361

2362+
[[package]]
2363+
name = "payjoin-fuzz"
2364+
version = "0.0.0"
2365+
dependencies = [
2366+
"bitcoin 0.32.7",
2367+
"bitcoin-ohttp",
2368+
"bitcoin_uri",
2369+
"libfuzzer-sys",
2370+
"payjoin",
2371+
"payjoin-test-utils",
2372+
"url",
2373+
]
2374+
23462375
[[package]]
23472376
name = "payjoin-test-utils"
23482377
version = "0.0.1"

Cargo-recent.lock

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ version = "1.0.99"
152152
source = "registry+https://github.com/rust-lang/crates.io-index"
153153
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
154154

155+
[[package]]
156+
name = "arbitrary"
157+
version = "1.4.2"
158+
source = "registry+https://github.com/rust-lang/crates.io-index"
159+
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
160+
155161
[[package]]
156162
name = "arraydeque"
157163
version = "0.5.1"
@@ -1920,6 +1926,16 @@ version = "0.2.174"
19201926
source = "registry+https://github.com/rust-lang/crates.io-index"
19211927
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
19221928

1929+
[[package]]
1930+
name = "libfuzzer-sys"
1931+
version = "0.4.10"
1932+
source = "registry+https://github.com/rust-lang/crates.io-index"
1933+
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
1934+
dependencies = [
1935+
"arbitrary",
1936+
"cc",
1937+
]
1938+
19231939
[[package]]
19241940
name = "libredox"
19251941
version = "0.1.3"
@@ -2343,6 +2359,19 @@ dependencies = [
23432359
"url",
23442360
]
23452361

2362+
[[package]]
2363+
name = "payjoin-fuzz"
2364+
version = "0.0.0"
2365+
dependencies = [
2366+
"bitcoin 0.32.7",
2367+
"bitcoin-ohttp",
2368+
"bitcoin_uri",
2369+
"libfuzzer-sys",
2370+
"payjoin",
2371+
"payjoin-test-utils",
2372+
"url",
2373+
]
2374+
23462375
[[package]]
23472376
name = "payjoin-test-utils"
23482377
version = "0.0.1"

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["payjoin", "payjoin-cli", "payjoin-directory", "payjoin-test-utils", "payjoin-ffi"]
2+
members = ["payjoin", "payjoin-cli", "payjoin-directory", "payjoin-test-utils", "payjoin-ffi", "fuzz"]
33
resolver = "2"
44

55
[patch.crates-io]

fuzz/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
target
2+
corpus
3+
artifacts
4+
coverage

fuzz/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "payjoin-fuzz"
3+
version = "0.0.0"
4+
publish = false
5+
edition = "2021"
6+
7+
[package.metadata]
8+
cargo-fuzz = true
9+
10+
[features]
11+
default = ["fuzzing"]
12+
fuzzing = []
13+
14+
[dependencies]
15+
bitcoin = { version = "0.32.7", features = ["base64"] }
16+
bitcoin_uri = { version = "0.1.0" }
17+
payjoin = { version = "1.0.0-rc.0", default-features = false, features = ["_core", "v1", "v2"] }
18+
payjoin-test-utils = { path = "../payjoin-test-utils" }
19+
libfuzzer-sys = { version = "0.4.0" }
20+
ohttp = { package = "bitcoin-ohttp", version = "0.6.0" }
21+
url = { version = "2.5.4", default-features=false, features = ["serde"] }
22+
23+
[[bin]]
24+
name = "uri_deserialize_pjuri"
25+
path = "fuzz_targets/uri/deserialize_pjuri.rs"
26+
test = false
27+
doc = false
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#![no_main]
2+
3+
#[cfg(feature = "fuzzing")]
4+
use libfuzzer_sys::fuzz_target;
5+
use ohttp::hpke::{Aead, Kdf, Kem};
6+
use ohttp::{KeyId, SymmetricSuite};
7+
use payjoin::{HpkeKeyPair, HpkePublicKey, OhttpKeys};
8+
use url::Url;
9+
10+
fn do_test(data: &[u8]) {
11+
let data_str = String::from_utf8_lossy(data);
12+
let mut url = Url::parse("https://example.com").unwrap();
13+
14+
let serialized = "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC";
15+
const KEY_ID: KeyId = 1;
16+
const KEM: Kem = Kem::K256Sha256;
17+
const SYMMETRIC: &[SymmetricSuite] =
18+
&[ohttp::SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305)];
19+
let ohttp_keys = OhttpKeys(ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap());
20+
//let ohttp_keys = OhttpKeys::from_str(serialized).unwrap();
21+
url.set_ohttp(ohttp_keys.clone());
22+
assert_eq!(url.fragment(), Some(serialized));
23+
24+
let receiver_pubkey = HpkeKeyPair::gen_keypair().1;
25+
url.set_receiver_pubkey(receiver_pubkey);
26+
27+
let exp_time = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1720547781);
28+
url.set_exp(exp_time);
29+
30+
let compressed_receiver_pubkey = match url.receiver_pubkey() {
31+
Ok(rpk) => rpk.to_compressed_bytes(),
32+
Err(_) => return,
33+
};
34+
let receiver_pubkey_roundtrip =
35+
match HpkePublicKey::from_compressed_bytes(&compressed_receiver_pubkey) {
36+
Ok(rpk) => rpk.0,
37+
Err(_) => return,
38+
};
39+
40+
assert_eq!(url.ohttp().unwrap(), ohttp_keys);
41+
assert_eq!(url.receiver_pubkey().unwrap().0, receiver_pubkey_roundtrip);
42+
assert_eq!(url.exp().unwrap(), exp_time);
43+
}
44+
45+
#[cfg(feature = "fuzzing")]
46+
fuzz_target!(|data| {
47+
do_test(data);
48+
});
49+
50+
#[cfg(test)]
51+
mod tests {
52+
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
53+
let mut b = 0;
54+
for (idx, c) in hex.as_bytes().iter().enumerate() {
55+
b <<= 4;
56+
match *c {
57+
b'A'..=b'F' => b |= c - b'A' + 10,
58+
b'a'..=b'f' => b |= c - b'a' + 10,
59+
b'0'..=b'9' => b |= c - b'0',
60+
_ => panic!("Bad hex"),
61+
}
62+
if (idx & 1) == 1 {
63+
out.push(b);
64+
b = 0;
65+
}
66+
}
67+
}
68+
69+
#[test]
70+
fn duplicate_crash() {
71+
let mut a = Vec::new();
72+
extend_vec_from_hex("00000000", &mut a);
73+
super::do_test(&a);
74+
}
75+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#![no_main]
2+
3+
use std::any::{Any, TypeId};
4+
5+
use bitcoin::Amount;
6+
use bitcoin_uri::Param;
7+
#[cfg(feature = "fuzzing")]
8+
use libfuzzer_sys::fuzz_target;
9+
use payjoin::{Uri, UriExt};
10+
11+
fn do_test(data: &[u8]) {
12+
let data_str = String::from_utf8_lossy(data).trim_end_matches('\n').to_string();
13+
let pj_uri = match data_str.parse::<Uri<_>>() {
14+
Ok(pj_uri) => pj_uri.assume_checked(),
15+
Err(_) => return,
16+
};
17+
let address = pj_uri.address.is_spend_standard();
18+
if !address {
19+
return;
20+
}
21+
let amount = pj_uri.amount;
22+
23+
if let Some(label) = pj_uri.clone().label {
24+
if TypeId::of::<Param>() != label.type_id() {
25+
return;
26+
}
27+
};
28+
if let Some(message) = pj_uri.clone().message {
29+
if TypeId::of::<Param>() != message.type_id() {
30+
return;
31+
}
32+
};
33+
let extras = pj_uri.clone().check_pj_supported().unwrap().extras;
34+
assert_eq!(pj_uri.to_string(), data_str);
35+
assert!(amount.is_none_or(|btc| btc < Amount::MAX_MONEY));
36+
assert!(TypeId::of::<payjoin::OutputSubstitution>() == extras.output_substitution().type_id());
37+
assert!(TypeId::of::<String>() == extras.endpoint().type_id())
38+
}
39+
40+
#[cfg(feature = "fuzzing")]
41+
fuzz_target!(|data| {
42+
do_test(data);
43+
});
44+
45+
#[cfg(test)]
46+
mod tests {
47+
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
48+
let mut b = 0;
49+
for (idx, c) in hex.as_bytes().iter().enumerate() {
50+
b <<= 4;
51+
match *c {
52+
b'A'..=b'F' => b |= c - b'A' + 10,
53+
b'a'..=b'f' => b |= c - b'a' + 10,
54+
b'0'..=b'9' => b |= c - b'0',
55+
_ => panic!("Bad hex"),
56+
}
57+
if (idx & 1) == 1 {
58+
out.push(b);
59+
b = 0;
60+
}
61+
}
62+
}
63+
64+
#[test]
65+
fn duplicate_crash() {
66+
let mut a = Vec::new();
67+
extend_vec_from_hex("00000000", &mut a);
68+
super::do_test(&a);
69+
}
70+
}

0 commit comments

Comments
 (0)