nostro2-nips 0.2.0

Nostro2 is a simple toolset for interacting with the Nostr protocol.
Documentation
#[derive(Debug, thiserror::Error)]
pub enum Nip82Error {
    #[error("Failed to encrypt message {0}")]
    Nip44Error(#[from] crate::nip_44::Nip44Error),
    #[error("Failed to sign message {0}")]
    Nostro2Error(#[from] nostro2::errors::NostrErrors),
    #[error("Failed to serialize message {0}")]
    SerializationError(#[from] serde_json::Error),
    #[error("Failed to parse message {0}")]
    ParseError(String),
    #[error("Failed to sign message")]
    SigningError,
}
pub trait Nip82: crate::nip_44::Nip44 + nostro2::NostrSigner + Sized + std::str::FromStr {
    /// Creates a NIP-82 request note.
    ///
    /// # Errors
    ///
    /// Returns an error if the note fails to sign or encrypt.
    fn encrypted_wrap(
        &self,
        fhir_note: &mut nostro2::NostrNote,
        peer_pubkey: &str,
        wrap_id: &str,
    ) -> Result<nostro2::NostrNote, Nip82Error> {
        let signing_key = Self::generate(true);
        self.sign_nostr_note(fhir_note)?;
        let serialized = fhir_note.serialize()?;
        let pubkey = signing_key.public_key();
        let encrypted = signing_key.nip_44_encrypt(&serialized, &pubkey)?;
        let mut wrapped = nostro2::NostrNote {
            content: encrypted.to_string(),
            pubkey: signing_key.public_key(),
            kind: 32225,
            ..Default::default()
        };
        wrapped.tags.add_parameter_tag(wrap_id);
        wrapped.tags.add_pubkey_tag(&self.public_key(), None);
        wrapped.tags.add_pubkey_tag(peer_pubkey, None);
        wrapped.tags.0.push(vec![
            "key".to_string(),
            self.public_key(),
            signing_key
                .nip_44_encrypt(&signing_key.secret_key(), &self.public_key())?
                .to_string(),
        ]);
        wrapped.tags.0.push(vec![
            "key".to_string(),
            peer_pubkey.to_string(),
            signing_key
                .nip_44_encrypt(&signing_key.secret_key(), peer_pubkey)?
                .to_string(),
        ]);
        signing_key.sign_nostr_note(&mut wrapped)?;
        Ok(wrapped)
    }
    /// Decrypts a NIP-82 request note.
    ///
    /// # Errors
    ///
    /// Returns an error if the note fails to decrypt or parse.
    fn decrypt_wrap(
        &self,
        fhir_note: &nostro2::NostrNote,
    ) -> Result<nostro2::NostrNote, Nip82Error> {
        let encrypted_signing_key = fhir_note
            .tags
            .0
            .iter()
            .find_map(|tag_list| {
                (tag_list.first().is_some_and(|tag| tag == "key")
                    && tag_list.get(1) == Some(&self.public_key()))
                .then(|| tag_list.get(2))
                .flatten()
            })
            .ok_or_else(|| Nip82Error::ParseError("Failed to get signing key".to_string()))?;
        let decrypted_signing_key =
            self.nip_44_decrypt(encrypted_signing_key.as_str(), &fhir_note.pubkey)?;
        let signing_key: Self = decrypted_signing_key
            .parse()
            .map_err(|_| Nip82Error::ParseError("Failed to parse signing key".to_string()))?;
        let decrypted_note =
            signing_key.nip_44_decrypt(fhir_note.content.as_str(), &fhir_note.pubkey)?;
        let decrypted_wrap = decrypted_note
            .parse::<nostro2::NostrNote>()
            .map_err(|e| Nip82Error::ParseError(e.to_string()))?;
        Ok(decrypted_wrap)
    }
    /// Decrypts a NIP-82 request note and returns the signing key.
    ///
    /// # Errors
    ///
    /// Returns an error if the note fails to decrypt or parse.
    fn signing_key(&self, fhir_note: &nostro2::NostrNote) -> Result<Self, Nip82Error> {
        let encrypted_signing_key = fhir_note
            .tags
            .0
            .iter()
            .find_map(|tag_list| {
                (tag_list.first().is_some_and(|tag| tag == "key")
                    && tag_list.get(1) == Some(&self.public_key()))
                .then(|| tag_list.get(2))
                .flatten()
            })
            .ok_or_else(|| Nip82Error::ParseError("Failed to get signing key".to_string()))?;
        let decrypted_signing_key =
            self.nip_44_decrypt(encrypted_signing_key.as_str(), &fhir_note.pubkey)?;
        let signing_key: Self = decrypted_signing_key
            .parse()
            .map_err(|_| Nip82Error::ParseError("Failed to parse signing key".to_string()))?;
        Ok(signing_key)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tests::NipTester;
    use nostro2::{NostrNote, NostrSigner};

    #[test]
    fn test_nip82() {
        let peer_one = NipTester::_peer_one();
        let peer_two = NipTester::_peer_two();
        let mut note = NostrNote {
            content: "Hello, world!".to_string(),
            ..Default::default()
        };
        let wrapped_note = peer_one
            .encrypted_wrap(&mut note, &peer_two.public_key(), "wrap_id")
            .unwrap();
        let decrypted_note = peer_two.decrypt_wrap(&wrapped_note).unwrap();
        let peer_one_decrypted_note = peer_one.decrypt_wrap(&wrapped_note).unwrap();
        assert_eq!(decrypted_note.content, note.content);
        assert_eq!(peer_one_decrypted_note.content, note.content);
        assert_eq!(decrypted_note.content, peer_one_decrypted_note.content);
    }
}