use crate::field::Goldilocks;
use rand::{Rng, rng};
pub struct SpectralPrivacy;
#[derive(Debug, Clone)]
pub struct BlindingMetadata {
pub count: usize,
pub hamming_weight: u64,
pub energy: Goldilocks,
}
impl SpectralPrivacy {
pub fn shield_with_boolean_entropy(trace: &mut Vec<Goldilocks>, k: usize) -> BlindingMetadata {
let mut hamming_weight = 0u64;
for _ in 0..k {
let rand_bit: u64 = rng().random::<bool>() as u64;
trace.push(Goldilocks::new(rand_bit));
hamming_weight += rand_bit;
}
let energy = Goldilocks::new(hamming_weight);
BlindingMetadata {
count: k,
hamming_weight,
energy,
}
}
#[deprecated(note = "Use shield_with_boolean_entropy for Parseval compatibility")]
pub fn shield_with_entropy(trace: &mut Vec<Goldilocks>, k: usize) {
for _ in 0..k {
let rand_val: u64 = rng().random();
let noise = Goldilocks::new(rand_val);
trace.push(noise);
}
}
pub fn pad_to_power_of_two(trace: &mut Vec<Goldilocks>) -> BlindingMetadata {
let target_len = trace.len().next_power_of_two();
let padding_needed = target_len - trace.len();
if padding_needed == 0 {
return BlindingMetadata {
count: 0,
hamming_weight: 0,
energy: Goldilocks::new(0),
};
}
Self::shield_with_boolean_entropy(trace, padding_needed)
}
#[deprecated(note = "Use shield_with_boolean_entropy instead")]
pub fn add_blinding_points(trace: &mut Vec<Goldilocks>, k: usize) {
#[allow(deprecated)]
Self::shield_with_entropy(trace, k);
}
pub fn apply_linear_constraint_preserving_blind(
manifolds: &mut [crate::signal::SpectralManifold],
coeffs: &[Goldilocks],
) {
assert_eq!(manifolds.len(), coeffs.len());
let k = manifolds.len();
assert!(k >= 2, "Need at least 2 manifolds to balance equation");
let n = manifolds[0].values.len();
for m in manifolds.iter() {
assert_eq!(
m.values.len(),
n,
"All manifolds must have same spectral length"
);
}
let mut weighted_sum = vec![Goldilocks::new(0); n];
let mut noises: Vec<Vec<Goldilocks>> = Vec::with_capacity(k);
for i in 0..k - 1 {
let mut noise_vec = Vec::with_capacity(n);
for j in 0..n {
let r = Goldilocks::new(rng().random());
noise_vec.push(r);
let term = coeffs[i].mul(r);
weighted_sum[j] = weighted_sum[j].add(term);
}
noises.push(noise_vec);
}
let c_k = coeffs[k - 1];
let inv_c_k = c_k.inv();
let mut final_noise = Vec::with_capacity(n);
for j in 0..n {
let sum = weighted_sum[j];
let neg_sum = Goldilocks::new(0).sub(sum);
let b_k = neg_sum.mul(inv_c_k);
final_noise.push(b_k);
}
noises.push(final_noise);
for (i, m) in manifolds.iter_mut().enumerate() {
for j in 0..n {
m.values[j] = m.values[j].add(noises[i][j]);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boolean_blinding_energy() {
let mut trace = vec![Goldilocks::new(1), Goldilocks::new(0)];
let meta = SpectralPrivacy::shield_with_boolean_entropy(&mut trace, 10);
for i in 2..trace.len() {
assert!(
trace[i].0 == 0 || trace[i].0 == 1,
"Blinding point must be boolean"
);
}
assert_eq!(meta.energy.0, meta.hamming_weight);
}
#[test]
fn test_pad_to_power_of_two() {
let mut trace = vec![Goldilocks::new(1); 5];
let meta = SpectralPrivacy::pad_to_power_of_two(&mut trace);
assert_eq!(trace.len(), 8, "Should pad to 8");
assert_eq!(meta.count, 3, "Should add 3 padding points");
}
}