use crate::Error;
use crate::entropy::{EntropyError, EntropySource};
use crate::secret::SecretKeyHex;
use secp256k1::SecretKey;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct PrivateKey {
bytes: Box<[u8; 32]>,
}
impl PrivateKey {
fn zeroed() -> Self {
Self {
bytes: Box::new([0u8; 32]),
}
}
fn validated(self) -> Result<PrivateKey, Error> {
if !is_valid_key(self.as_bytes()) {
return Err(Error("not a valid secp256k1 scalar".into()));
}
Ok(self)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.bytes
}
pub fn to_secret_key(&self) -> SecretKey {
SecretKey::from_byte_array(*self.as_bytes())
.expect("PrivateKey always holds a validated scalar")
}
#[must_use]
pub fn to_hex(&self) -> SecretKeyHex {
const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
let mut hex = SecretKeyHex::zeroed();
let digits = hex.bytes_mut();
for (i, byte) in self.bytes.iter().enumerate() {
digits[i * 2] = HEX_DIGITS[usize::from(byte >> 4)];
digits[i * 2 + 1] = HEX_DIGITS[usize::from(byte & 0x0f)];
}
hex
}
pub fn from_bytes(mut bytes: [u8; 32]) -> Result<PrivateKey, Error> {
let mut key = Self::zeroed();
key.bytes.copy_from_slice(&bytes);
bytes.zeroize();
key.validated()
}
pub fn from_hex(hex: &str) -> Result<PrivateKey, Error> {
if hex.len() != 64 {
return Err(Error(format!(
"expected 64 hex characters, got {}",
hex.len()
)));
}
let mut key = Self::zeroed();
for i in 0..32 {
key.bytes[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16)
.map_err(|_| Error(format!("invalid hex at position {}", i * 2)))?;
}
key.validated()
}
}
pub fn is_valid_key(bytes: &[u8; 32]) -> bool {
match SecretKey::from_byte_array(*bytes) {
Ok(mut key) => {
key.non_secure_erase();
true
}
Err(_) => false,
}
}
pub(crate) fn generate_with_entropy(
entropy: &dyn EntropySource,
) -> Result<PrivateKey, EntropyError> {
for _ in 0..MAX_RETRIES {
let mut key = PrivateKey::zeroed();
entropy.fill_bytes(&mut key.bytes[..])?;
if is_valid_key(key.as_bytes()) {
return Ok(key);
}
}
Err(EntropyError(
"failed to generate valid key after maximum retries".into(),
))
}
pub fn generate() -> Result<PrivateKey, crate::Error> {
generate_with_entropy(&crate::entropy::OsEntropy).map_err(crate::Error::from)
}
const MAX_RETRIES: u32 = 32;
#[cfg(test)]
mod tests {
use super::*;
use crate::entropy::{FailingEntropy, FixedEntropy};
const CURVE_ORDER: [u8; 32] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36,
0x41, 0x41,
];
fn curve_order_minus_one() -> [u8; 32] {
let mut bytes = CURVE_ORDER;
bytes[31] -= 1;
bytes
}
fn curve_order_plus_one() -> [u8; 32] {
let mut bytes = CURVE_ORDER;
bytes[31] += 1;
bytes
}
#[test]
fn test_zero_key_rejected() {
let zero = [0u8; 32];
assert!(!is_valid_key(&zero), "zero must not be a valid private key");
}
#[test]
fn test_one_key_valid() {
let mut one = [0u8; 32];
one[31] = 1;
assert!(is_valid_key(&one), "scalar 1 must be a valid private key");
}
#[test]
fn test_curve_order_minus_one_valid() {
let n_minus_1 = curve_order_minus_one();
assert!(
is_valid_key(&n_minus_1),
"n-1 must be a valid private key (maximum scalar)"
);
}
#[test]
fn test_curve_order_rejected() {
assert!(
!is_valid_key(&CURVE_ORDER),
"the curve order n itself must not be a valid private key"
);
}
#[test]
fn test_curve_order_plus_one_rejected() {
let n_plus_1 = curve_order_plus_one();
assert!(
!is_valid_key(&n_plus_1),
"n+1 must not be a valid private key"
);
}
#[test]
fn test_all_ff_rejected() {
let all_ff = [0xFF; 32];
assert!(
!is_valid_key(&all_ff),
"all 0xFF bytes exceed curve order and must be rejected"
);
}
#[test]
fn test_valid_midrange_key() {
let mut key = [0u8; 32];
key[0] = 0x0A;
key[31] = 0x0B;
assert!(is_valid_key(&key));
}
#[test]
fn test_fixed_entropy_produces_expected_key() {
let mut key_bytes = [0u8; 32];
key_bytes[31] = 0x01; let entropy = FixedEntropy::new(key_bytes.to_vec());
let key = generate_with_entropy(&entropy).expect("generation should succeed");
assert_eq!(key.as_bytes(), &key_bytes);
}
#[test]
fn test_different_entropy_produces_different_keys() {
let mut bytes_a = [0u8; 32];
bytes_a[31] = 0x01;
let mut bytes_b = [0u8; 32];
bytes_b[31] = 0x02;
let key_a = generate_with_entropy(&FixedEntropy::new(bytes_a.to_vec())).unwrap();
let key_b = generate_with_entropy(&FixedEntropy::new(bytes_b.to_vec())).unwrap();
assert_ne!(key_a.as_bytes(), key_b.as_bytes());
}
#[test]
fn test_same_entropy_produces_same_key() {
let mut key_bytes = [0u8; 32];
key_bytes[31] = 0x05;
let key1 = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
let key2 = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
assert_eq!(key1.as_bytes(), key2.as_bytes());
}
#[test]
fn test_invalid_entropy_triggers_retry() {
let mut data = CURVE_ORDER.to_vec();
let mut valid = [0u8; 32];
valid[31] = 0x01;
data.extend_from_slice(&valid);
let entropy = FixedEntropy::new(data);
let key = generate_with_entropy(&entropy).expect("should succeed after retry");
assert_eq!(key.as_bytes(), &valid);
}
#[test]
fn test_entropy_failure_propagates() {
let result = generate_with_entropy(&FailingEntropy);
assert!(result.is_err(), "entropy failure must propagate as error");
}
#[test]
fn test_generated_key_converts_to_secret_key() {
let mut key_bytes = [0u8; 32];
key_bytes[31] = 0x01;
let key = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
let _sk = key.to_secret_key();
}
#[test]
fn test_from_bytes_accepts_valid_scalar() {
let mut bytes = [0u8; 32];
bytes[31] = 0x01;
let key = PrivateKey::from_bytes(bytes).expect("scalar 1 must be accepted");
assert_eq!(key.as_bytes(), &bytes);
}
#[test]
fn test_from_bytes_rejects_invalid_scalar() {
let zero = [0u8; 32];
assert!(
PrivateKey::from_bytes(zero).is_err(),
"invalid scalar must be rejected"
);
}
#[test]
fn test_from_hex_accepts_valid_scalar_one() {
let hex = "0000000000000000000000000000000000000000000000000000000000000001";
let key = PrivateKey::from_hex(hex).expect("scalar 1 must be accepted");
let mut expected = [0u8; 32];
expected[31] = 0x01;
assert_eq!(key.as_bytes(), &expected);
}
#[test]
fn test_to_hex_round_trips_from_hex() {
let hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
let key = PrivateKey::from_hex(hex).unwrap();
assert_eq!(key.to_hex().expose_str(), hex);
}
#[test]
fn test_to_hex_is_lowercase_and_64_bytes() {
let key = PrivateKey::from_hex(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140",
)
.unwrap();
let hex = key.to_hex();
assert_eq!(hex.expose_bytes().len(), 64);
assert_eq!(
hex.expose_str(),
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"
);
}
#[test]
fn test_from_hex_rejects_wrong_length() {
assert!(
PrivateKey::from_hex("01").is_err(),
"hex not exactly 64 chars must be rejected"
);
}
#[test]
fn test_from_hex_rejects_non_hex_characters() {
let hex = "zz00000000000000000000000000000000000000000000000000000000000001";
assert!(
PrivateKey::from_hex(hex).is_err(),
"non-hex characters must be rejected"
);
}
#[test]
fn test_from_hex_propagates_invalid_scalar() {
let hex = "0000000000000000000000000000000000000000000000000000000000000000";
assert!(
PrivateKey::from_hex(hex).is_err(),
"invalid scalar from from_bytes must propagate"
);
}
}