use std::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::utils::hex;
use crate::utils::sign::curve::Point;
use crate::utils::sign::fp5::Fp5;
use crate::utils::sign::goldilocks::Fp;
use crate::utils::sign::poseidon2::hash_to_fp5;
use crate::utils::sign::scalar::Scalar;
#[derive(Clone, PartialEq, Eq)]
pub struct SecretKey(Scalar);
impl Drop for SecretKey {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl ZeroizeOnDrop for SecretKey {}
impl fmt::Debug for SecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SecretKey(..)")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PublicKey([u8; 40]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Signature([u8; 80]);
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadKey {
#[error("a key is 40 bytes, 80 hex digits")]
Length,
#[error("a public key's limbs must each be below the field's modulus")]
NotCanonical,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("no randomness to sign with: {0}")]
pub struct NoRandomness(pub String);
impl SecretKey {
#[must_use]
pub fn from_le_bytes(bytes: &[u8; 40]) -> SecretKey {
SecretKey(Scalar::from_le_bytes(bytes))
}
pub fn from_hex(text: &str) -> Result<SecretKey, BadKey> {
let mut bytes = zeroize::Zeroizing::new([0u8; 40]);
hex::decode_into(text, bytes.as_mut_slice()).map_err(|_| BadKey::Length)?;
Ok(SecretKey::from_le_bytes(&bytes))
}
#[must_use]
pub const fn from_scalar(scalar: Scalar) -> SecretKey {
SecretKey(scalar)
}
#[must_use]
pub fn to_le_bytes(&self) -> zeroize::Zeroizing<[u8; 40]> {
zeroize::Zeroizing::new(self.0.to_le_bytes())
}
#[must_use]
pub fn to_hex(&self) -> zeroize::Zeroizing<String> {
zeroize::Zeroizing::new(hex::encode_prefixed(self.to_le_bytes().as_slice()))
}
#[must_use]
pub fn public_key(&self) -> PublicKey {
PublicKey(Point::GENERATOR.mul(self.0).encode().to_le_bytes())
}
#[must_use]
pub fn sign_with(&self, hashed: Fp5, k: Scalar) -> Signature {
let r = Point::GENERATOR.mul(k).encode();
let e = challenge(r, hashed);
let s = k.sub(e.mul(self.0));
let mut bytes = [0u8; 80];
bytes[..40].copy_from_slice(&s.to_le_bytes());
bytes[40..].copy_from_slice(&e.to_le_bytes());
Signature(bytes)
}
pub fn sign_drawing<E>(
&self,
hashed: Fp5,
draw: impl FnMut(&mut [u8; 40]) -> Result<(), E>,
) -> Result<Signature, E> {
let mut k = sample_scalar(draw)?;
let signature = self.sign_with(hashed, k);
k.zeroize();
Ok(signature)
}
#[cfg(feature = "sign")]
pub fn sign(&self, hashed: Fp5) -> Result<Signature, NoRandomness> {
self.sign_drawing(hashed, os_draw)
}
#[cfg(feature = "sign")]
pub fn generate() -> Result<SecretKey, NoRandomness> {
SecretKey::generate_drawing(os_draw)
}
pub fn generate_drawing<E>(draw: impl FnMut(&mut [u8; 40]) -> Result<(), E>) -> Result<SecretKey, E> {
sample_scalar(draw).map(SecretKey)
}
#[must_use]
pub fn verifies(&self, hashed: Fp5, signature: &Signature) -> bool {
let Some((s, e)) = signature.halves() else { return false };
let public = Point::GENERATOR.mul(self.0);
let r = Point::GENERATOR.mul(s).add(public.mul(e)).encode();
challenge(r, hashed) == e
}
}
fn challenge(r: Fp5, hashed: Fp5) -> Scalar {
let mut preimage = [Fp::ZERO; 10];
preimage[..5].copy_from_slice(&r.0);
preimage[5..].copy_from_slice(&hashed.0);
Scalar::from_fp5(hash_to_fp5(&preimage))
}
pub fn sample_scalar<E>(mut draw: impl FnMut(&mut [u8; 40]) -> Result<(), E>) -> Result<Scalar, E> {
loop {
let mut bytes = zeroize::Zeroizing::new([0u8; 40]);
draw(&mut bytes)?;
if let Some(scalar) = Scalar::from_rand_draw(&bytes) {
return Ok(scalar);
}
}
}
#[cfg(feature = "sign")]
pub fn os_draw(bytes: &mut [u8; 40]) -> Result<(), NoRandomness> {
getrandom02::getrandom(bytes).map_err(|error| NoRandomness(error.to_string()))
}
impl PublicKey {
#[must_use]
pub const fn bytes(&self) -> &[u8; 40] {
&self.0
}
pub fn from_bytes(bytes: [u8; 40]) -> Result<PublicKey, BadKey> {
match Fp5::from_le_bytes(&bytes) {
Some(_) => Ok(PublicKey(bytes)),
None => Err(BadKey::NotCanonical),
}
}
pub fn from_hex(text: &str) -> Result<PublicKey, BadKey> {
let bytes = hex::decode_array::<40>(text).map_err(|_| BadKey::Length)?;
PublicKey::from_bytes(bytes)
}
#[must_use]
pub fn limbs(&self) -> Fp5 {
Fp5::from_le_bytes(&self.0).unwrap_or(Fp5::ZERO)
}
#[must_use]
pub fn to_hex(&self) -> String {
hex::encode_prefixed(&self.0)
}
#[must_use]
pub fn verifies(&self, hashed: Fp5, signature: &Signature) -> bool {
let Some((s, e)) = signature.halves() else { return false };
let Some(public) = Point::decode(self.limbs()) else { return false };
let r = Point::GENERATOR.mul(s).add(public.mul(e)).encode();
challenge(r, hashed) == e
}
#[must_use]
pub fn is(&self, listed: &str) -> bool {
PublicKey::from_hex(listed).is_ok_and(|key| key == *self)
}
}
impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
impl Signature {
#[must_use]
pub const fn from_bytes(bytes: [u8; 80]) -> Signature {
Signature(bytes)
}
#[must_use]
pub const fn bytes(&self) -> &[u8; 80] {
&self.0
}
#[must_use]
pub fn halves(&self) -> Option<(Scalar, Scalar)> {
let mut s = [0u8; 40];
let mut e = [0u8; 40];
s.copy_from_slice(&self.0[..40]);
e.copy_from_slice(&self.0[40..]);
Some((Scalar::from_canonical_le_bytes(&s)?, Scalar::from_canonical_le_bytes(&e)?))
}
#[must_use]
pub fn to_hex(&self) -> String {
hex::encode(&self.0)
}
}
#[cfg(test)]
#[path = "tests/schnorr.rs"]
mod tests;