use super::{
KernelDescriptor, ProgramInfo, StackInputs, StackOutputs,
domain::{EXECUTION_CLAIM_DOMAIN_ID, PROOF_REQUEST_DOMAIN_ID, domain_selector},
};
use crate::{Felt, Word, ZERO, chiplets::hasher};
pub const NUM_CLAIM_ELEMENTS: usize = 40;
pub const CLAIM_DOMAIN_TAG: Felt = domain_selector(EXECUTION_CLAIM_DOMAIN_ID, 1);
pub const REQUEST_DOMAIN_TAG: Felt = domain_selector(PROOF_REQUEST_DOMAIN_ID, 1);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecutionClaim {
program_root: Word,
kernel: KernelDescriptor,
stack_inputs: StackInputs,
stack_outputs: StackOutputs,
}
impl ExecutionClaim {
pub const fn new(
program_root: Word,
kernel: KernelDescriptor,
stack_inputs: StackInputs,
stack_outputs: StackOutputs,
) -> Self {
Self {
program_root,
kernel,
stack_inputs,
stack_outputs,
}
}
pub fn from_program_info(
program_info: ProgramInfo,
stack_inputs: StackInputs,
stack_outputs: StackOutputs,
) -> Self {
let (program_root, kernel) = program_info.into_parts();
Self::new(program_root, kernel, stack_inputs, stack_outputs)
}
pub const fn program_root(&self) -> Word {
self.program_root
}
pub const fn kernel(&self) -> &KernelDescriptor {
&self.kernel
}
pub fn to_program_info(&self) -> ProgramInfo {
ProgramInfo::new(self.program_root, self.kernel.clone())
}
pub const fn stack_inputs(&self) -> &StackInputs {
&self.stack_inputs
}
pub const fn stack_outputs(&self) -> &StackOutputs {
&self.stack_outputs
}
pub fn into_parts(self) -> (Word, KernelDescriptor, StackInputs, StackOutputs) {
(self.program_root, self.kernel, self.stack_inputs, self.stack_outputs)
}
pub fn to_elements(&self) -> [Felt; NUM_CLAIM_ELEMENTS] {
let mut elements = [ZERO; NUM_CLAIM_ELEMENTS];
elements[0..4].copy_from_slice(self.program_root.as_elements());
elements[4..8].copy_from_slice(self.kernel.commitment().as_elements());
elements[8..24].copy_from_slice(&self.stack_inputs[..]);
elements[24..40].copy_from_slice(&self.stack_outputs[..]);
elements
}
pub fn commitment(&self) -> Word {
claim_commitment(&self.to_elements())
}
}
pub fn claim_commitment(elements: &[Felt; NUM_CLAIM_ELEMENTS]) -> Word {
hasher::hash_elements_in_domain(elements, CLAIM_DOMAIN_TAG)
}
pub fn request_key(verifier_root: Word, claim_commitment: Word) -> Word {
let mut preimage = [ZERO; 2 * 4];
preimage[0..4].copy_from_slice(claim_commitment.as_elements());
preimage[4..8].copy_from_slice(verifier_root.as_elements());
hasher::hash_elements_in_domain(&preimage, REQUEST_DOMAIN_TAG)
}
#[cfg(test)]
mod tests {
use super::{
super::{KERNEL_DOMAIN_TAG, KernelDescriptor},
*,
};
fn test_claim() -> ExecutionClaim {
let word = |a: u64| -> Word {
[
Felt::new_unchecked(a),
Felt::new_unchecked(a + 1),
Felt::new_unchecked(a + 2),
Felt::new_unchecked(a + 3),
]
.into()
};
let kernel = KernelDescriptor::from_hashes(vec![word(100)]).unwrap();
let program_info = ProgramInfo::new(word(1), kernel);
let inputs = StackInputs::new(&[Felt::new_unchecked(5), Felt::new_unchecked(6)]).unwrap();
let outputs = StackOutputs::new(&[Felt::new_unchecked(7)]).unwrap();
ExecutionClaim::from_program_info(program_info, inputs, outputs)
}
#[test]
fn commitment_binds_fields_order_and_domain() {
let base = test_claim();
let base_commitment = base.commitment();
let base_elements = base.to_elements();
let mut mutated = base.clone();
mutated.program_root = [Felt::new_unchecked(999), ZERO, ZERO, ZERO].into();
assert_ne!(mutated.commitment(), base_commitment, "P not bound");
let mut mutated = base.clone();
mutated.kernel = KernelDescriptor::from_hashes(vec![
[Felt::new_unchecked(200), ZERO, ZERO, ZERO].into(),
])
.unwrap();
assert_ne!(mutated.commitment(), base_commitment, "K not bound");
let mut mutated = base.clone();
mutated.stack_inputs =
StackInputs::new(&[Felt::new_unchecked(5), Felt::new_unchecked(60)]).unwrap();
assert_ne!(mutated.commitment(), base_commitment, "I not bound");
let mut mutated = base.clone();
mutated.stack_outputs = StackOutputs::new(&[Felt::new_unchecked(70)]).unwrap();
assert_ne!(mutated.commitment(), base_commitment, "O not bound");
let mut mutated = base;
mutated.stack_inputs = StackInputs::new(&[Felt::new_unchecked(7)]).unwrap();
mutated.stack_outputs =
StackOutputs::new(&[Felt::new_unchecked(5), Felt::new_unchecked(6)]).unwrap();
assert_ne!(mutated.commitment(), base_commitment, "I/O order not bound");
let elements = base_elements;
assert_ne!(
base_commitment,
hasher::hash_elements(&elements),
"claim commitment must differ from the untagged hash"
);
assert_ne!(
base_commitment,
hasher::hash_elements_in_domain(&elements, KERNEL_DOMAIN_TAG),
"claim commitment must differ from a kernel-tagged hash of the same data"
);
assert_eq!(
CLAIM_DOMAIN_TAG.as_canonical_u64(),
(u64::from(EXECUTION_CLAIM_DOMAIN_ID) << 8) | 1
);
}
}