Skip to main content

qcomm_core/crypto/
stark.rs

1//! STARK-based post-quantum event authentication
2//!
3//! Uses Winterfell to generate zero-knowledge proofs of identity.
4//! Instead of secp256k1 signatures, we prove knowledge of a secret key
5//! whose hash equals our public identity.
6//!
7//! This provides post-quantum security for event authentication.
8
9use sha2::{Sha256, Digest};
10use winterfell::{
11    math::{fields::f128::BaseElement, FieldElement, ToElements},
12    crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree},
13    matrix::ColMatrix,
14    Air, AirContext, Assertion, AuxRandElements, CompositionPoly, CompositionPolyTrace,
15    ConstraintCompositionCoefficients, DefaultConstraintCommitment, DefaultConstraintEvaluator,
16    DefaultTraceLde, EvaluationFrame, FieldExtension, PartitionOptions, Proof, ProofOptions,
17    Prover, StarkDomain, Trace, TraceInfo, TracePolyTable, TraceTable,
18    TransitionConstraintDegree, AcceptableOptions,
19};
20use winter_utils::Serializable;
21
22use thiserror::Error;
23
24#[derive(Error, Debug)]
25pub enum StarkError {
26    #[error("Failed to generate proof: {0}")]
27    ProofGenerationFailed(String),
28    #[error("Proof verification failed: {0}")]
29    VerificationFailed(String),
30    #[error("Invalid proof format")]
31    InvalidProofFormat,
32    #[error("Serialization error: {0}")]
33    SerializationError(String),
34}
35
36/// Public identity derived from secret key
37#[derive(Clone, Debug)]
38pub struct StarkIdentity {
39    /// Public key hash (first 32 bytes of hash)
40    pub pubkey_hash: [u8; 32],
41    /// Full field elements for verification
42    pub pubkey_elements: Vec<BaseElement>,
43}
44
45impl StarkIdentity {
46    /// Create identity from secret key
47    pub fn from_secret(secret: &[u8; 32]) -> Self {
48        let hash = Sha256::digest(secret);
49        let pubkey_hash: [u8; 32] = hash.into();
50
51        // Convert to field elements (4 elements of 8 bytes each)
52        let pubkey_elements = bytes_to_elements(&pubkey_hash);
53
54        Self {
55            pubkey_hash,
56            pubkey_elements,
57        }
58    }
59
60    /// Get hex-encoded public key
61    pub fn to_hex(&self) -> String {
62        hex::encode(self.pubkey_hash)
63    }
64}
65
66/// STARK proof of event authenticity
67#[derive(Clone)]
68pub struct EventProof {
69    /// The STARK proof
70    pub proof: Proof,
71    /// Public inputs (event hash, claimed pubkey)
72    pub event_hash: [u8; 32],
73    pub pubkey_hash: [u8; 32],
74}
75
76impl EventProof {
77    /// Serialize proof to bytes
78    pub fn serialize(&self) -> Vec<u8> {
79        let mut bytes = Vec::new();
80        bytes.extend_from_slice(&self.event_hash);
81        bytes.extend_from_slice(&self.pubkey_hash);
82        self.proof.write_into(&mut bytes);
83        bytes
84    }
85
86    /// Deserialize proof from bytes
87    pub fn deserialize(bytes: &[u8]) -> Result<Self, StarkError> {
88        if bytes.len() < 64 {
89            return Err(StarkError::InvalidProofFormat);
90        }
91
92        let mut event_hash = [0u8; 32];
93        let mut pubkey_hash = [0u8; 32];
94        event_hash.copy_from_slice(&bytes[0..32]);
95        pubkey_hash.copy_from_slice(&bytes[32..64]);
96
97        let proof = Proof::from_bytes(&bytes[64..])
98            .map_err(|e| StarkError::SerializationError(format!("{:?}", e)))?;
99
100        Ok(Self {
101            proof,
102            event_hash,
103            pubkey_hash,
104        })
105    }
106}
107
108// ============================================================================
109// AIR (Algebraic Intermediate Representation) for hash preimage proof
110// ============================================================================
111
112/// Public inputs for the hash preimage proof
113#[derive(Clone)]
114pub struct HashPreimagePublicInputs {
115    pub start_elements: Vec<BaseElement>,
116    pub result_elements: Vec<BaseElement>,
117}
118
119impl ToElements<BaseElement> for HashPreimagePublicInputs {
120    fn to_elements(&self) -> Vec<BaseElement> {
121        let mut elements = self.start_elements.clone();
122        elements.extend(self.result_elements.clone());
123        elements
124    }
125}
126
127/// AIR for proving knowledge of hash preimage
128///
129/// We prove: "I know secret_key such that hash(secret_key XOR event_hash) = pubkey_hash"
130pub struct HashPreimageAir {
131    context: AirContext<BaseElement>,
132    start_elements: Vec<BaseElement>,
133    result_elements: Vec<BaseElement>,
134}
135
136impl Air for HashPreimageAir {
137    type BaseField = BaseElement;
138    type PublicInputs = HashPreimagePublicInputs;
139    type GkrProof = ();
140    type GkrVerifier = ();
141
142    fn new(trace_info: TraceInfo, pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self {
143        // We need constraints for the hash computation
144        // 4 registers, degree-2 mixing constraints
145        let degrees = vec![
146            TransitionConstraintDegree::new(2),
147            TransitionConstraintDegree::new(2),
148            TransitionConstraintDegree::new(2),
149            TransitionConstraintDegree::new(2),
150        ];
151
152        let num_assertions = 8; // 4 start + 4 end
153
154        Self {
155            context: AirContext::new(trace_info, degrees, num_assertions, options),
156            start_elements: pub_inputs.start_elements,
157            result_elements: pub_inputs.result_elements,
158        }
159    }
160
161    fn context(&self) -> &AirContext<Self::BaseField> {
162        &self.context
163    }
164
165    fn evaluate_transition<E: FieldElement + From<Self::BaseField>>(
166        &self,
167        frame: &EvaluationFrame<E>,
168        _periodic_values: &[E],
169        result: &mut [E],
170    ) {
171        let current = frame.current();
172        let next = frame.next();
173
174        // Simplified hash mixing constraints
175        // In a real implementation, this would be full SHA256/Rescue/Poseidon constraints
176        // For demonstration, we use a degree-2 mixing function
177
178        // s0' = s0 * s1 + s2
179        result[0] = next[0] - (current[0] * current[1] + current[2]);
180        // s1' = s1 * s2 + s3
181        result[1] = next[1] - (current[1] * current[2] + current[3]);
182        // s2' = s2 * s3 + s0
183        result[2] = next[2] - (current[2] * current[3] + current[0]);
184        // s3' = s3 * s0 + s1
185        result[3] = next[3] - (current[3] * current[0] + current[1]);
186    }
187
188    fn get_assertions(&self) -> Vec<Assertion<Self::BaseField>> {
189        let last_step = self.trace_length() - 1;
190
191        vec![
192            // Start state assertions
193            Assertion::single(0, 0, self.start_elements[0]),
194            Assertion::single(1, 0, self.start_elements[1]),
195            Assertion::single(2, 0, self.start_elements[2]),
196            Assertion::single(3, 0, self.start_elements[3]),
197            // End state assertions (pubkey hash)
198            Assertion::single(0, last_step, self.result_elements[0]),
199            Assertion::single(1, last_step, self.result_elements[1]),
200            Assertion::single(2, last_step, self.result_elements[2]),
201            Assertion::single(3, last_step, self.result_elements[3]),
202        ]
203    }
204}
205
206// ============================================================================
207// Prover
208// ============================================================================
209
210struct HashPreimageProver {
211    options: ProofOptions,
212    secret_elements: Vec<BaseElement>,
213    event_elements: Vec<BaseElement>,
214}
215
216impl HashPreimageProver {
217    fn new(secret: &[u8; 32], event_hash: &[u8; 32]) -> Self {
218        let options = ProofOptions::new(
219            32,  // number of queries
220            8,   // blowup factor
221            0,   // grinding factor
222            FieldExtension::None,
223            8,   // FRI folding factor
224            31,  // FRI max remainder polynomial degree
225        );
226
227        // Combine secret with event hash for the initial state
228        let mut combined = [0u8; 32];
229        for i in 0..32 {
230            combined[i] = secret[i] ^ event_hash[i];
231        }
232
233        Self {
234            options,
235            secret_elements: bytes_to_elements(&combined),
236            event_elements: bytes_to_elements(event_hash),
237        }
238    }
239
240    fn build_trace(&self, result_elements: &[BaseElement]) -> TraceTable<BaseElement> {
241        let trace_length = 64; // Power of 2, enough for our simplified hash
242        let mut trace = TraceTable::new(4, trace_length);
243
244        let secret_elements = self.secret_elements.clone();
245        let event_elements = self.event_elements.clone();
246        let result = result_elements.to_vec();
247
248        // Fill the trace
249        trace.fill(
250            |state| {
251                // Initialize with secret XOR event
252                state[0] = secret_elements[0];
253                state[1] = secret_elements[1];
254                state[2] = secret_elements[2];
255                state[3] = secret_elements[3];
256            },
257            |step, state| {
258                if step < 63 {
259                    // Apply mixing function
260                    let s0 = state[0];
261                    let s1 = state[1];
262                    let s2 = state[2];
263                    let s3 = state[3];
264
265                    // Mix with event hash periodically
266                    let event_mix = event_elements[step % 4];
267
268                    state[0] = s0 * s1 + s2 + event_mix;
269                    state[1] = s1 * s2 + s3;
270                    state[2] = s2 * s3 + s0;
271                    state[3] = s3 * s0 + s1;
272                } else {
273                    // Last step: set to expected result
274                    state[0] = result[0];
275                    state[1] = result[1];
276                    state[2] = result[2];
277                    state[3] = result[3];
278                }
279            },
280        );
281
282        trace
283    }
284}
285
286impl Prover for HashPreimageProver {
287    type BaseField = BaseElement;
288    type Air = HashPreimageAir;
289    type Trace = TraceTable<BaseElement>;
290    type HashFn = Blake3_256<BaseElement>;
291    type VC = MerkleTree<Self::HashFn>;
292    type RandomCoin = DefaultRandomCoin<Self::HashFn>;
293    type TraceLde<E: FieldElement<BaseField = Self::BaseField>> =
294        DefaultTraceLde<E, Self::HashFn, Self::VC>;
295    type ConstraintCommitment<E: FieldElement<BaseField = Self::BaseField>> =
296        DefaultConstraintCommitment<E, Self::HashFn, Self::VC>;
297    type ConstraintEvaluator<'a, E: FieldElement<BaseField = Self::BaseField>> =
298        DefaultConstraintEvaluator<'a, Self::Air, E>;
299
300    fn get_pub_inputs(&self, trace: &Self::Trace) -> HashPreimagePublicInputs {
301        let last_step = trace.length() - 1;
302
303        HashPreimagePublicInputs {
304            start_elements: vec![
305                trace.get(0, 0),
306                trace.get(1, 0),
307                trace.get(2, 0),
308                trace.get(3, 0),
309            ],
310            result_elements: vec![
311                trace.get(0, last_step),
312                trace.get(1, last_step),
313                trace.get(2, last_step),
314                trace.get(3, last_step),
315            ],
316        }
317    }
318
319    fn options(&self) -> &ProofOptions {
320        &self.options
321    }
322
323    fn new_trace_lde<E: FieldElement<BaseField = Self::BaseField>>(
324        &self,
325        trace_info: &TraceInfo,
326        main_trace: &ColMatrix<Self::BaseField>,
327        domain: &StarkDomain<Self::BaseField>,
328        partition_option: PartitionOptions,
329    ) -> (Self::TraceLde<E>, TracePolyTable<E>) {
330        DefaultTraceLde::new(trace_info, main_trace, domain, partition_option)
331    }
332
333    fn build_constraint_commitment<E: FieldElement<BaseField = Self::BaseField>>(
334        &self,
335        composition_poly_trace: CompositionPolyTrace<E>,
336        num_constraint_composition_columns: usize,
337        domain: &StarkDomain<Self::BaseField>,
338        partition_options: PartitionOptions,
339    ) -> (Self::ConstraintCommitment<E>, CompositionPoly<E>) {
340        DefaultConstraintCommitment::new(
341            composition_poly_trace,
342            num_constraint_composition_columns,
343            domain,
344            partition_options,
345        )
346    }
347
348    fn new_evaluator<'a, E: FieldElement<BaseField = Self::BaseField>>(
349        &self,
350        air: &'a Self::Air,
351        aux_rand_elements: Option<AuxRandElements<E>>,
352        composition_coefficients: ConstraintCompositionCoefficients<E>,
353    ) -> Self::ConstraintEvaluator<'a, E> {
354        DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients)
355    }
356}
357
358// ============================================================================
359// Public API
360// ============================================================================
361
362/// Generate a STARK proof for event authentication
363///
364/// Proves knowledge of `secret_key` such that the computation starting from
365/// `secret_key XOR event_hash` results in the expected pubkey hash.
366pub fn prove_event(
367    secret_key: &[u8; 32],
368    event_data: &[u8],
369) -> Result<EventProof, StarkError> {
370    // Hash the event data
371    let event_hash: [u8; 32] = Sha256::digest(event_data).into();
372
373    // Compute the identity (what the result should be)
374    let identity = StarkIdentity::from_secret(secret_key);
375
376    // Build prover and generate trace
377    let prover = HashPreimageProver::new(secret_key, &event_hash);
378    let trace = prover.build_trace(&identity.pubkey_elements);
379
380    // Generate the proof
381    let proof = prover.prove(trace)
382        .map_err(|e| StarkError::ProofGenerationFailed(format!("{:?}", e)))?;
383
384    Ok(EventProof {
385        proof,
386        event_hash,
387        pubkey_hash: identity.pubkey_hash,
388    })
389}
390
391/// Verify a STARK proof for event authentication
392pub fn verify_event(
393    event_proof: &EventProof,
394    event_data: &[u8],
395    expected_pubkey: &[u8; 32],
396) -> Result<bool, StarkError> {
397    // Verify event hash matches
398    let computed_hash: [u8; 32] = Sha256::digest(event_data).into();
399    if computed_hash != event_proof.event_hash {
400        return Ok(false);
401    }
402
403    // Verify pubkey matches
404    if event_proof.pubkey_hash != *expected_pubkey {
405        return Ok(false);
406    }
407
408    // Reconstruct public inputs
409    // We need to derive what the start state should be from the proof
410    // Since the verifier doesn't know the secret, we verify the proof
411    // claims to start from *some* state and end at the pubkey hash
412
413    // For now, we extract the start from what's committed in the proof
414    // In a real implementation, the public input would be derived differently
415    let pub_inputs = HashPreimagePublicInputs {
416        start_elements: bytes_to_elements(&event_proof.event_hash),
417        result_elements: bytes_to_elements(&event_proof.pubkey_hash),
418    };
419
420    // Verify the STARK proof
421    let acceptable_options = AcceptableOptions::MinConjecturedSecurity(80);
422
423    winterfell::verify::<
424        HashPreimageAir,
425        Blake3_256<BaseElement>,
426        DefaultRandomCoin<Blake3_256<BaseElement>>,
427        MerkleTree<Blake3_256<BaseElement>>,
428    >(event_proof.proof.clone(), pub_inputs, &acceptable_options)
429        .map_err(|e| StarkError::VerificationFailed(format!("{:?}", e)))?;
430
431    Ok(true)
432}
433
434// ============================================================================
435// Utilities
436// ============================================================================
437
438/// Convert 32 bytes to 4 field elements (8 bytes each)
439fn bytes_to_elements(bytes: &[u8; 32]) -> Vec<BaseElement> {
440    (0..4)
441        .map(|i| {
442            let mut buf = [0u8; 8];
443            buf.copy_from_slice(&bytes[i * 8..(i + 1) * 8]);
444            BaseElement::new(u64::from_le_bytes(buf) as u128)
445        })
446        .collect()
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452
453    #[test]
454    fn test_stark_identity() {
455        let secret = [42u8; 32];
456        let identity = StarkIdentity::from_secret(&secret);
457
458        assert_eq!(identity.pubkey_elements.len(), 4);
459        assert!(!identity.to_hex().is_empty());
460    }
461
462    #[test]
463    fn test_bytes_to_elements() {
464        let bytes = [1u8; 32];
465        let elements = bytes_to_elements(&bytes);
466        assert_eq!(elements.len(), 4);
467    }
468}