nostr_nostd/
lib.rs

1#![no_std]
2//! Implementation of [Nostr](https://nostr.com/) for a #![no_std] environment. It supports note creation and parsing relay responses.
3//! An example project using an esp32 can be seen [here](https://github.com/isaac-asdf/esp32-nostr-client).
4//!
5//! # Example
6//! ```
7//! use nostr_nostd::{Note, String, ClientMsgKinds};
8//! let privkey = "a5084b35a58e3e1a26f5efb46cb9dbada73191526aa6d11bccb590cbeb2d8fa3";
9//! let content: String<400> = String::from("Hello, World!");
10//! let tag: String<150> = String::from("relay,wss://relay.example.com/");
11//! // aux_rand should be generated from a random number generator
12//! // required to keep PRIVKEY secure with Schnorr signatures
13//! let aux_rand = [0; 32];
14//! let note = Note::new_builder(privkey)
15//!     .unwrap()
16//!     .content(content)
17//!     .add_tag(tag)
18//!     .build(1686880020, aux_rand)
19//!     .unwrap();
20//! let msg = note.serialize_to_relay(ClientMsgKinds::Event);
21//! ```
22//!
23
24pub use heapless::{String, Vec};
25use relay_responses::AuthMessage;
26use secp256k1::{self, ffi::types::AlignedType, KeyPair, Message, XOnlyPublicKey};
27use sha2::{Digest, Sha256};
28use utils::to_decimal_str;
29
30pub mod errors;
31mod nip04;
32mod parse_json;
33pub mod query;
34pub mod relay_responses;
35mod utils;
36
37const TAG_SIZE: usize = 150;
38const NOTE_SIZE: usize = 400;
39const MAX_DM_SIZE: usize = 400;
40
41/// Defined by the [nostr protocol](https://github.com/nostr-protocol/nips/tree/master#event-kinds)
42#[derive(Debug, Copy, Clone, PartialEq)]
43pub enum NoteKinds {
44    /// For most short text based notes
45    ShortNote,
46    /// DM
47    DM,
48    /// IOT Event,
49    IOT,
50    /// Ephemeral event for authentication to relay
51    Auth,
52    /// Regular Events (must be between 1000 and <=9999)
53    Regular(u16),
54    /// Replacabe event (must be between 10000 and <20000)
55    Replaceable(u16),
56    /// Ephemeral event (must be between 20000 and <30000)
57    Ephemeral(u16),
58    /// Parameterized Replacabe event (must be between 30000 and <40000)
59    ParameterizedReplaceable(u16),
60    /// Custom
61    Custom(u16),
62}
63
64impl NoteKinds {
65    pub fn serialize(&self) -> String<10> {
66        // will ignore large bytes when serializing
67        let n: u16 = match self {
68            NoteKinds::ShortNote => 1,
69            NoteKinds::DM => 4,
70            NoteKinds::IOT => 5732,
71            NoteKinds::Auth => 22242,
72            NoteKinds::Regular(val) => *val,
73            NoteKinds::Replaceable(val) => *val,
74            NoteKinds::Ephemeral(val) => *val,
75            NoteKinds::ParameterizedReplaceable(val) => *val,
76            NoteKinds::Custom(val) => *val,
77        };
78
79        to_decimal_str(n as u32)
80    }
81}
82
83impl From<u16> for NoteKinds {
84    fn from(value: u16) -> Self {
85        match value {
86            1 => NoteKinds::ShortNote,
87            4 => NoteKinds::DM,
88            5732 => NoteKinds::IOT,
89            22242 => NoteKinds::Auth,
90            x if (1_000..10_000).contains(&x) => NoteKinds::Regular(x as u16),
91            x if (10_000..20_000).contains(&x) => NoteKinds::Replaceable(x as u16),
92            x if (20_000..30_000).contains(&x) => NoteKinds::Ephemeral(x as u16),
93            x if (30_000..40_000).contains(&x) => NoteKinds::ParameterizedReplaceable(x as u16),
94            x => NoteKinds::Custom(x),
95        }
96    }
97}
98
99#[derive(Debug, Copy, Clone, PartialEq)]
100pub enum ClientMsgKinds {
101    Event,
102    Req,
103    Auth,
104    Close,
105}
106
107/// Representation of Nostr Note
108#[derive(Debug, PartialEq)]
109pub struct Note {
110    /// ID of note
111    id: [u8; 64],
112    /// Derived from privkey, refers to note creator
113    pubkey: [u8; 64],
114    /// Unix timestamp
115    created_at: u32,
116    /// Default to kind 1
117    kind: NoteKinds,
118    tags: Vec<String<TAG_SIZE>, 5>,
119    content: Option<String<NOTE_SIZE>>,
120    sig: [u8; 128],
121}
122
123/// Impl for tags which can had an additional tag added.
124/// ie, not implemented for FiveTags but implemented for all others
125pub trait AddTag {
126    type Next: TagCount;
127    // Add a generic parameter for the return type, bounded by TagCount
128    fn next(self) -> Self::Next;
129}
130/// Number of tags added
131pub trait TagCount {}
132/// No tags have been added
133pub struct ZeroTags;
134/// One tag has been added
135pub struct OneTag;
136/// Two tags have been added
137pub struct TwoTags;
138/// Three tags have been added
139pub struct ThreeTags;
140/// Four tags have been added
141pub struct FourTags;
142/// Five tags have been added
143pub struct FiveTags;
144
145impl TagCount for ZeroTags {}
146impl TagCount for OneTag {}
147impl TagCount for TwoTags {}
148impl TagCount for ThreeTags {}
149impl TagCount for FourTags {}
150impl TagCount for FiveTags {}
151
152impl AddTag for ZeroTags {
153    type Next = OneTag;
154    // Implement the next method to return a new MyType instance
155    #[inline]
156    fn next(self) -> OneTag {
157        OneTag
158    }
159}
160impl AddTag for OneTag {
161    type Next = TwoTags;
162    #[inline]
163    fn next(self) -> TwoTags {
164        TwoTags
165    }
166}
167impl AddTag for TwoTags {
168    type Next = ThreeTags;
169    #[inline]
170    fn next(self) -> ThreeTags {
171        ThreeTags
172    }
173}
174impl AddTag for ThreeTags {
175    type Next = FourTags;
176    #[inline]
177    fn next(self) -> FourTags {
178        FourTags
179    }
180}
181impl AddTag for FourTags {
182    type Next = FiveTags;
183    #[inline]
184    fn next(self) -> FiveTags {
185        FiveTags
186    }
187}
188
189/// Used to track the addition of the time created and the number of tags added
190pub struct BuildStatus<B> {
191    tags: B,
192}
193
194/// Used to fill in the fields of a Note.
195pub struct NoteBuilder<B> {
196    keypair: KeyPair,
197    build_status: BuildStatus<B>,
198    note: Note,
199}
200
201impl<T, NextAddTag> NoteBuilder<T>
202where
203    T: AddTag<Next = NextAddTag>,
204    NextAddTag: TagCount,
205{
206    /// Adds a new tag to the note.
207    /// The maximum number of tags currently allowed is 5.
208    /// Attempts to add too many tags will be a compilation error.
209    #[inline]
210    pub fn add_tag(mut self, tag: String<TAG_SIZE>) -> NoteBuilder<NextAddTag> {
211        let next_tags = self.build_status.tags.next();
212        self.note
213            .tags
214            .push(tag)
215            .expect("AddTag impl error, should be impossible to err here");
216
217        NoteBuilder {
218            build_status: BuildStatus { tags: next_tags },
219            keypair: self.keypair,
220            note: self.note,
221        }
222    }
223}
224
225impl<B> NoteBuilder<B> {
226    /// Sets the "kind" field of the note
227    pub fn set_kind(mut self, kind: NoteKinds) -> Self {
228        self.note.kind = kind;
229        self
230    }
231
232    /// Sets the "content" field of Note
233    pub fn content(mut self, content: String<NOTE_SIZE>) -> Self {
234        self.note.content = Some(content);
235        self
236    }
237}
238
239impl NoteBuilder<ZeroTags> {
240    /// Creates an auth note per NIP42
241    #[inline]
242    pub fn create_auth(
243        mut self,
244        auth: &AuthMessage,
245        relay: &str,
246    ) -> Result<NoteBuilder<TwoTags>, errors::Error> {
247        let mut tags = Vec::new();
248        let mut challenge_string: String<TAG_SIZE> = String::from("challenge,");
249        challenge_string
250            .push_str(&auth.challenge_string)
251            .map_err(|_| errors::Error::ContentOverflow)?;
252        tags.push(challenge_string).expect("impossible");
253        let mut relay_str: String<TAG_SIZE> = String::from("relay,");
254        relay_str
255            .push_str(relay)
256            .map_err(|_| errors::Error::ContentOverflow)?;
257        tags.push(relay_str).expect("impossible");
258        self.note.tags = tags;
259        self.note.kind = NoteKinds::Auth;
260        Ok(NoteBuilder {
261            keypair: self.keypair,
262            note: self.note,
263            build_status: BuildStatus { tags: TwoTags },
264        })
265    }
266
267    /// Sets the "content" field according to NIP04 and adds the tag for receiver pubkey.
268    /// iv should be generated from a random source
269    #[inline]
270    pub fn create_dm(
271        mut self,
272        content: &str,
273        rcvr_pubkey: &str,
274        iv: [u8; 16],
275    ) -> Result<NoteBuilder<OneTag>, errors::Error> {
276        let mut msg = [0_u8; 32];
277        base16ct::lower::decode(&rcvr_pubkey, &mut msg)
278            .map_err(|_| errors::Error::InvalidPubkey)?;
279        let pubkey = XOnlyPublicKey::from_slice(&msg).map_err(|_| errors::Error::InvalidPubkey)?;
280        let encrypted = nip04::encrypt(&self.keypair.secret_key(), &pubkey, content, iv)?;
281        self.note.content = Some(encrypted);
282        let mut tag = String::from("p,");
283        tag.push_str(rcvr_pubkey).expect("impossible");
284        Ok(self.add_tag(tag))
285    }
286}
287
288impl<A> NoteBuilder<A> {
289    /// Set the 'created_at' and sign the note.
290    #[inline]
291    pub fn build(mut self, created_at: u32, aux_rnd: [u8; 32]) -> Result<Note, errors::Error> {
292        self.note.created_at = created_at;
293        self.note.set_pubkey(&self.keypair.x_only_public_key().0)?;
294        self.note.set_id()?;
295        self.note.set_sig(&self.keypair, &aux_rnd)?;
296        Ok(self.note)
297    }
298}
299
300impl Note {
301    /// Returns a NoteBuilder, can error if the privkey is invalid
302    #[inline]
303    pub fn new_builder(privkey: &str) -> Result<NoteBuilder<ZeroTags>, errors::Error> {
304        let mut buf = [AlignedType::zeroed(); 64];
305        let sig_obj = secp256k1::Secp256k1::preallocated_new(&mut buf)
306            .map_err(|_| errors::Error::Secp256k1Error)?;
307        let key_pair: KeyPair = KeyPair::from_seckey_str(&sig_obj, privkey)
308            .map_err(|_| errors::Error::InvalidPrivkey)?;
309        Ok(NoteBuilder {
310            build_status: BuildStatus { tags: ZeroTags },
311            keypair: key_pair,
312            note: Note {
313                id: [0; 64],
314                pubkey: [0; 64],
315                created_at: 0,
316                kind: NoteKinds::ShortNote,
317                tags: Vec::new(),
318                content: None,
319                sig: [0; 128],
320            },
321        })
322    }
323
324    fn timestamp_bytes(&self) -> String<10> {
325        to_decimal_str(self.created_at)
326    }
327
328    fn to_hash_str(&self) -> ([u8; 1536], usize) {
329        let mut hash_str = [0; 1536];
330        let mut count = 0;
331        br#"[0,""#.iter().for_each(|bs| {
332            hash_str[count] = *bs;
333            count += 1;
334        });
335        self.pubkey.iter().for_each(|bs| {
336            hash_str[count] = *bs;
337            count += 1;
338        });
339        br#"","#.iter().for_each(|bs| {
340            hash_str[count] = *bs;
341            count += 1;
342        });
343        self.timestamp_bytes().chars().for_each(|bs| {
344            hash_str[count] = bs as u8;
345            count += 1;
346        });
347        hash_str[count] = 44; // 44 = ,
348        count += 1;
349        self.kind.serialize().chars().for_each(|bs| {
350            hash_str[count] = bs as u8;
351            count += 1;
352        });
353        hash_str[count] = 44; // 44 = ,
354        count += 1;
355        // tags
356        br#"["#.iter().for_each(|bs| {
357            hash_str[count] = *bs;
358            count += 1;
359        });
360        let mut tags_present = false;
361        self.tags.iter().for_each(|tag| {
362            // add opening [
363            hash_str[count] = 91;
364            count += 1;
365            tag.split(",").for_each(|element| {
366                // add opening "
367                hash_str[count] = 34;
368                count += 1;
369                element.as_bytes().iter().for_each(|bs| {
370                    hash_str[count] = *bs;
371                    count += 1;
372                });
373                // add closing "
374                hash_str[count] = 34;
375                count += 1;
376                // add , separator back in
377                hash_str[count] = 44;
378                count += 1;
379            });
380            // remove last comma
381            count -= 1;
382            // add closing ]
383            hash_str[count] = 93;
384            count += 1;
385
386            // add closing ,
387            hash_str[count] = 44;
388            count += 1;
389            tags_present = true;
390        });
391        if tags_present {
392            // remove last comma
393            count -= 1;
394        }
395        br#"],""#.iter().for_each(|bs| {
396            hash_str[count] = *bs;
397            count += 1;
398        });
399        if let Some(content) = &self.content {
400            content.as_bytes().iter().for_each(|bs| {
401                hash_str[count] = *bs;
402                count += 1;
403            });
404        }
405        br#""]"#.iter().for_each(|bs| {
406            hash_str[count] = *bs;
407            count += 1;
408        });
409        (hash_str, count)
410    }
411
412    fn set_pubkey(&mut self, pubkey: &XOnlyPublicKey) -> Result<(), errors::Error> {
413        let pubkey = &pubkey.serialize();
414        base16ct::lower::encode(pubkey, &mut self.pubkey)
415            .map_err(|_| errors::Error::EncodeError)?;
416        Ok(())
417    }
418
419    fn set_id(&mut self) -> Result<(), errors::Error> {
420        let (remaining, len) = self.to_hash_str();
421        let mut hasher = Sha256::new();
422        hasher.update(&remaining[..len]);
423        let results = hasher.finalize();
424        base16ct::lower::encode(&results, &mut self.id).map_err(|_| errors::Error::EncodeError)?;
425        Ok(())
426    }
427
428    fn set_sig(&mut self, key_pair: &KeyPair, aux_rnd: &[u8; 32]) -> Result<(), errors::Error> {
429        // figure out what size we need and why
430        let mut buf = [AlignedType::zeroed(); 64];
431        let sig_obj = secp256k1::Secp256k1::preallocated_new(&mut buf)
432            .map_err(|_| errors::Error::Secp256k1Error)?;
433
434        let mut msg = [0_u8; 32];
435        base16ct::lower::decode(&self.id, &mut msg)
436            .map_err(|_| errors::Error::InternalSigningError)?;
437
438        let message = Message::from_slice(&msg).map_err(|_| errors::Error::InternalSigningError)?;
439
440        let sig = sig_obj.sign_schnorr_with_aux_rand(&message, key_pair, aux_rnd);
441        base16ct::lower::encode(sig.as_ref(), &mut self.sig)
442            .map_err(|_| errors::Error::EncodeError)?;
443        Ok(())
444    }
445
446    fn to_json(&self) -> Vec<u8, 1000> {
447        let mut output: Vec<u8, 1000> = Vec::new();
448        br#"{"content":""#.iter().for_each(|bs| {
449            // handle result?
450            output
451                .push(*bs)
452                .expect("Impossible due to size constraints of content, tags");
453        });
454        if let Some(content) = &self.content {
455            content.as_bytes().iter().for_each(|bs| {
456                output
457                    .push(*bs)
458                    .expect("Impossible due to size constraints of content, tags");
459            });
460        }
461        br#"","created_at":"#.iter().for_each(|bs| {
462            output
463                .push(*bs)
464                .expect("Impossible due to size constraints of content, tags");
465        });
466        self.timestamp_bytes().chars().for_each(|bs| {
467            output
468                .push(bs as u8)
469                .expect("Impossible due to size constraints of content, tags");
470        });
471        br#","id":""#.iter().for_each(|bs| {
472            output
473                .push(*bs)
474                .expect("Impossible due to size constraints of content, tags");
475        });
476        self.id.iter().for_each(|bs| {
477            output
478                .push(*bs)
479                .expect("Impossible due to size constraints of content, tags");
480        });
481        br#"","kind":"#.iter().for_each(|bs| {
482            output
483                .push(*bs)
484                .expect("Impossible due to size constraints of content, tags");
485        });
486        self.kind.serialize().chars().for_each(|bs| {
487            output
488                .push(bs as u8)
489                .expect("Impossible due to size constraints of content, tags");
490        });
491        br#","pubkey":""#.iter().for_each(|bs| {
492            output
493                .push(*bs)
494                .expect("Impossible due to size constraints of content, tags");
495        });
496        self.pubkey.iter().for_each(|bs| {
497            output
498                .push(*bs)
499                .expect("Impossible due to size constraints of content, tags");
500        });
501        br#"","sig":""#.iter().for_each(|bs| {
502            output
503                .push(*bs)
504                .expect("Impossible due to size constraints of content, tags");
505        });
506        self.sig.iter().for_each(|bs| {
507            output
508                .push(*bs)
509                .expect("Impossible due to size constraints of content, tags");
510        });
511        br#"","tags":["#.iter().for_each(|bs| {
512            output
513                .push(*bs)
514                .expect("Impossible due to size constraints of content, tags");
515        });
516        let mut tags_present = false;
517        self.tags.iter().for_each(|tag| {
518            // add opening [
519            output.push(91).expect("impossible");
520            tag.split(",").for_each(|element| {
521                // add opening "
522                output.push(34).expect("impossible");
523                element.as_bytes().iter().for_each(|bs| {
524                    output.push(*bs).expect("impossible");
525                });
526                // add closing "
527                output.push(34).expect("impossible");
528                // add a comma separator
529                output.push(44).expect("impossible");
530            });
531            // remove last comma
532            output.pop().expect("impossible");
533            // add closing ]
534            output.push(93).expect("impossible");
535            // add a comma separator
536            output.push(44).expect("impossible");
537            tags_present = true;
538        });
539        if tags_present {
540            // remove last comma
541            output.pop().expect("impossible");
542        }
543        br#"]}"#.iter().for_each(|bs| {
544            output
545                .push(*bs)
546                .expect("Impossible due to size constraints of content, tags");
547        });
548
549        output
550    }
551
552    /// Serializes the note for sending to relay
553    #[inline]
554    pub fn serialize_to_relay(self, msg_type: ClientMsgKinds) -> Vec<u8, 1000> {
555        let wire_lead = match msg_type {
556            ClientMsgKinds::Event => r#"["EVENT","#,
557            ClientMsgKinds::Req => r#"["REQ","#,
558            ClientMsgKinds::Auth => r#"["AUTH","#,
559            ClientMsgKinds::Close => r#"["CLOSE","#,
560        };
561        let mut output: Vec<u8, 1000> = Vec::new();
562        // fill in output
563        wire_lead.as_bytes().iter().for_each(|bs| {
564            output
565                .push(*bs)
566                .expect("Impossible due to size constraints of content, tags");
567        });
568        let json = self.to_json();
569        json.iter().for_each(|bs| {
570            output
571                .push(*bs)
572                .expect("Impossible due to size constraints of content, tags");
573        });
574        output
575            .push(93)
576            .expect("Impossible due to size constraints of content, tags");
577        output
578    }
579
580    /// Get associated values with a given tag name.
581    /// Returns up to 5 instances for the searched for label.
582    #[inline]
583    pub fn get_tag(&self, tag: &str) -> Result<Vec<Vec<&str, 5>, 5>, errors::Error> {
584        let mut search_tag: String<10> = String::from(tag);
585        search_tag
586            .push_str(",")
587            .map_err(|_| errors::Error::TagNameTooLong)?;
588        Ok(self
589            .tags
590            .iter()
591            .filter(|my_tag| my_tag.starts_with(search_tag.as_str()))
592            // each tag will look like tag_name,val1,val2,etc...
593            .map(|tag| {
594                let mut splits = tag.split(",");
595                // remove tag_name from splits
596                splits.next();
597                splits.collect()
598            })
599            .collect())
600    }
601
602    /// Decode an encrypted DM
603    #[inline]
604    pub fn read_dm(&self, privkey: &str) -> Result<String<MAX_DM_SIZE>, errors::Error> {
605        let mut buf = [AlignedType::zeroed(); 64];
606        let sig_obj = secp256k1::Secp256k1::preallocated_new(&mut buf)
607            .map_err(|_| errors::Error::Secp256k1Error)?;
608        let key_pair: KeyPair = KeyPair::from_seckey_str(&sig_obj, privkey)
609            .map_err(|_| errors::Error::InvalidPrivkey)?;
610        let sk = key_pair.secret_key();
611        let pk_tag = self.get_tag("p")?;
612        let pk_tag = *pk_tag
613            .first()
614            .ok_or(errors::Error::MalformedContent)?
615            .first()
616            .ok_or(errors::Error::MalformedContent)?;
617        let mut msg = [0_u8; 32];
618        base16ct::lower::decode(&pk_tag, &mut msg).map_err(|_| errors::Error::EncodeError)?;
619        let pk = XOnlyPublicKey::from_slice(&msg).map_err(|_| errors::Error::InvalidPubkey)?;
620        nip04::decrypt(
621            &sk,
622            &pk,
623            self.content
624                .as_ref()
625                .ok_or(errors::Error::MalformedContent)?
626                .as_str(),
627        )
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634    const PRIVKEY: &str = "a5084b35a58e3e1a26f5efb46cb9dbada73191526aa6d11bccb590cbeb2d8fa3";
635
636    fn get_note() -> Note {
637        Note::new_builder(PRIVKEY)
638            .unwrap()
639            .content("esptest".into())
640            .build(1686880020, [0; 32])
641            .expect("infallible")
642    }
643
644    #[test]
645    fn test_note_with_tag() {
646        let note = Note::new_builder(PRIVKEY)
647            .unwrap()
648            .content("esptest".into())
649            .add_tag("l,bitcoin".into())
650            .build(1686880020, [0; 32])
651            .expect("infallible");
652        let test = note.serialize_to_relay(ClientMsgKinds::Event);
653        let expected = br#"["EVENT",{"content":"esptest","created_at":1686880020,"id":"f5a693c9a4add3739a4186c0422f925981f75cb1f7a0adfc48852e54973415a6","kind":1,"pubkey":"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf","sig":"ff68b2c739f6d19df47c5ae5f150895e11876458afcf8bf169636e55c2b6cce1230d0c54ce9869b555b3395018c1efdad5b4c5a4afbc2748e1f8c3a34da787ec","tags":[["l","bitcoin"]]}]"#;
654        assert_eq!(test, expected);
655    }
656
657    #[test]
658    fn pubkey_test() {
659        let note = get_note();
660        let pubkey = note.pubkey;
661        assert_eq!(
662            pubkey,
663            *b"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf"
664        );
665    }
666
667    #[test]
668    fn id_test() {
669        let note = get_note();
670        let id = note.id;
671        assert_eq!(
672            id,
673            *b"b515da91ac5df638fae0a6e658e03acc1dda6152dd2107d02d5702ccfcf927e8"
674        );
675    }
676
677    #[test]
678    fn timestamp_test() {
679        let note = get_note();
680        let ts = note.timestamp_bytes();
681        assert_eq!(ts, String::<10>::from("1686880020"));
682    }
683
684    #[test]
685    fn hashstr_test() {
686        let note = get_note();
687        let hash_correct = br#"[0,"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf",1686880020,1,[],"esptest"]"#;
688        let (hashed, len) = note.to_hash_str();
689        let hashed = &hashed[..len];
690        assert_eq!(hashed, hash_correct);
691    }
692
693    #[test]
694    fn json_test() {
695        let output =  br#"{"content":"esptest","created_at":1686880020,"id":"b515da91ac5df638fae0a6e658e03acc1dda6152dd2107d02d5702ccfcf927e8","kind":1,"pubkey":"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf","sig":"89a4f1ad4b65371e6c3167ea8cb13e73cf64dd5ee71224b1edd8c32ad817af2312202cadb2f22f35d599793e8b1c66b3979d4030f1e7a252098da4a4e0c48fab","tags":[]}"#;
696        let note = get_note();
697        let msg = note.to_json();
698        assert_eq!(&msg, output);
699    }
700
701    #[test]
702    fn serialize_to_relay_test() {
703        let output =  br#"["EVENT",{"content":"esptest","created_at":1686880020,"id":"b515da91ac5df638fae0a6e658e03acc1dda6152dd2107d02d5702ccfcf927e8","kind":1,"pubkey":"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf","sig":"89a4f1ad4b65371e6c3167ea8cb13e73cf64dd5ee71224b1edd8c32ad817af2312202cadb2f22f35d599793e8b1c66b3979d4030f1e7a252098da4a4e0c48fab","tags":[]}]"#;
704        let note = get_note();
705        let msg = note.serialize_to_relay(ClientMsgKinds::Event);
706        assert_eq!(&msg, output);
707    }
708
709    #[test]
710    fn test_from_json() {
711        let json = r#"{"content":"esptest","created_at":1686880020,"id":"b515da91ac5df638fae0a6e658e03acc1dda6152dd2107d02d5702ccfcf927e8","kind":1,"pubkey":"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf","sig":"89a4f1ad4b65371e6c3167ea8cb13e73cf64dd5ee71224b1edd8c32ad817af2312202cadb2f22f35d599793e8b1c66b3979d4030f1e7a252098da4a4e0c48fab","tags":[]"#;
712        let note = Note::try_from(json).expect("infallible");
713        let expected_note = get_note();
714        assert_eq!(note, expected_note);
715    }
716
717    #[test]
718    fn test_tags() {
719        let dm_rcv: &str = r#"{"content":"sZhES/uuV1uMmt9neb6OQw6mykdLYerAnTN+LodleSI=?iv=eM0mGFqFhxmmMwE4YPsQMQ==","created_at":1691110186,"id":"517a5f0f29f5037d763bbd5fbe96c9082c1d39eca917aa22b514c5effc36bab9","kind":4,"pubkey":"ed984a5438492bdc75860aad15a59f8e2f858792824d615401fb49d79c2087b0","sig":"3097de7d5070b892b81b245a5b276eccd7cb283a29a934a71af4960188e55e87d639b774cc331eb9f94ea7c46373c52b8ab39bfee75fe4bb11a1dd4c187e1f3e","tags":[["p","098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf"]]}"#;
720        let note = Note::try_from(dm_rcv).unwrap();
721
722        let tags = note.get_tag("p").unwrap();
723        let pubkey = tags.first().unwrap().first().unwrap();
724        assert_eq!(
725            *pubkey,
726            "098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf"
727        );
728    }
729
730    #[test]
731    fn test_get_tag() {
732        let mut tags = Vec::new();
733        tags.push(String::from("p,test_pubkey")).unwrap();
734        let note = Note {
735            id: [0; 64],
736            pubkey: [0; 64],
737            created_at: 0,
738            kind: NoteKinds::DM,
739            tags,
740            content: None,
741            sig: [0; 128],
742        };
743        let tags = note.get_tag("p").unwrap();
744        let pubkey = tags.first().unwrap().first().unwrap();
745        assert_eq!(*pubkey, "test_pubkey");
746    }
747
748    #[test]
749    fn test_get_two_tags() {
750        let mut tags = Vec::new();
751        tags.push(String::from("l,labeled,another label")).unwrap();
752        tags.push(String::from("l,ignore the other label")).unwrap();
753        let note = Note {
754            id: [0; 64],
755            pubkey: [0; 64],
756            created_at: 0,
757            kind: NoteKinds::DM,
758            tags,
759            content: None,
760            sig: [0; 128],
761        };
762        let binding = note.get_tag("l").unwrap();
763        let mut tags = binding.iter();
764        let label = tags.next().unwrap();
765        let mut labels = label.iter();
766        assert_eq!(*labels.next().unwrap(), "labeled");
767        assert_eq!(*labels.next().unwrap(), "another label");
768
769        let label = tags.next().unwrap();
770        let mut labels = label.iter();
771        assert_eq!(*labels.next().unwrap(), "ignore the other label");
772    }
773
774    #[test]
775    fn test_auth_msg() {
776        let note = Note::new_builder(PRIVKEY)
777            .unwrap()
778            .create_auth(
779                &AuthMessage {
780                    challenge_string: "challenge_me".into(),
781                },
782                "wss://relay.damus.io",
783            )
784            .unwrap()
785            .build(1691712199, [0; 32])
786            .unwrap();
787
788        let expected = br#"{"content":"","created_at":1691712199,"id":"762b497576a41636c41eb5c74c0eb80894ecb2444c3e5117da0d00d9870d914a","kind":22242,"pubkey":"098ef66bce60dd4cf10b4ae5949d1ec6dd777ddeb4bc49b47f97275a127a63cf","sig":"afb892c683222936537ac1ea1ecdade47adf572e96773dfc6ca021d929d3485ecd7d086b14503e545312f61bd8ffdbd48887cd27b3ab2e4f70aab62a4a1afd1b","tags":[["challenge","challenge_me"],["relay","wss://relay.damus.io"]]}"#;
789        assert_eq!(note.to_json(), expected);
790    }
791}