nostr_types/types/
naddr.rs

1use super::{EventKind, PublicKey, UncheckedUrl};
2use crate::Error;
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "speedy")]
5use speedy::{Readable, Writable};
6use std::hash::{Hash, Hasher};
7
8/// An 'naddr': data to address a possibly parameterized replaceable event (d-tag, kind, author, and relays)
9#[derive(Clone, Debug, Serialize, Deserialize)]
10#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
11pub struct NAddr {
12    /// the 'd' tag of the Event, or an empty string if the kind is not parameterized
13    pub d: String,
14
15    /// Some of the relays where this could be found
16    pub relays: Vec<UncheckedUrl>,
17
18    /// Kind
19    pub kind: EventKind,
20
21    /// Author
22    pub author: PublicKey,
23}
24
25impl NAddr {
26    /// Export as a bech32 encoded string ("naddr")
27    pub fn as_bech32_string(&self) -> String {
28        // Compose
29        let mut tlv: Vec<u8> = Vec::new();
30
31        // Push d tag
32        tlv.push(0); // the special value, in this case the 'd' tag
33        let len = self.d.len() as u8;
34        tlv.push(len); // the length of the d tag
35        tlv.extend(&self.d.as_bytes()[..len as usize]);
36
37        // Push relays
38        for relay in &self.relays {
39            tlv.push(1); // type 'relay'
40            let len = relay.0.len() as u8;
41            tlv.push(len); // the length of the string
42            tlv.extend(&relay.0.as_bytes()[..len as usize]);
43        }
44
45        // Push kind
46        let kindnum: u32 = From::from(self.kind);
47        let bytes = kindnum.to_be_bytes();
48        tlv.push(3); // type 'kind'
49        tlv.push(bytes.len() as u8); // '4'
50        tlv.extend(bytes);
51
52        // Push author
53        tlv.push(2); // type 'author'
54        tlv.push(32); // the length of the value (always 32 for public key)
55        tlv.extend(self.author.as_bytes());
56
57        bech32::encode::<bech32::Bech32>(*crate::HRP_NADDR, &tlv).unwrap()
58    }
59
60    /// Import from a bech32 encoded string ("naddr")
61    pub fn try_from_bech32_string(s: &str) -> Result<NAddr, Error> {
62        let data = bech32::decode(s)?;
63        if data.0 != *crate::HRP_NADDR {
64            Err(Error::WrongBech32(
65                crate::HRP_NADDR.to_lowercase(),
66                data.0.to_lowercase(),
67            ))
68        } else {
69            let mut maybe_d: Option<String> = None;
70            let mut relays: Vec<UncheckedUrl> = Vec::new();
71            let mut maybe_kind: Option<EventKind> = None;
72            let mut maybe_author: Option<PublicKey> = None;
73
74            let tlv = data.1;
75            let mut pos = 0;
76            loop {
77                // we need at least 2 more characters for anything meaningful
78                if pos > tlv.len() - 2 {
79                    break;
80                }
81                let ty = tlv[pos];
82                let len = tlv[pos + 1] as usize;
83                pos += 2;
84                if pos + len > tlv.len() {
85                    return Err(Error::InvalidProfile);
86                }
87                let raw = &tlv[pos..pos + len];
88                match ty {
89                    0 => {
90                        // special (bytes of d tag)
91                        maybe_d = Some(std::str::from_utf8(raw)?.to_string());
92                    }
93                    1 => {
94                        // relay
95                        let relay_str = std::str::from_utf8(raw)?;
96                        let relay = UncheckedUrl::from_str(relay_str);
97                        relays.push(relay);
98                    }
99                    2 => {
100                        // author
101                        //
102                        // Don't fail if the pubkey is bad, just don't include it.
103                        // Some client is generating these, and we want to tolerate it
104                        // as much as we can.
105                        if let Ok(pk) = PublicKey::from_bytes(raw, true) {
106                            maybe_author = Some(pk);
107                        }
108                    }
109                    3 => {
110                        // kind
111                        let kindnum = u32::from_be_bytes(
112                            raw.try_into().map_err(|_| Error::WrongLengthKindBytes)?,
113                        );
114                        maybe_kind = Some(kindnum.into());
115                    }
116                    _ => {} // unhandled type for nprofile
117                }
118                pos += len;
119            }
120
121            match (maybe_d, maybe_kind, maybe_author) {
122                (Some(d), Some(kind), Some(author)) => {
123                    if !kind.is_replaceable() {
124                        Err(Error::NonReplaceableAddr)
125                    } else {
126                        Ok(NAddr {
127                            d,
128                            relays,
129                            kind,
130                            author,
131                        })
132                    }
133                }
134                _ => Err(Error::InvalidNAddr),
135            }
136        }
137    }
138
139    // Mock data for testing
140    #[allow(dead_code)]
141    pub(crate) fn mock() -> NAddr {
142        let d = "Test D Indentifier 1lkjf23".to_string();
143
144        NAddr {
145            d,
146            relays: vec![
147                UncheckedUrl::from_str("wss://relay.example.com"),
148                UncheckedUrl::from_str("wss://relay2.example.com"),
149            ],
150            kind: EventKind::LongFormContent,
151            author: PublicKey::mock_deterministic(),
152        }
153    }
154}
155
156impl PartialEq for NAddr {
157    fn eq(&self, other: &Self) -> bool {
158        self.d == other.d && self.kind == other.kind && self.author == other.author
159        // We do not compare the relays field!
160    }
161}
162
163impl Eq for NAddr {}
164
165impl Hash for NAddr {
166    fn hash<H: Hasher>(&self, state: &mut H) {
167        self.d.hash(state);
168        self.kind.hash(state);
169        self.author.hash(state);
170        // We do not hash relays field!
171    }
172}
173
174#[cfg(test)]
175mod test {
176    use super::*;
177
178    test_serde! {NAddr, test_naddr_serde}
179
180    #[test]
181    fn test_profile_bech32() {
182        let bech32 = NAddr::mock().as_bech32_string();
183        println!("{bech32}");
184        assert_eq!(
185            NAddr::mock(),
186            NAddr::try_from_bech32_string(&bech32).unwrap()
187        );
188    }
189}