Skip to main content

neco_secp/
lib.rs

1//! Minimal secp256k1 and Nostr signing core.
2
3mod 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        // id 以外のメタデータは一致するが、sig は aux_rand が異なるため不一致
502        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}