Skip to content

Instantly share code, notes, and snippets.

@blakejakopovic
Created March 21, 2023 17:53
Show Gist options
  • Save blakejakopovic/f084749a912b9fcab60128ef9c6c98c5 to your computer and use it in GitHub Desktop.
Save blakejakopovic/f084749a912b9fcab60128ef9c6c98c5 to your computer and use it in GitHub Desktop.
Nostr NIP-19 Rust TLV decode
#[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);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment