use crate::{Domain, Error, Keys, PublicKey, Signature};
use curve25519_dalek::{EdwardsPoint, Scalar};
use subtle::ConstantTimeEq;
use zeroize::Zeroize;
#[derive(Clone, Eq, Hash)]
pub struct SecretKey(pub(crate) Scalar);
impl core::fmt::LowerHex for SecretKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for x in self.0.to_bytes() {
write!(f, "{:02x}", x)?;
}
Ok(())
}
}
impl core::fmt::Debug for SecretKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SecretKey {:x}", *self)
}
}
impl PartialEq for SecretKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl ConstantTimeEq for SecretKey {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.0.ct_eq(&other.0)
}
}
impl Drop for SecretKey {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl From<Scalar> for SecretKey {
fn from(value: Scalar) -> Self {
SecretKey(value)
}
}
impl SecretKey {
pub fn parse_bits(bits: &[u8; 32]) -> Result<SecretKey, Error> {
let sk = SecretKey::from_bits(bits);
if sk.to_bits() == *bits {
Ok(sk)
} else {
Err(Error::InvalidSecretKey)
}
}
pub fn to_bits(&self) -> [u8; 32] {
self.0.to_bytes()
}
pub fn from_bits(value: &[u8; 32]) -> SecretKey {
SecretKey(Scalar::from_bytes_mod_order(*value))
}
pub fn sign_domain(&self, domain: Domain, msg: &[u8]) -> Signature {
let nonce = self.derive_nonce(domain, msg);
self.sign_nonce(domain, &nonce, msg)
}
pub fn sign(&self, msg: &[u8]) -> Signature {
self.sign_domain(Domain::default(), msg)
}
pub fn sign_nonce(&self, domain: Domain, nonce: &[u8; 32], msg: &[u8]) -> Signature {
let k = Scalar::from_bytes_mod_order(*nonce);
let r = EdwardsPoint::mul_base(&k);
let e = domain.hash_to_scalar(&r, msg);
let s = k - (e * self.0);
Signature { e, s }
}
pub fn derive_public(&self) -> PublicKey {
let point = EdwardsPoint::mul_base(&self.0);
let compressed = point.compress();
PublicKey { compressed, point }
}
pub fn derive_keys(&self) -> Keys {
let pk = self.derive_public();
let sk = SecretKey(self.0);
Keys { pk, sk }
}
pub(crate) fn derive_nonce(&self, domain: Domain, msg: &[u8]) -> [u8; 32] {
domain.hash(&self.0.as_bytes(), msg)
}
}