use affinidi_encoding::{BLS12381_G2_PUB, MultiEncoded, MultiEncodedBuf};
use base58::{FromBase58, ToBase58};
use crate::{CryptoError, error::Result};
const DID_KEY_PREFIX: &str = "did:key:";
pub const BLS12381_G2_PUB_LEN: usize = 96;
pub fn g2_pub_to_did_key(pubkey: &[u8; BLS12381_G2_PUB_LEN]) -> String {
let multikey = MultiEncodedBuf::encode_bytes(BLS12381_G2_PUB, pubkey)
.into_bytes()
.to_base58();
format!("{DID_KEY_PREFIX}z{multikey}")
}
pub fn did_key_to_g2_pub(did: &str) -> Result<[u8; BLS12381_G2_PUB_LEN]> {
let multibase = did
.strip_prefix(DID_KEY_PREFIX)
.ok_or_else(|| CryptoError::Decoding(format!("Expected '{DID_KEY_PREFIX}' prefix")))?;
let base58 = multibase.strip_prefix('z').ok_or_else(|| {
CryptoError::Decoding("Expected multibase 'z' prefix after 'did:key:'".into())
})?;
let decoded = base58
.from_base58()
.map_err(|_| CryptoError::Decoding("Couldn't decode base58".into()))?;
let multicodec = MultiEncoded::new(decoded.as_slice())?;
if multicodec.codec() != BLS12381_G2_PUB {
return Err(CryptoError::KeyError(format!(
"Expected BLS12-381 G2 public key, instead received codec 0x{:x}",
multicodec.codec()
)));
}
let data = multicodec.data();
if data.len() != BLS12381_G2_PUB_LEN {
return Err(CryptoError::KeyError(format!(
"Invalid public key byte length: expected {BLS12381_G2_PUB_LEN}, got {}",
data.len()
)));
}
let mut out = [0u8; BLS12381_G2_PUB_LEN];
out.copy_from_slice(data);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use affinidi_encoding::ED25519_PUB;
fn sample_g2() -> [u8; BLS12381_G2_PUB_LEN] {
let mut k = [0u8; BLS12381_G2_PUB_LEN];
for (i, b) in k.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(7).wrapping_add(1);
}
k
}
#[test]
fn g2_pub_to_did_key_roundtrip() {
let key = sample_g2();
let did = g2_pub_to_did_key(&key);
assert!(
did.starts_with("did:key:z"),
"expected did:key:z prefix, got {did}"
);
let back = did_key_to_g2_pub(&did).unwrap();
assert_eq!(back, key);
}
#[test]
fn g2_pub_to_did_key_is_deterministic() {
let key = sample_g2();
assert_eq!(g2_pub_to_did_key(&key), g2_pub_to_did_key(&key));
}
#[test]
fn did_key_to_g2_pub_missing_did_key_prefix() {
let err = did_key_to_g2_pub("zUC7abc").unwrap_err();
assert!(
matches!(err, CryptoError::Decoding(ref m) if m.contains("did:key:")),
"unexpected error: {err:?}"
);
}
#[test]
fn did_key_to_g2_pub_missing_multibase_prefix() {
let err = did_key_to_g2_pub("did:key:UC7abc").unwrap_err();
assert!(
matches!(err, CryptoError::Decoding(ref m) if m.contains('z')),
"unexpected error: {err:?}"
);
}
#[test]
fn did_key_to_g2_pub_wrong_multicodec() {
let payload = MultiEncodedBuf::encode_bytes(ED25519_PUB, &[0u8; 32])
.into_bytes()
.to_base58();
let did = format!("did:key:z{payload}");
let err = did_key_to_g2_pub(&did).unwrap_err();
assert!(
matches!(err, CryptoError::KeyError(ref m) if m.contains("codec")),
"unexpected error: {err:?}"
);
}
#[test]
fn did_key_to_g2_pub_wrong_length() {
let payload = MultiEncodedBuf::encode_bytes(BLS12381_G2_PUB, &[0u8; 48])
.into_bytes()
.to_base58();
let did = format!("did:key:z{payload}");
let err = did_key_to_g2_pub(&did).unwrap_err();
assert!(
matches!(err, CryptoError::KeyError(ref m) if m.contains("length") || m.contains("96")),
"unexpected error: {err:?}"
);
}
}