|
| 1 | +#![no_main] |
| 2 | + |
| 3 | +use arbitrary::Arbitrary; |
| 4 | +use bip0032::{ |
| 5 | + ChildNumber, DerivationPath, ExtendedKeyPayload, ExtendedPrivateKey, ExtendedPublicKey, |
| 6 | + KnownVersion, backend::K256Backend, |
| 7 | +}; |
| 8 | +use libfuzzer_sys::fuzz_target; |
| 9 | + |
| 10 | +#[derive(Debug, Arbitrary)] |
| 11 | +struct Input<'a> { |
| 12 | + seed: &'a [u8], |
| 13 | + path_bytes: &'a [u8], |
| 14 | + max_seed_len: u8, |
| 15 | + max_path_len: u8, |
| 16 | +} |
| 17 | + |
| 18 | +fn bounded_slice(bytes: &[u8], max_len: usize) -> &[u8] { |
| 19 | + let len = bytes.len().min(max_len); |
| 20 | + &bytes[..len] |
| 21 | +} |
| 22 | + |
| 23 | +fn build_path(bytes: &[u8], max_children: usize) -> (DerivationPath, bool) { |
| 24 | + let mut children = Vec::new(); |
| 25 | + let mut has_hardened = false; |
| 26 | + |
| 27 | + for chunk in bytes.chunks(5).take(max_children) { |
| 28 | + if chunk.len() < 5 { |
| 29 | + break; |
| 30 | + } |
| 31 | + let mut index_bytes = [0u8; 4]; |
| 32 | + index_bytes.copy_from_slice(&chunk[..4]); |
| 33 | + let mut index = u32::from_le_bytes(index_bytes); |
| 34 | + index &= 0x7FFF_FFFF; |
| 35 | + let hardened = (chunk[4] & 1) == 1; |
| 36 | + |
| 37 | + if let Ok(child) = ChildNumber::new(index, hardened) { |
| 38 | + has_hardened |= hardened; |
| 39 | + children.push(child); |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + (DerivationPath::from(children), has_hardened) |
| 44 | +} |
| 45 | + |
| 46 | +fn roundtrip_xprv(key: &ExtendedPrivateKey<K256Backend>) -> String { |
| 47 | + let encoded = key.encode_with(KnownVersion::Xprv.version()).unwrap().to_string(); |
| 48 | + let payload = encoded.parse::<ExtendedKeyPayload>().unwrap(); |
| 49 | + let decoded = ExtendedPrivateKey::<K256Backend>::try_from(payload).unwrap(); |
| 50 | + let encoded2 = decoded.encode_with(KnownVersion::Xprv.version()).unwrap().to_string(); |
| 51 | + assert_eq!(encoded2, encoded); |
| 52 | + encoded |
| 53 | +} |
| 54 | + |
| 55 | +fn roundtrip_xpub(key: &ExtendedPublicKey<K256Backend>) -> String { |
| 56 | + let encoded = key.encode_with(KnownVersion::Xpub.version()).unwrap().to_string(); |
| 57 | + let payload = encoded.parse::<ExtendedKeyPayload>().unwrap(); |
| 58 | + let decoded = ExtendedPublicKey::<K256Backend>::try_from(payload).unwrap(); |
| 59 | + let encoded2 = decoded.encode_with(KnownVersion::Xpub.version()).unwrap().to_string(); |
| 60 | + assert_eq!(encoded2, encoded); |
| 61 | + encoded |
| 62 | +} |
| 63 | + |
| 64 | +fuzz_target!(|input: Input<'_>| { |
| 65 | + let seed_len = (input.max_seed_len as usize).min(64); |
| 66 | + let seed = bounded_slice(input.seed, seed_len); |
| 67 | + |
| 68 | + let max_children = (input.max_path_len as usize).min(32); |
| 69 | + let (path, has_hardened) = build_path(input.path_bytes, max_children); |
| 70 | + |
| 71 | + let master = match ExtendedPrivateKey::<K256Backend>::new(seed) { |
| 72 | + Ok(master) => master, |
| 73 | + Err(_) => return, |
| 74 | + }; |
| 75 | + |
| 76 | + let derived = match master.derive_path(&path) { |
| 77 | + Ok(derived) => derived, |
| 78 | + Err(_) => { |
| 79 | + if !has_hardened { |
| 80 | + let _ = master.public_key().derive_path(&path); |
| 81 | + } |
| 82 | + return; |
| 83 | + }, |
| 84 | + }; |
| 85 | + |
| 86 | + let xprv = roundtrip_xprv(&derived); |
| 87 | + let xpub = roundtrip_xpub(&derived.public_key()); |
| 88 | + |
| 89 | + let path_str = path.to_string(); |
| 90 | + let parsed_path = path_str.parse::<DerivationPath>().unwrap(); |
| 91 | + assert_eq!(parsed_path.to_string(), path_str); |
| 92 | + |
| 93 | + if !has_hardened { |
| 94 | + let derived_pub = master.public_key().derive_path(&path).unwrap(); |
| 95 | + let xpub_from_pub = roundtrip_xpub(&derived_pub); |
| 96 | + assert_eq!(xpub_from_pub, xpub); |
| 97 | + } else { |
| 98 | + assert!(master.public_key().derive_path(&path).is_err()); |
| 99 | + } |
| 100 | + |
| 101 | + let xprv_payload = xprv.parse::<ExtendedKeyPayload>().unwrap(); |
| 102 | + assert!(xprv_payload.version().is_private()); |
| 103 | + let xpub_payload = xpub.parse::<ExtendedKeyPayload>().unwrap(); |
| 104 | + assert!(xpub_payload.version().is_public()); |
| 105 | +}); |
0 commit comments