neco-secp 0.1.1

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

pub fn create_seal(
    inner: UnsignedEvent,
    sender: &SecretKey,
    recipient: &XOnlyPublicKey,
) -> Result<SignedEvent, SecpError> {
    let signed_inner = nostr::finalize_event(inner, sender)?;
    let json = nostr::serialize_signed_event(&signed_inner)?;
    let conversation_key = nip44::get_conversation_key(sender, recipient)?;
    let encrypted = nip44::encrypt(&json, &conversation_key, None)?;
    let seal = UnsignedEvent {
        created_at: randomized_timestamp(signed_inner.created_at),
        kind: 13,
        tags: Vec::new(),
        content: encrypted,
    };
    nostr::finalize_event(seal, sender)
}

pub fn open_seal(seal: &SignedEvent, recipient: &SecretKey) -> Result<SignedEvent, SecpError> {
    if seal.kind != 13 {
        return Err(SecpError::InvalidEvent("seal must have kind 13"));
    }
    nostr::verify_event(seal)?;
    let conversation_key = nip44::get_conversation_key(recipient, &seal.pubkey)?;
    let json = nip44::decrypt(&seal.content, &conversation_key)?;
    let inner = nostr::parse_signed_event(&json)?;
    nostr::verify_event(&inner)?;
    Ok(inner)
}

pub fn create_gift_wrap(
    seal: &SignedEvent,
    recipient: &XOnlyPublicKey,
) -> Result<SignedEvent, SecpError> {
    if seal.kind != 13 {
        return Err(SecpError::InvalidEvent("seal must have kind 13"));
    }
    nostr::verify_event(seal)?;
    let ephemeral = SecretKey::generate()?;
    let json = nostr::serialize_signed_event(seal)?;
    let conversation_key = nip44::get_conversation_key(&ephemeral, recipient)?;
    let encrypted = nip44::encrypt(&json, &conversation_key, None)?;
    let wrap = UnsignedEvent {
        created_at: randomized_timestamp(seal.created_at),
        kind: 1059,
        tags: vec![vec!["p".to_string(), recipient.to_hex()]],
        content: encrypted,
    };
    nostr::finalize_event(wrap, &ephemeral)
}

pub fn open_gift_wrap(
    gift_wrap: &SignedEvent,
    recipient: &SecretKey,
) -> Result<SignedEvent, SecpError> {
    if gift_wrap.kind != 1059 {
        return Err(SecpError::InvalidEvent("gift wrap must have kind 1059"));
    }
    nostr::verify_event(gift_wrap)?;
    let conversation_key = nip44::get_conversation_key(recipient, &gift_wrap.pubkey)?;
    let json = nip44::decrypt(&gift_wrap.content, &conversation_key)?;
    let seal = nostr::parse_signed_event(&json)?;
    open_seal(&seal, recipient)
}

fn randomized_timestamp(base: u64) -> u64 {
    let mut buf = [0u8; 4];
    getrandom::getrandom(&mut buf).expect("getrandom");
    let offset = (u32::from_le_bytes(buf) % 345_601) as u64;
    base.saturating_sub(172_800) + offset
}