use hkdf::Hkdf;
use k256::schnorr::{SigningKey, VerifyingKey};
use sha2::Sha256;
use zeroize::Zeroize;
const HKDF_INFO: &[u8] = b"nostr-secp256k1-v1";
#[derive(Debug, thiserror::Error)]
pub enum KeyError {
#[error("invalid secret key bytes (not a valid secp256k1 scalar)")]
InvalidSecretKey,
#[error("invalid public key hex: {0}")]
InvalidPublicKeyHex(String),
#[error("invalid public key bytes")]
InvalidPublicKey,
#[error("signing failed: {0}")]
SigningFailed(String),
#[error("signature verification failed")]
VerifyFailed,
#[error("invalid signature bytes")]
InvalidSignature,
#[error("HKDF expand failed")]
HkdfExpandFailed,
}
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct SecretKey {
bytes: [u8; 32],
}
impl SecretKey {
pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, KeyError> {
SigningKey::from_bytes(&bytes).map_err(|_| KeyError::InvalidSecretKey)?;
Ok(Self { bytes })
}
pub fn public_key(&self) -> PublicKey {
let sk = SigningKey::from_bytes(&self.bytes)
.expect("SecretKey invariant: bytes are always valid");
let vk = sk.verifying_key();
let mut pk_bytes = [0u8; 32];
pk_bytes.copy_from_slice(vk.to_bytes().as_slice());
PublicKey { bytes: pk_bytes }
}
pub fn sign(&self, message: &[u8; 32]) -> Result<Signature, KeyError> {
let sk = SigningKey::from_bytes(&self.bytes)
.expect("SecretKey invariant: bytes are always valid");
let aux_rand = [0u8; 32];
let sig = sk
.sign_raw(message, &aux_rand)
.map_err(|e| KeyError::SigningFailed(e.to_string()))?;
let mut sig_bytes = [0u8; 64];
sig_bytes.copy_from_slice(&sig.to_bytes());
Ok(Signature { bytes: sig_bytes })
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.bytes
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKey {
bytes: [u8; 32],
}
impl PublicKey {
pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, KeyError> {
VerifyingKey::from_bytes(&bytes).map_err(|_| KeyError::InvalidPublicKey)?;
Ok(Self { bytes })
}
pub fn from_hex(hex_str: &str) -> Result<Self, KeyError> {
let decoded =
hex::decode(hex_str).map_err(|_| KeyError::InvalidPublicKeyHex(hex_str.to_string()))?;
if decoded.len() != 32 {
return Err(KeyError::InvalidPublicKeyHex(format!(
"expected 32 bytes, got {}",
decoded.len()
)));
}
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&decoded);
VerifyingKey::from_bytes(&bytes).map_err(|_| KeyError::InvalidPublicKey)?;
Ok(Self { bytes })
}
pub fn to_hex(&self) -> String {
hex::encode(self.bytes)
}
pub fn verify(&self, message: &[u8; 32], sig: &Signature) -> Result<(), KeyError> {
let vk = VerifyingKey::from_bytes(&self.bytes).map_err(|_| KeyError::InvalidPublicKey)?;
let k256_sig = k256::schnorr::Signature::try_from(sig.bytes.as_slice())
.map_err(|_| KeyError::InvalidSignature)?;
vk.verify_raw(message, &k256_sig)
.map_err(|_| KeyError::VerifyFailed)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.bytes
}
}
pub struct Keypair {
pub secret: SecretKey,
pub public: PublicKey,
}
#[derive(Clone, Debug)]
pub struct Signature {
bytes: [u8; 64],
}
impl Signature {
pub fn from_bytes(bytes: [u8; 64]) -> Self {
Self { bytes }
}
pub fn as_bytes(&self) -> &[u8; 64] {
&self.bytes
}
pub fn to_hex(&self) -> String {
hex::encode(self.bytes)
}
}
pub fn pubkey_hex(secret_key: &[u8; 32]) -> Result<String, KeyError> {
let sk = SecretKey::from_bytes(*secret_key)?;
Ok(sk.public_key().to_hex())
}
pub fn signing_key_from_bytes(secret_key: &[u8; 32]) -> Result<SigningKey, KeyError> {
SigningKey::from_bytes(secret_key).map_err(|_| KeyError::InvalidSecretKey)
}
pub fn derive_from_prf(prf_output: &[u8; 32]) -> Result<Keypair, KeyError> {
let hk = Hkdf::<Sha256>::new(Some(&[]), prf_output);
let mut okm = [0u8; 32];
hk.expand(HKDF_INFO, &mut okm)
.map_err(|_| KeyError::HkdfExpandFailed)?;
let secret = SecretKey::from_bytes(okm)?;
let public = secret.public_key();
okm.zeroize();
Ok(Keypair { secret, public })
}
pub fn generate_keypair() -> Result<Keypair, KeyError> {
let mut bytes = [0u8; 32];
getrandom::getrandom(&mut bytes).expect("getrandom failed");
match SecretKey::from_bytes(bytes) {
Ok(secret) => {
let public = secret.public_key();
bytes.zeroize();
Ok(Keypair { secret, public })
}
Err(_) => {
bytes.zeroize();
generate_keypair() }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hkdf_derive(ikm: &[u8; 32]) -> [u8; 32] {
let hk = Hkdf::<Sha256>::new(Some(&[]), ikm);
let mut out = [0u8; 32];
hk.expand(b"nostr-secp256k1-v1", &mut out).unwrap();
out
}
#[test]
fn derive_from_prf_produces_expected_key() {
let prf_input = [0x01u8; 32];
let expected_secret = hkdf_derive(&prf_input);
let kp = derive_from_prf(&prf_input).unwrap();
assert_eq!(kp.secret.as_bytes(), &expected_secret);
let pk = kp.secret.public_key();
assert_eq!(pk, kp.public);
}
#[test]
fn derive_from_prf_deterministic() {
let prf = [0xABu8; 32];
let kp1 = derive_from_prf(&prf).unwrap();
let kp2 = derive_from_prf(&prf).unwrap();
assert_eq!(kp1.secret.as_bytes(), kp2.secret.as_bytes());
assert_eq!(kp1.public, kp2.public);
}
#[test]
fn derive_from_prf_different_inputs_differ() {
let kp1 = derive_from_prf(&[0x01u8; 32]).unwrap();
let kp2 = derive_from_prf(&[0x02u8; 32]).unwrap();
assert_ne!(kp1.secret.as_bytes(), kp2.secret.as_bytes());
assert_ne!(kp1.public, kp2.public);
}
#[test]
fn generate_keypair_sign_verify_roundtrip() {
let kp = generate_keypair().unwrap();
use sha2::Digest;
let message = Sha256::digest(b"hello nostr");
let msg: [u8; 32] = message.into();
let sig = kp.secret.sign(&msg).unwrap();
kp.public.verify(&msg, &sig).unwrap();
}
#[test]
fn sign_verify_with_derived_keypair() {
let kp = derive_from_prf(&[0xFFu8; 32]).unwrap();
let msg = [0x42u8; 32];
let sig = kp.secret.sign(&msg).unwrap();
kp.public.verify(&msg, &sig).unwrap();
}
#[test]
fn verify_wrong_key_fails() {
let kp_a = generate_keypair().unwrap();
let kp_b = generate_keypair().unwrap();
let msg = [0x00u8; 32];
let sig = kp_a.secret.sign(&msg).unwrap();
let result = kp_b.public.verify(&msg, &sig);
assert!(result.is_err());
}
#[test]
fn verify_wrong_message_fails() {
let kp = generate_keypair().unwrap();
let msg1 = [0x01u8; 32];
let msg2 = [0x02u8; 32];
let sig = kp.secret.sign(&msg1).unwrap();
let result = kp.public.verify(&msg2, &sig);
assert!(result.is_err());
}
#[test]
fn public_key_from_hex_valid() {
let kp = generate_keypair().unwrap();
let hex_str = kp.public.to_hex();
let pk2 = PublicKey::from_hex(&hex_str).unwrap();
assert_eq!(kp.public, pk2);
}
#[test]
fn public_key_from_hex_invalid_length() {
let result = PublicKey::from_hex("abcd");
assert!(matches!(result, Err(KeyError::InvalidPublicKeyHex(_))));
}
#[test]
fn public_key_from_hex_invalid_chars() {
let result = PublicKey::from_hex(&"zz".repeat(32));
assert!(matches!(result, Err(KeyError::InvalidPublicKeyHex(_))));
}
#[test]
fn public_key_from_hex_not_on_curve() {
let result = PublicKey::from_hex(&"00".repeat(32));
assert!(matches!(result, Err(KeyError::InvalidPublicKey)));
}
#[test]
fn secret_key_from_bytes_zero_rejected() {
let result = SecretKey::from_bytes([0u8; 32]);
assert!(matches!(result, Err(KeyError::InvalidSecretKey)));
}
#[test]
fn signature_hex_roundtrip() {
let kp = generate_keypair().unwrap();
let msg = [0x33u8; 32];
let sig = kp.secret.sign(&msg).unwrap();
let hex_str = sig.to_hex();
assert_eq!(hex_str.len(), 128);
}
#[test]
fn public_key_hex_roundtrip() {
let kp = generate_keypair().unwrap();
let hex_str = kp.public.to_hex();
assert_eq!(hex_str.len(), 64);
let pk2 = PublicKey::from_hex(&hex_str).unwrap();
assert_eq!(kp.public, pk2);
}
#[test]
fn pubkey_hex_produces_64_char_hex() {
let secret = [0x01u8; 32];
let pk = pubkey_hex(&secret).unwrap();
assert_eq!(pk.len(), 64);
assert!(pk.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn signing_key_roundtrip() {
let secret = [0x02u8; 32];
let sk = signing_key_from_bytes(&secret).unwrap();
let pk = hex::encode(sk.verifying_key().to_bytes());
assert_eq!(pk.len(), 64);
}
}