use crate::field::Goldilocks;
use crate::folding::SpectralFolding;
use crate::fri::{FriParams, FriProver, FriProof, FriVerifier, SpectralFri};
use crate::fwht::FWHT;
use crate::merkle::MerkleTree;
use crate::privacy::SpectralPrivacy;
use crate::signal::SpectralSignal;
use crate::transcript::Transcript;
use crate::vm::{InstructionRecord, SovereignVM};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsevalEnergyProof {
pub trace_hamming_weight: u64,
pub trace_actual_energy: u64,
pub blinding_hamming_weight: u64,
pub total_energy: Goldilocks,
pub final_energy: Goldilocks,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SovereignAttestation {
pub roots: Vec<Vec<u8>>,
pub final_val: Goldilocks,
pub queries: Vec<SpectralPathAttestation>,
pub n_trace: usize,
pub n_trace_commitment: Vec<u8>,
pub parseval_proof: ParsevalEnergyProof,
pub instruction_trace: Vec<InstructionRecord>,
pub fri_proof: Option<FriProof>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpectralPathAttestation {
pub initial_index: usize,
pub layers: Vec<FoldingLayerEvidence>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FoldingLayerEvidence {
pub val_0: Goldilocks,
pub val_1: Goldilocks,
pub path_0: Vec<Vec<u8>>,
pub path_1: Vec<Vec<u8>>,
}
pub struct SovereignProver;
impl SovereignProver {
pub fn prove(vm: &SovereignVM, boolean_trace: &SpectralSignal, l_queries: usize) -> SovereignAttestation {
let mut transcript = Transcript::new();
let n_trace = boolean_trace.values.len();
let trace_actual_energy: u64 = boolean_trace.values.iter().map(|&v| (v * v) as u64).sum();
let trace_sum: u64 = boolean_trace.values.iter().map(|&v| v.unsigned_abs()).sum();
let trace_hamming_weight = trace_sum;
transcript.append_usize(b"n_trace", n_trace);
transcript.append(b"trace_hamming", &trace_hamming_weight.to_le_bytes());
transcript.append(b"trace_actual_energy", &trace_actual_energy.to_le_bytes());
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(b"N_TRACE_BINDING");
hasher.update(&(n_trace as u64).to_le_bytes());
let n_trace_commitment = hasher.finalize().to_vec();
let mut current_data: Vec<Goldilocks> = boolean_trace
.values
.iter()
.map(|&v| Goldilocks::from_i64(v))
.collect();
let min_blinding = 2.max(128 / (current_data.len().trailing_zeros() as usize + 1));
let blinding_meta_1 =
SpectralPrivacy::shield_with_boolean_entropy(&mut current_data, min_blinding);
let blinding_meta_2 = SpectralPrivacy::pad_to_power_of_two(&mut current_data);
let blinding_hamming_weight =
blinding_meta_1.hamming_weight + blinding_meta_2.hamming_weight;
transcript.append(b"blinding_hamming", &blinding_hamming_weight.to_le_bytes());
let total_hamming = trace_hamming_weight + blinding_hamming_weight;
let n_padded = current_data.len() as u64;
let total_energy = Goldilocks::new(total_hamming * n_padded);
let mut roots = Vec::new();
let mut layers_data = Vec::new();
let mut challenges = Vec::new();
while current_data.len() > 1 {
let data_bits: Vec<i64> = current_data.iter().map(|f| f.0 as i64).collect();
let tree = MerkleTree::commit(&data_bits);
transcript.append(b"root", &tree.root);
roots.push(tree.root.clone());
let alpha = transcript.challenge();
challenges.push(alpha);
let prev_data = current_data.clone();
current_data = SpectralFolding::fold_goldilocks(¤t_data, alpha);
layers_data.push((tree, prev_data));
}
let final_val = current_data[0];
let final_energy = final_val.mul(final_val);
transcript.append(b"final_val", &final_val.0.to_le_bytes());
transcript.append(b"final_energy", &final_energy.0.to_le_bytes());
let mut queries = Vec::new();
for q in 0..l_queries {
let mut q_transcript = transcript.clone();
q_transcript.append(b"q_idx", &(q as u64).to_le_bytes());
let challenge_val = q_transcript.challenge();
let initial_idx = (challenge_val.0 as usize) % (layers_data[0].1.len() / 2);
let mut path_layers = Vec::new();
let mut curr_idx = initial_idx;
for i in 0..layers_data.len() {
let (ref tree, ref data) = layers_data[i];
let n = data.len();
path_layers.push(FoldingLayerEvidence {
val_0: data[curr_idx],
val_1: data[curr_idx + n / 2],
path_0: tree.prove(curr_idx),
path_1: tree.prove(curr_idx + n / 2),
});
if i < layers_data.len() - 1 {
curr_idx %= n / 4;
}
}
queries.push(SpectralPathAttestation {
initial_index: initial_idx,
layers: path_layers,
});
}
let fri_params = FriParams {
codeword_size: 1024, blowup_factor: 2,
num_queries: 32,
final_degree: 1,
};
let message = vec![Goldilocks::from_i64(0); fri_params.codeword_size / fri_params.blowup_factor]; let fri_proof = FriProver::prove_from_message(&message, &fri_params, &mut transcript);
SovereignAttestation {
roots,
final_val,
queries,
n_trace,
n_trace_commitment,
parseval_proof: ParsevalEnergyProof {
trace_hamming_weight,
trace_actual_energy,
blinding_hamming_weight,
total_energy,
final_energy,
},
instruction_trace: vm.instruction_trace.clone(),
fri_proof: Some(fri_proof),
}
}
}
pub struct SovereignVerifier;
#[derive(Debug, Clone)]
pub enum AttestationError {
PathIndexMismatch { expected: usize, got: usize },
MerkleIntegrityViolation { layer: usize, index: usize },
BooleanityViolation {
layer: usize,
index: usize,
value: u64,
},
CollinearityViolation { layer: usize },
NTraceManipulation { claimed: usize },
TraceBooleanityViolation {
trace_sum: u64,
trace_actual_energy: u64,
},
ParsevalViolation {
expected_energy: u64,
claimed_energy: u64,
},
FinalEnergyMismatch { expected: u64, claimed: u64 },
}
impl SovereignVerifier {
fn verify_mul_constraint(record: &InstructionRecord) -> bool {
let op1_signal = SpectralSignal::new(vec![record.operand1.0 as i64]);
let op2_signal = SpectralSignal::new(vec![record.operand2.0 as i64]);
let result_signal = SpectralSignal::new(vec![record.result.0 as i64]);
let fwht_op1 = FWHT::fwht(&op1_signal);
let fwht_op2 = FWHT::fwht(&op2_signal);
let fwht_result = FWHT::fwht(&result_signal);
fwht_result.values.iter().zip(
fwht_op1.values.iter().zip(fwht_op2.values.iter())
.map(|(a, b)| a.mul(*b))
).all(|(actual, expected)| actual.0 == expected.0)
}
fn verify_div_constraint(record: &InstructionRecord) -> bool {
if record.operand2.0 == 0 {
return false; }
let op1_signal = SpectralSignal::new(vec![record.operand1.0 as i64]);
let op2_signal = SpectralSignal::new(vec![record.operand2.0 as i64]);
let result_signal = SpectralSignal::new(vec![record.result.0 as i64]);
let fwht_op1 = FWHT::fwht(&op1_signal);
let fwht_op2 = FWHT::fwht(&op2_signal);
let fwht_result = FWHT::fwht(&result_signal);
fwht_result.values.iter().zip(
fwht_op2.values.iter().zip(fwht_op1.values.iter())
.map(|(b, a)| a.mul(b.inv())) ).all(|(actual, expected)| actual.0 == expected.0)
}
pub fn verify(attestation: &SovereignAttestation, l_queries: usize) -> bool {
match Self::verify_strict(attestation, l_queries) {
Ok(()) => true,
Err(_e) => {
false
}
}
}
pub fn verify_strict(
attestation: &SovereignAttestation,
l_queries: usize,
) -> Result<(), AttestationError> {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(b"N_TRACE_BINDING");
hasher.update(&(attestation.n_trace as u64).to_le_bytes());
let expected_commitment = hasher.finalize().to_vec();
if attestation.n_trace_commitment != expected_commitment {
return Err(AttestationError::NTraceManipulation {
claimed: attestation.n_trace,
});
}
if attestation.parseval_proof.trace_actual_energy
!= attestation.parseval_proof.trace_hamming_weight
{
return Err(AttestationError::TraceBooleanityViolation {
trace_sum: attestation.parseval_proof.trace_hamming_weight,
trace_actual_energy: attestation.parseval_proof.trace_actual_energy,
});
}
let n_padded = (1u64 << attestation.roots.len()) as u64;
let total_hamming = attestation.parseval_proof.trace_hamming_weight
+ attestation.parseval_proof.blinding_hamming_weight;
let expected_energy = total_hamming * n_padded;
if attestation.parseval_proof.total_energy.0 != expected_energy {
return Err(AttestationError::ParsevalViolation {
expected_energy,
claimed_energy: attestation.parseval_proof.total_energy.0,
});
}
let expected_final_energy = attestation.final_val.mul(attestation.final_val);
if attestation.parseval_proof.final_energy != expected_final_energy {
return Err(AttestationError::FinalEnergyMismatch {
expected: expected_final_energy.0,
claimed: attestation.parseval_proof.final_energy.0,
});
}
for record in &attestation.instruction_trace {
match record.op {
crate::vm::SpectralOp::S_MUL => {
if !Self::verify_mul_constraint(record) {
return Err(AttestationError::TraceBooleanityViolation {
trace_sum: 0, trace_actual_energy: 1,
});
}
}
crate::vm::SpectralOp::S_DIV => {
if !Self::verify_div_constraint(record) {
return Err(AttestationError::TraceBooleanityViolation {
trace_sum: 0, trace_actual_energy: 2,
});
}
}
_ => {} }
}
let mut transcript = Transcript::new();
transcript.append_usize(b"n_trace", attestation.n_trace);
transcript.append(
b"trace_hamming",
&attestation
.parseval_proof
.trace_hamming_weight
.to_le_bytes(),
);
transcript.append(
b"trace_actual_energy",
&attestation.parseval_proof.trace_actual_energy.to_le_bytes(),
);
transcript.append(
b"blinding_hamming",
&attestation
.parseval_proof
.blinding_hamming_weight
.to_le_bytes(),
);
let mut challenges = Vec::new();
let n_start = 1 << attestation.roots.len();
for root in &attestation.roots {
transcript.append(b"root", root);
challenges.push(transcript.challenge());
}
transcript.append(b"final_val", &attestation.final_val.0.to_le_bytes());
transcript.append(
b"final_energy",
&attestation.parseval_proof.final_energy.0.to_le_bytes(),
);
let one_f = Goldilocks::from_i64(1);
for q in 0..l_queries {
let path = &attestation.queries[q];
let mut q_transcript = transcript.clone();
q_transcript.append(b"q_idx", &(q as u64).to_le_bytes());
let challenge_val = q_transcript.challenge();
let expected_initial_idx = (challenge_val.0 as usize) % (n_start / 2);
if path.initial_index != expected_initial_idx {
return Err(AttestationError::PathIndexMismatch {
expected: expected_initial_idx,
got: path.initial_index,
});
}
let mut curr_idx = expected_initial_idx;
let mut curr_n = n_start;
for i in 0..path.layers.len() {
let layer = &path.layers[i];
let root = &attestation.roots[i];
let alpha = challenges[i];
if !MerkleTree::verify(root, curr_idx, layer.val_0.0 as i64, &layer.path_0) {
return Err(AttestationError::MerkleIntegrityViolation {
layer: i,
index: curr_idx,
});
}
if !MerkleTree::verify(
root,
curr_idx + curr_n / 2,
layer.val_1.0 as i64,
&layer.path_1,
) {
return Err(AttestationError::MerkleIntegrityViolation {
layer: i,
index: curr_idx + curr_n / 2,
});
}
if i == 0 {
if layer.val_0.0 != 0 && layer.val_0.0 != 1 {
return Err(AttestationError::BooleanityViolation {
layer: i,
index: curr_idx,
value: layer.val_0.0,
});
}
if layer.val_1.0 != 0 && layer.val_1.0 != 1 {
return Err(AttestationError::BooleanityViolation {
layer: i,
index: curr_idx + curr_n / 2,
value: layer.val_1.0,
});
}
}
let v0_f = layer.val_0;
let v1_f = layer.val_1;
let expected_folded = one_f.add(alpha).mul(v0_f).add(one_f.sub(alpha).mul(v1_f));
if i == path.layers.len() - 1 {
if expected_folded != attestation.final_val {
return Err(AttestationError::CollinearityViolation { layer: i });
}
} else {
let next_layer = &path.layers[i + 1];
let next_n = curr_n / 2;
let next_val = if curr_idx < next_n / 2 {
next_layer.val_0
} else {
next_layer.val_1
};
if expected_folded != next_val {
return Err(AttestationError::CollinearityViolation { layer: i });
}
}
if i < path.layers.len() - 1 {
curr_idx %= curr_n / 4;
}
curr_n /= 2;
}
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum SerializationError {
#[error("Bincode serialization error: {0}")]
Bincode(#[from] Box<bincode::ErrorKind>),
}
impl SovereignAttestation {
pub fn serialize(&self) -> Result<Vec<u8>, SerializationError> {
bincode::serialize(self).map_err(|e| e.into())
}
pub fn deserialize(data: &[u8]) -> Result<Self, SerializationError> {
bincode::deserialize(data).map_err(|e| e.into())
}
pub fn serialize_compressed(&self) -> Result<Vec<u8>, SerializationError> {
self.serialize()
}
pub fn deserialize_compressed(data: &[u8]) -> Result<Self, SerializationError> {
Self::deserialize(data)
}
}
#[deprecated(note = "Use SovereignAttestation instead")]
pub type SpectralProof = SovereignAttestation;
#[deprecated(note = "Use SovereignProver instead")]
pub type Prover = SovereignProver;
#[deprecated(note = "Use SovereignVerifier instead")]
pub type Verifier = SovereignVerifier;