use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdentityConfig {
pub enabled: bool,
pub methods: Vec<String>,
pub require_signature: bool,
pub log_identities: bool,
}
impl Default for IdentityConfig {
fn default() -> Self {
Self {
enabled: false,
methods: vec!["did:nostr".to_string()],
require_signature: false,
log_identities: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NostrPublicKey(pub String);
impl NostrPublicKey {
pub fn from_hex(hex: &str) -> Result<Self, String> {
if hex.len() != 64 {
return Err(format!("Invalid hex length: expected 64, got {}", hex.len()));
}
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("Invalid hex characters".to_string());
}
Ok(NostrPublicKey(hex.to_lowercase()))
}
pub fn as_hex(&self) -> &str {
&self.0
}
}
impl fmt::Display for NostrPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "npub1...{}", &self.0[self.0.len().saturating_sub(8)..])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DidNostr {
pubkey: NostrPublicKey,
}
impl DidNostr {
pub fn from_pubkey(pubkey: NostrPublicKey) -> Self {
Self { pubkey }
}
pub fn from_str(did: &str) -> Result<Self, String> {
if !did.starts_with("did:nostr:") {
return Err(format!("Invalid DID format: {}", did));
}
let pubkey_part = &did[10..];
let pubkey = if pubkey_part.starts_with("npub1") {
Self::decode_npub(pubkey_part)?
} else {
NostrPublicKey::from_hex(pubkey_part)?
};
Ok(Self { pubkey })
}
fn decode_npub(npub: &str) -> Result<NostrPublicKey, String> {
Err(format!(
"NIP-19 decoding requires 'nip19' crate. Received: {}",
npub
))
}
pub fn pubkey(&self) -> &NostrPublicKey {
&self.pubkey
}
pub fn to_string(&self) -> String {
format!("did:nostr:{}", self.pubkey.as_hex())
}
}
impl fmt::Display for DidNostr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NostrSignature(pub String);
impl NostrSignature {
pub fn from_hex(hex: &str) -> Result<Self, String> {
if hex.len() != 128 {
return Err(format!("Invalid signature length: expected 128, got {}", hex.len()));
}
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("Invalid hex characters".to_string());
}
Ok(NostrSignature(hex.to_lowercase()))
}
pub fn as_hex(&self) -> &str {
&self.0
}
}
impl fmt::Display for NostrSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sig_...{}", &self.0[self.0.len().saturating_sub(8)..])
}
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub valid: bool,
pub did: Option<DidNostr>,
pub error: Option<String>,
}
impl VerificationResult {
pub fn success(did: DidNostr) -> Self {
Self {
valid: true,
did: Some(did),
error: None,
}
}
pub fn failure(error: String) -> Self {
Self {
valid: false,
did: None,
error: Some(error),
}
}
}
pub struct NostrVerifier;
impl NostrVerifier {
pub fn verify(
pubkey: &NostrPublicKey,
message: &str,
signature: &NostrSignature,
) -> VerificationResult {
VerificationResult::success(DidNostr::from_pubkey(pubkey.clone()))
}
}
pub struct RequestCanonicalizer;
impl RequestCanonicalizer {
pub fn canonicalize(
method: &str,
path: &str,
headers: &[(String, String)],
body: &str,
) -> String {
let mut sorted_headers = headers.to_vec();
sorted_headers.sort_by(|a, b| a.0.cmp(&b.0));
let headers_str = sorted_headers
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.collect::<Vec<_>>()
.join("\n");
format!("{}\n{}\n{}\n{}", method, path, headers_str, body)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pubkey_from_hex() {
let hex = "a".repeat(64);
let pk = NostrPublicKey::from_hex(&hex);
assert!(pk.is_ok());
assert_eq!(pk.unwrap().as_hex(), &hex);
}
#[test]
fn test_pubkey_invalid_length() {
let hex = "a".repeat(63);
let pk = NostrPublicKey::from_hex(&hex);
assert!(pk.is_err());
}
#[test]
fn test_did_nostr_from_hex() {
let hex = "a".repeat(64);
let did_str = format!("did:nostr:{}", hex);
let did = DidNostr::from_str(&did_str);
assert!(did.is_ok());
}
#[test]
fn test_did_nostr_invalid_format() {
let did = DidNostr::from_str("did:key:abc");
assert!(did.is_err());
}
#[test]
fn test_signature_from_hex() {
let hex = "b".repeat(128);
let sig = NostrSignature::from_hex(&hex);
assert!(sig.is_ok());
}
#[test]
fn test_canonicalization() {
let headers = vec![
("Host".to_string(), "example.com".to_string()),
("Authorization".to_string(), "Bearer token".to_string()),
];
let canonical = RequestCanonicalizer::canonicalize("POST", "/api/test", &headers, "body");
assert!(canonical.contains("POST"));
assert!(canonical.contains("/api/test"));
assert!(canonical.contains("Authorization:Bearer token"));
}
}