use blst::min_pk::{PublicKey, SecretKey, Signature};
use blst::BLST_ERROR;
use thiserror::Error;
const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
#[derive(Clone)]
pub struct BlsSecretKey {
inner: SecretKey,
}
impl BlsSecretKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlsError> {
if bytes.len() != 32 {
return Err(BlsError::InvalidKeyLength {
expected: 32,
actual: bytes.len(),
});
}
let inner = SecretKey::from_bytes(bytes).map_err(|e| BlsError::InvalidKey(format!("{:?}", e)))?;
Ok(Self { inner })
}
pub fn random() -> Self {
let mut ikm = [0u8; 32];
getrandom(&mut ikm);
let inner = SecretKey::key_gen(&ikm, &[]).expect("key generation should not fail");
Self { inner }
}
pub fn public_key(&self) -> BlsPublicKey {
BlsPublicKey {
inner: self.inner.sk_to_pk(),
}
}
pub fn sign(&self, message: &[u8]) -> BlsSignature {
let sig = self.inner.sign(message, DST, &[]);
BlsSignature { inner: sig }
}
pub fn to_bytes(&self) -> [u8; 32] {
self.inner.to_bytes()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlsPublicKey {
inner: PublicKey,
}
impl BlsPublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlsError> {
if bytes.len() != 48 {
return Err(BlsError::InvalidKeyLength {
expected: 48,
actual: bytes.len(),
});
}
let inner = PublicKey::from_bytes(bytes).map_err(|e| BlsError::InvalidKey(format!("{:?}", e)))?;
Ok(Self { inner })
}
pub fn to_bytes(&self) -> [u8; 48] {
self.inner.to_bytes()
}
pub fn to_hex(&self) -> String {
format!("0x{}", hex::encode(self.to_bytes()))
}
pub fn from_hex(s: &str) -> Result<Self, BlsError> {
let s = s.strip_prefix("0x").unwrap_or(s);
let bytes = hex::decode(s).map_err(|e| BlsError::InvalidHex(e.to_string()))?;
Self::from_bytes(&bytes)
}
pub fn verify(&self, message: &[u8], signature: &BlsSignature) -> bool {
let result = signature.inner.verify(true, message, DST, &[], &self.inner, true);
result == BLST_ERROR::BLST_SUCCESS
}
}
#[derive(Clone, Debug)]
pub struct BlsSignature {
inner: Signature,
}
impl BlsSignature {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlsError> {
if bytes.len() != 96 {
return Err(BlsError::InvalidSignatureLength {
expected: 96,
actual: bytes.len(),
});
}
let inner = Signature::from_bytes(bytes).map_err(|e| BlsError::InvalidSignature(format!("{:?}", e)))?;
Ok(Self { inner })
}
pub fn to_bytes(&self) -> [u8; 96] {
self.inner.to_bytes()
}
pub fn to_hex(&self) -> String {
format!("0x{}", hex::encode(self.to_bytes()))
}
}
#[derive(Clone)]
pub struct BlsKeypair {
pub secret: BlsSecretKey,
pub public: BlsPublicKey,
}
impl std::fmt::Debug for BlsKeypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlsKeypair")
.field("public", &self.public.to_hex())
.field("secret", &"[REDACTED]")
.finish()
}
}
impl BlsKeypair {
pub fn from_secret(secret: BlsSecretKey) -> Self {
let public = secret.public_key();
Self { secret, public }
}
pub fn random() -> Self {
Self::from_secret(BlsSecretKey::random())
}
pub fn generate() -> Self {
Self::random()
}
pub fn public_key_bytes(&self) -> [u8; 48] {
self.public.to_bytes()
}
pub fn sign(&self, message: &[u8]) -> BlsSignature {
self.secret.sign(message)
}
pub fn verify(&self, message: &[u8], signature: &BlsSignature) -> bool {
self.public.verify(message, signature)
}
}
#[derive(Debug, Error)]
pub enum BlsError {
#[error("Invalid key length: expected {expected}, got {actual}")]
InvalidKeyLength { expected: usize, actual: usize },
#[error("Invalid signature length: expected {expected}, got {actual}")]
InvalidSignatureLength { expected: usize, actual: usize },
#[error("Invalid key: {0}")]
InvalidKey(String),
#[error("Invalid signature: {0}")]
InvalidSignature(String),
#[error("Invalid hex: {0}")]
InvalidHex(String),
}
fn getrandom(dest: &mut [u8]) {
use std::io::Read;
#[cfg(unix)]
{
let mut file = std::fs::File::open("/dev/urandom").expect("Failed to open /dev/urandom");
file.read_exact(dest).expect("Failed to read random bytes");
}
#[cfg(windows)]
{
for byte in dest.iter_mut() {
*byte = (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.subsec_nanos() as u8)
.wrapping_add(*byte);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keypair_generation() {
let keypair = BlsKeypair::random();
let pubkey_bytes = keypair.public.to_bytes();
assert_eq!(pubkey_bytes.len(), 48);
}
#[test]
fn test_sign_and_verify() {
let keypair = BlsKeypair::random();
let message = b"test message";
let signature = keypair.sign(message);
assert!(keypair.verify(message, &signature));
assert!(!keypair.verify(b"wrong message", &signature));
}
#[test]
fn test_public_key_hex() {
let keypair = BlsKeypair::random();
let hex = keypair.public.to_hex();
assert!(hex.starts_with("0x"));
assert_eq!(hex.len(), 98);
let recovered = BlsPublicKey::from_hex(&hex).unwrap();
assert_eq!(recovered, keypair.public);
}
#[test]
fn test_signature_serialization() {
let keypair = BlsKeypair::random();
let message = b"test message";
let signature = keypair.sign(message);
let bytes = signature.to_bytes();
assert_eq!(bytes.len(), 96);
let recovered = BlsSignature::from_bytes(&bytes).unwrap();
assert!(keypair.verify(message, &recovered));
}
}