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 execute_zk_script(&script, &cache, &reused_values).unwrap();
136
137 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 execute_zk_script(&script, &cache, &reused_values).unwrap();
313
314 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 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}