Skip to content

Instantly share code, notes, and snippets.

@blakejakopovic
Created March 21, 2023 17:53
Show Gist options
  • Select an option

  • Save blakejakopovic/f084749a912b9fcab60128ef9c6c98c5 to your computer and use it in GitHub Desktop.

Select an option

Save blakejakopovic/f084749a912b9fcab60128ef9c6c98c5 to your computer and use it in GitHub Desktop.

Revisions

  1. blakejakopovic created this gist Mar 21, 2023.
    205 changes: 205 additions & 0 deletions nip19.rs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,205 @@
    #[macro_use]
    extern crate log;
    use anyhow::Result;

    use bech32::FromBase32;
    use secp256k1::hashes::hex::ToHex;
    use std::convert::TryFrom;

    fn parse_tlv(data: &[u8]) -> Vec<(u8, &[u8])> {
    let mut result = vec![];
    let mut rest = data;

    while !rest.is_empty() {
    let t = rest[0];
    let l = rest[1] as usize;
    let v = &rest[2..2 + l];

    rest = &rest[2 + l..];

    if v.len() < l {
    continue;
    }

    result.push((t, v));
    }

    result
    }

    #[derive(Debug, PartialEq)]
    pub enum NostrBech32 {
    NPub {
    pubkey: String,
    },
    NSec {
    seckey: String,
    },
    Note {
    event_id: String,
    },
    NProfile {
    pubkey: String,
    relays: Vec<String>
    },
    NEvent {
    event_id: String,
    relays: Vec<String>
    },
    NAddr {
    identifier: String,
    pubkey: String,
    relays: Vec<String>,
    kind: u32
    },
    NRelay {
    relay: String
    },
    }

    fn decode_nip19(data: &str) -> Result<NostrBech32> {

    let (hrp, data, _) = bech32::decode(&data)?;
    let decoded = Vec::<u8>::from_base32(&data)?;

    let result = match hrp.as_str() {
    "nsec" => {
    let seckey = decoded.to_hex();
    NostrBech32::NSec { seckey }
    },
    "npub" => {
    let pubkey = decoded.to_hex();
    NostrBech32::NPub { pubkey }
    },
    "note" => {
    let event_id = decoded.to_hex();
    NostrBech32::Note { event_id }
    },
    "nprofile" => {
    let tlv = parse_tlv(&decoded);
    let pubkey = tlv[0].1.to_hex();
    let relays: Vec<_> = tlv.iter().filter(|x| x.0 == 1).map(|r| std::str::from_utf8(r.1).unwrap().to_string()).collect();

    NostrBech32::NProfile {pubkey, relays}
    },
    "nevent" => {
    let tlv = parse_tlv(&decoded);
    let event_id = tlv[0].1.to_hex();
    let relays: Vec<_> = tlv.iter().filter(|x| x.0 == 1).map(|r| std::str::from_utf8(r.1).unwrap().to_string()).collect();

    NostrBech32::NEvent {event_id, relays}
    },
    "naddr" => {
    let tlv = parse_tlv(&decoded);
    let identifier = std::str::from_utf8(tlv[0].1)?.to_string();
    let pubkey = tlv[1].1.to_hex();
    let kind = u32::from_be_bytes(<[u8; 4]>::try_from(tlv[2].1)?);
    let relays: Vec<_> = tlv.iter().filter(|x| x.0 == 1).map(|r| std::str::from_utf8(r.1).unwrap().to_string()).collect();

    NostrBech32::NAddr {
    identifier,
    pubkey,
    relays,
    kind,
    }
    },
    "nrelay" => {
    let tlv = parse_tlv(&decoded);
    let relay = std::str::from_utf8(tlv[0].1).unwrap().to_string();

    NostrBech32::NRelay {
    relay
    }
    }
    _ => todo!(),
    };

    Ok(result)
    }

    #[tokio::main]
    async fn main() -> Result<()> {

    env_logger::init();

    // let data = "naddr1qq98yetxv4ex2mnrv4esygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2xkpjspsgqqqw4rsf34vl5";
    // NAddr { identifier: "references", pubkey: "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", relays: [], kind: 30023 }

    // let result = decode_nip19(data)?;

    // println!("{result:?}");

    Ok(())
    }

    #[cfg(test)]
    mod tests {
    use super::*;

    #[test]
    fn decode_npub() {
    let data = "npub1ktw5qzt7f5ztrft0kwm9lsw34tef9xknplvy936ddzuepp6yf9dsjrmrvj";
    let result = decode_nip19(data).unwrap();

    if let NostrBech32::NPub { pubkey } = result {
    assert_eq!(pubkey, "b2dd40097e4d04b1a56fb3b65fc1d1aaf2929ad30fd842c74d68b9908744495b");
    }
    }

    #[test]
    fn decode_nsec() {
    let data = "nsec1hn8v5thqd929j5ey68tzhlgqyle5a08je6yzmv8jsj35x5papepswaxwgc";
    let result = decode_nip19(data).unwrap();

    if let NostrBech32::NSec { seckey } = result {
    assert_eq!(seckey, "bcceca2ee06954595324d1d62bfd0027f34ebcf2ce882db0f284a343503d0e43");
    }
    }

    #[test]
    fn decode_note() {
    let data = "note1l43cfzr4umnv4f6qld2qgj3gvaspfjne8lnaz3xxgl4m9kfn6pwqcyduwy";
    let result = decode_nip19(data).unwrap();

    if let NostrBech32::Note { event_id } = result {
    assert_eq!(event_id, "fd63848875e6e6caa740fb54044a28676014ca793fe7d144c647ebb2d933d05c");
    }
    }

    #[test]
    fn decode_nprofile() {
    let data = "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p";
    let result = decode_nip19(data).unwrap();

    if let NostrBech32::NProfile { pubkey, relays } = result {
    assert_eq!(pubkey, "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d");
    assert_eq!(relays, ["wss://r.x.com", "wss://djbas.sadkb.com"]);
    }
    }

    #[test]
    fn decode_naddr() {
    let data = "naddr1qq98yetxv4ex2mnrv4esygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2xkpjspsgqqqw4rsf34vl5";
    let result = decode_nip19(data).unwrap();

    if let NostrBech32::NAddr { identifier, pubkey, relays, kind } = result {
    assert_eq!(identifier, "references");
    assert_eq!(pubkey, "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194");
    assert_eq!(relays, vec![] as Vec<String>);
    assert_eq!(kind, 30023);
    }
    }

    #[test]
    fn decode_naddr2() {
    let data = "naddr1qqrxyctwv9hxzq3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqp65wqfwwaehxw309aex2mrp0yhxummnw3ezuetcv9khqmr99ekhjer0d4skjm3wv4uxzmtsd3jjucm0d5q3vamnwvaz7tmwdaehgu3wvfskuctwvyhxxmmd0zfmwx";
    let result = decode_nip19(data).unwrap();

    if let NostrBech32::NAddr { identifier, pubkey, relays, kind } = result {
    assert_eq!(identifier, "banana");
    assert_eq!(pubkey, "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d");
    assert_eq!(relays, ["wss://relay.nostr.example.mydomain.example.com", "wss://nostr.banana.com"]);
    assert_eq!(kind, 30023);
    }
    }
    }