use ark_bls12_381::{G1Affine, G2Affine, G2Projective, g2::Config as G2Config};
use ark_ec::{
AffineRepr, CurveGroup,
hashing::{HashToCurve, curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher},
};
use ark_ff::{Zero, field_hashers::DefaultFieldHasher};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use sha2::Sha256;
const BLS_SIG_DOMAIN: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
const BLS_G1_MULTICODEC: u64 = 0xea;
#[derive(Debug, thiserror::Error)]
pub enum BlsError {
#[error("hash-to-curve failed")]
HashToCurve,
#[error("serialization failed")]
Serialize,
#[error("deserialization failed")]
Deserialize,
#[error("invalid signature")]
InvalidSignature,
#[error("invalid secret key")]
InvalidSecretKey,
#[error("invalid public key")]
InvalidPublicKey,
}
pub fn hash_to_g2(msg: &[u8]) -> Result<G2Affine, BlsError> {
type G2Hasher =
MapToCurveBasedHasher<G2Projective, DefaultFieldHasher<Sha256>, WBMap<G2Config>>;
let hasher = G2Hasher::new(BLS_SIG_DOMAIN).map_err(|_| BlsError::HashToCurve)?;
let point: G2Affine = hasher.hash(msg).map_err(|_| BlsError::HashToCurve)?;
if point.is_zero() {
return Err(BlsError::HashToCurve);
}
Ok(point)
}
pub fn sign(secret_key: &ark_bls12_381::Fr, msg: &[u8]) -> Result<Vec<u8>, BlsError> {
if secret_key.is_zero() {
return Err(BlsError::InvalidSecretKey);
}
let h_msg = hash_to_g2(msg)?;
let sig: G2Affine = (G2Projective::from(h_msg) * secret_key).into_affine();
let mut bytes = Vec::with_capacity(96);
sig.serialize_compressed(&mut bytes)
.map_err(|_| BlsError::Serialize)?;
Ok(bytes)
}
pub fn verify(pubkey: &G1Affine, msg: &[u8], sig_bytes: &[u8]) -> Result<(), BlsError> {
if pubkey.is_zero() {
return Err(BlsError::InvalidSignature);
}
let mut encoded_key = [0u8; 48];
pubkey
.serialize_compressed(encoded_key.as_mut_slice())
.map_err(|_| BlsError::Serialize)?;
verify_compressed(&encoded_key, msg, sig_bytes).map(|_| ())
}
pub fn verify_and_identify(
pubkey: &[u8],
msg: &[u8],
signature: &[u8],
) -> Result<String, BlsError> {
let key = verify_compressed(pubkey, msg, signature)?;
Ok(did_from_encoded_key(&key.to_bytes()))
}
fn verify_compressed(
pubkey: &[u8],
msg: &[u8],
sig_bytes: &[u8],
) -> Result<blst::min_pk::PublicKey, BlsError> {
if pubkey.len() != 48 || sig_bytes.len() != 96 {
return Err(BlsError::Deserialize);
}
let key = blst::min_pk::PublicKey::from_bytes(pubkey).map_err(|_| BlsError::Deserialize)?;
let signature =
blst::min_pk::Signature::from_bytes(sig_bytes).map_err(|_| BlsError::Deserialize)?;
if signature.verify(true, msg, BLS_SIG_DOMAIN, &[], &key, true)
!= blst::BLST_ERROR::BLST_SUCCESS
{
return Err(BlsError::InvalidSignature);
}
Ok(key)
}
pub fn deserialize_pubkey(bytes: &[u8]) -> Result<G1Affine, BlsError> {
let pk = G1Affine::deserialize_compressed(bytes).map_err(|_| BlsError::Deserialize)?;
if pk.is_zero() {
return Err(BlsError::InvalidPublicKey);
}
Ok(pk)
}
pub fn did_from_bls_pubkey(pubkey: &G1Affine) -> Result<String, BlsError> {
if pubkey.is_zero() {
return Err(BlsError::InvalidPublicKey);
}
let mut pubkey_bytes = [0u8; 48];
pubkey
.serialize_compressed(pubkey_bytes.as_mut_slice())
.map_err(|_| BlsError::Serialize)?;
Ok(did_from_encoded_key(&pubkey_bytes))
}
pub fn did_from_compressed_pubkey(bytes: &[u8]) -> Result<String, BlsError> {
if bytes.len() != 48 {
return Err(BlsError::Deserialize);
}
let key = blst::min_pk::PublicKey::from_bytes(bytes).map_err(|_| BlsError::Deserialize)?;
key.validate().map_err(|_| BlsError::InvalidPublicKey)?;
Ok(did_from_encoded_key(&key.to_bytes()))
}
fn did_from_encoded_key(pubkey: &[u8; 48]) -> String {
let mut varint_buf = [0u8; 10];
let varint = unsigned_varint::encode::u64(BLS_G1_MULTICODEC, &mut varint_buf);
let mut codec_bytes = Vec::with_capacity(varint.len() + pubkey.len());
codec_bytes.extend_from_slice(varint);
codec_bytes.extend_from_slice(pubkey);
let encoded = multibase::encode(multibase::Base::Base58Btc, &codec_bytes);
format!("did:key:{encoded}")
}
#[cfg(test)]
mod tests {
use ark_bls12_381::{Fr, G1Affine, G1Projective, G2Affine};
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::UniformRand;
use ark_serialize::CanonicalSerialize;
use ark_std::test_rng;
use super::*;
fn generate_keypair() -> (Fr, G1Affine) {
let mut rng = test_rng();
let sk = Fr::rand(&mut rng);
let pk = (G1Projective::from(G1Affine::generator()) * sk).into_affine();
(sk, pk)
}
#[test]
fn sign_and_verify_roundtrip() {
let (sk, pk) = generate_keypair();
let msg = b"test message";
let sig = sign(&sk, msg).expect("sign");
verify(&pk, msg, &sig).expect("verify");
}
#[test]
fn verify_rejects_non_subgroup_public_key_and_trailing_signature_bytes() {
let (sk, pk) = generate_keypair();
let mut signature = sign(&sk, b"message").unwrap();
let torsion =
G1Affine::new_unchecked(ark_bls12_381::Fq::from(0), ark_bls12_381::Fq::from(2));
assert!(torsion.is_on_curve());
assert!(!torsion.is_in_correct_subgroup_assuming_on_curve());
assert!(verify(&torsion, b"message", &signature).is_err());
signature.push(0);
assert!(verify(&pk, b"message", &signature).is_err());
}
#[test]
fn compressed_verification_preserves_identity_and_rejects_invalid_inputs() {
let (sk, pk) = generate_keypair();
let mut encoded = Vec::new();
pk.serialize_compressed(&mut encoded).unwrap();
let signature = sign(&sk, b"message").unwrap();
assert_eq!(
verify_and_identify(&encoded, b"message", &signature).unwrap(),
did_from_bls_pubkey(&pk).unwrap()
);
assert!(verify_and_identify(&encoded, b"other", &signature).is_err());
assert!(verify_and_identify(&encoded[..47], b"message", &signature).is_err());
let torsion =
G1Affine::new_unchecked(ark_bls12_381::Fq::from(0), ark_bls12_381::Fq::from(2));
for invalid in [G1Affine::zero(), torsion] {
let mut encoded = Vec::new();
invalid.serialize_compressed(&mut encoded).unwrap();
assert!(verify_and_identify(&encoded, b"message", &signature).is_err());
}
let mut identity_signature = Vec::new();
G2Affine::zero()
.serialize_compressed(&mut identity_signature)
.unwrap();
assert!(verify_and_identify(&encoded, b"message", &identity_signature).is_err());
}
#[test]
fn verify_rejects_wrong_message() {
let (sk, pk) = generate_keypair();
let sig = sign(&sk, b"correct message").expect("sign");
let result = verify(&pk, b"wrong message", &sig);
assert!(result.is_err());
}
#[test]
fn verify_rejects_wrong_key() {
let (sk, _pk) = generate_keypair();
let msg = b"test message";
let sig = sign(&sk, msg).expect("sign");
let mut rng = test_rng();
let _ = Fr::rand(&mut rng);
let sk2 = Fr::rand(&mut rng);
let pk2 = (G1Projective::from(G1Affine::generator()) * sk2).into_affine();
let result = verify(&pk2, msg, &sig);
assert!(result.is_err());
}
#[test]
fn did_derivation_valid_format() {
let (_sk, pk) = generate_keypair();
let did = did_from_bls_pubkey(&pk).expect("did");
assert!(
did.starts_with("did:key:z"),
"DID should start with did:key:z, got: {did}"
);
let multibase_part = &did["did:key:".len()..];
let (_base, decoded) = multibase::decode(multibase_part).expect("multibase decode");
let mut varint_buf = [0u8; 10];
let expected_prefix = unsigned_varint::encode::u64(BLS_G1_MULTICODEC, &mut varint_buf);
assert_eq!(&decoded[..expected_prefix.len()], expected_prefix);
let recovered_pk =
G1Affine::deserialize_compressed(&decoded[expected_prefix.len()..]).expect("deser pk");
assert_eq!(recovered_pk, pk);
}
#[test]
fn did_is_deterministic() {
let (_sk, pk) = generate_keypair();
let did1 = did_from_bls_pubkey(&pk).expect("did1");
let did2 = did_from_bls_pubkey(&pk).expect("did2");
assert_eq!(did1, did2);
}
#[test]
fn compressed_did_derivation_matches_validated_arkworks_keys() {
for scalar in 1..=32_u64 {
let key = (G1Projective::from(G1Affine::generator()) * Fr::from(scalar)).into_affine();
let mut encoded = Vec::new();
key.serialize_compressed(&mut encoded).unwrap();
let decoded = deserialize_pubkey(&encoded).unwrap();
assert_eq!(
did_from_compressed_pubkey(&encoded).unwrap(),
did_from_bls_pubkey(&decoded).unwrap()
);
}
}
#[test]
fn compressed_did_rejects_invalid_encoding_identity_and_torsion() {
let torsion =
G1Affine::new_unchecked(ark_bls12_381::Fq::from(0), ark_bls12_381::Fq::from(2));
assert!(torsion.is_on_curve());
assert!(!torsion.is_in_correct_subgroup_assuming_on_curve());
for key in [G1Affine::zero(), torsion] {
let mut encoded = Vec::new();
key.serialize_compressed(&mut encoded).unwrap();
assert!(did_from_compressed_pubkey(&encoded).is_err());
}
let mut valid = Vec::new();
G1Affine::generator()
.serialize_compressed(&mut valid)
.unwrap();
assert!(did_from_compressed_pubkey(&[]).is_err());
assert!(did_from_compressed_pubkey(&valid[..47]).is_err());
valid.push(0);
assert!(did_from_compressed_pubkey(&valid).is_err());
assert!(did_from_compressed_pubkey(&[0xff; 48]).is_err());
let mut uncompressed = Vec::new();
G1Affine::generator()
.serialize_uncompressed(&mut uncompressed)
.unwrap();
assert!(did_from_compressed_pubkey(&uncompressed).is_err());
}
#[test]
fn sign_rejects_zero_secret_key() {
let result = sign(&Fr::from(0u64), b"test message");
assert!(result.is_err());
}
#[test]
fn deserialize_pubkey_rejects_identity() {
let mut bytes = Vec::new();
G1Affine::zero()
.serialize_compressed(&mut bytes)
.expect("serialize identity");
assert!(deserialize_pubkey(&bytes).is_err());
}
#[test]
fn did_rejects_identity_pubkey() {
let identity = G1Affine::zero();
assert!(did_from_bls_pubkey(&identity).is_err());
}
#[test]
fn verify_rejects_identity_pubkey() {
let (sk, _pk) = generate_keypair();
let msg = b"test message";
let sig = sign(&sk, msg).expect("sign");
let identity = G1Affine::zero();
assert!(verify(&identity, msg, &sig).is_err());
}
#[test]
fn verify_rejects_identity_signature() {
let (_sk, pk) = generate_keypair();
let msg = b"test message";
let mut sig_bytes = Vec::new();
G2Affine::zero()
.serialize_compressed(&mut sig_bytes)
.expect("serialize identity");
assert!(verify(&pk, msg, &sig_bytes).is_err());
}
#[test]
fn pubkey_serialization_roundtrip() {
let (_sk, pk) = generate_keypair();
let mut bytes = Vec::with_capacity(48);
pk.serialize_compressed(&mut bytes).expect("serialize");
assert_eq!(bytes.len(), 48);
let recovered = deserialize_pubkey(&bytes).expect("deserialize");
assert_eq!(recovered, pk);
}
}