use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use crate::util::hex::{self, HexError};
use crate::util::rng::{self, RngError};
pub const SECRET_KEY_SIZE: usize = 32;
#[derive(Debug, Clone, Copy, Error)]
#[non_exhaustive]
pub enum SecretKeyError {
#[error("invalid hex encoding: {0}")]
Hex(#[from] HexError),
#[error("invalid length: expected {SECRET_KEY_SIZE} bytes, got {0}")]
InvalidLength(usize),
#[error("not a valid secp256k1 scalar")]
InvalidScalar,
#[error("entropy unavailable: {0}")]
Rng(#[from] RngError),
}
#[derive(Clone, PartialEq, Eq)]
#[allow(
missing_copy_implementations,
reason = "do not copy secret key material implicitly; clone explicitly"
)]
pub struct SecretKey(secp256k1::SecretKey);
impl SecretKey {
pub fn from_byte_array(bytes: [u8; SECRET_KEY_SIZE]) -> Result<Self, SecretKeyError> {
secp256k1::SecretKey::from_byte_array(bytes)
.map(Self)
.map_err(|_| SecretKeyError::InvalidScalar)
}
pub fn from_slice(bytes: &[u8]) -> Result<Self, SecretKeyError> {
let array: [u8; SECRET_KEY_SIZE] = bytes
.try_into()
.map_err(|_| SecretKeyError::InvalidLength(bytes.len()))?;
Self::from_byte_array(array)
}
pub fn parse<S>(input: S) -> Result<Self, SecretKeyError>
where
S: AsRef<str>,
{
let bytes = hex::decode(input.as_ref())?;
Self::from_slice(&bytes)
}
pub fn generate() -> Result<Self, SecretKeyError> {
let bytes: [u8; SECRET_KEY_SIZE] = rng::random_bytes()?;
Self::from_byte_array(bytes)
}
#[must_use]
pub fn to_byte_array(&self) -> [u8; SECRET_KEY_SIZE] {
self.0.secret_bytes()
}
#[must_use]
pub fn to_hex(&self) -> String {
hex::encode(self.0.secret_bytes())
}
#[must_use]
pub const fn as_inner(&self) -> &secp256k1::SecretKey {
&self.0
}
}
impl fmt::Debug for SecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SecretKey").field(&"<redacted>").finish()
}
}
impl Drop for SecretKey {
fn drop(&mut self) {
self.0.non_secure_erase();
}
}
impl FromStr for SecretKey {
type Err = SecretKeyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl Serialize for SecretKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for SecretKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = <&str>::deserialize(deserializer)?;
Self::parse(raw).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
use super::*;
const VALID_SECRET: [u8; 32] =
hex!("0000000000000000000000000000000000000000000000000000000000000001");
#[test]
fn from_byte_array_valid() {
let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
assert_eq!(sk.to_byte_array(), VALID_SECRET);
}
#[test]
fn from_byte_array_zero_is_invalid() {
let zero = [0_u8; 32];
let err = SecretKey::from_byte_array(zero).unwrap_err();
assert!(matches!(err, SecretKeyError::InvalidScalar));
}
#[test]
fn from_slice_wrong_length() {
let err = SecretKey::from_slice(&[0_u8; 16]).unwrap_err();
assert!(matches!(err, SecretKeyError::InvalidLength(16)));
}
#[test]
fn parse_round_trip() {
let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
let hex_str = sk.to_hex();
assert_eq!(hex_str.len(), 64);
assert!(hex_str.chars().all(|c| c.is_ascii_hexdigit()));
let parsed = SecretKey::parse(&hex_str).unwrap();
assert_eq!(parsed, sk);
}
#[test]
fn generate_distinct() {
let lhs = SecretKey::generate().unwrap();
let rhs = SecretKey::generate().unwrap();
assert_ne!(lhs, rhs);
}
#[test]
fn debug_redacts() {
let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
let dbg = format!("{sk:?}");
assert!(dbg.contains("redacted"));
assert!(!dbg.contains(&sk.to_hex()));
}
#[test]
fn serde_round_trip() {
let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
let json = serde_json::to_string(&sk).unwrap();
let parsed: SecretKey = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, sk);
}
#[test]
fn serde_rejects_short_hex() {
let result: Result<SecretKey, _> = serde_json::from_str("\"abcdef\"");
assert!(result.is_err());
}
#[test]
fn from_str_works() {
let sk: SecretKey = "0000000000000000000000000000000000000000000000000000000000000001"
.parse()
.unwrap();
assert_eq!(sk.to_byte_array(), VALID_SECRET);
}
#[test]
fn drop_runs_non_secure_erase() {
let _ = SecretKey::from_byte_array(VALID_SECRET).unwrap();
}
}