lnurl/
auth.rs

1use anyhow::anyhow;
2use bitcoin::bip32::{ChildNumber, DerivationPath};
3use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
4use std::convert::TryInto;
5use std::str::FromStr;
6use url::Url;
7
8/// Derive a derivation path from a hashing key and a url
9/// This is for LUD-05
10pub fn get_derivation_path(hashing_key: [u8; 32], url: &Url) -> anyhow::Result<DerivationPath> {
11    // There exists a private hashingKey which is derived by user LN WALLET using m/138'/0 path.
12    let mut engine = HmacEngine::<sha256::Hash>::new(&hashing_key);
13
14    // LN SERVICE full domain name is extracted from login LNURL
15    let host = url.host().ok_or(anyhow!("No host"))?;
16
17    // and then hashed using hmacSha256(hashingKey, full service domain name)
18    engine.input(host.to_string().as_bytes());
19    let derivation_mat = Hmac::<sha256::Hash>::from_engine(engine);
20
21    // First 16 bytes are taken from resulting hash and then turned into a sequence of 4 u32 values
22    let uints: [u32; 4] = (0..4)
23        .map(|i| u32::from_be_bytes(derivation_mat[(i * 4)..((i + 1) * 4)].try_into().unwrap()))
24        .collect::<Vec<u32>>()
25        .try_into()
26        .expect("slice with incorrect length");
27    // parse into ChildNumbers so we handle hardened vs unhardened
28    let children = uints.map(ChildNumber::from);
29
30    // which are in turn used to derive a service-specific linkingKey using m/138'/<long1>/<long2>/<long3>/<long4> path
31    let path = DerivationPath::from_str(&format!(
32        "m/138'/{}/{}/{}/{}",
33        children[0], children[1], children[2], children[3]
34    ))
35    .map_err(|e| anyhow!("Error deriving path: {e}"))?;
36
37    Ok(path)
38}
39
40#[cfg(test)]
41mod test {
42    use bitcoin::bip32::{ChildNumber, DerivationPath};
43    use bitcoin::hashes::hex::FromHex;
44    use std::str::FromStr;
45    use url::Url;
46
47    #[test]
48    fn test_lud_05_static_test_vector() {
49        let hashing_key: [u8; 32] =
50            FromHex::from_hex("7d417a6a5e9a6a4a879aeaba11a11838764c8fa2b959c242d43dea682b3e409b")
51                .unwrap();
52        let url = Url::parse("https://site.com").unwrap();
53
54        let path = super::get_derivation_path(hashing_key, &url).unwrap();
55        let expected = DerivationPath::from_str(&format!(
56            "m/138'/{}/{}/{}/{}",
57            ChildNumber::from(1588488367),
58            ChildNumber::from(2659270754),
59            ChildNumber::from(38110259),
60            ChildNumber::from(4136336762),
61        ))
62        .unwrap();
63
64        assert_eq!(path, expected);
65    }
66}