use affinidi_bbs::{self as bbs, PublicKey, SecretKey, Signature};
use sha2::{Digest, Sha256};
use crate::DataIntegrityError;
pub fn sign_base(
claims: &[(&str, &[u8])],
header: &[u8],
sk: &SecretKey,
pk: &PublicKey,
) -> Result<(Signature, Vec<Vec<u8>>), DataIntegrityError> {
let messages: Vec<Vec<u8>> = claims
.iter()
.map(|(key, value)| {
let mut msg = key.as_bytes().to_vec();
msg.push(b':');
msg.extend_from_slice(value);
msg
})
.collect();
let msg_refs: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
let signature = bbs::sign(sk, pk, header, &msg_refs)
.map_err(|e| DataIntegrityError::CryptoError(format!("BBS sign failed: {e}")))?;
Ok((signature, messages))
}
pub fn derive_proof(
pk: &PublicKey,
signature: &Signature,
header: &[u8],
presentation_header: &[u8],
messages: &[Vec<u8>],
disclosed_indexes: &[usize],
) -> Result<bbs::Proof, DataIntegrityError> {
let msg_refs: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
bbs::proof_gen(
pk,
signature,
header,
presentation_header,
&msg_refs,
disclosed_indexes,
)
.map_err(|e| DataIntegrityError::CryptoError(format!("BBS proof generation failed: {e}")))
}
pub fn verify_proof(
pk: &PublicKey,
proof: &bbs::Proof,
header: &[u8],
presentation_header: &[u8],
disclosed_messages: &[&[u8]],
disclosed_indexes: &[usize],
) -> Result<bool, DataIntegrityError> {
bbs::proof_verify(
pk,
proof,
header,
presentation_header,
disclosed_messages,
disclosed_indexes,
)
.map_err(|e| {
DataIntegrityError::VerificationError(format!("BBS proof verification failed: {e}"))
})
}
pub fn compute_bbs_header(proof_options: &[u8], mandatory_statements: &[&[u8]]) -> Vec<u8> {
let mut header = Vec::with_capacity(64);
let options_hash = Sha256::digest(proof_options);
header.extend_from_slice(&options_hash);
let mut mandatory_hasher = Sha256::new();
for statement in mandatory_statements {
mandatory_hasher.update((statement.len() as u64).to_be_bytes());
mandatory_hasher.update(statement);
}
let mandatory_hash = mandatory_hasher.finalize();
header.extend_from_slice(&mandatory_hash);
header
}
#[cfg(test)]
mod tests {
use super::*;
fn test_keypair() -> (SecretKey, PublicKey) {
let sk = bbs::keygen(b"test-key-material-for-bbs-2023!!", b"").unwrap();
let pk = bbs::sk_to_pk(&sk);
(sk, pk)
}
#[test]
fn sign_base_and_verify() {
let (sk, pk) = test_keypair();
let claims = vec![
("given_name", b"John".as_ref()),
("family_name", b"Doe"),
("age_over_18", b"true"),
];
let header = compute_bbs_header(b"proof-options", &[b"mandatory-1"]);
let (signature, messages) = sign_base(&claims, &header, &sk, &pk).unwrap();
let msg_refs: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
let valid = bbs::verify(&pk, &signature, &header, &msg_refs).unwrap();
assert!(valid);
}
#[test]
fn derive_and_verify_selective_proof() {
let (sk, pk) = test_keypair();
let claims = vec![
("given_name", b"John".as_ref()),
("family_name", b"Doe"),
("age_over_18", b"true"),
("nationality", b"DE"),
];
let header = compute_bbs_header(b"proof-options", &[]);
let (signature, messages) = sign_base(&claims, &header, &sk, &pk).unwrap();
let proof = derive_proof(
&pk,
&signature,
&header,
b"verifier-session-nonce",
&messages,
&[2],
)
.unwrap();
let disclosed_msg = messages[2].as_slice();
let valid = verify_proof(
&pk,
&proof,
&header,
b"verifier-session-nonce",
&[disclosed_msg],
&[2],
)
.unwrap();
assert!(valid);
}
#[test]
fn derive_proof_wrong_message_fails() {
let (sk, pk) = test_keypair();
let claims = vec![("name", b"Alice".as_ref())];
let header = compute_bbs_header(b"opts", &[]);
let (signature, messages) = sign_base(&claims, &header, &sk, &pk).unwrap();
let proof = derive_proof(&pk, &signature, &header, b"ph", &messages, &[0]).unwrap();
let valid = verify_proof(
&pk,
&proof,
&header,
b"ph",
&[b"name:Bob"], &[0],
)
.unwrap();
assert!(!valid);
}
#[test]
fn proofs_are_unlinkable() {
let (sk, pk) = test_keypair();
let claims = vec![("attr", b"value".as_ref())];
let header = compute_bbs_header(b"opts", &[]);
let (signature, messages) = sign_base(&claims, &header, &sk, &pk).unwrap();
let proof1 = derive_proof(&pk, &signature, &header, b"session1", &messages, &[0]).unwrap();
let proof2 = derive_proof(&pk, &signature, &header, b"session2", &messages, &[0]).unwrap();
assert_ne!(proof1.to_bytes(), proof2.to_bytes());
let msg = messages[0].as_slice();
assert!(verify_proof(&pk, &proof1, &header, b"session1", &[msg], &[0]).unwrap());
assert!(verify_proof(&pk, &proof2, &header, b"session2", &[msg], &[0]).unwrap());
}
#[test]
fn zero_knowledge_existence_proof() {
let (sk, pk) = test_keypair();
let claims = vec![("secret1", b"hidden".as_ref()), ("secret2", b"also_hidden")];
let header = compute_bbs_header(b"opts", &[]);
let (signature, messages) = sign_base(&claims, &header, &sk, &pk).unwrap();
let proof = derive_proof(&pk, &signature, &header, b"ph", &messages, &[]).unwrap();
let valid = verify_proof(&pk, &proof, &header, b"ph", &[], &[]).unwrap();
assert!(valid);
}
#[test]
fn compute_header_deterministic() {
let h1 = compute_bbs_header(b"opts", &[b"stmt1", b"stmt2"]);
let h2 = compute_bbs_header(b"opts", &[b"stmt1", b"stmt2"]);
assert_eq!(h1, h2);
assert_eq!(h1.len(), 64); }
#[test]
fn compute_header_different_inputs() {
let h1 = compute_bbs_header(b"opts1", &[b"stmt"]);
let h2 = compute_bbs_header(b"opts2", &[b"stmt"]);
assert_ne!(h1, h2);
}
}