neco-secp 0.1.1

minimum dependency secp256k1 and Nostr signing core
Documentation
use crate::{nostr, SecpError, SecretKey, SignedEvent, UnsignedEvent, XOnlyPublicKey};

pub fn create_auth_event(
    challenge: &str,
    relay_url: &str,
    signer: &SecretKey,
    now_unix_seconds: u64,
) -> Result<SignedEvent, SecpError> {
    let event = UnsignedEvent {
        created_at: now_unix_seconds,
        kind: 22_242,
        tags: vec![
            vec!["relay".to_string(), relay_url.to_string()],
            vec!["challenge".to_string(), challenge.to_string()],
        ],
        content: String::new(),
    };
    nostr::finalize_event(event, signer)
}

pub fn validate_auth_event(
    event: &SignedEvent,
    challenge: &str,
    relay_url: &str,
) -> Result<XOnlyPublicKey, SecpError> {
    nostr::verify_event(event)?;
    if event.kind != 22_242 {
        return Err(SecpError::InvalidEvent("auth event must have kind 22242"));
    }
    if tag_value(&event.tags, "relay") != Some(relay_url) {
        return Err(SecpError::InvalidEvent("auth event relay tag mismatch"));
    }
    if tag_value(&event.tags, "challenge") != Some(challenge) {
        return Err(SecpError::InvalidEvent("auth event challenge tag mismatch"));
    }
    Ok(event.pubkey)
}

fn tag_value<'a>(tags: &'a [Vec<String>], name: &str) -> Option<&'a str> {
    tags.iter()
        .find(|tag| tag.first().is_some_and(|value| value == name))
        .and_then(|tag| tag.get(1))
        .map(String::as_str)
}