use std::{fmt, sync::LazyLock};
use ic_bls12_381::{
G1Affine, G1Projective, G2Affine, G2Prepared, Gt, Scalar,
hash_to_curve::{ExpandMsgXmd, HashToCurve},
multi_miller_loop,
};
mod sha256;
const CIPHERSUITE: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
static NEGATIVE_GENERATOR: LazyLock<G2Prepared> =
LazyLock::new(|| G2Prepared::from(-G2Affine::generator()));
#[derive(Clone, Eq, PartialEq)]
pub struct PublicKey(G2Affine);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum InvalidPublicKey {
WrongLength,
InvalidPoint,
}
impl PublicKey {
pub const BYTES: usize = 96;
pub fn deserialize(bytes: &[u8]) -> Result<Self, InvalidPublicKey> {
let bytes = bytes
.try_into()
.map_err(|_| InvalidPublicKey::WrongLength)?;
Option::from(G2Affine::from_compressed(bytes))
.map(Self)
.ok_or(InvalidPublicKey::InvalidPoint)
}
pub fn serialize(&self) -> [u8; Self::BYTES] {
self.0.to_compressed()
}
pub fn prepare(&self) -> PreparedPublicKey {
PreparedPublicKey {
point: G2Prepared::from(self.0),
identity: bool::from(self.0.is_identity()),
}
}
#[expect(
clippy::result_unit_err,
reason = "The public BLS contract specifies opaque verification failure"
)]
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), ()> {
if bool::from(self.0.is_identity()) {
return Err(());
}
self.prepare().verify(message, signature)
}
}
impl fmt::Debug for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
encoded_debug(f, "PublicKey", &self.serialize())
}
}
pub struct PreparedPublicKey {
point: G2Prepared,
identity: bool,
}
impl PreparedPublicKey {
#[expect(
clippy::result_unit_err,
reason = "The public BLS contract specifies opaque verification failure"
)]
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), ()> {
if self.identity {
return Err(());
}
let message_point = G1Affine::from(hash_message(message));
let product = multi_miller_loop(&[
(&message_point, &self.point),
(&signature.0, &NEGATIVE_GENERATOR),
])
.final_exponentiation();
if product == Gt::identity() {
Ok(())
} else {
Err(())
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Signature(G1Affine);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum InvalidSignature {
WrongLength,
InvalidPoint,
}
impl Signature {
pub const BYTES: usize = 48;
pub fn deserialize(bytes: &[u8]) -> Result<Self, InvalidSignature> {
let bytes = bytes
.try_into()
.map_err(|_| InvalidSignature::WrongLength)?;
Option::from(G1Affine::from_compressed(bytes))
.map(Self)
.ok_or(InvalidSignature::InvalidPoint)
}
pub fn serialize(&self) -> [u8; Self::BYTES] {
self.0.to_compressed()
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
encoded_debug(f, "Signature", &self.serialize())
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct PrivateKey(Scalar);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum InvalidPrivateKey {
WrongLength,
OutOfRange,
}
impl PrivateKey {
pub const BYTES: usize = 32;
pub fn deserialize(bytes: &[u8]) -> Result<Self, InvalidPrivateKey> {
let mut bytes: [u8; Self::BYTES] = bytes
.try_into()
.map_err(|_| InvalidPrivateKey::WrongLength)?;
bytes.reverse();
Option::from(Scalar::from_bytes(&bytes))
.map(Self)
.ok_or(InvalidPrivateKey::OutOfRange)
}
pub fn serialize(&self) -> [u8; Self::BYTES] {
let mut bytes = self.0.to_bytes();
bytes.reverse();
bytes
}
pub fn public_key(&self) -> PublicKey {
PublicKey(G2Affine::from(G2Affine::generator() * self.0))
}
pub fn sign(&self, message: &[u8]) -> Signature {
Signature(G1Affine::from(hash_message(message) * self.0))
}
}
impl fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("PrivateKey(REDACTED)")
}
}
#[expect(
clippy::result_unit_err,
reason = "The public BLS contract specifies opaque verification failure"
)]
pub fn verify_bls_signature(signature: &[u8], message: &[u8], public_key: &[u8]) -> Result<(), ()> {
let signature = Signature::deserialize(signature).map_err(|_| ())?;
let key = PublicKey::deserialize(public_key).map_err(|_| ())?;
key.verify(message, &signature)
}
fn hash_message(message: &[u8]) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha256::Sha256>>>::hash_to_curve(message, CIPHERSUITE)
}
fn encoded_debug(f: &mut fmt::Formatter<'_>, name: &str, bytes: &[u8]) -> fmt::Result {
write!(f, "{name}(")?;
for byte in bytes {
write!(f, "{byte:02x}")?;
}
f.write_str(")")
}