use crate::saap::SaapValidationError;
use crate::sampling::RejectionError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdentityError {
InvalidInputLength,
SerializationError,
RejectionSamplingFailed,
NormBoundViolation,
ChallengeMismatch,
InvalidAttributeCommitment,
ThresholdNotMet,
InvalidShareSet,
WireBadMagic,
WireBadVersion,
WireLengthMismatch,
CoefficientOutOfRange,
}
impl From<SaapValidationError> for IdentityError {
fn from(e: SaapValidationError) -> Self {
match e {
SaapValidationError::NormBoundViolation => IdentityError::NormBoundViolation,
SaapValidationError::ChallengeMismatch => IdentityError::ChallengeMismatch,
SaapValidationError::InvalidAttributeCommitment => {
IdentityError::InvalidAttributeCommitment
}
}
}
}
impl From<RejectionError> for IdentityError {
fn from(e: RejectionError) -> Self {
match e {
RejectionError::AllIterationsRejected => IdentityError::RejectionSamplingFailed,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::htss::SecretSharer;
use crate::plp::{checked_project_at_context, EphemeralProjection};
#[test]
fn every_saap_validation_error_maps() {
assert_eq!(
IdentityError::from(SaapValidationError::NormBoundViolation),
IdentityError::NormBoundViolation
);
assert_eq!(
IdentityError::from(SaapValidationError::ChallengeMismatch),
IdentityError::ChallengeMismatch
);
assert_eq!(
IdentityError::from(SaapValidationError::InvalidAttributeCommitment),
IdentityError::InvalidAttributeCommitment
);
}
#[test]
fn rejection_sampling_failure_maps() {
assert_eq!(
IdentityError::from(RejectionError::AllIterationsRejected),
IdentityError::RejectionSamplingFailed
);
}
const RHO: [u8; 32] = [0x5au8; 32];
#[test]
fn invalid_input_length_from_a_short_secret() {
let short_secret = [0u8; 31];
let result = checked_project_at_context(&short_secret, b"context", &RHO);
assert_eq!(result.err(), Some(IdentityError::InvalidInputLength));
}
#[test]
fn invalid_input_length_from_short_randomness() {
let secret = [0x42u8; 32];
let short_rho = [0u8; 31];
let result = checked_project_at_context(&secret, b"context", &short_rho);
assert_eq!(result.err(), Some(IdentityError::InvalidInputLength));
}
#[test]
fn checked_project_at_context_succeeds_with_a_valid_secret() {
let secret = [0x42u8; 32];
assert!(checked_project_at_context(&secret, b"context", &RHO).is_ok());
}
#[test]
fn serialization_error_from_truncated_projection_bytes() {
let truncated = [0u8; 10];
let result = EphemeralProjection::from_bytes(&truncated);
assert_eq!(result.err(), Some(IdentityError::SerializationError));
}
#[test]
fn ephemeral_projection_round_trips_through_bytes() {
let secret = [0x99u8; 32];
let projection = checked_project_at_context(&secret, b"round-trip", &RHO).unwrap();
let bytes = projection.to_bytes();
let decoded =
EphemeralProjection::from_bytes(&bytes).expect("well-formed bytes must decode");
assert_eq!(decoded.tau, projection.tau);
let flat_a = |p: &EphemeralProjection| {
p.matrix_a
.iter()
.flat_map(|row| row.iter().flat_map(|q| q.coeffs().to_vec()))
.collect::<alloc::vec::Vec<u32>>()
};
let flat_b = |p: &EphemeralProjection| {
p.public_b
.iter()
.flat_map(|q| q.coeffs().to_vec())
.collect::<alloc::vec::Vec<u32>>()
};
assert_eq!(flat_a(&decoded), flat_a(&projection));
assert_eq!(flat_b(&decoded), flat_b(&projection));
}
#[test]
#[allow(deprecated)] fn threshold_not_met_with_fewer_than_three_shares() {
let shares = SecretSharer::split_secret(12_345u64, 3, 5, 0xdead_beef);
let result = SecretSharer::reconstruct_secret_checked(&shares[0..2]);
assert_eq!(result.err(), Some(IdentityError::ThresholdNotMet));
}
#[test]
#[allow(deprecated)] fn reconstruct_secret_checked_succeeds_at_the_threshold() {
let secret = 12_345u64;
let shares = SecretSharer::split_secret(secret, 3, 5, 0xdead_beef);
let result = SecretSharer::reconstruct_secret_checked(&shares[0..3]);
assert_eq!(result, Ok(secret));
}
}