use coracle_lib::keys::{KeyError, PublicKey, SecretKey}; fn fixed_secret() -> SecretKey { let bytes: [u8; 32] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ]; SecretKey::from_hex(&hex::encode(bytes)).unwrap() } #[test] fn public_key_hex_roundtrip() { let pk = fixed_secret().public_key(); let hex = pk.to_hex(); assert_eq!(hex.len(), 64); assert_eq!(PublicKey::from_hex(&hex).unwrap(), pk); } #[test] fn public_key_npub_roundtrip() { let pk = fixed_secret().public_key(); let npub = pk.to_npub(); assert!(npub.starts_with("npub1")); assert_eq!(PublicKey::from_npub(&npub).unwrap(), pk); } #[test] fn secret_key_hex_roundtrip() { let sk = fixed_secret(); let parsed = SecretKey::from_hex(&sk.to_hex()).unwrap(); assert_eq!(sk.to_hex(), parsed.to_hex()); } #[test] fn secret_key_nsec_roundtrip() { let sk = fixed_secret(); let nsec = sk.to_nsec(); assert!(nsec.starts_with("nsec1")); let parsed = SecretKey::from_nsec(&nsec).unwrap(); assert_eq!(sk.to_hex(), parsed.to_hex()); } #[test] fn from_str_auto_detects() { let sk = fixed_secret(); let pk = sk.public_key(); let from_hex: PublicKey = pk.to_hex().parse().unwrap(); let from_npub: PublicKey = pk.to_npub().parse().unwrap(); assert_eq!(from_hex, pk); assert_eq!(from_npub, pk); let sk_from_hex: SecretKey = sk.to_hex().parse().unwrap(); let sk_from_nsec: SecretKey = sk.to_nsec().parse().unwrap(); assert_eq!(sk_from_hex.to_hex(), sk.to_hex()); assert_eq!(sk_from_nsec.to_hex(), sk.to_hex()); } #[test] fn wrong_prefix_rejected() { let npub = fixed_secret().public_key().to_npub(); match SecretKey::from_nsec(&npub) { Err(KeyError::WrongPrefix { expected: "nsec", .. }) => (), other => panic!("expected WrongPrefix error, got {other:?}"), } } #[test] fn debug_is_redacted() { let sk = fixed_secret(); let rendered = format!("{sk:?}"); assert_eq!(rendered, "SecretKey()"); assert!(!rendered.contains(&sk.to_hex())); } #[test] fn generate_produces_distinct_keys() { let a = SecretKey::generate(); let b = SecretKey::generate(); assert_ne!(a.to_hex(), b.to_hex()); } /// Test vector from NIP-49: /// #[test] fn ncryptsec_spec_vector() { let ncryptsec = "ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p"; let expected = "3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683"; let sk = SecretKey::from_ncryptsec(ncryptsec, "nostr").unwrap(); assert_eq!(sk.to_hex(), expected); } #[test] fn ncryptsec_wrong_password_rejected() { let ncryptsec = "ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p"; match SecretKey::from_ncryptsec(ncryptsec, "wrong-password") { Err(KeyError::DecryptionFailed) => (), other => panic!("expected DecryptionFailed, got {other:?}"), } } #[test] fn ncryptsec_wrong_prefix_rejected() { let nsec = fixed_secret().to_nsec(); match SecretKey::from_ncryptsec(&nsec, "nostr") { Err(KeyError::WrongPrefix { expected: "ncryptsec", .. }) => (), other => panic!("expected WrongPrefix error, got {other:?}"), } } #[test] fn ncryptsec_roundtrip() { let sk = fixed_secret(); // log_n = 16 is the low end of NIP-49's recommended range; picked here for // test speed. security_byte = 0x01 means "has not been handled insecurely". let ncryptsec = sk.to_ncryptsec("hunter2", 16, 0x01).unwrap(); assert!(ncryptsec.starts_with("ncryptsec1")); let decrypted = SecretKey::from_ncryptsec(&ncryptsec, "hunter2").unwrap(); assert_eq!(decrypted.to_hex(), sk.to_hex()); } #[test] fn ncryptsec_roundtrip_is_nondeterministic() { // Encrypting the same key twice must yield different strings, since salt // and nonce are sampled fresh. Both must still decrypt to the same secret. let sk = fixed_secret(); let a = sk.to_ncryptsec("hunter2", 16, 0x01).unwrap(); let b = sk.to_ncryptsec("hunter2", 16, 0x01).unwrap(); assert_ne!(a, b); assert_eq!( SecretKey::from_ncryptsec(&a, "hunter2").unwrap().to_hex(), SecretKey::from_ncryptsec(&b, "hunter2").unwrap().to_hex(), ); } #[test] fn ncryptsec_nfkc_normalizes_password() { // The character "ñ" can be encoded either as U+00F1 (precomposed) or as // U+006E U+0303 (n + combining tilde). NFKC collapses them to the same // form, so both strings should unlock the same ncryptsec. let sk = fixed_secret(); let precomposed = "ma\u{00F1}ana"; let decomposed = "man\u{0303}ana"; let ncryptsec = sk.to_ncryptsec(precomposed, 16, 0x01).unwrap(); let decrypted = SecretKey::from_ncryptsec(&ncryptsec, decomposed).unwrap(); assert_eq!(decrypted.to_hex(), sk.to_hex()); }