use std::fmt::Debug;
use super::sig::Signature;
use crate::signing::{
curve::{Point, SCALAR_BYTES, Scalar},
field::Fp5,
};
const PUBLIC_KEY_BYTES: usize = 40;
#[derive(Clone)]
pub struct PrivateKey(Scalar);
impl Debug for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("PrivateKey(<redacted>)")
}
}
impl PrivateKey {
#[inline]
#[must_use]
pub fn from_scalar(s: Scalar) -> Self {
Self(s)
}
#[inline]
#[must_use]
pub fn from_le_bytes_reduce(bytes: [u8; SCALAR_BYTES]) -> Self {
Self(Scalar::from_le_bytes_reduce(bytes))
}
#[inline]
#[must_use]
pub fn as_scalar(&self) -> Scalar {
self.0
}
#[inline]
#[must_use]
pub fn to_le_bytes(&self) -> [u8; SCALAR_BYTES] {
self.0.to_le_bytes()
}
#[must_use]
pub fn public_key(&self) -> PublicKey {
PublicKey(Point::mulgen_ct(self.0).encode())
}
#[inline]
#[must_use]
pub fn sign(&self, hashed_msg: Fp5, k: Scalar) -> Signature {
super::sig::sign(self.0, hashed_msg, k)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PublicKey(Fp5);
impl PublicKey {
#[inline]
#[must_use]
pub fn from_fp5(w: Fp5) -> Self {
Self(w)
}
#[inline]
#[must_use]
pub fn as_fp5(&self) -> Fp5 {
self.0
}
#[inline]
#[must_use]
pub fn try_from_le_bytes(bytes: [u8; PUBLIC_KEY_BYTES]) -> Option<Self> {
Fp5::try_from_le_bytes(bytes).map(Self)
}
#[inline]
#[must_use]
pub fn to_le_bytes(&self) -> [u8; PUBLIC_KEY_BYTES] {
self.0.to_le_bytes()
}
#[inline]
#[must_use]
pub fn verify(&self, hashed_msg: Fp5, sig: &Signature) -> bool {
super::sig::verify(self.0, hashed_msg, sig)
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::signing::field::MODULUS;
#[rstest]
fn private_key_debug_redacts_secret_limbs() {
let secret_pattern = [0xABu8; SCALAR_BYTES];
let sk = PrivateKey::from_le_bytes_reduce(secret_pattern);
let formatted = format!("{sk:?}");
assert_eq!(formatted, "PrivateKey(<redacted>)");
assert!(
!formatted.contains("ab") && !formatted.contains("AB"),
"Debug must not leak secret bytes, was {formatted}",
);
}
#[rstest]
fn try_from_le_bytes_accepts_canonical_pubkey() {
let pk_bytes = PrivateKey::from_le_bytes_reduce([0x42; SCALAR_BYTES])
.public_key()
.to_le_bytes();
let parsed = PublicKey::try_from_le_bytes(pk_bytes)
.expect("canonical pk bytes must round trip through try_from_le_bytes");
assert_eq!(parsed.to_le_bytes(), pk_bytes);
}
#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(3)]
#[case(4)]
fn try_from_le_bytes_rejects_non_canonical_limb(#[case] limb_index: usize) {
let mut bytes = [0u8; PUBLIC_KEY_BYTES];
bytes[limb_index * 8..(limb_index + 1) * 8].copy_from_slice(&MODULUS.to_le_bytes());
assert!(
PublicKey::try_from_le_bytes(bytes).is_none(),
"limb {limb_index} == MODULUS must be rejected",
);
bytes[limb_index * 8..(limb_index + 1) * 8].copy_from_slice(&u64::MAX.to_le_bytes());
assert!(
PublicKey::try_from_le_bytes(bytes).is_none(),
"limb {limb_index} == u64::MAX must be rejected",
);
}
}