1mod error;
4pub mod event;
5pub mod hex;
6pub mod keys;
7#[cfg(feature = "batch")]
8pub mod mining;
9pub mod sig;
10
11#[cfg(feature = "nip04")]
12pub mod nip04;
13#[cfg(all(feature = "nostr", feature = "nip44"))]
14pub mod nip17;
15#[cfg(feature = "nip19")]
16pub mod nip19;
17#[cfg(feature = "nostr")]
18pub mod nip42;
19#[cfg(feature = "nip44")]
20pub mod nip44;
21#[cfg(feature = "nostr")]
22pub mod nostr;
23
24pub use error::SecpError;
25pub use event::{EventId, SignedEvent, UnsignedEvent};
26#[cfg(feature = "nip19")]
27pub use event::{NAddr, NEvent, NProfile, NRelay, Nip19};
28pub use keys::{PublicKey, SecretKey, XOnlyPublicKey};
29pub use sig::{EcdsaSignature, SchnorrSignature};
30
31#[cfg(feature = "batch")]
32pub use mining::{mine_pow, mine_pow_best};
33#[cfg(all(feature = "batch", feature = "nip19"))]
34pub use mining::{mine_vanity_npub, mine_vanity_npub_candidates, VanityCandidate};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct KeyBundle {
38 secret: SecretKey,
39 xonly: XOnlyPublicKey,
40}
41
42impl KeyBundle {
43 pub fn generate() -> Result<Self, SecpError> {
44 let secret = SecretKey::generate()?;
45 let xonly = secret.xonly_public_key()?;
46 Ok(Self { secret, xonly })
47 }
48
49 pub fn secret(&self) -> &SecretKey {
50 &self.secret
51 }
52
53 pub fn xonly_public_key(&self) -> &XOnlyPublicKey {
54 &self.xonly
55 }
56
57 #[cfg(feature = "batch")]
58 pub fn generate_batch(count: usize) -> Result<Vec<Self>, SecpError> {
59 let mut bundles = Vec::with_capacity(count);
60 for _ in 0..count {
61 bundles.push(Self::generate()?);
62 }
63 Ok(bundles)
64 }
65
66 #[cfg(feature = "nip19")]
67 pub fn npub(&self) -> Result<String, SecpError> {
68 nip19::encode_npub(&self.xonly)
69 }
70
71 #[cfg(feature = "nip19")]
72 pub fn nsec(&self) -> Result<String, SecpError> {
73 nip19::encode_nsec(&self.secret)
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 const SECP256K1_ORDER: [u8; 32] = [
82 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
83 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36,
84 0x41, 0x41,
85 ];
86
87 fn scalar_sub(minuend: [u8; 32], subtrahend: [u8; 32]) -> [u8; 32] {
88 let mut out = [0u8; 32];
89 let mut borrow = 0u16;
90
91 for i in (0..32).rev() {
92 let lhs = u16::from(minuend[i]);
93 let rhs = u16::from(subtrahend[i]) + borrow;
94 if lhs >= rhs {
95 out[i] = u8::try_from(lhs - rhs).expect("byte subtraction stays in range");
96 borrow = 0;
97 } else {
98 out[i] =
99 u8::try_from((lhs + 256) - rhs).expect("borrowed subtraction stays in range");
100 borrow = 1;
101 }
102 }
103
104 assert_eq!(borrow, 0, "subtraction must not underflow");
105 out
106 }
107
108 #[test]
109 fn secret_key_roundtrip() {
110 let secret = SecretKey::generate().expect("secret key");
111 let restored = SecretKey::from_bytes(secret.to_bytes()).expect("restore");
112 assert_eq!(secret.to_bytes(), restored.to_bytes());
113 }
114
115 #[test]
116 fn schnorr_sign_and_verify() {
117 let secret = SecretKey::generate().expect("secret key");
118 let pubkey = secret.xonly_public_key().expect("pubkey");
119 let digest = [7u8; 32];
120 let sig = secret.sign_schnorr_prehash(digest).expect("sign");
121 pubkey.verify_schnorr_prehash(digest, &sig).expect("verify");
122 }
123
124 #[test]
125 fn invalid_secret_key_is_rejected() {
126 let error = SecretKey::from_bytes([0u8; 32]).expect_err("must reject zero secret key");
127 assert!(matches!(error, SecpError::InvalidSecretKey));
128 }
129
130 #[test]
131 fn xonly_public_key_roundtrip() {
132 let secret = SecretKey::generate().expect("secret key");
133 let public = secret.xonly_public_key().expect("public key");
134 let restored = XOnlyPublicKey::from_bytes(public.to_bytes()).expect("restored public key");
135 assert_eq!(public.to_bytes(), restored.to_bytes());
136 }
137
138 #[test]
139 fn public_key_sec1_roundtrip() {
140 let secret = SecretKey::generate().expect("secret key");
141 let public = secret.public_key().expect("public key");
142 let restored =
143 PublicKey::from_sec1_bytes(&public.to_sec1_bytes()).expect("restored public key");
144 assert_eq!(public.to_sec1_bytes(), restored.to_sec1_bytes());
145 }
146
147 #[test]
148 fn secret_key_hex_roundtrip() {
149 let key = SecretKey::generate().expect("generate");
150 let hex = key.to_hex();
151 assert_eq!(hex.len(), 64);
152 let decoded = SecretKey::from_hex(&hex).expect("from_hex");
153 assert_eq!(key, decoded);
154 }
155
156 #[test]
157 fn xonly_hex_roundtrip() {
158 let key = SecretKey::generate().expect("generate");
159 let xonly = key.xonly_public_key().expect("xonly");
160 let hex = xonly.to_hex();
161 assert_eq!(hex.len(), 64);
162 let decoded = XOnlyPublicKey::from_hex(&hex).expect("from_hex");
163 assert_eq!(xonly, decoded);
164 }
165
166 #[test]
167 fn public_key_hex_roundtrip() {
168 let key = SecretKey::generate().expect("generate");
169 let pubkey = key.public_key().expect("pubkey");
170 let hex = pubkey.to_hex();
171 assert_eq!(hex.len(), 66);
172 let decoded = PublicKey::from_hex(&hex).expect("from_hex");
173 assert_eq!(pubkey, decoded);
174 }
175
176 #[test]
177 fn event_id_hex_roundtrip() {
178 let id = EventId::from_bytes([0xab; 32]);
179 let hex = id.to_hex();
180 assert_eq!(hex, "ab".repeat(32));
181 let decoded = EventId::from_hex(&hex).expect("from_hex");
182 assert_eq!(id, decoded);
183 }
184
185 #[test]
186 fn hex_decode_case_insensitive() {
187 let upper = "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789";
188 let lower = upper.to_lowercase();
189 let id_upper = EventId::from_hex(upper).expect("upper");
190 let id_lower = EventId::from_hex(&lower).expect("lower");
191 assert_eq!(id_upper, id_lower);
192 }
193
194 #[test]
195 fn hex_decode_odd_length_fails() {
196 assert!(matches!(
197 EventId::from_hex("abc"),
198 Err(SecpError::InvalidHex(_))
199 ));
200 }
201
202 #[test]
203 fn hex_decode_invalid_char_fails() {
204 assert!(matches!(
205 EventId::from_hex("zz00000000000000000000000000000000000000000000000000000000000000"),
206 Err(SecpError::InvalidHex(_))
207 ));
208 }
209
210 #[test]
211 fn hex_decode_wrong_length_fails() {
212 assert!(matches!(
213 SecretKey::from_hex("aabb"),
214 Err(SecpError::InvalidHex(_))
215 ));
216 }
217
218 #[test]
219 fn hex_encode_is_lowercase() {
220 let id = EventId::from_bytes([0xAB; 32]);
221 let hex = id.to_hex();
222 assert_eq!(hex, hex.to_lowercase());
223 }
224
225 #[test]
226 fn keybundle_generate() {
227 let bundle = KeyBundle::generate().expect("key bundle");
228 let derived = bundle
229 .secret()
230 .xonly_public_key()
231 .expect("derived public key");
232 assert_eq!(*bundle.xonly_public_key(), derived);
233 }
234
235 #[cfg(feature = "batch")]
236 #[test]
237 fn keybundle_generate_batch() {
238 let bundles = KeyBundle::generate_batch(4).expect("batch");
239 assert_eq!(bundles.len(), 4);
240 for bundle in bundles {
241 let derived = bundle
242 .secret()
243 .xonly_public_key()
244 .expect("derived public key");
245 assert_eq!(*bundle.xonly_public_key(), derived);
246 }
247 }
248
249 #[cfg(feature = "batch")]
250 #[test]
251 fn keybundle_generate_batch_zero() {
252 let bundles = KeyBundle::generate_batch(0).expect("batch");
253 assert!(bundles.is_empty());
254 }
255
256 #[cfg(all(feature = "batch", feature = "nip19"))]
257 #[test]
258 #[ignore]
259 fn mine_vanity_npub_finds_match() {
260 let bundle = mine_vanity_npub("q", 100_000).expect("vanity match");
261 assert!(bundle.npub().expect("npub")[5..].starts_with("q"));
262 }
263
264 #[cfg(all(feature = "batch", feature = "nip19"))]
265 #[test]
266 fn mine_vanity_npub_exhausted() {
267 let error = mine_vanity_npub("zzzzzzzzzz", 10).expect_err("must exhaust attempts");
268 assert!(matches!(error, SecpError::ExhaustedAttempts));
269 }
270
271 #[test]
272 #[cfg(all(feature = "batch", feature = "nip19"))]
273 #[ignore]
274 fn vanity_candidates_returns_top_k() {
275 let candidates = mine_vanity_npub_candidates("q", 10_000, 3).expect("candidates");
276 assert!(candidates.len() <= 3);
277 for c in &candidates {
278 assert!(c.matched_len() >= 1);
279 }
280 for w in candidates.windows(2) {
281 assert!(w[0].matched_len() >= w[1].matched_len());
282 }
283 }
284
285 #[test]
286 #[cfg(all(feature = "batch", feature = "nip19"))]
287 fn vanity_candidates_zero_top_k() {
288 let candidates = mine_vanity_npub_candidates("q", 100, 0).expect("candidates");
289 assert!(candidates.is_empty());
290 }
291
292 #[test]
293 #[cfg(all(feature = "batch", feature = "nip19"))]
294 #[ignore]
295 fn vanity_candidates_exact_match_included() {
296 let candidates = mine_vanity_npub_candidates("q", 100_000, 5).expect("candidates");
297 let has_exact = candidates.iter().any(|c| c.matched_len() == 1);
298 assert!(
299 has_exact,
300 "should find at least one exact single-char match"
301 );
302 }
303
304 #[test]
305 #[cfg(all(feature = "batch", feature = "nip19"))]
306 fn vanity_prefix_match_counter_matches_encoded_npub() {
307 let secret = SecretKey::from_bytes([0x31; 32]).expect("secret");
308 let xonly = secret.xonly_public_key().expect("pubkey");
309 let npub = nip19::encode_npub(&xonly).expect("npub");
310 let npub_data = &npub[5..];
311
312 for len in [0usize, 1, 2, 3, 5, 8, 12] {
313 let prefix = &npub_data[..len];
314 let matched =
315 mining::count_npub_prefix_matches(&xonly.to_bytes(), prefix).expect("matched");
316 assert_eq!(matched, len);
317 }
318
319 let mismatched = format!("{}x", &npub_data[..4]);
320 let matched =
321 mining::count_npub_prefix_matches(&xonly.to_bytes(), &mismatched).expect("matched");
322 assert_eq!(matched, 4);
323 }
324
325 #[test]
326 #[cfg(all(feature = "batch", feature = "nip19"))]
327 fn vanity_prefix_rejects_invalid_bech32_chars() {
328 let err = mine_vanity_npub("!", 1).expect_err("invalid prefix");
329 assert!(matches!(
330 err,
331 SecpError::InvalidNip19("invalid npub vanity prefix")
332 ));
333
334 let err = mine_vanity_npub_candidates("I", 1, 1).expect_err("invalid prefix");
335 assert!(matches!(
336 err,
337 SecpError::InvalidNip19("invalid npub vanity prefix")
338 ));
339 }
340
341 #[cfg(feature = "batch")]
342 #[test]
343 #[ignore]
344 fn mine_pow_finds_match() {
345 let bundle = mine_pow(1, 100_000).expect("pow match");
346 assert!(mining::count_leading_zero_nibbles(&bundle.xonly_public_key().to_bytes()) >= 1);
347 }
348
349 #[cfg(feature = "batch")]
350 #[test]
351 fn mine_pow_exhausted() {
352 let error = mine_pow(64, 10).expect_err("must exhaust attempts");
353 assert!(matches!(error, SecpError::ExhaustedAttempts));
354 }
355
356 #[test]
357 #[cfg(feature = "batch")]
358 #[ignore]
359 fn mine_pow_best_returns_best() {
360 let (bundle, diff) = mine_pow_best(1, 100_000).expect("pow best");
361 assert!(diff >= 1);
362 let actual = mining::count_leading_zero_nibbles(&bundle.xonly_public_key().to_bytes());
363 assert_eq!(diff, actual);
364 }
365
366 #[test]
367 #[cfg(feature = "batch")]
368 fn mine_pow_best_exhausted() {
369 let err = mine_pow_best(64, 10).expect_err("should exhaust");
370 assert!(matches!(err, SecpError::ExhaustedAttempts));
371 }
372
373 #[cfg(feature = "batch")]
374 #[test]
375 fn count_leading_zero_nibbles_cases() {
376 assert_eq!(mining::count_leading_zero_nibbles(&[0x00, 0x0a, 0xff]), 3);
377 assert_eq!(mining::count_leading_zero_nibbles(&[0x00, 0x00]), 4);
378 assert_eq!(mining::count_leading_zero_nibbles(&[0xab]), 0);
379 assert_eq!(mining::count_leading_zero_nibbles(&[0x0f]), 1);
380 }
381
382 #[test]
383 fn schnorr_verify_rejects_wrong_digest() {
384 let secret = SecretKey::generate().expect("secret key");
385 let pubkey = secret.xonly_public_key().expect("pubkey");
386 let sig = secret
387 .sign_schnorr_prehash([1u8; 32])
388 .expect("signature for digest");
389 let error = pubkey
390 .verify_schnorr_prehash([2u8; 32], &sig)
391 .expect_err("must reject wrong digest");
392 assert!(matches!(error, SecpError::InvalidSignature));
393 }
394
395 #[test]
396 fn schnorr_verify_rejects_wrong_public_key() {
397 let secret_a = SecretKey::generate().expect("secret key a");
398 let secret_b = SecretKey::generate().expect("secret key b");
399 let pubkey_b = secret_b.xonly_public_key().expect("pubkey b");
400 let sig = secret_a
401 .sign_schnorr_prehash([3u8; 32])
402 .expect("signature for digest");
403 let error = pubkey_b
404 .verify_schnorr_prehash([3u8; 32], &sig)
405 .expect_err("must reject wrong public key");
406 assert!(matches!(error, SecpError::InvalidSignature));
407 }
408
409 #[test]
410 fn ecdsa_sign_verify_roundtrip() {
411 let secret = SecretKey::generate().expect("secret key");
412 let public = secret.public_key().expect("public key");
413 let digest = [9u8; 32];
414
415 let sig = secret.sign_ecdsa_prehash(digest).expect("sign");
416 assert_eq!(sig.to_bytes().len(), 64);
417 public.verify_ecdsa_prehash(digest, &sig).expect("verify");
418 }
419
420 #[test]
421 fn ecdsa_verify_wrong_message() {
422 let secret = SecretKey::generate().expect("secret key");
423 let public = secret.public_key().expect("public key");
424 let sig = secret
425 .sign_ecdsa_prehash([0x21; 32])
426 .expect("signature for digest");
427
428 let error = public
429 .verify_ecdsa_prehash([0x22; 32], &sig)
430 .expect_err("must reject wrong digest");
431 assert!(matches!(error, SecpError::InvalidSignature));
432 }
433
434 #[test]
435 fn ecdsa_reject_high_s() {
436 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret key");
437 let public = secret.public_key().expect("public key");
438 let digest = [0x33; 32];
439 let low_s = secret.sign_ecdsa_prehash(digest).expect("sign");
440
441 let mut sig_bytes = low_s.to_bytes();
442 let s_bytes: [u8; 32] = sig_bytes[32..].try_into().unwrap();
443 let high_s_bytes = scalar_sub(SECP256K1_ORDER, s_bytes);
444 sig_bytes[32..].copy_from_slice(&high_s_bytes);
445 let high_s = EcdsaSignature::from_bytes(sig_bytes);
446
447 let error = public
448 .verify_ecdsa_prehash(digest, &high_s)
449 .expect_err("must reject high-S signature");
450 assert!(matches!(error, SecpError::InvalidSignature));
451 }
452
453 #[cfg(feature = "nostr")]
454 #[test]
455 fn finalize_and_verify_event() {
456 let secret = SecretKey::generate().expect("secret key");
457 let event = UnsignedEvent {
458 created_at: 1_700_000_000,
459 kind: 1,
460 tags: vec![vec!["t".to_string(), "rust".to_string()]],
461 content: "hello".to_string(),
462 };
463
464 let signed = nostr::finalize_event(event, &secret).expect("finalize");
465 nostr::verify_event(&signed).expect("verify");
466 }
467
468 #[cfg(feature = "nostr")]
469 #[test]
470 fn finalize_event_deterministic_is_reproducible() {
471 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
472 let event = UnsignedEvent {
473 created_at: 1_700_000_000,
474 kind: 1,
475 tags: vec![vec!["p".to_string(), "abc".to_string()]],
476 content: "hello".to_string(),
477 };
478
479 let first = nostr::finalize_event_deterministic(event.clone(), &secret).expect("first");
480 let second = nostr::finalize_event_deterministic(event, &secret).expect("second");
481
482 assert_eq!(first, second);
483 nostr::verify_event(&first).expect("verify first");
484 nostr::verify_event(&second).expect("verify second");
485 }
486
487 #[cfg(feature = "nostr")]
488 #[test]
489 fn finalize_event_random_differs_per_call() {
490 let secret = SecretKey::from_bytes([0x22; 32]).expect("secret");
491 let event = UnsignedEvent {
492 created_at: 1_700_000_000,
493 kind: 1,
494 tags: vec![],
495 content: "hello".to_string(),
496 };
497
498 let first = nostr::finalize_event(event.clone(), &secret).expect("first");
499 let second = nostr::finalize_event(event, &secret).expect("second");
500
501 assert_eq!(first.id, second.id);
503 assert_eq!(first.pubkey, second.pubkey);
504 assert_ne!(first.sig, second.sig);
505 nostr::verify_event(&first).expect("verify first");
506 nostr::verify_event(&second).expect("verify second");
507 }
508
509 #[cfg(feature = "nostr")]
510 #[test]
511 fn nostr_matches_known_nostr_tools_fixture() {
512 let secret = SecretKey::from_hex(
513 "d217c1ff2f8a65c3e3a1740db3b9f58b\
514 8c848bb45e26d00ed4714e4a0f4ceecf",
515 )
516 .expect("secret");
517 let pubkey = secret.xonly_public_key().expect("pubkey");
518 let unsigned = UnsignedEvent {
519 created_at: 1_617_932_115,
520 kind: 1,
521 tags: vec![],
522 content: "Hello, world!".to_string(),
523 };
524
525 let serialized = nostr::serialize_event(&pubkey, &unsigned).expect("serialize");
526 let hash = nostr::compute_event_id(&pubkey, &unsigned).expect("hash");
527 let signed = nostr::finalize_event(unsigned, &secret).expect("finalize");
528
529 assert_eq!(
530 pubkey.to_hex(),
531 "6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f"
532 );
533 assert_eq!(
534 serialized,
535 r#"[0,"6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f",1617932115,1,[],"Hello, world!"]"#
536 );
537 assert_eq!(
538 hash.to_hex(),
539 "b2a44af84ca99b14820ae91c44e1ef0908f8aadc4e10620a6e6caa344507f03c"
540 );
541 assert_eq!(signed.id.to_hex(), hash.to_hex());
542 assert_eq!(
543 signed
544 .sig
545 .to_bytes()
546 .iter()
547 .map(|byte| format!("{byte:02x}"))
548 .collect::<String>()
549 .len(),
550 128
551 );
552 nostr::verify_event(&signed).expect("verify");
553 }
554
555 #[cfg(feature = "nostr")]
556 #[test]
557 fn serialize_event_matches_expected_shape() {
558 let secret = SecretKey::generate().expect("secret key");
559 let public = secret.xonly_public_key().expect("public key");
560 let event = UnsignedEvent {
561 created_at: 1_700_000_123,
562 kind: 7,
563 tags: vec![vec!["p".to_string(), "abcd".to_string()]],
564 content: "hello\nworld".to_string(),
565 };
566
567 let serialized = nostr::serialize_event(&public, &event).expect("serialize");
568 let value = neco_json::parse(serialized.as_bytes()).expect("json");
569 let array = value.as_array().expect("array payload");
570 assert_eq!(array.len(), 6);
571 assert_eq!(array[0].as_u64(), Some(0));
572 assert_eq!(array[2].as_u64(), Some(event.created_at));
573 assert_eq!(array[3].as_u64(), Some(event.kind as u64));
574 let expected_tags = neco_json::JsonValue::Array(
575 event
576 .tags
577 .iter()
578 .map(|tag| {
579 neco_json::JsonValue::Array(
580 tag.iter()
581 .map(|s| neco_json::JsonValue::String(s.clone()))
582 .collect(),
583 )
584 })
585 .collect(),
586 );
587 assert_eq!(array[4], expected_tags);
588 assert_eq!(array[5].as_str(), Some(event.content.as_str()));
589 }
590
591 #[cfg(feature = "nostr")]
592 #[test]
593 fn compute_event_id_is_reproducible() {
594 let secret = SecretKey::generate().expect("secret key");
595 let public = secret.xonly_public_key().expect("public key");
596 let event = UnsignedEvent {
597 created_at: 10,
598 kind: 1,
599 tags: vec![vec!["e".to_string(), "1".to_string()]],
600 content: "same".to_string(),
601 };
602
603 let id_a = nostr::compute_event_id(&public, &event).expect("id a");
604 let id_b = nostr::compute_event_id(&public, &event).expect("id b");
605 assert_eq!(id_a, id_b);
606 }
607
608 #[cfg(feature = "nostr")]
609 #[test]
610 fn verify_rejects_tampered_content() {
611 let secret = SecretKey::generate().expect("secret key");
612 let event = UnsignedEvent {
613 created_at: 1,
614 kind: 1,
615 tags: Vec::new(),
616 content: "hello".to_string(),
617 };
618 let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
619 signed.content = "tampered".to_string();
620 let error = nostr::verify_event(&signed).expect_err("must fail");
621 assert!(matches!(error, SecpError::InvalidEvent(_)));
622 }
623
624 #[cfg(feature = "nostr")]
625 #[test]
626 fn verify_rejects_tampered_tags() {
627 let secret = SecretKey::generate().expect("secret key");
628 let event = UnsignedEvent {
629 created_at: 2,
630 kind: 1,
631 tags: vec![vec!["t".to_string(), "rust".to_string()]],
632 content: "hello".to_string(),
633 };
634 let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
635 signed.tags.push(vec!["p".to_string(), "peer".to_string()]);
636 let error = nostr::verify_event(&signed).expect_err("must fail");
637 assert!(matches!(error, SecpError::InvalidEvent(_)));
638 }
639
640 #[cfg(feature = "nostr")]
641 #[test]
642 fn verify_rejects_tampered_created_at() {
643 let secret = SecretKey::generate().expect("secret key");
644 let event = UnsignedEvent {
645 created_at: 3,
646 kind: 1,
647 tags: Vec::new(),
648 content: "hello".to_string(),
649 };
650 let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
651 signed.created_at += 1;
652 let error = nostr::verify_event(&signed).expect_err("must fail");
653 assert!(matches!(error, SecpError::InvalidEvent(_)));
654 }
655
656 #[cfg(feature = "nostr")]
657 #[test]
658 fn verify_rejects_tampered_kind() {
659 let secret = SecretKey::generate().expect("secret key");
660 let event = UnsignedEvent {
661 created_at: 4,
662 kind: 1,
663 tags: Vec::new(),
664 content: "hello".to_string(),
665 };
666 let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
667 signed.kind = 42;
668 let error = nostr::verify_event(&signed).expect_err("must fail");
669 assert!(matches!(error, SecpError::InvalidEvent(_)));
670 }
671
672 #[cfg(feature = "nostr")]
673 #[test]
674 fn verify_rejects_tampered_id() {
675 let secret = SecretKey::generate().expect("secret key");
676 let event = UnsignedEvent {
677 created_at: 5,
678 kind: 1,
679 tags: Vec::new(),
680 content: "hello".to_string(),
681 };
682 let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
683 signed.id = EventId::from_bytes([9u8; 32]);
684 let error = nostr::verify_event(&signed).expect_err("must fail");
685 assert!(matches!(error, SecpError::InvalidEvent(_)));
686 }
687
688 #[cfg(feature = "nostr")]
689 #[test]
690 fn verify_rejects_tampered_signature() {
691 let secret = SecretKey::generate().expect("secret key");
692 let event = UnsignedEvent {
693 created_at: 6,
694 kind: 1,
695 tags: Vec::new(),
696 content: "hello".to_string(),
697 };
698 let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
699 let mut sig = signed.sig.to_bytes();
700 sig[0] ^= 0x01;
701 signed.sig = SchnorrSignature { bytes: sig };
702 let error = nostr::verify_event(&signed).expect_err("must fail");
703 assert!(matches!(error, SecpError::InvalidSignature));
704 }
705
706 #[cfg(feature = "nostr")]
707 #[test]
708 fn signed_event_serialize_parse_roundtrip() {
709 let secret = SecretKey::generate().expect("secret key");
710 let signed = nostr::finalize_event(
711 UnsignedEvent {
712 created_at: 1_700_000_222,
713 kind: 14,
714 tags: vec![vec!["p".to_string(), "peer".to_string()]],
715 content: "sealed".to_string(),
716 },
717 &secret,
718 )
719 .expect("finalize");
720
721 let json = nostr::serialize_signed_event(&signed).expect("serialize signed");
722 let parsed = nostr::parse_signed_event(&json).expect("parse signed");
723 assert_eq!(parsed, signed);
724 }
725
726 #[cfg(feature = "nostr")]
727 #[test]
728 fn nip42_create_and_validate() {
729 let secret = SecretKey::generate().expect("secret key");
730 let expected = secret.xonly_public_key().expect("pubkey");
731 let signed = nip42::create_auth_event(
732 "challenge-123",
733 "wss://relay.example.com",
734 &secret,
735 1_700_000_666,
736 )
737 .expect("create auth event");
738
739 assert_eq!(signed.kind, 22_242);
740 assert_eq!(signed.content, "");
741 assert_eq!(
742 signed.tags,
743 vec![
744 vec!["relay".to_string(), "wss://relay.example.com".to_string()],
745 vec!["challenge".to_string(), "challenge-123".to_string()],
746 ]
747 );
748 assert_eq!(
749 nip42::validate_auth_event(&signed, "challenge-123", "wss://relay.example.com")
750 .expect("validate auth event"),
751 expected
752 );
753 }
754
755 #[cfg(feature = "nostr")]
756 #[test]
757 fn nip42_wrong_challenge_fails() {
758 let secret = SecretKey::generate().expect("secret key");
759 let signed = nip42::create_auth_event(
760 "challenge-123",
761 "wss://relay.example.com",
762 &secret,
763 1_700_000_777,
764 )
765 .expect("create auth event");
766
767 let error =
768 nip42::validate_auth_event(&signed, "wrong-challenge", "wss://relay.example.com")
769 .expect_err("wrong challenge must fail");
770 assert!(matches!(
771 error,
772 SecpError::InvalidEvent("auth event challenge tag mismatch")
773 ));
774 }
775
776 #[cfg(feature = "nostr")]
777 #[test]
778 fn nip42_wrong_relay_fails() {
779 let secret = SecretKey::generate().expect("secret key");
780 let signed = nip42::create_auth_event(
781 "challenge-123",
782 "wss://relay.example.com",
783 &secret,
784 1_700_000_888,
785 )
786 .expect("create auth event");
787
788 let error = nip42::validate_auth_event(&signed, "challenge-123", "wss://other.example.com")
789 .expect_err("wrong relay must fail");
790 assert!(matches!(
791 error,
792 SecpError::InvalidEvent("auth event relay tag mismatch")
793 ));
794 }
795
796 #[cfg(all(feature = "nostr", feature = "nip44"))]
797 #[test]
798 fn nip17_seal_roundtrip() {
799 let sender = SecretKey::generate().expect("sender");
800 let recipient = SecretKey::generate().expect("recipient");
801 let recipient_pubkey = recipient.xonly_public_key().expect("recipient pubkey");
802 let inner = UnsignedEvent {
803 created_at: 1_700_000_333,
804 kind: 14,
805 tags: vec![vec!["p".to_string(), recipient_pubkey.to_hex()]],
806 content: "hello seal".to_string(),
807 };
808
809 let seal = nip17::create_seal(inner.clone(), &sender, &recipient_pubkey).expect("seal");
810 let opened = nip17::open_seal(&seal, &recipient).expect("open seal");
811
812 assert_eq!(seal.kind, 13);
813 assert!(seal.tags.is_empty());
814 assert_eq!(
815 seal.pubkey,
816 sender.xonly_public_key().expect("sender pubkey")
817 );
818 assert_eq!(opened.created_at, inner.created_at);
819 assert_eq!(opened.kind, inner.kind);
820 assert_eq!(opened.tags, inner.tags);
821 assert_eq!(opened.content, inner.content);
822 }
823
824 #[cfg(all(feature = "nostr", feature = "nip44"))]
825 #[test]
826 fn nip17_gift_wrap_roundtrip() {
827 let sender = SecretKey::generate().expect("sender");
828 let recipient = SecretKey::generate().expect("recipient");
829 let recipient_pubkey = recipient.xonly_public_key().expect("recipient pubkey");
830 let inner = UnsignedEvent {
831 created_at: 1_700_000_444,
832 kind: 14,
833 tags: vec![vec!["p".to_string(), recipient_pubkey.to_hex()]],
834 content: "hello wrap".to_string(),
835 };
836
837 let seal = nip17::create_seal(inner.clone(), &sender, &recipient_pubkey).expect("seal");
838 let gift_wrap = nip17::create_gift_wrap(&seal, &recipient_pubkey).expect("gift wrap");
839 let opened = nip17::open_gift_wrap(&gift_wrap, &recipient).expect("open gift wrap");
840
841 assert_eq!(gift_wrap.kind, 1059);
842 assert_eq!(
843 gift_wrap.tags,
844 vec![vec!["p".to_string(), recipient_pubkey.to_hex()]]
845 );
846 assert_ne!(
847 gift_wrap.pubkey,
848 sender.xonly_public_key().expect("sender pubkey")
849 );
850 assert_eq!(opened.created_at, inner.created_at);
851 assert_eq!(opened.kind, inner.kind);
852 assert_eq!(opened.tags, inner.tags);
853 assert_eq!(opened.content, inner.content);
854 }
855
856 #[cfg(all(feature = "nostr", feature = "nip44"))]
857 #[test]
858 fn nip17_wrong_recipient_fails() {
859 let sender = SecretKey::generate().expect("sender");
860 let recipient = SecretKey::generate().expect("recipient");
861 let wrong_recipient = SecretKey::generate().expect("wrong recipient");
862 let recipient_pubkey = recipient.xonly_public_key().expect("recipient pubkey");
863 let inner = UnsignedEvent {
864 created_at: 1_700_000_555,
865 kind: 14,
866 tags: vec![vec!["p".to_string(), recipient_pubkey.to_hex()]],
867 content: "secret".to_string(),
868 };
869
870 let seal = nip17::create_seal(inner, &sender, &recipient_pubkey).expect("seal");
871 let gift_wrap = nip17::create_gift_wrap(&seal, &recipient_pubkey).expect("gift wrap");
872 let error =
873 nip17::open_gift_wrap(&gift_wrap, &wrong_recipient).expect_err("wrong recipient");
874 assert!(matches!(
875 error,
876 SecpError::InvalidNip44(_) | SecpError::InvalidEvent(_)
877 ));
878 }
879
880 #[cfg(feature = "nip19")]
881 #[test]
882 fn nip19_nsec_roundtrip() {
883 let secret = SecretKey::generate().expect("secret key");
884 let encoded = nip19::encode_nsec(&secret).expect("encode");
885 let decoded = nip19::decode(&encoded).expect("decode");
886 assert_eq!(decoded, Nip19::Nsec(secret));
887 }
888
889 #[cfg(feature = "nip19")]
890 #[test]
891 fn nip19_npub_roundtrip() {
892 let secret = SecretKey::generate().expect("secret key");
893 let public = secret.xonly_public_key().expect("public key");
894 let encoded = nip19::encode_npub(&public).expect("encode");
895 let decoded = nip19::decode(&encoded).expect("decode");
896 assert_eq!(decoded, Nip19::Npub(public));
897 }
898
899 #[cfg(feature = "nip19")]
900 #[test]
901 fn keybundle_nip19() {
902 let bundle = KeyBundle::generate().expect("key bundle");
903 let npub = bundle.npub().expect("npub");
904 let nsec = bundle.nsec().expect("nsec");
905 assert_eq!(
906 nip19::decode(&npub).expect("decode npub"),
907 Nip19::Npub(*bundle.xonly_public_key())
908 );
909 assert_eq!(
910 nip19::decode(&nsec).expect("decode nsec"),
911 Nip19::Nsec(*bundle.secret())
912 );
913 }
914
915 #[cfg(feature = "nip19")]
916 #[test]
917 fn nip19_note_roundtrip() {
918 let id = EventId::from_bytes([0x42; 32]);
919 let encoded = nip19::encode_note(&id).expect("encode");
920 let decoded = nip19::decode(&encoded).expect("decode");
921 assert_eq!(decoded, Nip19::Note(id));
922 }
923
924 #[cfg(feature = "nip19")]
925 #[test]
926 fn nip19_decodes_known_valid_npub_fixture() {
927 let public = XOnlyPublicKey::from_bytes([
928 0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
929 0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
930 0x15, 0xde, 0xee, 0x93,
931 ])
932 .expect("fixture pubkey");
933 let decoded =
934 nip19::decode("npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc")
935 .expect("decode fixture");
936 assert_eq!(decoded, Nip19::Npub(public));
937 }
938
939 #[cfg(feature = "nip19")]
940 #[test]
941 fn nip19_matches_known_nostr_tools_fixture() {
942 let pubkey = XOnlyPublicKey::from_hex(
943 "6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f",
944 )
945 .expect("pubkey");
946 let event_id =
947 EventId::from_hex("b2a44af84ca99b14820ae91c44e1ef0908f8aadc4e10620a6e6caa344507f03c")
948 .expect("event id");
949 let profile = NProfile {
950 pubkey,
951 relays: vec![
952 "wss://relay.damus.io".to_string(),
953 "wss://nostr.example.com".to_string(),
954 ],
955 };
956 let event = NEvent {
957 id: event_id,
958 relays: vec![
959 "wss://relay.example.com".to_string(),
960 "wss://relay2.example.com".to_string(),
961 ],
962 author: Some(pubkey),
963 kind: Some(1),
964 };
965 let addr = NAddr {
966 identifier: "article-1".to_string(),
967 relays: vec![
968 "wss://relay.example.com".to_string(),
969 "wss://relay2.example.com".to_string(),
970 ],
971 author: pubkey,
972 kind: 30_023,
973 };
974
975 assert_eq!(
976 nip19::encode_npub(&pubkey).expect("npub"),
977 "npub1dtc0nhjc3uk98nkuhgnvtcjq9cxs4fjwc768e8udj76mc43t4d0sw73h32"
978 );
979 assert_eq!(
980 nip19::encode_nprofile(&profile).expect("nprofile"),
981 "nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0dehhxarj9ejhsctdwpkx2tnrdaksqgr27ruauky093fuah96ymz7yspwp592vnk8k37flrvhkk79v2attuwkrkwx"
982 );
983 assert_eq!(
984 nip19::encode_nevent(&event).expect("nevent"),
985 "nevent1qvzqqqqqqypzq6hsl8093rev208dew3xch3yqtsdp2nya3a50j0cm9a4h3tzh26lqythwumn8ghj7un9d3shjtn90psk6urvv5hxxmmdqyv8wumn8ghj7un9d3shjv3wv4uxzmtsd3jjucm0d5qzpv4yftuye2vmzjpq46gugns77zgglz4dcnssvg9xum92x3zs0upuv94f3z"
986 );
987 assert_eq!(
988 nip19::encode_naddr(&addr).expect("naddr"),
989 "naddr1qvzqqqr4gupzq6hsl8093rev208dew3xch3yqtsdp2nya3a50j0cm9a4h3tzh26lqythwumn8ghj7un9d3shjtn90psk6urvv5hxxmmdqyv8wumn8ghj7un9d3shjv3wv4uxzmtsd3jjucm0d5qqjctjw35kxmr995csn0d4es"
990 );
991 }
992
993 #[cfg(feature = "nip19")]
994 #[test]
995 fn nip19_nprofile_roundtrip_with_multiple_relays() {
996 let pubkey = XOnlyPublicKey::from_bytes([
997 0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
998 0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
999 0x15, 0xde, 0xee, 0x93,
1000 ])
1001 .expect("pubkey");
1002 let profile = NProfile {
1003 pubkey,
1004 relays: vec![
1005 "wss://relay.example".to_string(),
1006 "wss://relay2.example".to_string(),
1007 ],
1008 };
1009 let encoded = nip19::encode_nprofile(&profile).expect("encode");
1010 let decoded = nip19::decode(&encoded).expect("decode");
1011 assert_eq!(decoded, Nip19::NProfile(profile));
1012 }
1013
1014 #[cfg(feature = "nip19")]
1015 #[test]
1016 fn nip19_nevent_roundtrip_with_author_and_kind() {
1017 let event = NEvent {
1018 id: EventId::from_bytes([0x34; 32]),
1019 relays: vec!["wss://relay.example".to_string()],
1020 author: Some(
1021 XOnlyPublicKey::from_bytes([
1022 0x4f, 0x35, 0x5b, 0xdc, 0xb7, 0xcc, 0x0a, 0xf7, 0x28, 0xef, 0x3c, 0xce, 0xb9,
1023 0x61, 0x5d, 0x90, 0x68, 0x4b, 0xb5, 0xb2, 0xca, 0x5f, 0x85, 0x9a, 0xb0, 0xf0,
1024 0xb7, 0x04, 0x07, 0x58, 0x71, 0xaa,
1025 ])
1026 .expect("author"),
1027 ),
1028 kind: Some(30_023),
1029 };
1030 let encoded = nip19::encode_nevent(&event).expect("encode");
1031 let decoded = nip19::decode(&encoded).expect("decode");
1032 assert_eq!(decoded, Nip19::NEvent(event));
1033 }
1034
1035 #[cfg(feature = "nip19")]
1036 #[test]
1037 fn nip19_naddr_roundtrip_allows_empty_identifier() {
1038 let addr = NAddr {
1039 identifier: String::new(),
1040 relays: vec!["wss://relay.example".to_string()],
1041 author: XOnlyPublicKey::from_bytes([
1042 0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
1043 0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
1044 0x15, 0xde, 0xee, 0x93,
1045 ])
1046 .expect("author"),
1047 kind: 30_023,
1048 };
1049 let encoded = nip19::encode_naddr(&addr).expect("encode");
1050 let decoded = nip19::decode(&encoded).expect("decode");
1051 assert_eq!(decoded, Nip19::NAddr(addr));
1052 }
1053
1054 #[cfg(feature = "nip19")]
1055 #[test]
1056 fn nip19_nrelay_roundtrip() {
1057 let relay = NRelay {
1058 relay: "wss://relay.example".to_string(),
1059 };
1060 let encoded = nip19::encode_nrelay(&relay).expect("encode");
1061 let decoded = nip19::decode(&encoded).expect("decode");
1062 assert_eq!(decoded, Nip19::NRelay(relay));
1063 }
1064
1065 #[cfg(feature = "nip19")]
1066 #[test]
1067 fn nip19_decode_ignores_unknown_tlv_entries() {
1068 use bech32::ToBase32;
1069
1070 let mut payload = vec![9, 3, b'x', b'y', b'z', 0, 32];
1071 payload.extend_from_slice(&[
1072 0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
1073 0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
1074 0x15, 0xde, 0xee, 0x93,
1075 ]);
1076 let encoded = bech32::encode("nprofile", payload.to_base32(), bech32::Variant::Bech32)
1077 .expect("bech32");
1078 let decoded = nip19::decode(&encoded).expect("decode");
1079 assert_eq!(
1080 decoded,
1081 Nip19::NProfile(NProfile {
1082 pubkey: XOnlyPublicKey::from_bytes([
1083 0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23,
1084 0xb9, 0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c,
1085 0xd0, 0xee, 0x15, 0xde, 0xee, 0x93,
1086 ])
1087 .expect("pubkey"),
1088 relays: Vec::new(),
1089 })
1090 );
1091 }
1092
1093 #[cfg(feature = "nip19")]
1094 #[test]
1095 fn nip19_decode_rejects_missing_required_nprofile_pubkey() {
1096 use bech32::ToBase32;
1097
1098 let payload = vec![1, 5, b'r', b'e', b'l', b'a', b'y'];
1099 let encoded = bech32::encode("nprofile", payload.to_base32(), bech32::Variant::Bech32)
1100 .expect("bech32");
1101 let error = nip19::decode(&encoded).expect_err("missing pubkey must fail");
1102 assert!(matches!(
1103 error,
1104 SecpError::InvalidNip19("missing TLV 0 for nprofile")
1105 ));
1106 }
1107
1108 #[cfg(feature = "nip19")]
1109 #[test]
1110 fn nip19_decode_rejects_missing_required_naddr_fields() {
1111 use bech32::ToBase32;
1112
1113 let mut payload = vec![0, 0];
1114 payload.extend_from_slice(&[3, 4, 0, 0, 0x75, 0x37]);
1115 let encoded =
1116 bech32::encode("naddr", payload.to_base32(), bech32::Variant::Bech32).expect("bech32");
1117 let error = nip19::decode(&encoded).expect_err("missing author must fail");
1118 assert!(matches!(
1119 error,
1120 SecpError::InvalidNip19("missing TLV 2 for naddr")
1121 ));
1122 }
1123
1124 #[cfg(feature = "nip19")]
1125 #[test]
1126 fn nip19_decode_rejects_invalid_nevent_kind_length() {
1127 use bech32::ToBase32;
1128
1129 let mut payload = vec![0, 32];
1130 payload.extend_from_slice(&[0x34; 32]);
1131 payload.extend_from_slice(&[3, 3, 0, 0x75, 0x37]);
1132 let encoded =
1133 bech32::encode("nevent", payload.to_base32(), bech32::Variant::Bech32).expect("bech32");
1134 let error = nip19::decode(&encoded).expect_err("invalid kind length must fail");
1135 assert!(matches!(
1136 error,
1137 SecpError::InvalidNip19("TLV 3 should be 4 bytes")
1138 ));
1139 }
1140
1141 #[cfg(feature = "nip19")]
1142 #[test]
1143 fn nip19_nevent_kind_is_big_endian() {
1144 let event = NEvent {
1145 id: EventId::from_bytes([0x12; 32]),
1146 relays: Vec::new(),
1147 author: None,
1148 kind: Some(30_023),
1149 };
1150 let encoded = nip19::encode_nevent(&event).expect("encode");
1151 let decoded = nip19::decode(&encoded).expect("decode");
1152 assert_eq!(decoded, Nip19::NEvent(event));
1153 }
1154
1155 #[cfg(feature = "nip19")]
1156 #[test]
1157 fn nip19_decode_rejects_invalid_checksum() {
1158 let error =
1159 nip19::decode("npub1de5gss7lkafc0pe2s2sz8wjsx6v4hvxxg8l6e60v8uuguvs7m5fsq4qwxn")
1160 .expect_err("checksum must fail");
1161 assert!(matches!(error, SecpError::InvalidNip19(_)));
1162 }
1163
1164 #[cfg(feature = "nip19")]
1165 #[test]
1166 fn nip19_decode_rejects_invalid_length() {
1167 use bech32::ToBase32;
1168
1169 let short = bech32::encode("npub", vec![1u8; 31].to_base32(), bech32::Variant::Bech32)
1170 .expect("fixture encode");
1171 let error = nip19::decode(&short).expect_err("short payload must fail");
1172 assert!(matches!(error, SecpError::InvalidNip19(_)));
1173 }
1174
1175 #[cfg(feature = "nip44")]
1176 #[test]
1177 fn nip44_conversation_key_matches_from_both_sides() {
1178 let secret_a = SecretKey::from_bytes([0x11; 32]).expect("secret a");
1179 let secret_b = SecretKey::from_bytes([0x22; 32]).expect("secret b");
1180 let pubkey_a = secret_a.xonly_public_key().expect("pubkey a");
1181 let pubkey_b = secret_b.xonly_public_key().expect("pubkey b");
1182
1183 let key_ab = nip44::get_conversation_key(&secret_a, &pubkey_b).expect("key ab");
1184 let key_ba = nip44::get_conversation_key(&secret_b, &pubkey_a).expect("key ba");
1185 assert_eq!(key_ab, key_ba);
1186 }
1187
1188 #[cfg(feature = "nip44")]
1189 #[test]
1190 fn nip44_encrypt_and_decrypt_roundtrip() {
1191 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1192 let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
1193 let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
1194 let conversation_key =
1195 nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
1196 let nonce = [0x33; 32];
1197
1198 let payload = nip44::encrypt("hello from neco-secp", &conversation_key, Some(nonce))
1199 .expect("encrypt");
1200 let plaintext = nip44::decrypt(&payload, &conversation_key).expect("decrypt");
1201 assert_eq!(plaintext, "hello from neco-secp");
1202 }
1203
1204 #[cfg(feature = "nip44")]
1205 #[test]
1206 fn nip44_matches_known_oracle_fixture() {
1207 const ORACLE_NIP44_PLAINTEXT: &str = "cyphercat nip44 oracle";
1208
1209 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1210 let peer_pubkey = XOnlyPublicKey::from_bytes([
1211 0x46, 0x6d, 0x7f, 0xca, 0xe5, 0x63, 0xe5, 0xcb, 0x09, 0xa0, 0xd1, 0x87, 0x0b, 0xb5,
1212 0x80, 0x34, 0x48, 0x04, 0x61, 0x78, 0x79, 0xa1, 0x49, 0x49, 0xcf, 0x22, 0x28, 0x5f,
1213 0x1b, 0xae, 0x3f, 0x27,
1214 ])
1215 .expect("peer pubkey");
1216 let conversation_key =
1217 nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
1218 assert_eq!(
1219 conversation_key,
1220 [
1221 0x2c, 0xbd, 0xf0, 0x74, 0xf6, 0x01, 0x17, 0x8c, 0x24, 0xda, 0x3f, 0x82, 0x9b, 0x50,
1222 0x45, 0x07, 0xa1, 0xf5, 0x50, 0xf9, 0x7d, 0x47, 0x2a, 0xf0, 0xf3, 0xf2, 0xcc, 0x59,
1223 0xab, 0x77, 0x57, 0xd1,
1224 ]
1225 );
1226
1227 let payload = nip44::encrypt(ORACLE_NIP44_PLAINTEXT, &conversation_key, Some([0x33; 32]))
1228 .expect("encrypt");
1229 assert_eq!(
1230 payload,
1231 "AjMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzJDZzpLQFdobg13n/RufVeG0ps8acSBfghr22oozB/q91IVexzbaA/lxkSa0R+6Dly9F1gKsZLCy1tzW4LPplhuWg"
1232 );
1233 let plaintext = nip44::decrypt(&payload, &conversation_key).expect("decrypt");
1234 assert_eq!(plaintext, ORACLE_NIP44_PLAINTEXT);
1235 }
1236
1237 #[cfg(feature = "nip44")]
1238 #[test]
1239 fn nip44_calc_padded_len_contract() {
1240 assert_eq!(nip44::calc_padded_len(1).expect("len"), 32);
1241 assert_eq!(nip44::calc_padded_len(32).expect("len"), 32);
1242 assert_eq!(nip44::calc_padded_len(33).expect("len"), 64);
1243 assert_eq!(nip44::calc_padded_len(300).expect("len"), 320);
1244 }
1245
1246 #[cfg(feature = "nip44")]
1247 #[test]
1248 fn nip44_rejects_invalid_mac() {
1249 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1250 let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
1251 let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
1252 let conversation_key =
1253 nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
1254 let nonce = [0x33; 32];
1255
1256 let payload = nip44::encrypt("hello", &conversation_key, Some(nonce)).expect("encrypt");
1257 let mut raw = neco_base64::decode(&payload).expect("decode");
1258 let last = raw.len() - 1;
1259 raw[last] ^= 0x01;
1260 let tampered = neco_base64::encode(&raw);
1261
1262 let error = nip44::decrypt(&tampered, &conversation_key).expect_err("invalid mac");
1263 assert!(matches!(error, SecpError::InvalidNip44("invalid MAC")));
1264 }
1265
1266 #[cfg(feature = "nip44")]
1267 #[test]
1268 fn nip44_rejects_invalid_version() {
1269 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1270 let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
1271 let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
1272 let conversation_key =
1273 nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
1274 let nonce = [0x33; 32];
1275
1276 let payload = nip44::encrypt("hello", &conversation_key, Some(nonce)).expect("encrypt");
1277 let mut raw = neco_base64::decode(&payload).expect("decode");
1278 raw[0] = 3;
1279 let tampered = neco_base64::encode(&raw);
1280
1281 let error = nip44::decrypt(&tampered, &conversation_key).expect_err("invalid version");
1282 assert!(matches!(
1283 error,
1284 SecpError::InvalidNip44("unknown encryption version")
1285 ));
1286 }
1287
1288 #[cfg(feature = "nip44")]
1289 #[test]
1290 fn nip44_rejects_invalid_payload_length() {
1291 let error = nip44::decrypt("short", &[0u8; 32]).expect_err("invalid payload");
1292 assert!(matches!(
1293 error,
1294 SecpError::InvalidNip44("invalid payload length")
1295 ));
1296 }
1297
1298 #[cfg(feature = "nip04")]
1299 #[test]
1300 fn nip04_encrypt_and_decrypt_roundtrip() {
1301 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1302 let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
1303 let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
1304 let payload = nip04::encrypt(
1305 &secret,
1306 &peer_pubkey,
1307 "hello from neco-secp",
1308 Some([0x44; 16]),
1309 )
1310 .expect("encrypt");
1311 let plaintext =
1312 nip04::decrypt(&peer, &secret.xonly_public_key().expect("pubkey"), &payload)
1313 .expect("decrypt");
1314 assert_eq!(plaintext, "hello from neco-secp");
1315 }
1316
1317 #[cfg(feature = "nip04")]
1318 #[test]
1319 fn nip04_matches_known_oracle_fixture() {
1320 const ORACLE_NIP04_PLAINTEXT: &str = "cyphercat nip04 oracle";
1321
1322 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1323 let peer_pubkey = XOnlyPublicKey::from_bytes([
1324 0x46, 0x6d, 0x7f, 0xca, 0xe5, 0x63, 0xe5, 0xcb, 0x09, 0xa0, 0xd1, 0x87, 0x0b, 0xb5,
1325 0x80, 0x34, 0x48, 0x04, 0x61, 0x78, 0x79, 0xa1, 0x49, 0x49, 0xcf, 0x22, 0x28, 0x5f,
1326 0x1b, 0xae, 0x3f, 0x27,
1327 ])
1328 .expect("peer pubkey");
1329 let payload = nip04::encrypt(
1330 &secret,
1331 &peer_pubkey,
1332 ORACLE_NIP04_PLAINTEXT,
1333 Some([0x44; 16]),
1334 )
1335 .expect("encrypt");
1336 assert_eq!(
1337 payload,
1338 "xftPpDirMJGDoq3ktNutZsG6W+lmUsILU9XMp06pYmM=?iv=RERERERERERERERERERERA=="
1339 );
1340 }
1341
1342 #[cfg(feature = "nip04")]
1343 #[test]
1344 fn nip04_rejects_invalid_payload() {
1345 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1346 let peer_pubkey = SecretKey::from_bytes([0x22; 32])
1347 .expect("peer")
1348 .xonly_public_key()
1349 .expect("pubkey");
1350 let error = nip04::decrypt(&secret, &peer_pubkey, "invalid").expect_err("invalid payload");
1351 assert!(matches!(error, SecpError::InvalidNip04("invalid payload")));
1352 }
1353
1354 #[cfg(feature = "nip04")]
1355 #[test]
1356 fn nip04_rejects_invalid_iv() {
1357 let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
1358 let peer_pubkey = SecretKey::from_bytes([0x22; 32])
1359 .expect("peer")
1360 .xonly_public_key()
1361 .expect("pubkey");
1362 let error = nip04::decrypt(&secret, &peer_pubkey, "abcd?iv=bad!").expect_err("invalid iv");
1363 assert!(matches!(error, SecpError::InvalidNip04("invalid iv")));
1364 }
1365}