prover.rs (27376B)
1 use std::sync::atomic::{AtomicBool, Ordering}; 2 3 use kyn_vdf::{Form, KynVdfError, get_b}; 4 use num_bigint::{BigInt, BigUint}; 5 use num_traits::{One, ToPrimitive}; 6 7 use super::{VdfProgressPhase, limb_arithmetic}; 8 9 // Keep peak prover memory bounded. Larger parameter sets retain the old 10 // constant-memory algorithm instead of attempting an attacker-sized allocation. 11 const MAX_CHECKPOINTS: u64 = 262_144; 12 const MAX_BUCKETS: u64 = 65_536; 13 const INVALID_BUCKET: usize = usize::MAX; 14 15 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 16 struct ProofParameters { 17 k: u32, 18 l: u64, 19 checkpoint_count: u64, 20 bucket_count: u64, 21 } 22 23 #[derive(Clone, Copy)] 24 struct ClassGroup<'a> { 25 discriminant: &'a BigInt, 26 threshold: &'a BigInt, 27 } 28 29 struct CheckpointProofInput<'a> { 30 generator: &'a Form, 31 output: &'a Form, 32 checkpoints: &'a [limb_arithmetic::LimbForm], 33 rounds: u64, 34 parameters: ProofParameters, 35 } 36 37 #[cfg(test)] 38 pub(super) fn prove( 39 discriminant: &BigInt, 40 generator: &Form, 41 threshold: &BigInt, 42 rounds: u64, 43 progress: impl FnMut(VdfProgressPhase, u64), 44 ) -> Result<(Form, Form), KynVdfError> { 45 let cancelled = AtomicBool::new(false); 46 prove_cancellable( 47 discriminant, 48 generator, 49 threshold, 50 rounds, 51 progress, 52 &cancelled, 53 )? 54 .ok_or_else(|| arithmetic_error("non-cancellable VDF was cancelled")) 55 } 56 57 pub(super) fn prove_cancellable( 58 discriminant: &BigInt, 59 generator: &Form, 60 threshold: &BigInt, 61 rounds: u64, 62 progress: impl FnMut(VdfProgressPhase, u64), 63 cancelled: &AtomicBool, 64 ) -> Result<Option<(Form, Form)>, KynVdfError> { 65 let group = ClassGroup { 66 discriminant, 67 threshold, 68 }; 69 let parameters = ProofParameters::for_rounds(rounds); 70 if parameters.checkpoint_count > MAX_CHECKPOINTS || parameters.bucket_count > MAX_BUCKETS { 71 return prove_constant_memory_cancellable( 72 discriminant, 73 generator, 74 threshold, 75 rounds, 76 progress, 77 cancelled, 78 ); 79 } 80 81 prove_checkpointed_cancellable(group, generator, rounds, parameters, progress, cancelled) 82 } 83 84 fn prove_checkpointed_cancellable( 85 group: ClassGroup<'_>, 86 generator: &Form, 87 rounds: u64, 88 parameters: ProofParameters, 89 mut progress: impl FnMut(VdfProgressPhase, u64), 90 cancelled: &AtomicBool, 91 ) -> Result<Option<(Form, Form)>, KynVdfError> { 92 let checkpoint_capacity = usize::try_from(parameters.checkpoint_count) 93 .map_err(|_| arithmetic_error("checkpoint count does not fit in memory"))?; 94 let checkpoint_stride = u64::from(parameters.k) 95 .checked_mul(parameters.l) 96 .ok_or_else(|| arithmetic_error("checkpoint stride overflow"))?; 97 let limb_discriminant = limb_arithmetic::to_limb(group.discriminant); 98 let limb_threshold = limb_arithmetic::to_limb(group.threshold); 99 let mut checkpoints = Vec::with_capacity(checkpoint_capacity); 100 let mut output = limb_arithmetic::LimbForm::from_form(generator); 101 let mut output_scratch = limb_arithmetic::LimbFormScratch::default(); 102 for completed_rounds in 1..=rounds { 103 if cancelled.load(Ordering::Relaxed) { 104 return Ok(None); 105 } 106 if (completed_rounds - 1) % checkpoint_stride == 0 { 107 checkpoints.push(output.clone()); 108 } 109 output = output.nudupl_reduce_with_scratch( 110 &limb_discriminant, 111 &limb_threshold, 112 &mut output_scratch, 113 ); 114 progress(VdfProgressPhase::Output, completed_rounds); 115 } 116 117 let output = output.into_form(); 118 debug_assert_eq!(checkpoints.len(), checkpoint_capacity); 119 let Some(proof) = generate_checkpoint_proof_cancellable( 120 group, 121 CheckpointProofInput { 122 generator, 123 output: &output, 124 checkpoints: &checkpoints, 125 rounds, 126 parameters, 127 }, 128 &mut progress, 129 cancelled, 130 )? 131 else { 132 return Ok(None); 133 }; 134 Ok(Some((output, proof))) 135 } 136 137 fn generate_checkpoint_proof_cancellable( 138 group: ClassGroup<'_>, 139 input: CheckpointProofInput<'_>, 140 progress: &mut impl FnMut(VdfProgressPhase, u64), 141 cancelled: &AtomicBool, 142 ) -> Result<Option<Form>, KynVdfError> { 143 let CheckpointProofInput { 144 generator, 145 output, 146 checkpoints, 147 rounds, 148 parameters, 149 } = input; 150 let challenge = get_b(group.discriminant, generator, output)?; 151 let bucket_count = usize::try_from(parameters.bucket_count) 152 .map_err(|_| arithmetic_error("bucket count does not fit in memory"))?; 153 let k0 = parameters.k - parameters.k / 2; 154 let k1 = parameters.k / 2; 155 let row_count = 1_u64 << k1; 156 let column_count = 1_u64 << k0; 157 let work_per_pass = parameters 158 .checkpoint_count 159 .checked_add(row_count) 160 .and_then(|value| value.checked_add(column_count)) 161 .ok_or_else(|| arithmetic_error("proof progress calculation overflow"))?; 162 let total_work = parameters 163 .l 164 .checked_mul(work_per_pass) 165 .ok_or_else(|| arithmetic_error("proof progress calculation overflow"))?; 166 let mut completed_work = 0_u64; 167 let limb_discriminant = limb_arithmetic::to_limb(group.discriminant); 168 let limb_threshold = limb_arithmetic::to_limb(group.threshold); 169 let mut proof = limb_arithmetic::LimbForm::identity(&limb_discriminant); 170 let mut proof_scratch = limb_arithmetic::LimbFormScratch::default(); 171 let block_step_exponent = u64::from(parameters.k) 172 .checked_mul(parameters.l) 173 .ok_or_else(|| arithmetic_error("proof block step overflow"))?; 174 let block_step = BigUint::from(2_u8).modpow(&BigUint::from(block_step_exponent), &challenge); 175 176 for j in (0..parameters.l).rev() { 177 if cancelled.load(Ordering::Relaxed) { 178 return Ok(None); 179 } 180 proof = proof.fast_pow_u64_with_scratch( 181 1_u64 << parameters.k, 182 &limb_discriminant, 183 &limb_threshold, 184 &mut proof_scratch, 185 ); 186 187 let checkpoint_blocks = 188 get_blocks_for_pass(j, parameters, rounds, &challenge, &block_step)?; 189 let mut buckets: Vec<Option<limb_arithmetic::LimbForm>> = vec![None; bucket_count]; 190 for (i, checkpoint) in checkpoints.iter().enumerate() { 191 if cancelled.load(Ordering::Relaxed) { 192 return Ok(None); 193 } 194 let bucket = checkpoint_blocks[i]; 195 if bucket != INVALID_BUCKET { 196 let bucket_form = buckets[bucket].take(); 197 buckets[bucket] = Some(match bucket_form { 198 Some(bucket_form) => bucket_form.compose_unreduced( 199 checkpoint, 200 &limb_discriminant, 201 &limb_threshold, 202 &mut proof_scratch, 203 ), 204 None => checkpoint.clone(), 205 }); 206 } 207 208 completed_work += 1; 209 report_proof_progress(progress, rounds, completed_work, total_work); 210 } 211 212 for b1 in 0..row_count { 213 if cancelled.load(Ordering::Relaxed) { 214 return Ok(None); 215 } 216 let row_start = b1 << k0; 217 let mut aggregate: Option<limb_arithmetic::LimbForm> = None; 218 for b0 in 0..column_count { 219 if let Some(bucket) = &buckets[(row_start + b0) as usize] { 220 aggregate = Some(match aggregate { 221 Some(aggregate) => aggregate.compose_unreduced( 222 bucket, 223 &limb_discriminant, 224 &limb_threshold, 225 &mut proof_scratch, 226 ), 227 None => bucket.clone(), 228 }); 229 } 230 } 231 if row_start != 0 { 232 if let Some(aggregate) = aggregate { 233 let aggregate = aggregate.fast_pow_u64_with_scratch( 234 row_start, 235 &limb_discriminant, 236 &limb_threshold, 237 &mut proof_scratch, 238 ); 239 proof = proof.compose_unreduced( 240 &aggregate, 241 &limb_discriminant, 242 &limb_threshold, 243 &mut proof_scratch, 244 ); 245 } 246 } 247 completed_work += 1; 248 report_proof_progress(progress, rounds, completed_work, total_work); 249 } 250 251 for b0 in 0..column_count { 252 if cancelled.load(Ordering::Relaxed) { 253 return Ok(None); 254 } 255 let mut aggregate: Option<limb_arithmetic::LimbForm> = None; 256 for b1 in 0..row_count { 257 if let Some(bucket) = &buckets[((b1 << k0) + b0) as usize] { 258 aggregate = Some(match aggregate { 259 Some(aggregate) => aggregate.compose_unreduced( 260 bucket, 261 &limb_discriminant, 262 &limb_threshold, 263 &mut proof_scratch, 264 ), 265 None => bucket.clone(), 266 }); 267 } 268 } 269 if b0 != 0 { 270 if let Some(aggregate) = aggregate { 271 let aggregate = aggregate.fast_pow_u64_with_scratch( 272 b0, 273 &limb_discriminant, 274 &limb_threshold, 275 &mut proof_scratch, 276 ); 277 proof = proof.compose_unreduced( 278 &aggregate, 279 &limb_discriminant, 280 &limb_threshold, 281 &mut proof_scratch, 282 ); 283 } 284 } 285 completed_work += 1; 286 report_proof_progress(progress, rounds, completed_work, total_work); 287 } 288 } 289 290 proof.reduce(); 291 progress(VdfProgressPhase::Proof, rounds); 292 Ok(Some(proof.into_form())) 293 } 294 295 fn get_blocks_for_pass( 296 j: u64, 297 parameters: ProofParameters, 298 rounds: u64, 299 challenge: &BigUint, 300 step: &BigUint, 301 ) -> Result<Vec<usize>, KynVdfError> { 302 let checkpoint_count = usize::try_from(parameters.checkpoint_count) 303 .map_err(|_| arithmetic_error("checkpoint count does not fit in memory"))?; 304 let mut blocks = vec![INVALID_BUCKET; checkpoint_count]; 305 if checkpoint_count == 0 { 306 return Ok(blocks); 307 } 308 309 let mut index = checkpoint_count - 1; 310 loop { 311 let position = checkpoint_position(index, j, parameters.l)?; 312 if rounds >= block_end(position, parameters.k)? { 313 break; 314 } 315 if index == 0 { 316 return Ok(blocks); 317 } 318 index -= 1; 319 } 320 321 let position = checkpoint_position(index, j, parameters.l)?; 322 let exponent = rounds 323 .checked_sub(block_end(position, parameters.k)?) 324 .ok_or_else(|| arithmetic_error("proof block exceeds iteration count"))?; 325 let mut residue = BigUint::from(2_u8).modpow(&BigUint::from(exponent), challenge); 326 loop { 327 blocks[index] = block_from_residue(&residue, parameters.k, challenge)?; 328 if index == 0 { 329 break; 330 } 331 index -= 1; 332 residue = (residue * step) % challenge; 333 } 334 335 Ok(blocks) 336 } 337 338 fn report_proof_progress( 339 progress: &mut impl FnMut(VdfProgressPhase, u64), 340 rounds: u64, 341 completed: u64, 342 total: u64, 343 ) { 344 let proof_rounds = completed.saturating_mul(rounds) / total.max(1); 345 progress(VdfProgressPhase::Proof, proof_rounds); 346 } 347 348 fn checkpoint_position(index: usize, j: u64, l: u64) -> Result<u64, KynVdfError> { 349 u64::try_from(index) 350 .map_err(|_| arithmetic_error("checkpoint index does not fit in u64"))? 351 .checked_mul(l) 352 .and_then(|value| value.checked_add(j)) 353 .ok_or_else(|| arithmetic_error("checkpoint position overflow")) 354 } 355 356 fn block_end(position: u64, k: u32) -> Result<u64, KynVdfError> { 357 u64::from(k) 358 .checked_mul( 359 position 360 .checked_add(1) 361 .ok_or_else(|| arithmetic_error("checkpoint position overflow"))?, 362 ) 363 .ok_or_else(|| arithmetic_error("checkpoint block overflow")) 364 } 365 366 fn block_from_residue( 367 residue: &BigUint, 368 k: u32, 369 challenge: &BigUint, 370 ) -> Result<usize, KynVdfError> { 371 let block = (residue << k) / challenge; 372 block 373 .to_usize() 374 .filter(|value| *value < (1_usize << k)) 375 .ok_or_else(|| arithmetic_error("proof block does not fit in its bucket range")) 376 } 377 378 fn prove_constant_memory_cancellable( 379 discriminant: &BigInt, 380 generator: &Form, 381 threshold: &BigInt, 382 rounds: u64, 383 mut progress: impl FnMut(VdfProgressPhase, u64), 384 cancelled: &AtomicBool, 385 ) -> Result<Option<(Form, Form)>, KynVdfError> { 386 let limb_discriminant = limb_arithmetic::to_limb(discriminant); 387 let limb_threshold = limb_arithmetic::to_limb(threshold); 388 let mut output = limb_arithmetic::LimbForm::from_form(generator); 389 let mut output_scratch = limb_arithmetic::LimbFormScratch::default(); 390 for completed_rounds in 1..=rounds { 391 if cancelled.load(Ordering::Relaxed) { 392 return Ok(None); 393 } 394 output = output.nudupl_reduce_with_scratch( 395 &limb_discriminant, 396 &limb_threshold, 397 &mut output_scratch, 398 ); 399 progress(VdfProgressPhase::Output, completed_rounds); 400 } 401 let output = output.into_form(); 402 403 let challenge = get_b(discriminant, generator, &output)?; 404 let generator_limb = limb_arithmetic::LimbForm::from_form(generator); 405 let mut proof = limb_arithmetic::LimbForm::identity(&limb_discriminant); 406 let mut proof_scratch = limb_arithmetic::LimbFormScratch::default(); 407 let mut remainder = BigUint::one() % &challenge; 408 for completed_rounds in 1..=rounds { 409 if cancelled.load(Ordering::Relaxed) { 410 return Ok(None); 411 } 412 let doubled = &remainder << 1_usize; 413 let carry = doubled >= challenge; 414 proof = proof.nudupl_reduce_with_scratch( 415 &limb_discriminant, 416 &limb_threshold, 417 &mut proof_scratch, 418 ); 419 if carry { 420 proof = proof.nucomp_reduce_with_scratch( 421 &generator_limb, 422 &limb_discriminant, 423 &limb_threshold, 424 &mut proof_scratch, 425 ); 426 } 427 remainder = doubled % &challenge; 428 progress(VdfProgressPhase::Proof, completed_rounds); 429 } 430 431 Ok(Some((output, proof.into_form()))) 432 } 433 434 #[cfg(test)] 435 fn prove_checkpointed( 436 group: ClassGroup<'_>, 437 generator: &Form, 438 rounds: u64, 439 parameters: ProofParameters, 440 progress: impl FnMut(VdfProgressPhase, u64), 441 ) -> Result<(Form, Form), KynVdfError> { 442 let cancelled = AtomicBool::new(false); 443 prove_checkpointed_cancellable(group, generator, rounds, parameters, progress, &cancelled)? 444 .ok_or_else(|| arithmetic_error("non-cancellable VDF was cancelled")) 445 } 446 447 #[cfg(test)] 448 fn generate_checkpoint_proof( 449 group: ClassGroup<'_>, 450 generator: &Form, 451 output: &Form, 452 checkpoints: &[limb_arithmetic::LimbForm], 453 rounds: u64, 454 parameters: ProofParameters, 455 progress: &mut impl FnMut(VdfProgressPhase, u64), 456 ) -> Result<Form, KynVdfError> { 457 let cancelled = AtomicBool::new(false); 458 generate_checkpoint_proof_cancellable( 459 group, 460 CheckpointProofInput { 461 generator, 462 output, 463 checkpoints, 464 rounds, 465 parameters, 466 }, 467 progress, 468 &cancelled, 469 )? 470 .ok_or_else(|| arithmetic_error("non-cancellable VDF was cancelled")) 471 } 472 473 #[cfg(test)] 474 fn prove_constant_memory( 475 discriminant: &BigInt, 476 generator: &Form, 477 threshold: &BigInt, 478 rounds: u64, 479 progress: impl FnMut(VdfProgressPhase, u64), 480 ) -> Result<(Form, Form), KynVdfError> { 481 let cancelled = AtomicBool::new(false); 482 prove_constant_memory_cancellable( 483 discriminant, 484 generator, 485 threshold, 486 rounds, 487 progress, 488 &cancelled, 489 )? 490 .ok_or_else(|| arithmetic_error("non-cancellable VDF was cancelled")) 491 } 492 493 fn arithmetic_error(message: &str) -> KynVdfError { 494 KynVdfError::ArithmeticError(message.to_owned()) 495 } 496 497 impl ProofParameters { 498 #[allow(clippy::approx_constant)] // Match Chia's published parameter heuristic. 499 fn for_rounds(rounds: u64) -> Self { 500 let log_memory = 23.253_496_66_f64; 501 let log_rounds = (rounds as f64).log2(); 502 let l = if log_rounds - log_memory > 0.000_001 { 503 2_f64.powf(log_memory - 20.0).ceil() as u64 504 } else { 505 1 506 }; 507 let intermediate = rounds as f64 * 0.693_147_1 / (2.0 * l as f64); 508 let mut k = if intermediate <= 1.0 { 509 1 510 } else { 511 (intermediate.ln() - intermediate.ln().ln() + 0.25) 512 .round() 513 .max(1.0) as u32 514 }; 515 if rounds >= 100_000 { 516 k = k.max(10); 517 } 518 Self { 519 k, 520 l, 521 checkpoint_count: rounds.div_ceil(u64::from(k).saturating_mul(l).max(1)), 522 bucket_count: 1_u64.checked_shl(k).unwrap_or(u64::MAX), 523 } 524 .fit_checkpoint_budget(rounds, MAX_CHECKPOINTS) 525 } 526 527 // k and l only select the checkpoint prover's time-memory tradeoff; they do 528 // not affect the resulting Wesolowski proof. Increasing l lets large, honest 529 // protocol workloads stay on the checkpoint path without raising the memory 530 // cap or falling back to a second round-sized sequential pass. 531 fn fit_checkpoint_budget(mut self, rounds: u64, max_checkpoints: u64) -> Self { 532 if max_checkpoints == 0 || self.checkpoint_count <= max_checkpoints { 533 return self; 534 } 535 536 let checkpoint_budget_stride = u64::from(self.k).saturating_mul(max_checkpoints).max(1); 537 self.l = self.l.max(rounds.div_ceil(checkpoint_budget_stride)); 538 let checkpoint_stride = u64::from(self.k).saturating_mul(self.l).max(1); 539 self.checkpoint_count = rounds.div_ceil(checkpoint_stride); 540 self 541 } 542 } 543 544 #[cfg(test)] 545 mod tests { 546 use std::time::Instant; 547 548 use kyn_vdf::{Form, create_discriminant, isqrt_fourth}; 549 use num_traits::Signed; 550 551 use super::{ 552 ClassGroup, ProofParameters, VdfProgressPhase, prove, prove_checkpointed, 553 prove_constant_memory, 554 }; 555 556 #[test] 557 fn parameters_match_chia_reference_values() { 558 for (rounds, k, l, checkpoints, buckets) in [ 559 (1, 1, 1, 1, 2), 560 (100, 3, 1, 34, 8), 561 (300, 3, 1, 100, 8), 562 (100_000, 10, 1, 10_000, 1_024), 563 (1_000_000, 10, 1, 100_000, 1_024), 564 (1_500_000, 11, 1, 136_364, 2_048), 565 (20_000_000, 11, 10, 181_819, 2_048), 566 ] { 567 assert_eq!( 568 ProofParameters::for_rounds(rounds), 569 ProofParameters { 570 k, 571 l, 572 checkpoint_count: checkpoints, 573 bucket_count: buckets, 574 } 575 ); 576 } 577 } 578 579 #[test] 580 fn network_default_rounds_fit_the_checkpoint_memory_budget() { 581 let rounds = u64::from(crate::app::DEFAULT_VDF_ROUNDS); 582 let parameters = ProofParameters::for_rounds(rounds); 583 584 assert_eq!( 585 parameters, 586 ProofParameters { 587 k: 12, 588 l: 22, 589 checkpoint_count: 253_788, 590 bucket_count: 4_096, 591 } 592 ); 593 assert!(parameters.checkpoint_count <= super::MAX_CHECKPOINTS); 594 } 595 596 #[test] 597 fn checkpoint_proof_matches_constant_memory_proof() { 598 let discriminant = create_discriminant(b"iuna-vdf-checkpoint-differential", 1024).unwrap(); 599 let generator = Form::generator(&discriminant).unwrap(); 600 let threshold = isqrt_fourth(&discriminant.abs()); 601 602 for rounds in [1, 2, 16, 100, 300, 1_001] { 603 let checkpoint = 604 prove(&discriminant, &generator, &threshold, rounds, |_, _| {}).unwrap(); 605 let constant_memory = 606 prove_constant_memory(&discriminant, &generator, &threshold, rounds, |_, _| {}) 607 .unwrap(); 608 609 assert_eq!(checkpoint, constant_memory, "rounds={rounds}"); 610 } 611 } 612 613 #[test] 614 fn multi_pass_checkpoint_proof_matches_constant_memory_and_reports_monotonic_progress() { 615 let rounds = 301_u64; 616 let discriminant = create_discriminant(b"iuna-vdf-checkpoint-multi-pass", 1024).unwrap(); 617 let generator = Form::generator(&discriminant).unwrap(); 618 let threshold = isqrt_fourth(&discriminant.abs()); 619 let group = ClassGroup { 620 discriminant: &discriminant, 621 threshold: &threshold, 622 }; 623 let parameters = ProofParameters { 624 k: 3, 625 l: 2, 626 checkpoint_count: rounds.div_ceil(6), 627 bucket_count: 8, 628 }; 629 let mut proof_progress = Vec::new(); 630 631 let checkpoint = 632 prove_checkpointed(group, &generator, rounds, parameters, |phase, completed| { 633 if phase == VdfProgressPhase::Proof { 634 proof_progress.push(completed); 635 } 636 }) 637 .unwrap(); 638 let constant_memory = 639 prove_constant_memory(&discriminant, &generator, &threshold, rounds, |_, _| {}) 640 .unwrap(); 641 642 assert_eq!(checkpoint, constant_memory); 643 assert!(proof_progress.windows(2).all(|pair| pair[0] <= pair[1])); 644 assert_eq!(proof_progress.last(), Some(&rounds)); 645 } 646 647 #[test] 648 fn memory_fitted_checkpoint_proof_matches_constant_memory_proof() { 649 let rounds = 301_u64; 650 let discriminant = create_discriminant(b"iuna-vdf-checkpoint-memory-fit", 1024).unwrap(); 651 let generator = Form::generator(&discriminant).unwrap(); 652 let threshold = isqrt_fourth(&discriminant.abs()); 653 let group = ClassGroup { 654 discriminant: &discriminant, 655 threshold: &threshold, 656 }; 657 let parameters = ProofParameters::for_rounds(rounds).fit_checkpoint_budget(rounds, 20); 658 659 assert_eq!(parameters.k, 3); 660 assert_eq!(parameters.l, 6); 661 assert_eq!(parameters.checkpoint_count, 17); 662 663 let checkpoint = 664 prove_checkpointed(group, &generator, rounds, parameters, |_, _| {}).unwrap(); 665 let constant_memory = 666 prove_constant_memory(&discriminant, &generator, &threshold, rounds, |_, _| {}) 667 .unwrap(); 668 669 assert_eq!(checkpoint, constant_memory); 670 } 671 672 #[test] 673 #[ignore = "manual VDF prover benchmark"] 674 fn benchmark_checkpoint_prover_against_constant_memory() { 675 let rounds = 100_000_u64; 676 let discriminant = create_discriminant(b"iuna-vdf-prover-benchmark", 1024).unwrap(); 677 let generator = Form::generator(&discriminant).unwrap(); 678 let threshold = isqrt_fourth(&discriminant.abs()); 679 680 let started = Instant::now(); 681 let checkpoint = prove(&discriminant, &generator, &threshold, rounds, |_, _| {}).unwrap(); 682 let checkpoint_elapsed = started.elapsed(); 683 let started = Instant::now(); 684 let constant_memory = 685 prove_constant_memory(&discriminant, &generator, &threshold, rounds, |_, _| {}) 686 .unwrap(); 687 let constant_memory_elapsed = started.elapsed(); 688 689 assert_eq!(checkpoint, constant_memory); 690 eprintln!( 691 "rounds={rounds} checkpoint={checkpoint_elapsed:?} constant_memory={constant_memory_elapsed:?} speedup={:.2}x", 692 constant_memory_elapsed.as_secs_f64() / checkpoint_elapsed.as_secs_f64() 693 ); 694 } 695 696 #[test] 697 #[ignore = "manual VDF phase benchmark"] 698 fn benchmark_checkpoint_prover_phases() { 699 let rounds = 100_000_u64; 700 let discriminant = create_discriminant(b"iuna-vdf-prover-benchmark", 1024).unwrap(); 701 let generator = Form::generator(&discriminant).unwrap(); 702 let threshold = isqrt_fourth(&discriminant.abs()); 703 let group = ClassGroup { 704 discriminant: &discriminant, 705 threshold: &threshold, 706 }; 707 let parameters = ProofParameters::for_rounds(rounds); 708 let checkpoint_stride = u64::from(parameters.k) * parameters.l; 709 let mut checkpoints = Vec::with_capacity(parameters.checkpoint_count as usize); 710 let limb_discriminant = crate::domain::vdf::limb_arithmetic::to_limb(&discriminant); 711 let limb_threshold = crate::domain::vdf::limb_arithmetic::to_limb(&threshold); 712 let mut output = crate::domain::vdf::limb_arithmetic::LimbForm::from_form(&generator); 713 let mut output_scratch = crate::domain::vdf::limb_arithmetic::LimbFormScratch::default(); 714 715 let started = Instant::now(); 716 for completed_rounds in 1..=rounds { 717 if (completed_rounds - 1) % checkpoint_stride == 0 { 718 checkpoints.push(output.clone()); 719 } 720 output = output.nudupl_reduce_with_scratch( 721 &limb_discriminant, 722 &limb_threshold, 723 &mut output_scratch, 724 ); 725 } 726 let output = output.into_form(); 727 let output_elapsed = started.elapsed(); 728 729 let started = Instant::now(); 730 let proof = super::generate_checkpoint_proof( 731 group, 732 &generator, 733 &output, 734 &checkpoints, 735 rounds, 736 parameters, 737 &mut |_, _| {}, 738 ) 739 .unwrap(); 740 let proof_elapsed = started.elapsed(); 741 742 assert_eq!( 743 super::prove(&discriminant, &generator, &threshold, rounds, |_, _| {}).unwrap(), 744 (output, proof) 745 ); 746 eprintln!( 747 "rounds={rounds} output={output_elapsed:?} proof={proof_elapsed:?} total={:?}", 748 output_elapsed + proof_elapsed 749 ); 750 } 751 #[test] 752 #[ignore = "manual VDF square phase benchmark"] 753 fn benchmark_output_square_phases() { 754 let rounds = 100_000; 755 let discriminant = create_discriminant(b"iuna-vdf-prover-benchmark", 1024).unwrap(); 756 let threshold = isqrt_fourth(&discriminant.abs()); 757 let mut output = Form::generator(&discriminant).unwrap(); 758 let mut nudupl_elapsed = std::time::Duration::ZERO; 759 let mut reduce_elapsed = std::time::Duration::ZERO; 760 761 for _ in 0..rounds { 762 let started = Instant::now(); 763 output = 764 crate::domain::vdf::arithmetic::nudupl_owned(output, &discriminant, &threshold); 765 nudupl_elapsed += started.elapsed(); 766 767 let started = Instant::now(); 768 crate::domain::vdf::reducer::reduce(&mut output); 769 reduce_elapsed += started.elapsed(); 770 } 771 772 assert!(output.is_reduced()); 773 eprintln!( 774 "rounds={rounds} nudupl={nudupl_elapsed:?} reduce={reduce_elapsed:?} total={:?}", 775 nudupl_elapsed + reduce_elapsed 776 ); 777 } 778 }