extern crate alloc;
use alloc::string::ToString;
use alloc::vec;
use lib_q_core::Result;
use crate::ZkpProof;
use crate::air::{
IdentityProofAir,
IdentityProofInput,
MlDsaLevel,
TraceGenerator,
};
use crate::stark::{
StarkProver,
StarkVerifier,
default_config,
};
pub type IdentityToken = [u8; 16];
pub use super::auth::MlDsaPrivateKey;
#[inline]
fn ml_dsa_level_for_private_key_len(byte_len: usize) -> MlDsaLevel {
if byte_len <= 2528 {
MlDsaLevel::Level44
} else if byte_len <= 4000 {
MlDsaLevel::Level65
} else {
MlDsaLevel::Level87
}
}
#[doc(hidden)]
pub fn identity_token_from_secret(secret: &[u8]) -> IdentityToken {
use lib_q_poseidon::{
Poseidon,
Poseidon128,
};
use lib_q_stark_field::PrimeCharacteristicRing;
use lib_q_stark_field::extension::Complex;
use lib_q_stark_mersenne31::Mersenne31;
use crate::air::{
bytes_to_poseidon_field,
poseidon_field_to_bytes,
};
let mut secret_fields = bytes_to_poseidon_field(secret);
if !secret_fields.len().is_multiple_of(2) {
let zero_f = Complex::<Mersenne31>::new_complex(Mersenne31::ZERO, Mersenne31::ZERO);
secret_fields.push(zero_f);
}
let hash_output = Poseidon128.hash(&secret_fields);
let hash_bytes = poseidon_field_to_bytes(&hash_output);
let mut it = [0u8; 16];
let len = hash_bytes.len().min(16);
it[..len].copy_from_slice(&hash_bytes[..len]);
it
}
pub fn prove_it_ownership(_it: &IdentityToken, private_key: &MlDsaPrivateKey) -> Result<ZkpProof> {
use crate::ProofMetadata;
let dsa_level = ml_dsa_level_for_private_key_len(private_key.len());
let air = IdentityProofAir::new(dsa_level).map_err(|e| lib_q_core::Error::InternalError {
operation: "prove_it_ownership".to_string(),
details: e.to_string(),
})?;
let input = IdentityProofInput {
secret: private_key.clone(),
};
let trace = air
.generate_trace(&input)
.map_err(|e| lib_q_core::Error::InternalError {
operation: "prove_it_ownership".to_string(),
details: e.to_string(),
})?;
let public_values = air.public_values(&input);
let config = default_config();
let prover = StarkProver::new(config);
let stark_proof = prover.prove(&air, trace, &public_values).map_err(|e| {
lib_q_core::Error::InternalError {
operation: "STARK proof generation".to_string(),
details: e.to_string(),
}
})?;
let level_u8 = match dsa_level {
MlDsaLevel::Level44 => 44,
MlDsaLevel::Level65 => 65,
MlDsaLevel::Level87 => 87,
};
let metadata = ProofMetadata::Identity {
dsa_level: level_u8,
};
ZkpProof::from_stark_proof(&stark_proof, metadata)
}
pub fn verify_it_ownership(proof: &ZkpProof, it: &IdentityToken) -> Result<bool> {
use crate::ProofMetadata;
use crate::air::IdentityProofAir;
if proof.proof_type != crate::ProofType::Stark {
return Ok(false);
}
if proof.data.is_empty() {
return Ok(false);
}
let stark_proof = proof.to_stark_proof()?;
let dsa_level = match &proof.metadata {
ProofMetadata::Identity { dsa_level: 44 } => MlDsaLevel::Level44,
ProofMetadata::Identity { dsa_level: 87 } => MlDsaLevel::Level87,
ProofMetadata::Identity { .. } => MlDsaLevel::Level65,
_ => return Ok(false), };
let air = IdentityProofAir::new(dsa_level).map_err(|e| lib_q_core::Error::InternalError {
operation: "verify_it_ownership".to_string(),
details: e.to_string(),
})?;
use lib_q_stark_field::extension::Complex;
use lib_q_stark_mersenne31::Mersenne31;
use crate::air::it_bytes_to_public_value;
type Val = Complex<Mersenne31>;
let expected = it_bytes_to_public_value::<Val>(it);
let public_values = vec![expected];
let config = default_config();
let verifier = StarkVerifier::new(config);
match verifier.verify(&air, &stark_proof, &public_values) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::air::MlDsaLevel;
use crate::{
ProofMetadata,
ProofType,
ZkpProof,
};
#[test]
fn test_ml_dsa_level_for_private_key_len_buckets() {
assert_eq!(ml_dsa_level_for_private_key_len(1), MlDsaLevel::Level44);
assert_eq!(ml_dsa_level_for_private_key_len(2528), MlDsaLevel::Level44);
assert_eq!(ml_dsa_level_for_private_key_len(2529), MlDsaLevel::Level65);
assert_eq!(ml_dsa_level_for_private_key_len(4000), MlDsaLevel::Level65);
assert_eq!(ml_dsa_level_for_private_key_len(4001), MlDsaLevel::Level87);
}
#[test]
fn test_prove_it_ownership() {
let it = [42u8; 16];
let private_key = b"test-private-key".to_vec();
let result = prove_it_ownership(&it, &private_key);
assert!(result.is_ok());
}
#[test]
fn test_verify_it_ownership() {
let it = [42u8; 16];
let private_key = b"test-private-key".to_vec();
let proof = prove_it_ownership(&it, &private_key).unwrap();
let result = verify_it_ownership(&proof, &it);
assert!(result.is_ok());
}
#[test]
fn test_identity_token_from_secret_is_deterministic() {
let a = identity_token_from_secret(b"deterministic-secret");
let b = identity_token_from_secret(b"deterministic-secret");
assert_eq!(a, b);
}
#[test]
fn test_verify_it_ownership_rejects_empty_or_wrong_metadata() {
let it = [7u8; 16];
let empty = ZkpProof {
data: vec![],
proof_type: ProofType::Stark,
security_level: 1,
metadata: ProofMetadata::Identity { dsa_level: 44 },
};
assert!(!verify_it_ownership(&empty, &it).unwrap());
let mut wrong_metadata =
prove_it_ownership(&it, &b"test-private-key".to_vec()).expect("valid proof");
wrong_metadata.metadata = ProofMetadata::None;
assert!(!verify_it_ownership(&wrong_metadata, &it).unwrap());
}
#[test]
fn test_prove_it_ownership_sets_identity_level_metadata() {
let it = [42u8; 16];
let proof_44 = prove_it_ownership(&it, &vec![0u8; 1]).expect("prove");
assert!(matches!(
proof_44.metadata,
ProofMetadata::Identity { dsa_level: 44 }
));
let mut level_87_metadata_proof = proof_44.clone();
level_87_metadata_proof.metadata = ProofMetadata::Identity { dsa_level: 87 };
assert!(!verify_it_ownership(&level_87_metadata_proof, &it).unwrap());
}
}