use sha2::{Sha256, Digest};
use winterfell::{
math::{fields::f128::BaseElement, FieldElement, ToElements},
crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree},
matrix::ColMatrix,
Air, AirContext, Assertion, AuxRandElements, CompositionPoly, CompositionPolyTrace,
ConstraintCompositionCoefficients, DefaultConstraintCommitment, DefaultConstraintEvaluator,
DefaultTraceLde, EvaluationFrame, FieldExtension, PartitionOptions, Proof, ProofOptions,
Prover, StarkDomain, Trace, TraceInfo, TracePolyTable, TraceTable,
TransitionConstraintDegree, AcceptableOptions,
};
use winter_utils::Serializable;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StarkError {
#[error("Failed to generate proof: {0}")]
ProofGenerationFailed(String),
#[error("Proof verification failed: {0}")]
VerificationFailed(String),
#[error("Invalid proof format")]
InvalidProofFormat,
#[error("Serialization error: {0}")]
SerializationError(String),
}
#[derive(Clone, Debug)]
pub struct StarkIdentity {
pub pubkey_hash: [u8; 32],
pub pubkey_elements: Vec<BaseElement>,
}
impl StarkIdentity {
pub fn from_secret(secret: &[u8; 32]) -> Self {
let hash = Sha256::digest(secret);
let pubkey_hash: [u8; 32] = hash.into();
let pubkey_elements = bytes_to_elements(&pubkey_hash);
Self {
pubkey_hash,
pubkey_elements,
}
}
pub fn to_hex(&self) -> String {
hex::encode(self.pubkey_hash)
}
}
#[derive(Clone)]
pub struct EventProof {
pub proof: Proof,
pub event_hash: [u8; 32],
pub pubkey_hash: [u8; 32],
}
impl EventProof {
pub fn serialize(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.event_hash);
bytes.extend_from_slice(&self.pubkey_hash);
self.proof.write_into(&mut bytes);
bytes
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, StarkError> {
if bytes.len() < 64 {
return Err(StarkError::InvalidProofFormat);
}
let mut event_hash = [0u8; 32];
let mut pubkey_hash = [0u8; 32];
event_hash.copy_from_slice(&bytes[0..32]);
pubkey_hash.copy_from_slice(&bytes[32..64]);
let proof = Proof::from_bytes(&bytes[64..])
.map_err(|e| StarkError::SerializationError(format!("{:?}", e)))?;
Ok(Self {
proof,
event_hash,
pubkey_hash,
})
}
}
#[derive(Clone)]
pub struct HashPreimagePublicInputs {
pub start_elements: Vec<BaseElement>,
pub result_elements: Vec<BaseElement>,
}
impl ToElements<BaseElement> for HashPreimagePublicInputs {
fn to_elements(&self) -> Vec<BaseElement> {
let mut elements = self.start_elements.clone();
elements.extend(self.result_elements.clone());
elements
}
}
pub struct HashPreimageAir {
context: AirContext<BaseElement>,
start_elements: Vec<BaseElement>,
result_elements: Vec<BaseElement>,
}
impl Air for HashPreimageAir {
type BaseField = BaseElement;
type PublicInputs = HashPreimagePublicInputs;
type GkrProof = ();
type GkrVerifier = ();
fn new(trace_info: TraceInfo, pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self {
let degrees = vec![
TransitionConstraintDegree::new(2),
TransitionConstraintDegree::new(2),
TransitionConstraintDegree::new(2),
TransitionConstraintDegree::new(2),
];
let num_assertions = 8;
Self {
context: AirContext::new(trace_info, degrees, num_assertions, options),
start_elements: pub_inputs.start_elements,
result_elements: pub_inputs.result_elements,
}
}
fn context(&self) -> &AirContext<Self::BaseField> {
&self.context
}
fn evaluate_transition<E: FieldElement + From<Self::BaseField>>(
&self,
frame: &EvaluationFrame<E>,
_periodic_values: &[E],
result: &mut [E],
) {
let current = frame.current();
let next = frame.next();
result[0] = next[0] - (current[0] * current[1] + current[2]);
result[1] = next[1] - (current[1] * current[2] + current[3]);
result[2] = next[2] - (current[2] * current[3] + current[0]);
result[3] = next[3] - (current[3] * current[0] + current[1]);
}
fn get_assertions(&self) -> Vec<Assertion<Self::BaseField>> {
let last_step = self.trace_length() - 1;
vec![
Assertion::single(0, 0, self.start_elements[0]),
Assertion::single(1, 0, self.start_elements[1]),
Assertion::single(2, 0, self.start_elements[2]),
Assertion::single(3, 0, self.start_elements[3]),
Assertion::single(0, last_step, self.result_elements[0]),
Assertion::single(1, last_step, self.result_elements[1]),
Assertion::single(2, last_step, self.result_elements[2]),
Assertion::single(3, last_step, self.result_elements[3]),
]
}
}
struct HashPreimageProver {
options: ProofOptions,
secret_elements: Vec<BaseElement>,
event_elements: Vec<BaseElement>,
}
impl HashPreimageProver {
fn new(secret: &[u8; 32], event_hash: &[u8; 32]) -> Self {
let options = ProofOptions::new(
32, 8, 0, FieldExtension::None,
8, 31, );
let mut combined = [0u8; 32];
for i in 0..32 {
combined[i] = secret[i] ^ event_hash[i];
}
Self {
options,
secret_elements: bytes_to_elements(&combined),
event_elements: bytes_to_elements(event_hash),
}
}
fn build_trace(&self, result_elements: &[BaseElement]) -> TraceTable<BaseElement> {
let trace_length = 64; let mut trace = TraceTable::new(4, trace_length);
let secret_elements = self.secret_elements.clone();
let event_elements = self.event_elements.clone();
let result = result_elements.to_vec();
trace.fill(
|state| {
state[0] = secret_elements[0];
state[1] = secret_elements[1];
state[2] = secret_elements[2];
state[3] = secret_elements[3];
},
|step, state| {
if step < 63 {
let s0 = state[0];
let s1 = state[1];
let s2 = state[2];
let s3 = state[3];
let event_mix = event_elements[step % 4];
state[0] = s0 * s1 + s2 + event_mix;
state[1] = s1 * s2 + s3;
state[2] = s2 * s3 + s0;
state[3] = s3 * s0 + s1;
} else {
state[0] = result[0];
state[1] = result[1];
state[2] = result[2];
state[3] = result[3];
}
},
);
trace
}
}
impl Prover for HashPreimageProver {
type BaseField = BaseElement;
type Air = HashPreimageAir;
type Trace = TraceTable<BaseElement>;
type HashFn = Blake3_256<BaseElement>;
type VC = MerkleTree<Self::HashFn>;
type RandomCoin = DefaultRandomCoin<Self::HashFn>;
type TraceLde<E: FieldElement<BaseField = Self::BaseField>> =
DefaultTraceLde<E, Self::HashFn, Self::VC>;
type ConstraintCommitment<E: FieldElement<BaseField = Self::BaseField>> =
DefaultConstraintCommitment<E, Self::HashFn, Self::VC>;
type ConstraintEvaluator<'a, E: FieldElement<BaseField = Self::BaseField>> =
DefaultConstraintEvaluator<'a, Self::Air, E>;
fn get_pub_inputs(&self, trace: &Self::Trace) -> HashPreimagePublicInputs {
let last_step = trace.length() - 1;
HashPreimagePublicInputs {
start_elements: vec![
trace.get(0, 0),
trace.get(1, 0),
trace.get(2, 0),
trace.get(3, 0),
],
result_elements: vec![
trace.get(0, last_step),
trace.get(1, last_step),
trace.get(2, last_step),
trace.get(3, last_step),
],
}
}
fn options(&self) -> &ProofOptions {
&self.options
}
fn new_trace_lde<E: FieldElement<BaseField = Self::BaseField>>(
&self,
trace_info: &TraceInfo,
main_trace: &ColMatrix<Self::BaseField>,
domain: &StarkDomain<Self::BaseField>,
partition_option: PartitionOptions,
) -> (Self::TraceLde<E>, TracePolyTable<E>) {
DefaultTraceLde::new(trace_info, main_trace, domain, partition_option)
}
fn build_constraint_commitment<E: FieldElement<BaseField = Self::BaseField>>(
&self,
composition_poly_trace: CompositionPolyTrace<E>,
num_constraint_composition_columns: usize,
domain: &StarkDomain<Self::BaseField>,
partition_options: PartitionOptions,
) -> (Self::ConstraintCommitment<E>, CompositionPoly<E>) {
DefaultConstraintCommitment::new(
composition_poly_trace,
num_constraint_composition_columns,
domain,
partition_options,
)
}
fn new_evaluator<'a, E: FieldElement<BaseField = Self::BaseField>>(
&self,
air: &'a Self::Air,
aux_rand_elements: Option<AuxRandElements<E>>,
composition_coefficients: ConstraintCompositionCoefficients<E>,
) -> Self::ConstraintEvaluator<'a, E> {
DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients)
}
}
pub fn prove_event(
secret_key: &[u8; 32],
event_data: &[u8],
) -> Result<EventProof, StarkError> {
let event_hash: [u8; 32] = Sha256::digest(event_data).into();
let identity = StarkIdentity::from_secret(secret_key);
let prover = HashPreimageProver::new(secret_key, &event_hash);
let trace = prover.build_trace(&identity.pubkey_elements);
let proof = prover.prove(trace)
.map_err(|e| StarkError::ProofGenerationFailed(format!("{:?}", e)))?;
Ok(EventProof {
proof,
event_hash,
pubkey_hash: identity.pubkey_hash,
})
}
pub fn verify_event(
event_proof: &EventProof,
event_data: &[u8],
expected_pubkey: &[u8; 32],
) -> Result<bool, StarkError> {
let computed_hash: [u8; 32] = Sha256::digest(event_data).into();
if computed_hash != event_proof.event_hash {
return Ok(false);
}
if event_proof.pubkey_hash != *expected_pubkey {
return Ok(false);
}
let pub_inputs = HashPreimagePublicInputs {
start_elements: bytes_to_elements(&event_proof.event_hash),
result_elements: bytes_to_elements(&event_proof.pubkey_hash),
};
let acceptable_options = AcceptableOptions::MinConjecturedSecurity(80);
winterfell::verify::<
HashPreimageAir,
Blake3_256<BaseElement>,
DefaultRandomCoin<Blake3_256<BaseElement>>,
MerkleTree<Blake3_256<BaseElement>>,
>(event_proof.proof.clone(), pub_inputs, &acceptable_options)
.map_err(|e| StarkError::VerificationFailed(format!("{:?}", e)))?;
Ok(true)
}
fn bytes_to_elements(bytes: &[u8; 32]) -> Vec<BaseElement> {
(0..4)
.map(|i| {
let mut buf = [0u8; 8];
buf.copy_from_slice(&bytes[i * 8..(i + 1) * 8]);
BaseElement::new(u64::from_le_bytes(buf) as u128)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stark_identity() {
let secret = [42u8; 32];
let identity = StarkIdentity::from_secret(&secret);
assert_eq!(identity.pubkey_elements.len(), 4);
assert!(!identity.to_hex().is_empty());
}
#[test]
fn test_bytes_to_elements() {
let bytes = [1u8; 32];
let elements = bytes_to_elements(&bytes);
assert_eq!(elements.len(), 4);
}
}