Skip to main content

kaspa_txscript/zk_precompiles/tests/
mod.rs

1pub mod helpers;
2
3#[cfg(test)]
4mod fast_zk_tests {
5    use super::helpers::{
6        Groth16Fields, R0Fields, build_groth_script, build_groth_script_from_fields, build_stark_script, execute_p2sh_script,
7        execute_zk_script, load_groth_fields, load_stark_fields,
8    };
9    use crate::{
10        caches::Cache,
11        get_zk_script_units_upper_bound,
12        zk_precompiles::{groth16::Groth16Error, tags::ZkTag},
13    };
14    use ark_bn254::{Bn254, G1Affine};
15    use ark_groth16::{Proof, VerifyingKey};
16    use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
17    use kaspa_consensus_core::{
18        hashing::sighash::SigHashReusedValuesUnsync,
19        tx::{PopulatedTransaction, ScriptPublicKey},
20    };
21    use kaspa_txscript_errors::TxScriptError;
22    use risc0_circuit_recursion::CircuitImpl;
23    use risc0_zkp::adapter::CircuitInfo;
24
25    fn r0_script_with_seal(seal: &[u8]) -> Vec<u8> {
26        let mut fields = R0Fields::from_fixture();
27        fields.seal = seal.to_vec();
28        fields.script()
29    }
30
31    fn r0_script_with_control_digests(control_digests: &[u8]) -> Vec<u8> {
32        let mut fields = R0Fields::from_fixture();
33        fields.control_digests = control_digests.to_vec();
34        fields.script()
35    }
36
37    fn r0_script_with_control_id(control_id: &[u8]) -> Vec<u8> {
38        let mut fields = R0Fields::from_fixture();
39        fields.control_id = control_id.to_vec();
40        fields.script()
41    }
42
43    fn words_to_le_bytes(words: &[u32]) -> Vec<u8> {
44        words.iter().flat_map(|word| word.to_le_bytes()).collect()
45    }
46
47    fn expect_r0_receipt_format_err(result: Result<(), TxScriptError>, case: &str) {
48        match result {
49            Err(TxScriptError::ZkIntegrity(e)) if e == "R0: invalid receipt format" => {}
50            Err(e) => panic!("{case}: expected R0 receipt format error, got {e:?}"),
51            Ok(_) => panic!("{case}: expected R0 receipt format error, got success"),
52        }
53    }
54
55    #[derive(Copy, Clone)]
56    enum ExpectedZkError {
57        Exact(&'static str),
58        StartsWith(&'static str),
59        Groth16ArityMismatch,
60    }
61
62    type R0FailureCase = (&'static str, fn(&mut R0Fields), ExpectedZkError);
63    type Groth16FailureCase = (&'static str, fn(&mut Groth16Fields), ExpectedZkError);
64
65    fn expect_zk_err(result: Result<(), TxScriptError>, case: &str, expected: ExpectedZkError) {
66        match (result, expected) {
67            (Err(TxScriptError::ZkIntegrity(e)), ExpectedZkError::Exact(expected)) if e == expected => {}
68            (Err(TxScriptError::ZkIntegrity(e)), ExpectedZkError::StartsWith(expected)) if e.starts_with(expected) => {}
69            (Err(TxScriptError::ZkIntegrity(e)), ExpectedZkError::Groth16ArityMismatch)
70                if e == Groth16Error::ArkR1CS(ark_relations::gr1cs::SynthesisError::ArityMismatch).to_string() => {}
71            (Err(e), ExpectedZkError::Exact(expected)) => panic!("{case}: expected ZkIntegrity({expected:?}), got {e:?}"),
72            (Err(e), ExpectedZkError::StartsWith(expected)) => {
73                panic!("{case}: expected ZkIntegrity prefix {expected:?}, got {e:?}")
74            }
75            (Err(e), ExpectedZkError::Groth16ArityMismatch) => panic!("{case}: expected Groth16 arity mismatch, got {e:?}"),
76            (Ok(()), _) => panic!("{case}: expected ZkIntegrity error, got success"),
77        }
78    }
79
80    fn set_seal_word(seal: &mut [u8], index: usize, word: u32) {
81        let start = index * core::mem::size_of::<u32>();
82        seal[start..start + core::mem::size_of::<u32>()].copy_from_slice(&word.to_le_bytes());
83    }
84
85    fn truncate_one_digest(bytes: &mut Vec<u8>) {
86        bytes.truncate(bytes.len() - risc0_zkp::core::digest::DIGEST_BYTES);
87    }
88
89    fn expect_groth16_arity_mismatch(result: Result<(), TxScriptError>, case: &str) {
90        let expected = Groth16Error::ArkR1CS(ark_relations::gr1cs::SynthesisError::ArityMismatch).to_string();
91        match result {
92            Err(TxScriptError::ZkIntegrity(e)) if e == expected => {}
93            Err(e) => panic!("{case}: expected Groth16 arity mismatch, got {e:?}"),
94            Ok(_) => panic!("{case}: expected Groth16 arity mismatch, got success"),
95        }
96    }
97
98    fn serialize_vk(vk: &VerifyingKey<Bn254>) -> Vec<u8> {
99        let mut bytes = Vec::new();
100        vk.serialize_compressed(&mut bytes).expect("serialize VK");
101        bytes
102    }
103
104    fn serialize_proof(proof: &Proof<Bn254>) -> Vec<u8> {
105        let mut bytes = Vec::new();
106        proof.serialize_compressed(&mut bytes).expect("serialize proof");
107        bytes
108    }
109
110    fn set_empty_gamma_abc(fields: &mut Groth16Fields) {
111        let mut vk = VerifyingKey::<Bn254>::deserialize_compressed(&*fields.vk).expect("fixture VK must deserialize");
112        vk.gamma_abc_g1.clear();
113        fields.vk = serialize_vk(&vk);
114    }
115
116    fn set_parseable_wrong_vk(fields: &mut Groth16Fields) {
117        let mut vk = VerifyingKey::<Bn254>::deserialize_compressed(&*fields.vk).expect("fixture VK must deserialize");
118        vk.alpha_g1 = G1Affine::default();
119        fields.vk = serialize_vk(&vk);
120    }
121
122    fn set_parseable_wrong_proof(fields: &mut Groth16Fields) {
123        let mut proof = Proof::<Bn254>::deserialize_compressed(&*fields.proof).expect("fixture proof must deserialize");
124        proof.a = G1Affine::default();
125        fields.proof = serialize_proof(&proof);
126    }
127
128    #[test]
129    fn test_groth16_fast() {
130        let script = build_groth_script();
131        let cache = Cache::new(0);
132        let reused_values = SigHashReusedValuesUnsync::new();
133
134        // Verify execution
135        execute_zk_script(&script, &cache, &reused_values).unwrap();
136
137        // Verify ZK static cost estimation formula
138        let spk = ScriptPublicKey::from_vec(0, script);
139        let estimated = get_zk_script_units_upper_bound::<PopulatedTransaction, SigHashReusedValuesUnsync>(&[], &spk);
140        let expected = ZkTag::Groth16.cost();
141        assert_eq!(estimated, expected);
142    }
143
144    #[test]
145    fn verify_groth16_empty_gamma_abc_rejected() {
146        let (vk_bytes, proof, inputs) = load_groth_fields();
147        let mut vk = VerifyingKey::<ark_bn254::Bn254>::deserialize_compressed(&*vk_bytes).unwrap();
148        vk.gamma_abc_g1.clear();
149
150        let mut malformed_vk = Vec::new();
151        vk.serialize_compressed(&mut malformed_vk).unwrap();
152        let script = build_groth_script_from_fields(&malformed_vk, &proof, &inputs);
153        let cache = Cache::new(0);
154        let reused_values = SigHashReusedValuesUnsync::new();
155
156        execute_zk_script(&script, &cache, &reused_values).expect_err("malformed verifying key should fail");
157    }
158
159    #[test]
160    fn verify_groth16_missing_public_input_rejected() {
161        let (vk, proof, mut inputs) = load_groth_fields();
162        inputs.pop();
163        let script = build_groth_script_from_fields(&vk, &proof, &inputs);
164        let cache = Cache::new(0);
165        let reused_values = SigHashReusedValuesUnsync::new();
166
167        expect_groth16_arity_mismatch(execute_zk_script(&script, &cache, &reused_values), "missing public input");
168    }
169
170    #[test]
171    fn verify_groth16_extra_public_input_rejected() {
172        let (vk, proof, mut inputs) = load_groth_fields();
173        inputs.push(inputs[0].clone());
174        let script = build_groth_script_from_fields(&vk, &proof, &inputs);
175        let cache = Cache::new(0);
176        let reused_values = SigHashReusedValuesUnsync::new();
177
178        expect_groth16_arity_mismatch(execute_zk_script(&script, &cache, &reused_values), "extra public input");
179    }
180
181    #[test]
182    fn verify_groth16_failure_matrix() {
183        let cache = Cache::new(0);
184        let reused_values = SigHashReusedValuesUnsync::new();
185        let cases: &[Groth16FailureCase] = &[
186            (
187                "missing public input",
188                |fields| {
189                    fields.inputs.pop();
190                },
191                ExpectedZkError::Groth16ArityMismatch,
192            ),
193            (
194                "extra public input",
195                |fields| {
196                    fields.inputs.push(fields.inputs[0].clone());
197                },
198                ExpectedZkError::Groth16ArityMismatch,
199            ),
200            (
201                "short public input",
202                |fields| {
203                    fields.inputs[0].pop();
204                },
205                ExpectedZkError::StartsWith("Kaspa txscript error: ZK Integrity: Invalid Fr length:"),
206            ),
207            (
208                "long public input",
209                |fields| {
210                    fields.inputs[0].push(0);
211                },
212                ExpectedZkError::StartsWith("Kaspa txscript error: ZK Integrity: Invalid Fr length:"),
213            ),
214            (
215                "out of field public input",
216                |fields| {
217                    fields.inputs[0] = vec![0xff; 32];
218                },
219                ExpectedZkError::StartsWith("Kaspa txscript error: ZK Integrity: ARK serialization error:"),
220            ),
221            (
222                "truncated vk",
223                |fields| {
224                    fields.vk.pop();
225                },
226                ExpectedZkError::StartsWith("ARK serialization error:"),
227            ),
228            (
229                "trailing vk bytes",
230                |fields| {
231                    fields.vk.push(0);
232                },
233                ExpectedZkError::Exact("Groth16 verifying key has trailing bytes"),
234            ),
235            ("empty gamma_abc_g1", set_empty_gamma_abc, ExpectedZkError::Exact("Groth16 verifying key has empty gamma_abc_g1")),
236            ("wrong vk", set_parseable_wrong_vk, ExpectedZkError::Exact("Groth16 verification failed")),
237            (
238                "truncated proof",
239                |fields| {
240                    fields.proof.pop();
241                },
242                ExpectedZkError::StartsWith("ARK serialization error:"),
243            ),
244            (
245                "trailing proof bytes",
246                |fields| {
247                    fields.proof.push(0);
248                },
249                ExpectedZkError::Exact("Groth16 proof has trailing bytes"),
250            ),
251            ("wrong proof", set_parseable_wrong_proof, ExpectedZkError::Exact("Groth16 verification failed")),
252            (
253                "public input binding",
254                |fields| {
255                    fields.inputs[0][0] ^= 0x01;
256                },
257                ExpectedZkError::Exact("Groth16 verification failed"),
258            ),
259        ];
260
261        let fixture = Groth16Fields::from_fixture();
262        for (case, mutate, expected) in cases {
263            let mut fields = fixture.clone();
264            mutate(&mut fields);
265            expect_zk_err(execute_zk_script(&fields.script(), &cache, &reused_values), case, *expected);
266        }
267    }
268
269    #[test]
270    fn verify_groth16_p2sh_vk_in_redeem_script_verifies() {
271        let fields = Groth16Fields::from_fixture();
272        let (signature_script, redeem_script) = fields.p2sh_scripts();
273        let cache = Cache::new(0);
274        let reused_values = SigHashReusedValuesUnsync::new();
275
276        execute_p2sh_script(signature_script, &redeem_script, &cache, &reused_values)
277            .expect("P2SH Groth16 proof should verify with VK in redeem script");
278    }
279
280    #[test]
281    fn verify_groth16_p2sh_binding_matrix() {
282        let cache = Cache::new(0);
283        let reused_values = SigHashReusedValuesUnsync::new();
284        let cases: &[Groth16FailureCase] = &[
285            ("vk binding", set_parseable_wrong_vk, ExpectedZkError::Exact("Groth16 verification failed")),
286            ("proof binding", set_parseable_wrong_proof, ExpectedZkError::Exact("Groth16 verification failed")),
287            (
288                "public input binding",
289                |fields| {
290                    fields.inputs[0][0] ^= 0x01;
291                },
292                ExpectedZkError::Exact("Groth16 verification failed"),
293            ),
294        ];
295
296        let fixture = Groth16Fields::from_fixture();
297        for (case, mutate, expected) in cases {
298            let mut fields = fixture.clone();
299            mutate(&mut fields);
300            let (signature_script, redeem_script) = fields.p2sh_scripts();
301            expect_zk_err(execute_p2sh_script(signature_script, &redeem_script, &cache, &reused_values), case, *expected);
302        }
303    }
304
305    #[test]
306    fn test_r0_succinct_fast() {
307        let script = build_stark_script(false);
308        let cache = Cache::new(0);
309        let reused_values = SigHashReusedValuesUnsync::new();
310
311        // Verify execution
312        execute_zk_script(&script, &cache, &reused_values).unwrap();
313
314        // Verify ZK static cost estimation formula
315        let spk = ScriptPublicKey::from_vec(0, script);
316        let estimated = get_zk_script_units_upper_bound::<PopulatedTransaction, SigHashReusedValuesUnsync>(&[], &spk);
317        let expected = ZkTag::R0Succinct.cost();
318        assert_eq!(estimated, expected);
319    }
320
321    #[test]
322    fn test_r0_succinct_control_id_binding() {
323        let script = build_stark_script(true);
324        let cache = Cache::new(0);
325        let reused_values = SigHashReusedValuesUnsync::new();
326
327        // Verify execution
328        match execute_zk_script(&script, &cache, &reused_values) {
329            Ok(_) => panic!("Expected verification to fail due to broken control_id, but it succeeded"),
330            Err(e) => match e {
331                TxScriptError::ZkIntegrity(e) => {
332                    println!("Received expected ZkIntegrity error: {}", e);
333                }
334                _ => panic!("Expected ZkIntegrity error, got different error: {:?}", e),
335            },
336        }
337    }
338
339    #[test]
340    fn verify_r0_succinct_direct_failure_matrix() {
341        let cache = Cache::new(0);
342        let reused_values = SigHashReusedValuesUnsync::new();
343        let cases: &[R0FailureCase] = &[
344            (
345                "claim length",
346                |fields| {
347                    fields.claim.pop();
348                },
349                ExpectedZkError::Exact("Invalid digest length: 31"),
350            ),
351            (
352                "journal length",
353                |fields| {
354                    fields.journal.pop();
355                },
356                ExpectedZkError::Exact("Invalid digest length: 31"),
357            ),
358            (
359                "image id length",
360                |fields| {
361                    fields.image_id.pop();
362                },
363                ExpectedZkError::Exact("Invalid digest length: 31"),
364            ),
365            (
366                "control id length",
367                |fields| {
368                    fields.control_id.pop();
369                },
370                ExpectedZkError::Exact("Invalid digest length: 31"),
371            ),
372            (
373                "empty hashfn",
374                |fields| {
375                    fields.hashfn.clear();
376                },
377                ExpectedZkError::Exact("Invalid hash function encoding length: 0"),
378            ),
379            (
380                "long hashfn",
381                |fields| {
382                    fields.hashfn.push(0);
383                },
384                ExpectedZkError::Exact("Invalid hash function encoding length: 2"),
385            ),
386            (
387                "unknown hashfn",
388                |fields| {
389                    fields.hashfn[0] = 3;
390                },
391                ExpectedZkError::Exact("Invalid hash function id: 3"),
392            ),
393            (
394                "blake2b hashfn",
395                |fields| {
396                    fields.hashfn[0] = 0;
397                },
398                ExpectedZkError::Exact("Unsupported hash function: Blake2b"),
399            ),
400            (
401                "sha256 hashfn",
402                |fields| {
403                    fields.hashfn[0] = 2;
404                },
405                ExpectedZkError::Exact("Unsupported hash function: Sha256"),
406            ),
407            (
408                "unaligned seal",
409                |fields| {
410                    fields.seal.push(0);
411                },
412                ExpectedZkError::StartsWith("Invalid seal length:"),
413            ),
414            (
415                "control index length",
416                |fields| {
417                    fields.control_index.pop();
418                },
419                ExpectedZkError::Exact("Invalid merkle index length: 3"),
420            ),
421            (
422                "control digests length",
423                |fields| {
424                    fields.control_digests.push(0);
425                },
426                ExpectedZkError::StartsWith("Invalid digest list length:"),
427            ),
428            (
429                "claim binding",
430                |fields| {
431                    fields.claim[0] ^= 0x01;
432                },
433                ExpectedZkError::Exact("R0: journal digest mismatch detected"),
434            ),
435            (
436                "image id binding",
437                |fields| {
438                    fields.image_id[0] ^= 0x01;
439                },
440                ExpectedZkError::Exact("Verification failed"),
441            ),
442            (
443                "journal binding",
444                |fields| {
445                    fields.journal[0] ^= 0x01;
446                },
447                ExpectedZkError::Exact("Verification failed"),
448            ),
449            (
450                "control id binding",
451                |fields| {
452                    fields.control_id[0] ^= 0x01;
453                },
454                ExpectedZkError::StartsWith("R0: control_id mismatch:"),
455            ),
456            (
457                "control index binding",
458                |fields| {
459                    fields.control_index[0] ^= 0x01;
460                },
461                ExpectedZkError::StartsWith("R0: control_id mismatch:"),
462            ),
463            (
464                "control proof missing sibling",
465                |fields| {
466                    truncate_one_digest(&mut fields.control_digests);
467                },
468                ExpectedZkError::StartsWith("R0: control_id mismatch:"),
469            ),
470            (
471                "control proof extra sibling",
472                |fields| {
473                    fields.control_digests.extend_from_slice(&[0; risc0_zkp::core::digest::DIGEST_BYTES]);
474                },
475                ExpectedZkError::Exact("Control inclusion proof length 9 exceeds maximum 8"),
476            ),
477            (
478                "empty seal",
479                |fields| {
480                    fields.seal.clear();
481                },
482                ExpectedZkError::Exact("R0: invalid receipt format"),
483            ),
484            (
485                "short seal",
486                |fields| {
487                    fields.seal = words_to_le_bytes(&[0; CircuitImpl::OUTPUT_SIZE]);
488                },
489                ExpectedZkError::Exact("R0: invalid receipt format"),
490            ),
491            (
492                "first output elem at modulus",
493                |fields| {
494                    set_seal_word(&mut fields.seal, 0, risc0_zkp::field::baby_bear::P);
495                },
496                ExpectedZkError::Exact("R0: invalid receipt format"),
497            ),
498            (
499                "po2 elem above max",
500                |fields| {
501                    set_seal_word(&mut fields.seal, CircuitImpl::OUTPUT_SIZE, (risc0_zkp::MAX_CYCLES_PO2 + 1) as u32);
502                },
503                ExpectedZkError::Exact("R0: invalid receipt format"),
504            ),
505            (
506                "truncated proof after header",
507                |fields| {
508                    fields.seal.truncate((CircuitImpl::OUTPUT_SIZE + 1) * core::mem::size_of::<u32>());
509                },
510                ExpectedZkError::Exact("R0: invalid receipt format"),
511            ),
512            (
513                "trailing seal word",
514                |fields| {
515                    fields.seal.extend_from_slice(&0u32.to_le_bytes());
516                },
517                ExpectedZkError::Exact("R0: invalid receipt format"),
518            ),
519            (
520                "invalid seal merkle digest",
521                |fields| {
522                    set_seal_word(&mut fields.seal, CircuitImpl::OUTPUT_SIZE + 1, risc0_zkp::field::baby_bear::P);
523                },
524                ExpectedZkError::Exact("R0: invalid receipt format"),
525            ),
526        ];
527
528        let fixture = R0Fields::from_fixture();
529        for (case, mutate, expected) in cases {
530            let mut fields = fixture.clone();
531            mutate(&mut fields);
532            expect_zk_err(execute_zk_script(&fields.script(), &cache, &reused_values), case, *expected);
533        }
534    }
535
536    #[test]
537    fn verify_r0_succinct_p2sh_split_stack_verifies() {
538        let fields = R0Fields::from_fixture();
539        let (signature_script, redeem_script) = fields.p2sh_scripts();
540        let cache = Cache::new(0);
541        let reused_values = SigHashReusedValuesUnsync::new();
542
543        execute_p2sh_script(signature_script, &redeem_script, &cache, &reused_values).expect("split P2SH R0 proof should verify");
544    }
545
546    #[test]
547    fn verify_r0_succinct_p2sh_binding_matrix() {
548        let cache = Cache::new(0);
549        let reused_values = SigHashReusedValuesUnsync::new();
550        let cases: &[R0FailureCase] = &[
551            (
552                "claim binding",
553                |fields| {
554                    fields.claim[0] ^= 0x01;
555                },
556                ExpectedZkError::Exact("R0: journal digest mismatch detected"),
557            ),
558            (
559                "journal binding",
560                |fields| {
561                    fields.journal[0] ^= 0x01;
562                },
563                ExpectedZkError::Exact("Verification failed"),
564            ),
565            (
566                "image id binding",
567                |fields| {
568                    fields.image_id[0] ^= 0x01;
569                },
570                ExpectedZkError::Exact("Verification failed"),
571            ),
572            (
573                "control id binding",
574                |fields| {
575                    fields.control_id[0] ^= 0x01;
576                },
577                ExpectedZkError::StartsWith("R0: control_id mismatch:"),
578            ),
579        ];
580
581        let fixture = R0Fields::from_fixture();
582        for (case, mutate, expected) in cases {
583            let mut fields = fixture.clone();
584            mutate(&mut fields);
585            let (signature_script, redeem_script) = fields.p2sh_scripts();
586            expect_zk_err(execute_p2sh_script(signature_script, &redeem_script, &cache, &reused_values), case, *expected);
587        }
588    }
589
590    #[test]
591    fn test_r0_succinct_not_field_elem() {
592        let (_, seal, _, _, _, _, _, _) = load_stark_fields();
593        let seal_words = seal.as_chunks().0.iter().copied().map(u32::from_le_bytes).collect::<Vec<_>>();
594        let cache = Cache::new(0);
595        let reused_values = SigHashReusedValuesUnsync::new();
596        let cases = [
597            ("first output elem at modulus", 0, risc0_zkp::field::baby_bear::P),
598            ("last output elem at modulus", CircuitImpl::OUTPUT_SIZE - 1, risc0_zkp::field::baby_bear::P),
599            ("po2 elem above max", CircuitImpl::OUTPUT_SIZE, (risc0_zkp::MAX_CYCLES_PO2 + 1) as u32),
600        ];
601
602        for (case, i, invalid_word) in cases {
603            let mut seal_words = seal_words.clone();
604            seal_words[i] = invalid_word;
605            let seal = words_to_le_bytes(&seal_words);
606            let script = r0_script_with_seal(&seal);
607
608            expect_r0_receipt_format_err(execute_zk_script(&script, &cache, &reused_values), case);
609        }
610    }
611
612    #[test]
613    fn verify_r0_succinct_short_seal_rejected() {
614        let seal = words_to_le_bytes(&[0; CircuitImpl::OUTPUT_SIZE]);
615        let script = r0_script_with_seal(&seal);
616        let cache = Cache::new(0);
617        let reused_values = SigHashReusedValuesUnsync::new();
618
619        execute_zk_script(&script, &cache, &reused_values).expect_err("short seal should fail");
620    }
621
622    #[test]
623    fn verify_r0_succinct_invalid_po2_rejected() {
624        let mut seal_words = vec![0; CircuitImpl::OUTPUT_SIZE + 1];
625        seal_words[CircuitImpl::OUTPUT_SIZE] = (risc0_zkp::MAX_CYCLES_PO2 + 1) as u32;
626        let seal = words_to_le_bytes(&seal_words);
627        let script = r0_script_with_seal(&seal);
628        let cache = Cache::new(0);
629        let reused_values = SigHashReusedValuesUnsync::new();
630
631        execute_zk_script(&script, &cache, &reused_values).expect_err("invalid po2 should fail");
632    }
633
634    #[test]
635    fn verify_r0_succinct_truncated_proof_after_header_rejected() {
636        let (_, seal, _, _, _, _, _, _) = load_stark_fields();
637        let header_len = (CircuitImpl::OUTPUT_SIZE + 1) * core::mem::size_of::<u32>();
638        let script = r0_script_with_seal(&seal[..header_len]);
639        let cache = Cache::new(0);
640        let reused_values = SigHashReusedValuesUnsync::new();
641
642        execute_zk_script(&script, &cache, &reused_values).expect_err("truncated proof should fail");
643    }
644
645    #[test]
646    fn verify_r0_succinct_trailing_word_rejected() {
647        let (_, mut seal, _, _, _, _, _, _) = load_stark_fields();
648        seal.extend_from_slice(&0u32.to_le_bytes());
649        let script = r0_script_with_seal(&seal);
650        let cache = Cache::new(0);
651        let reused_values = SigHashReusedValuesUnsync::new();
652
653        execute_zk_script(&script, &cache, &reused_values).expect_err("trailing proof data should fail");
654    }
655
656    #[test]
657    fn verify_r0_succinct_invalid_control_proof_digest_rejected() {
658        let (_, _, _, _, _, mut control_digests, _, _) = load_stark_fields();
659        control_digests[..core::mem::size_of::<u32>()].copy_from_slice(&risc0_zkp::field::baby_bear::P.to_le_bytes());
660        let script = r0_script_with_control_digests(&control_digests);
661        let cache = Cache::new(0);
662        let reused_values = SigHashReusedValuesUnsync::new();
663
664        execute_zk_script(&script, &cache, &reused_values).expect_err("invalid Poseidon2 control proof digest should fail");
665    }
666
667    #[test]
668    fn verify_r0_succinct_invalid_control_id_digest_rejected() {
669        let (mut control_id, _, _, _, _, _, _, _) = load_stark_fields();
670        control_id[..core::mem::size_of::<u32>()].copy_from_slice(&risc0_zkp::field::baby_bear::P.to_le_bytes());
671        let script = r0_script_with_control_id(&control_id);
672        let cache = Cache::new(0);
673        let reused_values = SigHashReusedValuesUnsync::new();
674
675        match execute_zk_script(&script, &cache, &reused_values) {
676            Err(TxScriptError::ZkIntegrity(e)) if e.starts_with("R0: control_id mismatch:") => {}
677            Err(e) => panic!("expected R0 control_id mismatch, got {e:?}"),
678            Ok(_) => panic!("invalid Poseidon2 control id digest should fail"),
679        }
680    }
681
682    #[test]
683    fn verify_r0_succinct_invalid_seal_merkle_digest_rejected() {
684        let (_, seal, _, _, _, _, _, _) = load_stark_fields();
685        let mut seal_words = seal.as_chunks().0.iter().copied().map(u32::from_le_bytes).collect::<Vec<_>>();
686        seal_words[CircuitImpl::OUTPUT_SIZE + 1] = risc0_zkp::field::baby_bear::P;
687        let seal = words_to_le_bytes(&seal_words);
688        let script = r0_script_with_seal(&seal);
689        let cache = Cache::new(0);
690        let reused_values = SigHashReusedValuesUnsync::new();
691
692        expect_r0_receipt_format_err(execute_zk_script(&script, &cache, &reused_values), "invalid seal Merkle digest");
693    }
694
695    #[test]
696    fn verify_r0_succinct_long_control_proof_rejected() {
697        let mut fields = R0Fields::from_fixture();
698        fields.control_digests.extend_from_slice(&[0; risc0_zkp::core::digest::DIGEST_BYTES]);
699        let script = fields.script();
700        let cache = Cache::new(0);
701        let reused_values = SigHashReusedValuesUnsync::new();
702
703        expect_zk_err(
704            execute_zk_script(&script, &cache, &reused_values),
705            "long control proof",
706            ExpectedZkError::Exact("Control inclusion proof length 9 exceeds maximum 8"),
707        );
708    }
709}