mod error;
pub mod event;
pub mod hex;
pub mod keys;
#[cfg(feature = "batch")]
pub mod mining;
pub mod sig;
#[cfg(feature = "nip04")]
pub mod nip04;
#[cfg(all(feature = "nostr", feature = "nip44"))]
pub mod nip17;
#[cfg(feature = "nip19")]
pub mod nip19;
#[cfg(feature = "nostr")]
pub mod nip42;
#[cfg(feature = "nip44")]
pub mod nip44;
#[cfg(feature = "nostr")]
pub mod nostr;
pub use error::SecpError;
pub use event::{EventId, SignedEvent, UnsignedEvent};
#[cfg(feature = "nip19")]
pub use event::{NAddr, NEvent, NProfile, NRelay, Nip19};
pub use keys::{PublicKey, SecretKey, XOnlyPublicKey};
pub use sig::{EcdsaSignature, SchnorrSignature};
#[cfg(feature = "batch")]
pub use mining::{mine_pow, mine_pow_best};
#[cfg(all(feature = "batch", feature = "nip19"))]
pub use mining::{mine_vanity_npub, mine_vanity_npub_candidates, VanityCandidate};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyBundle {
secret: SecretKey,
xonly: XOnlyPublicKey,
}
impl KeyBundle {
pub fn generate() -> Result<Self, SecpError> {
let secret = SecretKey::generate()?;
let xonly = secret.xonly_public_key()?;
Ok(Self { secret, xonly })
}
pub fn secret(&self) -> &SecretKey {
&self.secret
}
pub fn xonly_public_key(&self) -> &XOnlyPublicKey {
&self.xonly
}
#[cfg(feature = "batch")]
pub fn generate_batch(count: usize) -> Result<Vec<Self>, SecpError> {
let mut bundles = Vec::with_capacity(count);
for _ in 0..count {
bundles.push(Self::generate()?);
}
Ok(bundles)
}
#[cfg(feature = "nip19")]
pub fn npub(&self) -> Result<String, SecpError> {
nip19::encode_npub(&self.xonly)
}
#[cfg(feature = "nip19")]
pub fn nsec(&self) -> Result<String, SecpError> {
nip19::encode_nsec(&self.secret)
}
}
#[cfg(test)]
mod tests {
use super::*;
const SECP256K1_ORDER: [u8; 32] = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36,
0x41, 0x41,
];
fn scalar_sub(minuend: [u8; 32], subtrahend: [u8; 32]) -> [u8; 32] {
let mut out = [0u8; 32];
let mut borrow = 0u16;
for i in (0..32).rev() {
let lhs = u16::from(minuend[i]);
let rhs = u16::from(subtrahend[i]) + borrow;
if lhs >= rhs {
out[i] = u8::try_from(lhs - rhs).expect("byte subtraction stays in range");
borrow = 0;
} else {
out[i] =
u8::try_from((lhs + 256) - rhs).expect("borrowed subtraction stays in range");
borrow = 1;
}
}
assert_eq!(borrow, 0, "subtraction must not underflow");
out
}
#[test]
fn secret_key_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let restored = SecretKey::from_bytes(secret.to_bytes()).expect("restore");
assert_eq!(secret.to_bytes(), restored.to_bytes());
}
#[test]
fn schnorr_sign_and_verify() {
let secret = SecretKey::generate().expect("secret key");
let pubkey = secret.xonly_public_key().expect("pubkey");
let digest = [7u8; 32];
let sig = secret.sign_schnorr_prehash(digest).expect("sign");
pubkey.verify_schnorr_prehash(digest, &sig).expect("verify");
}
#[test]
fn invalid_secret_key_is_rejected() {
let error = SecretKey::from_bytes([0u8; 32]).expect_err("must reject zero secret key");
assert!(matches!(error, SecpError::InvalidSecretKey));
}
#[test]
fn xonly_public_key_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.xonly_public_key().expect("public key");
let restored = XOnlyPublicKey::from_bytes(public.to_bytes()).expect("restored public key");
assert_eq!(public.to_bytes(), restored.to_bytes());
}
#[test]
fn public_key_sec1_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.public_key().expect("public key");
let restored =
PublicKey::from_sec1_bytes(&public.to_sec1_bytes()).expect("restored public key");
assert_eq!(public.to_sec1_bytes(), restored.to_sec1_bytes());
}
#[test]
fn secret_key_hex_roundtrip() {
let key = SecretKey::generate().expect("generate");
let hex = key.to_hex();
assert_eq!(hex.len(), 64);
let decoded = SecretKey::from_hex(&hex).expect("from_hex");
assert_eq!(key, decoded);
}
#[test]
fn xonly_hex_roundtrip() {
let key = SecretKey::generate().expect("generate");
let xonly = key.xonly_public_key().expect("xonly");
let hex = xonly.to_hex();
assert_eq!(hex.len(), 64);
let decoded = XOnlyPublicKey::from_hex(&hex).expect("from_hex");
assert_eq!(xonly, decoded);
}
#[test]
fn public_key_hex_roundtrip() {
let key = SecretKey::generate().expect("generate");
let pubkey = key.public_key().expect("pubkey");
let hex = pubkey.to_hex();
assert_eq!(hex.len(), 66);
let decoded = PublicKey::from_hex(&hex).expect("from_hex");
assert_eq!(pubkey, decoded);
}
#[test]
fn event_id_hex_roundtrip() {
let id = EventId::from_bytes([0xab; 32]);
let hex = id.to_hex();
assert_eq!(hex, "ab".repeat(32));
let decoded = EventId::from_hex(&hex).expect("from_hex");
assert_eq!(id, decoded);
}
#[test]
fn hex_decode_case_insensitive() {
let upper = "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789";
let lower = upper.to_lowercase();
let id_upper = EventId::from_hex(upper).expect("upper");
let id_lower = EventId::from_hex(&lower).expect("lower");
assert_eq!(id_upper, id_lower);
}
#[test]
fn hex_decode_odd_length_fails() {
assert!(matches!(
EventId::from_hex("abc"),
Err(SecpError::InvalidHex(_))
));
}
#[test]
fn hex_decode_invalid_char_fails() {
assert!(matches!(
EventId::from_hex("zz00000000000000000000000000000000000000000000000000000000000000"),
Err(SecpError::InvalidHex(_))
));
}
#[test]
fn hex_decode_wrong_length_fails() {
assert!(matches!(
SecretKey::from_hex("aabb"),
Err(SecpError::InvalidHex(_))
));
}
#[test]
fn hex_encode_is_lowercase() {
let id = EventId::from_bytes([0xAB; 32]);
let hex = id.to_hex();
assert_eq!(hex, hex.to_lowercase());
}
#[test]
fn keybundle_generate() {
let bundle = KeyBundle::generate().expect("key bundle");
let derived = bundle
.secret()
.xonly_public_key()
.expect("derived public key");
assert_eq!(*bundle.xonly_public_key(), derived);
}
#[cfg(feature = "batch")]
#[test]
fn keybundle_generate_batch() {
let bundles = KeyBundle::generate_batch(4).expect("batch");
assert_eq!(bundles.len(), 4);
for bundle in bundles {
let derived = bundle
.secret()
.xonly_public_key()
.expect("derived public key");
assert_eq!(*bundle.xonly_public_key(), derived);
}
}
#[cfg(feature = "batch")]
#[test]
fn keybundle_generate_batch_zero() {
let bundles = KeyBundle::generate_batch(0).expect("batch");
assert!(bundles.is_empty());
}
#[cfg(all(feature = "batch", feature = "nip19"))]
#[test]
#[ignore]
fn mine_vanity_npub_finds_match() {
let bundle = mine_vanity_npub("q", 100_000).expect("vanity match");
assert!(bundle.npub().expect("npub")[5..].starts_with("q"));
}
#[cfg(all(feature = "batch", feature = "nip19"))]
#[test]
fn mine_vanity_npub_exhausted() {
let error = mine_vanity_npub("zzzzzzzzzz", 10).expect_err("must exhaust attempts");
assert!(matches!(error, SecpError::ExhaustedAttempts));
}
#[test]
#[cfg(all(feature = "batch", feature = "nip19"))]
#[ignore]
fn vanity_candidates_returns_top_k() {
let candidates = mine_vanity_npub_candidates("q", 10_000, 3).expect("candidates");
assert!(candidates.len() <= 3);
for c in &candidates {
assert!(c.matched_len() >= 1);
}
for w in candidates.windows(2) {
assert!(w[0].matched_len() >= w[1].matched_len());
}
}
#[test]
#[cfg(all(feature = "batch", feature = "nip19"))]
fn vanity_candidates_zero_top_k() {
let candidates = mine_vanity_npub_candidates("q", 100, 0).expect("candidates");
assert!(candidates.is_empty());
}
#[test]
#[cfg(all(feature = "batch", feature = "nip19"))]
#[ignore]
fn vanity_candidates_exact_match_included() {
let candidates = mine_vanity_npub_candidates("q", 100_000, 5).expect("candidates");
let has_exact = candidates.iter().any(|c| c.matched_len() == 1);
assert!(
has_exact,
"should find at least one exact single-char match"
);
}
#[test]
#[cfg(all(feature = "batch", feature = "nip19"))]
fn vanity_prefix_match_counter_matches_encoded_npub() {
let secret = SecretKey::from_bytes([0x31; 32]).expect("secret");
let xonly = secret.xonly_public_key().expect("pubkey");
let npub = nip19::encode_npub(&xonly).expect("npub");
let npub_data = &npub[5..];
for len in [0usize, 1, 2, 3, 5, 8, 12] {
let prefix = &npub_data[..len];
let matched =
mining::count_npub_prefix_matches(&xonly.to_bytes(), prefix).expect("matched");
assert_eq!(matched, len);
}
let mismatched = format!("{}x", &npub_data[..4]);
let matched =
mining::count_npub_prefix_matches(&xonly.to_bytes(), &mismatched).expect("matched");
assert_eq!(matched, 4);
}
#[test]
#[cfg(all(feature = "batch", feature = "nip19"))]
fn vanity_prefix_rejects_invalid_bech32_chars() {
let err = mine_vanity_npub("!", 1).expect_err("invalid prefix");
assert!(matches!(
err,
SecpError::InvalidNip19("invalid npub vanity prefix")
));
let err = mine_vanity_npub_candidates("I", 1, 1).expect_err("invalid prefix");
assert!(matches!(
err,
SecpError::InvalidNip19("invalid npub vanity prefix")
));
}
#[cfg(feature = "batch")]
#[test]
#[ignore]
fn mine_pow_finds_match() {
let bundle = mine_pow(1, 100_000).expect("pow match");
assert!(mining::count_leading_zero_nibbles(&bundle.xonly_public_key().to_bytes()) >= 1);
}
#[cfg(feature = "batch")]
#[test]
fn mine_pow_exhausted() {
let error = mine_pow(64, 10).expect_err("must exhaust attempts");
assert!(matches!(error, SecpError::ExhaustedAttempts));
}
#[test]
#[cfg(feature = "batch")]
#[ignore]
fn mine_pow_best_returns_best() {
let (bundle, diff) = mine_pow_best(1, 100_000).expect("pow best");
assert!(diff >= 1);
let actual = mining::count_leading_zero_nibbles(&bundle.xonly_public_key().to_bytes());
assert_eq!(diff, actual);
}
#[test]
#[cfg(feature = "batch")]
fn mine_pow_best_exhausted() {
let err = mine_pow_best(64, 10).expect_err("should exhaust");
assert!(matches!(err, SecpError::ExhaustedAttempts));
}
#[cfg(feature = "batch")]
#[test]
fn count_leading_zero_nibbles_cases() {
assert_eq!(mining::count_leading_zero_nibbles(&[0x00, 0x0a, 0xff]), 3);
assert_eq!(mining::count_leading_zero_nibbles(&[0x00, 0x00]), 4);
assert_eq!(mining::count_leading_zero_nibbles(&[0xab]), 0);
assert_eq!(mining::count_leading_zero_nibbles(&[0x0f]), 1);
}
#[test]
fn schnorr_verify_rejects_wrong_digest() {
let secret = SecretKey::generate().expect("secret key");
let pubkey = secret.xonly_public_key().expect("pubkey");
let sig = secret
.sign_schnorr_prehash([1u8; 32])
.expect("signature for digest");
let error = pubkey
.verify_schnorr_prehash([2u8; 32], &sig)
.expect_err("must reject wrong digest");
assert!(matches!(error, SecpError::InvalidSignature));
}
#[test]
fn schnorr_verify_rejects_wrong_public_key() {
let secret_a = SecretKey::generate().expect("secret key a");
let secret_b = SecretKey::generate().expect("secret key b");
let pubkey_b = secret_b.xonly_public_key().expect("pubkey b");
let sig = secret_a
.sign_schnorr_prehash([3u8; 32])
.expect("signature for digest");
let error = pubkey_b
.verify_schnorr_prehash([3u8; 32], &sig)
.expect_err("must reject wrong public key");
assert!(matches!(error, SecpError::InvalidSignature));
}
#[test]
fn ecdsa_sign_verify_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.public_key().expect("public key");
let digest = [9u8; 32];
let sig = secret.sign_ecdsa_prehash(digest).expect("sign");
assert_eq!(sig.to_bytes().len(), 64);
public.verify_ecdsa_prehash(digest, &sig).expect("verify");
}
#[test]
fn ecdsa_verify_wrong_message() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.public_key().expect("public key");
let sig = secret
.sign_ecdsa_prehash([0x21; 32])
.expect("signature for digest");
let error = public
.verify_ecdsa_prehash([0x22; 32], &sig)
.expect_err("must reject wrong digest");
assert!(matches!(error, SecpError::InvalidSignature));
}
#[test]
fn ecdsa_reject_high_s() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret key");
let public = secret.public_key().expect("public key");
let digest = [0x33; 32];
let low_s = secret.sign_ecdsa_prehash(digest).expect("sign");
let mut sig_bytes = low_s.to_bytes();
let s_bytes: [u8; 32] = sig_bytes[32..].try_into().unwrap();
let high_s_bytes = scalar_sub(SECP256K1_ORDER, s_bytes);
sig_bytes[32..].copy_from_slice(&high_s_bytes);
let high_s = EcdsaSignature::from_bytes(sig_bytes);
let error = public
.verify_ecdsa_prehash(digest, &high_s)
.expect_err("must reject high-S signature");
assert!(matches!(error, SecpError::InvalidSignature));
}
#[cfg(feature = "nostr")]
#[test]
fn finalize_and_verify_event() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 1_700_000_000,
kind: 1,
tags: vec![vec!["t".to_string(), "rust".to_string()]],
content: "hello".to_string(),
};
let signed = nostr::finalize_event(event, &secret).expect("finalize");
nostr::verify_event(&signed).expect("verify");
}
#[cfg(feature = "nostr")]
#[test]
fn finalize_event_deterministic_is_reproducible() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let event = UnsignedEvent {
created_at: 1_700_000_000,
kind: 1,
tags: vec![vec!["p".to_string(), "abc".to_string()]],
content: "hello".to_string(),
};
let first = nostr::finalize_event_deterministic(event.clone(), &secret).expect("first");
let second = nostr::finalize_event_deterministic(event, &secret).expect("second");
assert_eq!(first, second);
nostr::verify_event(&first).expect("verify first");
nostr::verify_event(&second).expect("verify second");
}
#[cfg(feature = "nostr")]
#[test]
fn finalize_event_random_differs_per_call() {
let secret = SecretKey::from_bytes([0x22; 32]).expect("secret");
let event = UnsignedEvent {
created_at: 1_700_000_000,
kind: 1,
tags: vec![],
content: "hello".to_string(),
};
let first = nostr::finalize_event(event.clone(), &secret).expect("first");
let second = nostr::finalize_event(event, &secret).expect("second");
assert_eq!(first.id, second.id);
assert_eq!(first.pubkey, second.pubkey);
assert_ne!(first.sig, second.sig);
nostr::verify_event(&first).expect("verify first");
nostr::verify_event(&second).expect("verify second");
}
#[cfg(feature = "nostr")]
#[test]
fn nostr_matches_known_nostr_tools_fixture() {
let secret = SecretKey::from_hex(
"d217c1ff2f8a65c3e3a1740db3b9f58b\
8c848bb45e26d00ed4714e4a0f4ceecf",
)
.expect("secret");
let pubkey = secret.xonly_public_key().expect("pubkey");
let unsigned = UnsignedEvent {
created_at: 1_617_932_115,
kind: 1,
tags: vec![],
content: "Hello, world!".to_string(),
};
let serialized = nostr::serialize_event(&pubkey, &unsigned).expect("serialize");
let hash = nostr::compute_event_id(&pubkey, &unsigned).expect("hash");
let signed = nostr::finalize_event(unsigned, &secret).expect("finalize");
assert_eq!(
pubkey.to_hex(),
"6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f"
);
assert_eq!(
serialized,
r#"[0,"6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f",1617932115,1,[],"Hello, world!"]"#
);
assert_eq!(
hash.to_hex(),
"b2a44af84ca99b14820ae91c44e1ef0908f8aadc4e10620a6e6caa344507f03c"
);
assert_eq!(signed.id.to_hex(), hash.to_hex());
assert_eq!(
signed
.sig
.to_bytes()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>()
.len(),
128
);
nostr::verify_event(&signed).expect("verify");
}
#[cfg(feature = "nostr")]
#[test]
fn serialize_event_matches_expected_shape() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.xonly_public_key().expect("public key");
let event = UnsignedEvent {
created_at: 1_700_000_123,
kind: 7,
tags: vec![vec!["p".to_string(), "abcd".to_string()]],
content: "hello\nworld".to_string(),
};
let serialized = nostr::serialize_event(&public, &event).expect("serialize");
let value = neco_json::parse(serialized.as_bytes()).expect("json");
let array = value.as_array().expect("array payload");
assert_eq!(array.len(), 6);
assert_eq!(array[0].as_u64(), Some(0));
assert_eq!(array[2].as_u64(), Some(event.created_at));
assert_eq!(array[3].as_u64(), Some(event.kind as u64));
let expected_tags = neco_json::JsonValue::Array(
event
.tags
.iter()
.map(|tag| {
neco_json::JsonValue::Array(
tag.iter()
.map(|s| neco_json::JsonValue::String(s.clone()))
.collect(),
)
})
.collect(),
);
assert_eq!(array[4], expected_tags);
assert_eq!(array[5].as_str(), Some(event.content.as_str()));
}
#[cfg(feature = "nostr")]
#[test]
fn compute_event_id_is_reproducible() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.xonly_public_key().expect("public key");
let event = UnsignedEvent {
created_at: 10,
kind: 1,
tags: vec![vec!["e".to_string(), "1".to_string()]],
content: "same".to_string(),
};
let id_a = nostr::compute_event_id(&public, &event).expect("id a");
let id_b = nostr::compute_event_id(&public, &event).expect("id b");
assert_eq!(id_a, id_b);
}
#[cfg(feature = "nostr")]
#[test]
fn verify_rejects_tampered_content() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 1,
kind: 1,
tags: Vec::new(),
content: "hello".to_string(),
};
let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
signed.content = "tampered".to_string();
let error = nostr::verify_event(&signed).expect_err("must fail");
assert!(matches!(error, SecpError::InvalidEvent(_)));
}
#[cfg(feature = "nostr")]
#[test]
fn verify_rejects_tampered_tags() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 2,
kind: 1,
tags: vec![vec!["t".to_string(), "rust".to_string()]],
content: "hello".to_string(),
};
let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
signed.tags.push(vec!["p".to_string(), "peer".to_string()]);
let error = nostr::verify_event(&signed).expect_err("must fail");
assert!(matches!(error, SecpError::InvalidEvent(_)));
}
#[cfg(feature = "nostr")]
#[test]
fn verify_rejects_tampered_created_at() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 3,
kind: 1,
tags: Vec::new(),
content: "hello".to_string(),
};
let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
signed.created_at += 1;
let error = nostr::verify_event(&signed).expect_err("must fail");
assert!(matches!(error, SecpError::InvalidEvent(_)));
}
#[cfg(feature = "nostr")]
#[test]
fn verify_rejects_tampered_kind() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 4,
kind: 1,
tags: Vec::new(),
content: "hello".to_string(),
};
let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
signed.kind = 42;
let error = nostr::verify_event(&signed).expect_err("must fail");
assert!(matches!(error, SecpError::InvalidEvent(_)));
}
#[cfg(feature = "nostr")]
#[test]
fn verify_rejects_tampered_id() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 5,
kind: 1,
tags: Vec::new(),
content: "hello".to_string(),
};
let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
signed.id = EventId::from_bytes([9u8; 32]);
let error = nostr::verify_event(&signed).expect_err("must fail");
assert!(matches!(error, SecpError::InvalidEvent(_)));
}
#[cfg(feature = "nostr")]
#[test]
fn verify_rejects_tampered_signature() {
let secret = SecretKey::generate().expect("secret key");
let event = UnsignedEvent {
created_at: 6,
kind: 1,
tags: Vec::new(),
content: "hello".to_string(),
};
let mut signed = nostr::finalize_event(event, &secret).expect("finalize");
let mut sig = signed.sig.to_bytes();
sig[0] ^= 0x01;
signed.sig = SchnorrSignature { bytes: sig };
let error = nostr::verify_event(&signed).expect_err("must fail");
assert!(matches!(error, SecpError::InvalidSignature));
}
#[cfg(feature = "nostr")]
#[test]
fn signed_event_serialize_parse_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let signed = nostr::finalize_event(
UnsignedEvent {
created_at: 1_700_000_222,
kind: 14,
tags: vec![vec!["p".to_string(), "peer".to_string()]],
content: "sealed".to_string(),
},
&secret,
)
.expect("finalize");
let json = nostr::serialize_signed_event(&signed).expect("serialize signed");
let parsed = nostr::parse_signed_event(&json).expect("parse signed");
assert_eq!(parsed, signed);
}
#[cfg(feature = "nostr")]
#[test]
fn nip42_create_and_validate() {
let secret = SecretKey::generate().expect("secret key");
let expected = secret.xonly_public_key().expect("pubkey");
let signed = nip42::create_auth_event(
"challenge-123",
"wss://relay.example.com",
&secret,
1_700_000_666,
)
.expect("create auth event");
assert_eq!(signed.kind, 22_242);
assert_eq!(signed.content, "");
assert_eq!(
signed.tags,
vec![
vec!["relay".to_string(), "wss://relay.example.com".to_string()],
vec!["challenge".to_string(), "challenge-123".to_string()],
]
);
assert_eq!(
nip42::validate_auth_event(&signed, "challenge-123", "wss://relay.example.com")
.expect("validate auth event"),
expected
);
}
#[cfg(feature = "nostr")]
#[test]
fn nip42_wrong_challenge_fails() {
let secret = SecretKey::generate().expect("secret key");
let signed = nip42::create_auth_event(
"challenge-123",
"wss://relay.example.com",
&secret,
1_700_000_777,
)
.expect("create auth event");
let error =
nip42::validate_auth_event(&signed, "wrong-challenge", "wss://relay.example.com")
.expect_err("wrong challenge must fail");
assert!(matches!(
error,
SecpError::InvalidEvent("auth event challenge tag mismatch")
));
}
#[cfg(feature = "nostr")]
#[test]
fn nip42_wrong_relay_fails() {
let secret = SecretKey::generate().expect("secret key");
let signed = nip42::create_auth_event(
"challenge-123",
"wss://relay.example.com",
&secret,
1_700_000_888,
)
.expect("create auth event");
let error = nip42::validate_auth_event(&signed, "challenge-123", "wss://other.example.com")
.expect_err("wrong relay must fail");
assert!(matches!(
error,
SecpError::InvalidEvent("auth event relay tag mismatch")
));
}
#[cfg(all(feature = "nostr", feature = "nip44"))]
#[test]
fn nip17_seal_roundtrip() {
let sender = SecretKey::generate().expect("sender");
let recipient = SecretKey::generate().expect("recipient");
let recipient_pubkey = recipient.xonly_public_key().expect("recipient pubkey");
let inner = UnsignedEvent {
created_at: 1_700_000_333,
kind: 14,
tags: vec![vec!["p".to_string(), recipient_pubkey.to_hex()]],
content: "hello seal".to_string(),
};
let seal = nip17::create_seal(inner.clone(), &sender, &recipient_pubkey).expect("seal");
let opened = nip17::open_seal(&seal, &recipient).expect("open seal");
assert_eq!(seal.kind, 13);
assert!(seal.tags.is_empty());
assert_eq!(
seal.pubkey,
sender.xonly_public_key().expect("sender pubkey")
);
assert_eq!(opened.created_at, inner.created_at);
assert_eq!(opened.kind, inner.kind);
assert_eq!(opened.tags, inner.tags);
assert_eq!(opened.content, inner.content);
}
#[cfg(all(feature = "nostr", feature = "nip44"))]
#[test]
fn nip17_gift_wrap_roundtrip() {
let sender = SecretKey::generate().expect("sender");
let recipient = SecretKey::generate().expect("recipient");
let recipient_pubkey = recipient.xonly_public_key().expect("recipient pubkey");
let inner = UnsignedEvent {
created_at: 1_700_000_444,
kind: 14,
tags: vec![vec!["p".to_string(), recipient_pubkey.to_hex()]],
content: "hello wrap".to_string(),
};
let seal = nip17::create_seal(inner.clone(), &sender, &recipient_pubkey).expect("seal");
let gift_wrap = nip17::create_gift_wrap(&seal, &recipient_pubkey).expect("gift wrap");
let opened = nip17::open_gift_wrap(&gift_wrap, &recipient).expect("open gift wrap");
assert_eq!(gift_wrap.kind, 1059);
assert_eq!(
gift_wrap.tags,
vec![vec!["p".to_string(), recipient_pubkey.to_hex()]]
);
assert_ne!(
gift_wrap.pubkey,
sender.xonly_public_key().expect("sender pubkey")
);
assert_eq!(opened.created_at, inner.created_at);
assert_eq!(opened.kind, inner.kind);
assert_eq!(opened.tags, inner.tags);
assert_eq!(opened.content, inner.content);
}
#[cfg(all(feature = "nostr", feature = "nip44"))]
#[test]
fn nip17_wrong_recipient_fails() {
let sender = SecretKey::generate().expect("sender");
let recipient = SecretKey::generate().expect("recipient");
let wrong_recipient = SecretKey::generate().expect("wrong recipient");
let recipient_pubkey = recipient.xonly_public_key().expect("recipient pubkey");
let inner = UnsignedEvent {
created_at: 1_700_000_555,
kind: 14,
tags: vec![vec!["p".to_string(), recipient_pubkey.to_hex()]],
content: "secret".to_string(),
};
let seal = nip17::create_seal(inner, &sender, &recipient_pubkey).expect("seal");
let gift_wrap = nip17::create_gift_wrap(&seal, &recipient_pubkey).expect("gift wrap");
let error =
nip17::open_gift_wrap(&gift_wrap, &wrong_recipient).expect_err("wrong recipient");
assert!(matches!(
error,
SecpError::InvalidNip44(_) | SecpError::InvalidEvent(_)
));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_nsec_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let encoded = nip19::encode_nsec(&secret).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::Nsec(secret));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_npub_roundtrip() {
let secret = SecretKey::generate().expect("secret key");
let public = secret.xonly_public_key().expect("public key");
let encoded = nip19::encode_npub(&public).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::Npub(public));
}
#[cfg(feature = "nip19")]
#[test]
fn keybundle_nip19() {
let bundle = KeyBundle::generate().expect("key bundle");
let npub = bundle.npub().expect("npub");
let nsec = bundle.nsec().expect("nsec");
assert_eq!(
nip19::decode(&npub).expect("decode npub"),
Nip19::Npub(*bundle.xonly_public_key())
);
assert_eq!(
nip19::decode(&nsec).expect("decode nsec"),
Nip19::Nsec(*bundle.secret())
);
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_note_roundtrip() {
let id = EventId::from_bytes([0x42; 32]);
let encoded = nip19::encode_note(&id).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::Note(id));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decodes_known_valid_npub_fixture() {
let public = XOnlyPublicKey::from_bytes([
0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
0x15, 0xde, 0xee, 0x93,
])
.expect("fixture pubkey");
let decoded =
nip19::decode("npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc")
.expect("decode fixture");
assert_eq!(decoded, Nip19::Npub(public));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_matches_known_nostr_tools_fixture() {
let pubkey = XOnlyPublicKey::from_hex(
"6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f",
)
.expect("pubkey");
let event_id =
EventId::from_hex("b2a44af84ca99b14820ae91c44e1ef0908f8aadc4e10620a6e6caa344507f03c")
.expect("event id");
let profile = NProfile {
pubkey,
relays: vec![
"wss://relay.damus.io".to_string(),
"wss://nostr.example.com".to_string(),
],
};
let event = NEvent {
id: event_id,
relays: vec![
"wss://relay.example.com".to_string(),
"wss://relay2.example.com".to_string(),
],
author: Some(pubkey),
kind: Some(1),
};
let addr = NAddr {
identifier: "article-1".to_string(),
relays: vec![
"wss://relay.example.com".to_string(),
"wss://relay2.example.com".to_string(),
],
author: pubkey,
kind: 30_023,
};
assert_eq!(
nip19::encode_npub(&pubkey).expect("npub"),
"npub1dtc0nhjc3uk98nkuhgnvtcjq9cxs4fjwc768e8udj76mc43t4d0sw73h32"
);
assert_eq!(
nip19::encode_nprofile(&profile).expect("nprofile"),
"nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0dehhxarj9ejhsctdwpkx2tnrdaksqgr27ruauky093fuah96ymz7yspwp592vnk8k37flrvhkk79v2attuwkrkwx"
);
assert_eq!(
nip19::encode_nevent(&event).expect("nevent"),
"nevent1qvzqqqqqqypzq6hsl8093rev208dew3xch3yqtsdp2nya3a50j0cm9a4h3tzh26lqythwumn8ghj7un9d3shjtn90psk6urvv5hxxmmdqyv8wumn8ghj7un9d3shjv3wv4uxzmtsd3jjucm0d5qzpv4yftuye2vmzjpq46gugns77zgglz4dcnssvg9xum92x3zs0upuv94f3z"
);
assert_eq!(
nip19::encode_naddr(&addr).expect("naddr"),
"naddr1qvzqqqr4gupzq6hsl8093rev208dew3xch3yqtsdp2nya3a50j0cm9a4h3tzh26lqythwumn8ghj7un9d3shjtn90psk6urvv5hxxmmdqyv8wumn8ghj7un9d3shjv3wv4uxzmtsd3jjucm0d5qqjctjw35kxmr995csn0d4es"
);
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_nprofile_roundtrip_with_multiple_relays() {
let pubkey = XOnlyPublicKey::from_bytes([
0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
0x15, 0xde, 0xee, 0x93,
])
.expect("pubkey");
let profile = NProfile {
pubkey,
relays: vec![
"wss://relay.example".to_string(),
"wss://relay2.example".to_string(),
],
};
let encoded = nip19::encode_nprofile(&profile).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::NProfile(profile));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_nevent_roundtrip_with_author_and_kind() {
let event = NEvent {
id: EventId::from_bytes([0x34; 32]),
relays: vec!["wss://relay.example".to_string()],
author: Some(
XOnlyPublicKey::from_bytes([
0x4f, 0x35, 0x5b, 0xdc, 0xb7, 0xcc, 0x0a, 0xf7, 0x28, 0xef, 0x3c, 0xce, 0xb9,
0x61, 0x5d, 0x90, 0x68, 0x4b, 0xb5, 0xb2, 0xca, 0x5f, 0x85, 0x9a, 0xb0, 0xf0,
0xb7, 0x04, 0x07, 0x58, 0x71, 0xaa,
])
.expect("author"),
),
kind: Some(30_023),
};
let encoded = nip19::encode_nevent(&event).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::NEvent(event));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_naddr_roundtrip_allows_empty_identifier() {
let addr = NAddr {
identifier: String::new(),
relays: vec!["wss://relay.example".to_string()],
author: XOnlyPublicKey::from_bytes([
0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
0x15, 0xde, 0xee, 0x93,
])
.expect("author"),
kind: 30_023,
};
let encoded = nip19::encode_naddr(&addr).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::NAddr(addr));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_nrelay_roundtrip() {
let relay = NRelay {
relay: "wss://relay.example".to_string(),
};
let encoded = nip19::encode_nrelay(&relay).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::NRelay(relay));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decode_ignores_unknown_tlv_entries() {
use bech32::ToBase32;
let mut payload = vec![9, 3, b'x', b'y', b'z', 0, 32];
payload.extend_from_slice(&[
0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23, 0xb9,
0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c, 0xd0, 0xee,
0x15, 0xde, 0xee, 0x93,
]);
let encoded = bech32::encode("nprofile", payload.to_base32(), bech32::Variant::Bech32)
.expect("bech32");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(
decoded,
Nip19::NProfile(NProfile {
pubkey: XOnlyPublicKey::from_bytes([
0x6e, 0x46, 0x84, 0x22, 0xdf, 0xb7, 0x4a, 0x57, 0x38, 0x70, 0x2a, 0x88, 0x23,
0xb9, 0xb2, 0x81, 0x68, 0xab, 0xab, 0x86, 0x55, 0xfa, 0xac, 0xb6, 0x85, 0x3c,
0xd0, 0xee, 0x15, 0xde, 0xee, 0x93,
])
.expect("pubkey"),
relays: Vec::new(),
})
);
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decode_rejects_missing_required_nprofile_pubkey() {
use bech32::ToBase32;
let payload = vec![1, 5, b'r', b'e', b'l', b'a', b'y'];
let encoded = bech32::encode("nprofile", payload.to_base32(), bech32::Variant::Bech32)
.expect("bech32");
let error = nip19::decode(&encoded).expect_err("missing pubkey must fail");
assert!(matches!(
error,
SecpError::InvalidNip19("missing TLV 0 for nprofile")
));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decode_rejects_missing_required_naddr_fields() {
use bech32::ToBase32;
let mut payload = vec![0, 0];
payload.extend_from_slice(&[3, 4, 0, 0, 0x75, 0x37]);
let encoded =
bech32::encode("naddr", payload.to_base32(), bech32::Variant::Bech32).expect("bech32");
let error = nip19::decode(&encoded).expect_err("missing author must fail");
assert!(matches!(
error,
SecpError::InvalidNip19("missing TLV 2 for naddr")
));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decode_rejects_invalid_nevent_kind_length() {
use bech32::ToBase32;
let mut payload = vec![0, 32];
payload.extend_from_slice(&[0x34; 32]);
payload.extend_from_slice(&[3, 3, 0, 0x75, 0x37]);
let encoded =
bech32::encode("nevent", payload.to_base32(), bech32::Variant::Bech32).expect("bech32");
let error = nip19::decode(&encoded).expect_err("invalid kind length must fail");
assert!(matches!(
error,
SecpError::InvalidNip19("TLV 3 should be 4 bytes")
));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_nevent_kind_is_big_endian() {
let event = NEvent {
id: EventId::from_bytes([0x12; 32]),
relays: Vec::new(),
author: None,
kind: Some(30_023),
};
let encoded = nip19::encode_nevent(&event).expect("encode");
let decoded = nip19::decode(&encoded).expect("decode");
assert_eq!(decoded, Nip19::NEvent(event));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decode_rejects_invalid_checksum() {
let error =
nip19::decode("npub1de5gss7lkafc0pe2s2sz8wjsx6v4hvxxg8l6e60v8uuguvs7m5fsq4qwxn")
.expect_err("checksum must fail");
assert!(matches!(error, SecpError::InvalidNip19(_)));
}
#[cfg(feature = "nip19")]
#[test]
fn nip19_decode_rejects_invalid_length() {
use bech32::ToBase32;
let short = bech32::encode("npub", vec![1u8; 31].to_base32(), bech32::Variant::Bech32)
.expect("fixture encode");
let error = nip19::decode(&short).expect_err("short payload must fail");
assert!(matches!(error, SecpError::InvalidNip19(_)));
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_conversation_key_matches_from_both_sides() {
let secret_a = SecretKey::from_bytes([0x11; 32]).expect("secret a");
let secret_b = SecretKey::from_bytes([0x22; 32]).expect("secret b");
let pubkey_a = secret_a.xonly_public_key().expect("pubkey a");
let pubkey_b = secret_b.xonly_public_key().expect("pubkey b");
let key_ab = nip44::get_conversation_key(&secret_a, &pubkey_b).expect("key ab");
let key_ba = nip44::get_conversation_key(&secret_b, &pubkey_a).expect("key ba");
assert_eq!(key_ab, key_ba);
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_encrypt_and_decrypt_roundtrip() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
let conversation_key =
nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
let nonce = [0x33; 32];
let payload = nip44::encrypt("hello from neco-secp", &conversation_key, Some(nonce))
.expect("encrypt");
let plaintext = nip44::decrypt(&payload, &conversation_key).expect("decrypt");
assert_eq!(plaintext, "hello from neco-secp");
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_matches_known_oracle_fixture() {
const ORACLE_NIP44_PLAINTEXT: &str = "cyphercat nip44 oracle";
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer_pubkey = XOnlyPublicKey::from_bytes([
0x46, 0x6d, 0x7f, 0xca, 0xe5, 0x63, 0xe5, 0xcb, 0x09, 0xa0, 0xd1, 0x87, 0x0b, 0xb5,
0x80, 0x34, 0x48, 0x04, 0x61, 0x78, 0x79, 0xa1, 0x49, 0x49, 0xcf, 0x22, 0x28, 0x5f,
0x1b, 0xae, 0x3f, 0x27,
])
.expect("peer pubkey");
let conversation_key =
nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
assert_eq!(
conversation_key,
[
0x2c, 0xbd, 0xf0, 0x74, 0xf6, 0x01, 0x17, 0x8c, 0x24, 0xda, 0x3f, 0x82, 0x9b, 0x50,
0x45, 0x07, 0xa1, 0xf5, 0x50, 0xf9, 0x7d, 0x47, 0x2a, 0xf0, 0xf3, 0xf2, 0xcc, 0x59,
0xab, 0x77, 0x57, 0xd1,
]
);
let payload = nip44::encrypt(ORACLE_NIP44_PLAINTEXT, &conversation_key, Some([0x33; 32]))
.expect("encrypt");
assert_eq!(
payload,
"AjMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzJDZzpLQFdobg13n/RufVeG0ps8acSBfghr22oozB/q91IVexzbaA/lxkSa0R+6Dly9F1gKsZLCy1tzW4LPplhuWg"
);
let plaintext = nip44::decrypt(&payload, &conversation_key).expect("decrypt");
assert_eq!(plaintext, ORACLE_NIP44_PLAINTEXT);
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_calc_padded_len_contract() {
assert_eq!(nip44::calc_padded_len(1).expect("len"), 32);
assert_eq!(nip44::calc_padded_len(32).expect("len"), 32);
assert_eq!(nip44::calc_padded_len(33).expect("len"), 64);
assert_eq!(nip44::calc_padded_len(300).expect("len"), 320);
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_rejects_invalid_mac() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
let conversation_key =
nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
let nonce = [0x33; 32];
let payload = nip44::encrypt("hello", &conversation_key, Some(nonce)).expect("encrypt");
let mut raw = neco_base64::decode(&payload).expect("decode");
let last = raw.len() - 1;
raw[last] ^= 0x01;
let tampered = neco_base64::encode(&raw);
let error = nip44::decrypt(&tampered, &conversation_key).expect_err("invalid mac");
assert!(matches!(error, SecpError::InvalidNip44("invalid MAC")));
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_rejects_invalid_version() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
let conversation_key =
nip44::get_conversation_key(&secret, &peer_pubkey).expect("conversation key");
let nonce = [0x33; 32];
let payload = nip44::encrypt("hello", &conversation_key, Some(nonce)).expect("encrypt");
let mut raw = neco_base64::decode(&payload).expect("decode");
raw[0] = 3;
let tampered = neco_base64::encode(&raw);
let error = nip44::decrypt(&tampered, &conversation_key).expect_err("invalid version");
assert!(matches!(
error,
SecpError::InvalidNip44("unknown encryption version")
));
}
#[cfg(feature = "nip44")]
#[test]
fn nip44_rejects_invalid_payload_length() {
let error = nip44::decrypt("short", &[0u8; 32]).expect_err("invalid payload");
assert!(matches!(
error,
SecpError::InvalidNip44("invalid payload length")
));
}
#[cfg(feature = "nip04")]
#[test]
fn nip04_encrypt_and_decrypt_roundtrip() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer = SecretKey::from_bytes([0x22; 32]).expect("peer");
let peer_pubkey = peer.xonly_public_key().expect("peer pubkey");
let payload = nip04::encrypt(
&secret,
&peer_pubkey,
"hello from neco-secp",
Some([0x44; 16]),
)
.expect("encrypt");
let plaintext =
nip04::decrypt(&peer, &secret.xonly_public_key().expect("pubkey"), &payload)
.expect("decrypt");
assert_eq!(plaintext, "hello from neco-secp");
}
#[cfg(feature = "nip04")]
#[test]
fn nip04_matches_known_oracle_fixture() {
const ORACLE_NIP04_PLAINTEXT: &str = "cyphercat nip04 oracle";
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer_pubkey = XOnlyPublicKey::from_bytes([
0x46, 0x6d, 0x7f, 0xca, 0xe5, 0x63, 0xe5, 0xcb, 0x09, 0xa0, 0xd1, 0x87, 0x0b, 0xb5,
0x80, 0x34, 0x48, 0x04, 0x61, 0x78, 0x79, 0xa1, 0x49, 0x49, 0xcf, 0x22, 0x28, 0x5f,
0x1b, 0xae, 0x3f, 0x27,
])
.expect("peer pubkey");
let payload = nip04::encrypt(
&secret,
&peer_pubkey,
ORACLE_NIP04_PLAINTEXT,
Some([0x44; 16]),
)
.expect("encrypt");
assert_eq!(
payload,
"xftPpDirMJGDoq3ktNutZsG6W+lmUsILU9XMp06pYmM=?iv=RERERERERERERERERERERA=="
);
}
#[cfg(feature = "nip04")]
#[test]
fn nip04_rejects_invalid_payload() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer_pubkey = SecretKey::from_bytes([0x22; 32])
.expect("peer")
.xonly_public_key()
.expect("pubkey");
let error = nip04::decrypt(&secret, &peer_pubkey, "invalid").expect_err("invalid payload");
assert!(matches!(error, SecpError::InvalidNip04("invalid payload")));
}
#[cfg(feature = "nip04")]
#[test]
fn nip04_rejects_invalid_iv() {
let secret = SecretKey::from_bytes([0x11; 32]).expect("secret");
let peer_pubkey = SecretKey::from_bytes([0x22; 32])
.expect("peer")
.xonly_public_key()
.expect("pubkey");
let error = nip04::decrypt(&secret, &peer_pubkey, "abcd?iv=bad!").expect_err("invalid iv");
assert!(matches!(error, SecpError::InvalidNip04("invalid iv")));
}
}