use crate::error::{Error, Result};
use core::fmt;
use nwep_sys as sys;
pub const PUBKEY_SIZE: usize = sys::NWEP_BLS_PUBKEY_SIZE;
pub const SIGNATURE_SIZE: usize = sys::NWEP_BLS_SIGNATURE_SIZE;
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct BlsSignature(pub [u8; SIGNATURE_SIZE]);
impl BlsSignature {
pub fn as_bytes(&self) -> &[u8; SIGNATURE_SIZE] {
&self.0
}
pub fn from_bytes(bytes: [u8; SIGNATURE_SIZE]) -> BlsSignature {
BlsSignature(bytes)
}
}
impl fmt::Debug for BlsSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BlsSignature(96 bytes)")
}
}
pub struct BlsKeypair {
secret: [u8; sys::NWEP_BLS_SECKEY_SIZE],
public: [u8; PUBKEY_SIZE],
}
impl BlsKeypair {
pub fn generate() -> Result<BlsKeypair> {
let mut secret = [0u8; sys::NWEP_BLS_SECKEY_SIZE];
let mut public = [0u8; PUBKEY_SIZE];
Error::check(unsafe { sys::nwep_bls_keygen(secret.as_mut_ptr(), public.as_mut_ptr()) })?;
Ok(BlsKeypair { secret, public })
}
pub fn sign(&self, msg: &[u8]) -> Result<BlsSignature> {
let mut sig = [0u8; SIGNATURE_SIZE];
Error::check(unsafe {
sys::nwep_bls_sign(
sig.as_mut_ptr(),
self.secret.as_ptr(),
msg.as_ptr(),
msg.len(),
)
})?;
Ok(BlsSignature(sig))
}
pub fn public_key(&self) -> &[u8; PUBKEY_SIZE] {
&self.public
}
pub(crate) fn secret(&self) -> &[u8; sys::NWEP_BLS_SECKEY_SIZE] {
&self.secret
}
}
impl Drop for BlsKeypair {
fn drop(&mut self) {
unsafe { sys::nwep_zeroize(self.secret.as_mut_ptr().cast(), self.secret.len()) };
}
}
impl fmt::Debug for BlsKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BlsKeypair").finish_non_exhaustive()
}
}
pub fn verify(sig: &BlsSignature, public: &[u8; PUBKEY_SIZE], msg: &[u8]) -> bool {
unsafe { sys::nwep_bls_verify(sig.0.as_ptr(), public.as_ptr(), msg.as_ptr(), msg.len()) == 0 }
}
pub fn aggregate(sigs: &[BlsSignature]) -> Result<BlsSignature> {
if sigs.is_empty() {
return Err(Error::ConfigInvalid);
}
let mut out = [0u8; SIGNATURE_SIZE];
Error::check(unsafe {
sys::nwep_bls_aggregate(out.as_mut_ptr(), sigs.as_ptr().cast::<u8>(), sigs.len())
})?;
Ok(BlsSignature(out))
}
pub fn verify_aggregate(agg: &BlsSignature, publics: &[[u8; PUBKEY_SIZE]], msg: &[u8]) -> bool {
if publics.is_empty() {
return false;
}
unsafe {
sys::nwep_bls_verify_aggregate(
agg.0.as_ptr(),
publics.as_ptr().cast::<u8>(),
publics.len(),
msg.as_ptr(),
msg.len(),
) == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_and_verify_round_trip() {
let kp = BlsKeypair::generate().unwrap();
let msg = b"epoch 7 merkle root";
let sig = kp.sign(msg).unwrap();
assert!(verify(&sig, kp.public_key(), msg));
assert!(!verify(&sig, kp.public_key(), b"epoch 8 merkle root"));
}
#[test]
fn threshold_aggregate_verifies() {
let signers: Vec<BlsKeypair> = (0..3).map(|_| BlsKeypair::generate().unwrap()).collect();
let msg = b"checkpoint quorum";
let sigs: Vec<BlsSignature> = signers.iter().map(|s| s.sign(msg).unwrap()).collect();
let agg = aggregate(&sigs).unwrap();
let pks: Vec<[u8; PUBKEY_SIZE]> = signers.iter().map(|s| *s.public_key()).collect();
assert!(verify_aggregate(&agg, &pks, msg));
assert!(!verify_aggregate(&agg, &pks, b"other root"));
}
#[test]
fn empty_inputs_are_rejected() {
assert!(aggregate(&[]).is_err());
let kp = BlsKeypair::generate().unwrap();
let sig = kp.sign(b"x").unwrap();
assert!(!verify_aggregate(&sig, &[], b"x"));
}
}