1#![no_std]
2pub 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#[derive(Debug, Copy, Clone, PartialEq)]
43pub enum NoteKinds {
44 ShortNote,
46 DM,
48 IOT,
50 Auth,
52 Regular(u16),
54 Replaceable(u16),
56 Ephemeral(u16),
58 ParameterizedReplaceable(u16),
60 Custom(u16),
62}
63
64impl NoteKinds {
65 pub fn serialize(&self) -> String<10> {
66 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#[derive(Debug, PartialEq)]
109pub struct Note {
110 id: [u8; 64],
112 pubkey: [u8; 64],
114 created_at: u32,
116 kind: NoteKinds,
118 tags: Vec<String<TAG_SIZE>, 5>,
119 content: Option<String<NOTE_SIZE>>,
120 sig: [u8; 128],
121}
122
123pub trait AddTag {
126 type Next: TagCount;
127 fn next(self) -> Self::Next;
129}
130pub trait TagCount {}
132pub struct ZeroTags;
134pub struct OneTag;
136pub struct TwoTags;
138pub struct ThreeTags;
140pub struct FourTags;
142pub 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 #[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
189pub struct BuildStatus<B> {
191 tags: B,
192}
193
194pub 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 #[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 pub fn set_kind(mut self, kind: NoteKinds) -> Self {
228 self.note.kind = kind;
229 self
230 }
231
232 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 #[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 #[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 #[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 #[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; 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; count += 1;
355 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 hash_str[count] = 91;
364 count += 1;
365 tag.split(",").for_each(|element| {
366 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 hash_str[count] = 34;
375 count += 1;
376 hash_str[count] = 44;
378 count += 1;
379 });
380 count -= 1;
382 hash_str[count] = 93;
384 count += 1;
385
386 hash_str[count] = 44;
388 count += 1;
389 tags_present = true;
390 });
391 if tags_present {
392 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 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 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 output.push(91).expect("impossible");
520 tag.split(",").for_each(|element| {
521 output.push(34).expect("impossible");
523 element.as_bytes().iter().for_each(|bs| {
524 output.push(*bs).expect("impossible");
525 });
526 output.push(34).expect("impossible");
528 output.push(44).expect("impossible");
530 });
531 output.pop().expect("impossible");
533 output.push(93).expect("impossible");
535 output.push(44).expect("impossible");
537 tags_present = true;
538 });
539 if tags_present {
540 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 #[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 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 #[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 .map(|tag| {
594 let mut splits = tag.split(",");
595 splits.next();
597 splits.collect()
598 })
599 .collect())
600 }
601
602 #[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}