use crate::error::{Error, Result};
use crate::primitives::encoding::{from_base58_check, from_hex, to_base58_check, to_hex};
use crate::primitives::hash::sha256_hmac;
use crate::primitives::BigNumber;
use k256::ecdsa::signature::hazmat::PrehashSigner;
use k256::ecdsa::SigningKey;
use k256::elliptic_curve::rand_core::OsRng;
use k256::SecretKey;
use super::{PublicKey, Signature};
#[derive(Clone)]
pub struct PrivateKey {
inner: SecretKey,
}
impl PrivateKey {
pub fn random() -> Self {
let inner = SecretKey::random(&mut OsRng);
Self { inner }
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 32 {
return Err(Error::InvalidKeyLength {
expected: 32,
actual: bytes.len(),
});
}
let inner = SecretKey::from_slice(bytes)
.map_err(|e| Error::InvalidPrivateKey(format!("Invalid key bytes: {}", e)))?;
Ok(Self { inner })
}
pub fn from_hex(hex: &str) -> Result<Self> {
let bytes = from_hex(hex)?;
Self::from_bytes(&bytes)
}
pub fn from_wif(wif: &str) -> Result<Self> {
let (version, payload) = from_base58_check(wif)?;
if version.len() != 1 || (version[0] != 0x80 && version[0] != 0xef) {
return Err(Error::InvalidPrivateKey(format!(
"Invalid WIF version byte: expected 0x80 or 0xef, got {:02x}",
version.first().unwrap_or(&0)
)));
}
let key_bytes = if payload.len() == 33 && payload[32] == 0x01 {
&payload[..32]
} else if payload.len() == 32 {
&payload[..]
} else {
return Err(Error::InvalidPrivateKey(format!(
"Invalid WIF payload length: expected 32 or 33 bytes, got {}",
payload.len()
)));
};
Self::from_bytes(key_bytes)
}
pub fn public_key(&self) -> PublicKey {
let verifying_key = self.inner.public_key();
PublicKey::from_k256(verifying_key)
}
pub fn sign(&self, msg_hash: &[u8; 32]) -> Result<Signature> {
let signing_key = SigningKey::from(&self.inner);
let (sig, _recovery_id): (k256::ecdsa::Signature, _) =
signing_key
.sign_prehash(msg_hash)
.map_err(|e| Error::CryptoError(format!("Signing failed: {}", e)))?;
let r_bytes = sig.r().to_bytes();
let s_bytes = sig.s().to_bytes();
let mut r = [0u8; 32];
let mut s = [0u8; 32];
r.copy_from_slice(&r_bytes);
s.copy_from_slice(&s_bytes);
let signature = Signature::new(r, s);
Ok(signature.to_low_s())
}
pub fn to_bytes(&self) -> [u8; 32] {
let bytes = self.inner.to_bytes();
let mut result = [0u8; 32];
result.copy_from_slice(&bytes);
result
}
pub fn to_hex(&self) -> String {
to_hex(&self.to_bytes())
}
pub fn to_wif(&self) -> String {
self.to_wif_with_prefix(0x80)
}
pub fn to_wif_with_prefix(&self, prefix: u8) -> String {
let mut payload = Vec::with_capacity(33);
payload.extend_from_slice(&self.to_bytes());
payload.push(0x01);
to_base58_check(&payload, &[prefix])
}
pub fn derive_shared_secret(&self, other_pubkey: &PublicKey) -> Result<PublicKey> {
other_pubkey.mul_scalar(&self.to_bytes())
}
pub fn derive_child(
&self,
other_pubkey: &PublicKey,
invoice_number: &str,
) -> Result<PrivateKey> {
let shared_secret = self.derive_shared_secret(other_pubkey)?;
let hmac = sha256_hmac(&shared_secret.to_compressed(), invoice_number.as_bytes());
let self_scalar = BigNumber::from_bytes_be(&self.to_bytes());
let hmac_scalar = BigNumber::from_bytes_be(&hmac);
let order = BigNumber::secp256k1_order();
let new_scalar = self_scalar.add(&hmac_scalar).modulo(&order);
if new_scalar.is_zero() {
return Err(Error::CryptoError("Derived key would be zero".to_string()));
}
let new_bytes = new_scalar.to_bytes_be(32);
PrivateKey::from_bytes(&new_bytes)
}
}
impl Drop for PrivateKey {
fn drop(&mut self) {
}
}
impl std::fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrivateKey")
.field("public_key", &self.public_key().to_hex())
.finish_non_exhaustive()
}
}
impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
use subtle::ConstantTimeEq;
self.to_bytes().ct_eq(&other.to_bytes()).into()
}
}
impl Eq for PrivateKey {}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::hash::sha256;
#[test]
fn test_private_key_random() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
assert_ne!(key1.to_hex(), key2.to_hex());
assert_eq!(key1.to_bytes().len(), 32);
}
#[test]
fn test_private_key_from_bytes() {
let mut bytes = [0u8; 32];
bytes[31] = 1;
let key = PrivateKey::from_bytes(&bytes).unwrap();
assert_eq!(key.to_bytes(), bytes);
}
#[test]
fn test_private_key_from_hex() {
let hex = "0000000000000000000000000000000000000000000000000000000000000001";
let key = PrivateKey::from_hex(hex).unwrap();
let mut expected = [0u8; 32];
expected[31] = 1;
assert_eq!(key.to_bytes(), expected);
}
#[test]
fn test_private_key_invalid_length() {
let short = [0u8; 31];
assert!(PrivateKey::from_bytes(&short).is_err());
let long = [0u8; 33];
assert!(PrivateKey::from_bytes(&long).is_err());
}
#[test]
fn test_private_key_zero_is_invalid() {
let zero = [0u8; 32];
assert!(PrivateKey::from_bytes(&zero).is_err());
}
#[test]
fn test_private_key_wif_roundtrip() {
let wif = "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn";
let key = PrivateKey::from_wif(wif).unwrap();
assert_eq!(key.to_wif(), wif);
}
#[test]
fn test_private_key_wif_uncompressed() {
let wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
let key = PrivateKey::from_wif(wif).unwrap();
assert_eq!(key.to_bytes().len(), 32);
}
#[test]
fn test_private_key_to_public_key() {
let hex = "0000000000000000000000000000000000000000000000000000000000000001";
let key = PrivateKey::from_hex(hex).unwrap();
let pubkey = key.public_key();
assert_eq!(
pubkey.to_hex(),
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
);
}
#[test]
fn test_sign_and_verify() {
let key = PrivateKey::random();
let pubkey = key.public_key();
let msg_hash = sha256(b"Hello, BSV!");
let signature = key.sign(&msg_hash).unwrap();
assert!(signature.is_low_s());
assert!(pubkey.verify(&msg_hash, &signature));
}
#[test]
fn test_sign_deterministic() {
let key = PrivateKey::from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
let msg_hash = sha256(b"test");
let sig1 = key.sign(&msg_hash).unwrap();
let sig2 = key.sign(&msg_hash).unwrap();
assert_eq!(sig1.r(), sig2.r());
assert_eq!(sig1.s(), sig2.s());
}
#[test]
fn test_ecdh_shared_secret() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let alice_shared = alice.derive_shared_secret(&bob.public_key()).unwrap();
let bob_shared = bob.derive_shared_secret(&alice.public_key()).unwrap();
assert_eq!(alice_shared.to_compressed(), bob_shared.to_compressed());
}
#[test]
fn test_derive_child_consistency() {
let alice_priv = PrivateKey::random();
let bob_priv = PrivateKey::random();
let invoice = "test-invoice-12345";
let bob_child_priv = bob_priv
.derive_child(&alice_priv.public_key(), invoice)
.unwrap();
let bob_child_pub_from_alice = bob_priv
.public_key()
.derive_child(&alice_priv, invoice)
.unwrap();
assert_eq!(
bob_child_priv.public_key().to_compressed(),
bob_child_pub_from_alice.to_compressed()
);
}
#[test]
fn test_private_key_equality() {
let bytes = [1u8; 32];
let key1 = PrivateKey::from_bytes(&bytes).unwrap();
let key2 = PrivateKey::from_bytes(&bytes).unwrap();
assert_eq!(key1, key2);
}
#[test]
fn test_from_wif_empty_string() {
assert!(PrivateKey::from_wif("").is_err());
}
#[test]
fn test_from_wif_not_a_wif() {
assert!(PrivateKey::from_wif("not_a_wif").is_err());
}
#[test]
fn test_from_wif_bad_checksum() {
assert!(
PrivateKey::from_wif("L401GXuUSHauk19f9Cfpm1qfSXZuGLBUAC2VZM6vdmfMxRxAYkWq").is_err()
);
}
#[test]
fn test_from_wif_truncated() {
assert!(
PrivateKey::from_wif("L4o1GXuUSHauk19f9Cfpm1qfSXZuGLBUAC2VZM6vdmfMxRxAYkW").is_err()
);
}
#[test]
fn test_from_wif_too_long() {
assert!(PrivateKey::from_wif("L4o1GXuUSHauk19f9Cfpm1qfSXZuGLBUAC2VZM6vdmfMxRxAYkWqL4o1GXuUSHauk19f9Cfpm1qfSXZuGLBUAC2VZM6vdmfMxRxAYkWq").is_err());
}
#[test]
fn test_wif_testnet() {
let key = PrivateKey::random();
let wif = key.to_wif_with_prefix(0xef);
assert!(wif.starts_with('c'));
let recovered = PrivateKey::from_wif(&wif).unwrap();
assert_eq!(key.to_bytes(), recovered.to_bytes());
}
}