#![warn(
clippy::all,
clippy::missing_errors_doc,
clippy::style,
clippy::pedantic,
clippy::nursery
)]
pub mod bech32;
pub mod hex;
use hex::{FromHex as _, Hexable as _};
#[doc(hidden)]
pub trait SignerBech32: NostrSigner {
fn to_npub(&self) -> std::result::Result<String, bech32::Bech32Error> {
bech32::Bech32Crypto::encode("npub", &self.pubkey_bytes())
}
}
impl<T: NostrSigner + ?Sized> SignerBech32 for T {}
#[doc(hidden)]
pub trait KeypairBech32: NostrKeypair {
fn to_nsec(&self) -> std::result::Result<String, bech32::Bech32Error> {
bech32::Bech32Crypto::encode("nsec", &self.secret_bytes())
}
}
impl<T: NostrKeypair + ?Sized> KeypairBech32 for T {}
#[derive(Debug)]
pub enum SignerError {
MissingId,
MissingSignature,
InvalidPublicKey,
InvalidSignature,
Backend(String),
}
impl std::fmt::Display for SignerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MissingId => f.write_str("missing id on note"),
Self::MissingSignature => f.write_str("missing signature on note"),
Self::InvalidPublicKey => f.write_str("invalid public key"),
Self::InvalidSignature => f.write_str("invalid signature"),
Self::Backend(s) => write!(f, "signing backend error: {s}"),
}
}
}
impl std::error::Error for SignerError {}
pub type Result<T> = std::result::Result<T, SignerError>;
pub trait NostrSigner {
fn sign_prehash(&self, id: &[u8; 32]) -> Result<[u8; 64]>;
fn pubkey_bytes(&self) -> [u8; 32];
#[inline]
fn public_key(&self) -> String {
self.pubkey_bytes().to_hex()
}
}
pub trait NostrKeypair: NostrSigner {
fn secret_bytes(&self) -> [u8; 32];
fn generate() -> Self
where
Self: Sized;
fn from_secret_bytes(bytes: &[u8; 32]) -> Result<Self>
where
Self: Sized;
fn ecdh_x(&self, peer_xonly: &[u8; 32]) -> Result<[u8; 32]>;
#[inline]
fn secret_key(&self) -> String {
self.secret_bytes().to_hex()
}
fn shared_point(&self, peer_pubkey: &str) -> Result<[u8; 32]> {
let mut buf = [0_u8; 32];
peer_pubkey
.decode_hex_to_slice(&mut buf)
.map_err(|_| SignerError::InvalidPublicKey)?;
self.ecdh_x(&buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signer_error_display_all_variants() {
let cases = [
SignerError::MissingId,
SignerError::MissingSignature,
SignerError::InvalidPublicKey,
SignerError::InvalidSignature,
SignerError::Backend("test backend error".into()),
];
for err in &cases {
let msg = format!("{err}");
assert!(!msg.is_empty());
}
assert!(format!("{}", SignerError::Backend("x".into())).contains('x'));
}
#[test]
fn hex_error_display_all_variants() {
let cases = [
hex::HexError::OddLength,
hex::HexError::LengthMismatch,
hex::HexError::InvalidChar(b'G'),
];
for err in &cases {
let msg = format!("{err}");
assert!(!msg.is_empty());
}
assert!(format!("{}", hex::HexError::InvalidChar(b'Z')).contains('Z'));
}
}