use std::marker::PhantomData;
use zeroize::Zeroizing;
use crate::error::Result;
use crate::scheme::KeyScheme;
pub struct SignerHandle<K: KeyScheme> {
secret: Zeroizing<Vec<u8>>,
public: K::PublicKey,
_marker: PhantomData<fn() -> K>,
}
impl<K: KeyScheme> SignerHandle<K> {
pub(crate) fn from_parts(secret: Zeroizing<Vec<u8>>, public: K::PublicKey) -> Self {
Self {
secret,
public,
_marker: PhantomData,
}
}
pub fn public_key(&self) -> &K::PublicKey {
&self.public
}
pub fn sign(&self, msg: &[u8]) -> K::Signature {
K::sign(&self.secret, msg).expect("signer handle secret length is guaranteed valid")
}
pub fn try_sign(&self, msg: &[u8]) -> Result<K::Signature> {
K::sign(&self.secret, msg)
}
pub fn expose_secret(&self) -> &[u8] {
&self.secret
}
}
impl<K: KeyScheme> Clone for SignerHandle<K> {
fn clone(&self) -> Self {
Self {
secret: self.secret.clone(),
public: self.public.clone(),
_marker: PhantomData,
}
}
}
impl<K: KeyScheme> std::fmt::Debug for SignerHandle<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SignerHandle")
.field("scheme", &K::NAME)
.field("public", &self.public)
.field(
"secret",
&format_args!("<{} bytes zeroized>", self.secret.len()),
)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scheme::BlsSigning;
#[test]
fn debug_does_not_leak_secret() {
let secret = Zeroizing::new(vec![0xAAu8; 32]);
let public = BlsSigning::public_key(&secret).unwrap();
let handle: SignerHandle<BlsSigning> = SignerHandle::from_parts(secret, public);
let s = format!("{:?}", handle);
assert!(s.contains("<32 bytes zeroized>"));
assert!(!s.contains("AA"));
}
#[test]
fn sign_works() {
let secret = Zeroizing::new(vec![0x11u8; 32]);
let public = BlsSigning::public_key(&secret).unwrap();
let handle: SignerHandle<BlsSigning> = SignerHandle::from_parts(secret, public);
let sig = handle.sign(b"message");
assert!(chia_bls::verify(&sig, &public, b"message"));
}
#[test]
fn try_sign_succeeds_and_verifies() {
let secret = Zeroizing::new(vec![0x33u8; 32]);
let public = BlsSigning::public_key(&secret).unwrap();
let handle: SignerHandle<BlsSigning> = SignerHandle::from_parts(secret, public);
let sig = handle
.try_sign(b"payload")
.expect("try_sign should succeed");
assert!(chia_bls::verify(&sig, &public, b"payload"));
assert_eq!(sig.to_bytes(), handle.sign(b"payload").to_bytes());
}
#[test]
fn clone_preserves_equality() {
let secret = Zeroizing::new(vec![0x11u8; 32]);
let public = BlsSigning::public_key(&secret).unwrap();
let h1: SignerHandle<BlsSigning> = SignerHandle::from_parts(secret, public);
let h2 = h1.clone();
let s1 = h1.sign(b"x");
let s2 = h2.sign(b"x");
assert_eq!(s1.to_bytes(), s2.to_bytes());
}
#[test]
fn expose_secret_returns_original_bytes() {
let bytes = [0x77u8; 32];
let secret = Zeroizing::new(bytes.to_vec());
let public = BlsSigning::public_key(&secret).unwrap();
let handle: SignerHandle<BlsSigning> = SignerHandle::from_parts(secret, public);
assert_eq!(handle.expose_secret(), &bytes);
}
#[test]
fn expose_secret_borrow_scoped_to_handle() {
let secret = Zeroizing::new(vec![0x22u8; 32]);
let public = BlsSigning::public_key(&secret).unwrap();
let handle: SignerHandle<BlsSigning> = SignerHandle::from_parts(secret, public);
let borrowed: &[u8] = handle.expose_secret();
assert_eq!(borrowed.len(), 32);
drop(handle);
}
}