diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index c1a90b9..a254088 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -21,6 +21,7 @@ use craftlib::{ item::{CraftBuilder, MiningRecipe}, powpod::PowPod, predicates::ItemPredicates, + vdfpod::VdfPod, }; use plonky2::field::types::Field; use pod2::{ @@ -180,6 +181,7 @@ impl Helper { recipe: Recipe, item_def: ItemDef, input_item_pods: Vec, + vdf_pod: Option, pow_pod: Option, ) -> anyhow::Result { let prover = &Prover {}; @@ -229,7 +231,7 @@ impl Helper { CraftBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); let st_craft = match recipe { Recipe::Stone => { - // unwrap safe since if we're at Stone, pow_pod is Some + // unwrap safe since if we're at Stone, both pods are Some let pow_pod = pow_pod.unwrap(); let st_pow = pow_pod.pub_statements()[0].clone(); let main_pow_pod = MainPod { @@ -238,15 +240,47 @@ impl Helper { params: craft_builder.params.clone(), }; craft_builder.ctx.builder.add_pod(main_pow_pod); - craft_builder.st_is_stone(item_def, st_item_def.clone(), st_pow)? + + let vdf_pod = vdf_pod.unwrap(); + let st_vdf = vdf_pod.pub_statements()[0].clone(); + let main_vdf_pod = MainPod { + pod: Box::new(vdf_pod.clone()), + public_statements: vdf_pod.pub_statements(), + params: craft_builder.params.clone(), + }; + craft_builder.ctx.builder.add_pod(main_vdf_pod); + craft_builder.st_is_stone(item_def, st_item_def.clone(), st_pow, st_vdf)? + } + Recipe::Wood => { + // unwrap safe since if we're at Wood, pow_pod is Some + let pow_pod = pow_pod.unwrap(); + let st_pow = pow_pod.pub_statements()[0].clone(); + let main_pow_pod = MainPod { + pod: Box::new(pow_pod.clone()), + public_statements: pow_pod.pub_statements(), + params: craft_builder.params.clone(), + }; + craft_builder.ctx.builder.add_pod(main_pow_pod); + craft_builder.st_is_wood(item_def, st_item_def.clone(), st_pow)? + } + Recipe::Axe => { + // unwrap safe since if we're at Axe, pow_pod is Some + let pow_pod = pow_pod.unwrap(); + let st_pow = pow_pod.pub_statements()[0].clone(); + let main_pow_pod = MainPod { + pod: Box::new(pow_pod.clone()), + public_statements: pow_pod.pub_statements(), + params: craft_builder.params.clone(), + }; + craft_builder.ctx.builder.add_pod(main_pow_pod); + craft_builder.st_is_axe( + item_def, + st_item_def.clone(), + st_pow, + sts_input_craft[0].clone(), + sts_input_craft[1].clone(), + )? } - Recipe::Wood => craft_builder.st_is_wood(item_def, st_item_def.clone())?, - Recipe::Axe => craft_builder.st_is_axe( - item_def, - st_item_def.clone(), - sts_input_craft[0].clone(), - sts_input_craft[1].clone(), - )?, Recipe::WoodenAxe => craft_builder.st_is_wooden_axe( item_def, st_item_def.clone(), @@ -306,7 +340,7 @@ pub fn craft_item( let vd_set = DEFAULT_VD_SET.clone(); let key = rand_raw_value(); info!("About to craft \"{recipe}\" with key {key:#}"); - let (item_def, input_items, pow_pod) = match recipe { + let (item_def, input_items, vdf_pod, pow_pod) = match recipe { Recipe::Stone => { if !inputs.is_empty() { bail!("{recipe} takes 0 inputs"); @@ -320,16 +354,26 @@ pub fn craft_item( let pow_pod = PowPod::new( params, vd_set.clone(), - 3, // num_iters RawValue::from(ingredients_def.dict(params)?.commitment()), + STONE_MINING_MAX, )?; log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); + + let start = std::time::Instant::now(); + let vdf_pod = VdfPod::new( + params, + vd_set.clone(), + 3, // num_iters + RawValue::from(ingredients_def.dict(params)?.commitment()), + )?; + log::info!("[TIME] VdfPod proving time: {:?}", start.elapsed()); ( ItemDef { ingredients: ingredients_def.clone(), - work: pow_pod.output, + work: vdf_pod.output, }, vec![], + Some(vdf_pod), Some(pow_pod), ) } @@ -341,6 +385,15 @@ pub fn craft_item( let ingredients_def = mining_recipe .do_mining(params, key, 0, WOOD_MINING_MAX)? .unwrap(); + + let start = std::time::Instant::now(); + let pow_pod = PowPod::new( + params, + vd_set.clone(), + RawValue::from(ingredients_def.dict(params)?.commitment()), + WOOD_MINING_MAX, + )?; + log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); ( ItemDef { ingredients: ingredients_def.clone(), @@ -348,6 +401,7 @@ pub fn craft_item( }, vec![], None, + Some(pow_pod), ) } Recipe::Axe => { @@ -363,6 +417,15 @@ pub fn craft_item( let ingredients_def = mining_recipe .do_mining(params, key, 0, AXE_MINING_MAX)? .unwrap(); + + let start = std::time::Instant::now(); + let pow_pod = PowPod::new( + params, + vd_set.clone(), + RawValue::from(ingredients_def.dict(params)?.commitment()), + AXE_MINING_MAX, + )?; + log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); ( ItemDef { ingredients: ingredients_def.clone(), @@ -370,6 +433,7 @@ pub fn craft_item( }, vec![wood, stone], None, + Some(pow_pod), ) } Recipe::WoodenAxe => { @@ -392,13 +456,14 @@ pub fn craft_item( }, vec![wood1, wood2], None, + None, ) } }; let helper = Helper::new(params.clone(), vd_set); let input_item_pods: Vec<_> = input_items.iter().map(|item| &item.pod).cloned().collect(); - let pod = helper.make_item_pod(recipe, item_def.clone(), input_item_pods, pow_pod)?; + let pod = helper.make_item_pod(recipe, item_def.clone(), input_item_pods, vdf_pod, pow_pod)?; let crafted_item = CraftedItem { pod, def: item_def }; let mut file = std::fs::File::create(output)?; diff --git a/app_gui/src/crafting.rs b/app_gui/src/crafting.rs index d6f6b28..cbd1499 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -39,13 +39,15 @@ lazy_static! { description: "Stone. Hard to find.", outputs: &["Stone"], predicate: r#" -use intro Pow(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 +use intro Vdf(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 +use intro Pow(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod vd hash -IsStone(item, private: ingredients, inputs, key, work) = AND( +IsStone(item, private: ingredients, inputs, key, work, difficulty, ingredients_hash) = AND( ItemDef(item, ingredients, inputs, key, work) Equal(inputs, {}) DictContains(ingredients, "blueprint", "stone") - Pow(3, ingredients, work) + Pow(ingredients, difficulty) + Vdf(3, ingredients, work) )"#, ..Default::default() }; @@ -57,6 +59,7 @@ IsWood(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) Equal(inputs, {}) DictContains(ingredients, "blueprint", "wood") + Pow(ingredients, difficulty) )"#, ..Default::default() }; @@ -68,6 +71,7 @@ IsWood(item, private: ingredients, inputs, key, work) = AND( IsAxe(item, private: ingredients, inputs, key, work, s1, wood, stone) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "axe") + Pow(ingredients, difficulty) Equal(work, {}) // 2 ingredients @@ -136,7 +140,7 @@ IsTomato(item, private: batch, ingredients, inputs, key, work, farm_level) = AND TomatoRecipe(batch, farm_level, ingredients, inputs, key, work) ItemInBatch(item, batch, "tomato") ) - + UsedFarm(item, level, private: batch, ingredients, inputs, key, work) = AND( TomatoRecipe(batch, level ingredients, inputs, key, work) ItemInBatch(item, batch, "farm") @@ -164,7 +168,7 @@ SteelSwordRecipe(batch, ingredients, inputs, key, work, forge, steel1, steel2, w SetInsert(s3, s2, steel2) SetInsert(s4, s3, wood) SetInsert(inputs, s4, forge) - + IsForge(forge) IsSteel(steel1) IsSteel(steel2) @@ -215,7 +219,7 @@ IsH(item) = OR( IsH0(item) IsH1(item) ) - + IsO(item, private: batch, ingredients, inputs, key, work) = AND( DisassembleH2O(batch, ingredients, inputs, key, work) ItemInBatch(item, batch, "2") @@ -233,7 +237,7 @@ IsRefinedUranium(item, private: ingredients, inputs, key, work) = AND( SetInsert(inputs, {}, uranium) IsUranium(uranium) - Pow(100, ingredients, work) + Vdf(100, ingredients, work) )"#, ..Default::default() }; diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 4b967ca..5781a9a 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -36,12 +36,13 @@ impl MiningRecipe { mine_max: u64, ) -> pod2::middleware::Result> { log::info!("Mining..."); + let start = std::time::Instant::now(); for seed in start_seed..=i64::MAX { let ingredients = self.prep_ingredients(key, seed); let ingredients_hash = ingredients.hash(params)?; let mining_val = ingredients_hash.to_fields(params)[0]; if mining_val.0 <= mine_max { - log::info!("Mining complete!"); + log::info!("Mining complete! Time taken: {:?}", start.elapsed()); return Ok(Some(ingredients)); } } @@ -76,6 +77,7 @@ impl<'a> CraftBuilder<'a> { item_def: ItemDef, st_item_def: Statement, st_pow: Statement, + st_vdf: Statement, ) -> anyhow::Result { // Build IsStone(item) Ok(st_custom!(self.ctx, @@ -83,7 +85,8 @@ impl<'a> CraftBuilder<'a> { st_item_def, Equal(item_def.ingredients.inputs_set(self.params)?, EMPTY_VALUE), DictContains(item_def.ingredients.dict(self.params)?, "blueprint", STONE_BLUEPRINT), - st_pow + st_pow, + st_vdf ))?) } @@ -91,6 +94,7 @@ impl<'a> CraftBuilder<'a> { &mut self, item_def: ItemDef, st_item_def: Statement, + st_pow: Statement, ) -> anyhow::Result { // Build IsWood(item) Ok(st_custom!(self.ctx, @@ -98,6 +102,7 @@ impl<'a> CraftBuilder<'a> { st_item_def, Equal(item_def.ingredients.inputs_set(self.params)?, EMPTY_VALUE), DictContains(item_def.ingredients.dict(self.params)?, "blueprint", WOOD_BLUEPRINT), + st_pow, Equal(item_def.work, EMPTY_VALUE) ))?) } @@ -127,6 +132,7 @@ impl<'a> CraftBuilder<'a> { &mut self, item_def: ItemDef, st_item_def: Statement, + st_pow: Statement, st_is_wood: Statement, st_is_stone: Statement, ) -> anyhow::Result { @@ -136,6 +142,7 @@ impl<'a> CraftBuilder<'a> { IsAxe() = ( st_item_def, DictContains(item_def.ingredients.dict(self.params)?, "blueprint", AXE_BLUEPRINT), + st_pow, Equal(item_def.work, EMPTY_VALUE), st_axe_inputs ))?) @@ -200,9 +207,9 @@ mod tests { use super::*; use crate::{ constants::{STONE_BLUEPRINT, STONE_MINING_MAX, STONE_WORK}, - powpod::PowPod, predicates::ItemPredicates, test_util::test::mock_vd_set, + vdfpod::VdfPod, }; // Seed of 2612=0xA34 is a match with hash 6647892930992163=0x000A7EE9D427E832. @@ -212,7 +219,7 @@ mod tests { // Contains the following public predicates: ItemDef, ItemKey, IsStone fn prove_stone( item_def: ItemDef, - pow_pod: MainPod, + vdf_pod: MainPod, // TODO: All the args below might belong in a ItemBuilder object batches: &[Arc], @@ -227,11 +234,11 @@ mod tests { let st_item_key = item_builder.st_item_key(st_item_def.clone())?; item_builder.ctx.builder.reveal(&st_item_key); - let st_pow = pow_pod.public_statements[0].clone(); + let st_vdf = vdf_pod.public_statements[0].clone(); let mut craft_builder = CraftBuilder::new(BuildContext::new(&mut builder, batches), params); - craft_builder.ctx.builder.add_pod(pow_pod); - let st_is_stone = craft_builder.st_is_stone(item_def, st_item_def, st_pow)?; + craft_builder.ctx.builder.add_pod(vdf_pod); + let st_is_stone = craft_builder.st_is_stone(item_def, st_item_def, st_vdf)?; craft_builder.ctx.builder.reveal(&st_is_stone); // Prove MainPOD @@ -318,15 +325,15 @@ mod tests { .do_mining(¶ms, key, STONE_START_SEED, STONE_MINING_MAX)? .unwrap(); - let pow_pod = PowPod::new( + let vdf_pod = VdfPod::new( ¶ms, vd_set.clone(), 3, // num_iters RawValue::from(ingredients_def.dict(¶ms)?.commitment()), )?; - let main_pow_pod = MainPod { - pod: Box::new(pow_pod.clone()), - public_statements: pow_pod.pub_statements(), + let main_vdf_pod = MainPod { + pod: Box::new(vdf_pod.clone()), + public_statements: vdf_pod.pub_statements(), params: params.clone(), }; @@ -335,7 +342,7 @@ mod tests { let inputs_set = ingredients_def.inputs_set(¶ms)?; let item_def = ItemDef { ingredients: ingredients_def.clone(), - work: pow_pod.output, + work: vdf_pod.output, }; let item_hash = item_def.item_hash(¶ms)?; @@ -343,7 +350,7 @@ mod tests { // locally for future crafting. let stone_main_pod = prove_stone( item_def.clone(), - main_pow_pod, + main_vdf_pod, &batches, ¶ms, prover, @@ -389,7 +396,7 @@ mod tests { ("ingredients".to_string(), Value::from(ingredients_dict)), ("inputs".to_string(), Value::from(inputs_set)), ("key".to_string(), Value::from(key)), - ("work".to_string(), Value::from(pow_pod.output)), + ("work".to_string(), Value::from(vdf_pod.output)), ]), ); diff --git a/craftlib/src/lib.rs b/craftlib/src/lib.rs index 3305f8b..0b4c3c0 100644 --- a/craftlib/src/lib.rs +++ b/craftlib/src/lib.rs @@ -1,5 +1,6 @@ pub mod constants; pub mod item; +pub mod vdfpod; pub mod powpod; pub mod predicates; mod test_util; diff --git a/craftlib/src/powpod.rs b/craftlib/src/powpod.rs index 53c61ab..f09d958 100644 --- a/craftlib/src/powpod.rs +++ b/craftlib/src/powpod.rs @@ -1,50 +1,36 @@ -//! PowPod: Introduction Pod that used as a "Proof of Work". -//! - takes as input a custom value, which will be bounded into the recursive chain -//! - counts how many recursions have been performed +//! PowPod: Introduction Pod that proves Proof of Work (mining difficulty). +//! - takes as input a hash value and a difficulty target +//! - proves that hash[0] <= difficulty_target //! -//! The 'work' comes from the proof computation cost at the each recursive step. +//! This is used to prove that mining work was done to find a valid nonce/seed. //! -//! An other option would be to prove the traditional PoW (hash output within a -//! range / certain amount of zeroes) inside a circuit, which is easier to -//! parallelize to gain advantatge. -//! -//! Circuits structure: -//! 1. RecursiveCircuit, where for each recursive step: -//! -//! PowInnerCircuit contains the logic of: -//! - output = hash(input) -//! - count+1 -//! -//! And the RecursiveCircuit does the logic of: -//! - verify previous proof of itself +//! Circuit structure: +//! 1. PowCircuit: +//! - hash: RawValue (4 field elements - already a hash/commitment) +//! - difficulty_target: u64 constant +//! - proves: hash[0] <= difficulty_target //! //! 2. PowPod: -//! - satisfies in the pod2's Pod trait interface -//! - verifies the proof from RecursiveCircuit -//! +//! - satisfies the pod2's Pod trait interface +//! - verifies the proof from PowCircuit //! //! Usage: //! ```rust -//! use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::{Params, RawValue, hash_str}}; +//! use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::{Params, RawValue}}; //! use craftlib::powpod::PowPod; //! //! let params = Params::default(); //! let vd_set = &*DEFAULT_VD_SET; -//! let n_iters: usize = 2; -//! let input = RawValue::from(hash_str("starting input")); -//! let pow_pod = PowPod::new(¶ms, vd_set.clone(), n_iters, input).unwrap(); +//! let hash = RawValue::from(...); // ingredients commitment/hash +//! let difficulty = 0x0020_0000_0000_0000u64; +//! let pow_pod = PowPod::new(¶ms, vd_set.clone(), hash, difficulty).unwrap(); //! ``` -//! An complete example of usage can be found at the test `test_pow_pod` (bottom -//! of this file). -use anyhow::{Result, anyhow}; +use anyhow::Result; use itertools::Itertools; use plonky2::{ field::types::Field, - hash::{ - hash_types::{HashOut, HashOutTarget}, - poseidon::PoseidonHash, - }, + hash::hash_types::{HashOut, HashOutTarget}, iop::{ target::Target, witness::{PartialWitness, WitnessWrite}, @@ -52,7 +38,7 @@ use plonky2::{ plonk::{ circuit_builder::CircuitBuilder, circuit_data::{CircuitData, VerifierOnlyCircuitData}, - proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}, + proof::ProofWithPublicInputs, }, }; use pod2::{ @@ -67,34 +53,24 @@ use pod2::{ }, deserialize_proof, mainpod, mainpod::calculate_statements_hash, - recursion::{ - InnerCircuit, RecursiveCircuit, RecursiveParams, VerifiedProofTarget, - circuit::{dummy as dummy_recursive, hash_verifier_data_gadget}, - new_params as new_recursive_params, - }, serialize_proof, }, measure_gates_begin, measure_gates_end, middleware, middleware::{ - C, D, EMPTY_HASH, F, HASH_SIZE, Hash, IntroPredicateRef, Params, Pod, Proof, RawValue, - ToFields, VDSet, + C, D, EMPTY_HASH, F, Hash, IntroPredicateRef, Params, Pod, Proof, RawValue, ToFields, VDSet, }, timed, }; use serde::{Deserialize, Serialize}; -// ARITY is assumed to be one, this also assumed at the PowInnerCircuit. -const ARITY: usize = 1; -const NUM_PUBLIC_INPUTS: usize = 13; // 13: count + input + output + verified_data_hash -const POW_POD_TYPE: (usize, &str) = (2001, "Pow"); +const POW_POD_TYPE: (usize, &str) = (2002, "Pow"); static STANDARD_POW_POD_DATA: std::sync::LazyLock<(PowPodTarget, CircuitData)> = std::sync::LazyLock::new(|| build().expect("successful build")); + fn build() -> Result<(PowPodTarget, CircuitData)> { let params = Params::default(); - // use pod2's recursion config as config for the introduction pod; which if - // the zk feature enabled, it will have the zk property enabled let rec_circuit_data = &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data(); @@ -102,32 +78,19 @@ fn build() -> Result<(PowPodTarget, CircuitData)> { let config = common_data.config.clone(); let mut builder = CircuitBuilder::::new(config); - let pow_pod_verify_target = PowPodTarget::add_targets(&mut builder, ¶ms)?; + let pow_pod_target = PowPodTarget::add_targets(&mut builder, ¶ms)?; pod2::backends::plonky2::recursion::pad_circuit(&mut builder, &common_data); let data = timed!("PowPod build", builder.build::()); assert_eq!(common_data, data.common); - Ok((pow_pod_verify_target, data)) -} -static POW_RECURSIVE_CIRCUIT: std::sync::LazyLock<( - RecursiveCircuit, - RecursiveParams, -)> = std::sync::LazyLock::new(|| build_pow_recursive_circuit().expect("successful build")); -fn build_pow_recursive_circuit() -> Result<(RecursiveCircuit, RecursiveParams)> { - let recursive_params: RecursiveParams = - new_recursive_params::(ARITY, NUM_PUBLIC_INPUTS, &())?; - - let recursive_circuit = RecursiveCircuit::::build(&recursive_params, &())?; - - Ok((recursive_circuit, recursive_params)) + Ok((pow_pod_target, data)) } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct PowPod { pub params: Params, - pub count: F, - pub input: RawValue, - pub output: RawValue, // output = H(H(H( ...H(input) ))) (count times) + pub hash: RawValue, // The hash to check (e.g., dict commitment) + pub difficulty: F, // difficulty target as a field element pub vd_set: VDSet, pub statements_hash: Hash, @@ -138,63 +101,35 @@ pub struct PowPod { #[allow(dead_code)] impl PowPod { - /// returns a PowPod for the given n_iters and input. - pub fn new(params: &Params, vd_set: VDSet, n_iters: usize, input: RawValue) -> Result { - let (last_iteration_values, proof_with_pis): ( - PowInnerCircuitInput, - ProofWithPublicInputs, - ) = timed!( - "PowPod::gen_pow_recursive_circuit_proof", - PowPod::get_pow_recursive_circuit_proof(n_iters, input)? - ); - - // generate a new PowPod from the given count, input, output - let (count, input, output) = ( - last_iteration_values.count, - last_iteration_values.input, - last_iteration_values.output, - ); - let pow_pod = timed!( - "PowPod::construct", - PowPod::construct(params, vd_set, count, input, output, proof_with_pis)? - ); - - #[cfg(test)] // sanity check - pow_pod.verify()?; + /// Creates a PowPod proving that hash[0] <= difficulty + pub fn new(params: &Params, vd_set: VDSet, hash: RawValue, difficulty: u64) -> Result { + // Pre-check difficulty (optional, for early bail) + if hash.0[0].0 > difficulty { + anyhow::bail!("Hash does not meet difficulty requirement"); + } - Ok(pow_pod) - } + let difficulty_f = F::from_canonical_u64(difficulty); - /// given the proof from RecursiveCircuit, constructs the - /// PowPod which verifies it. - fn construct( - params: &Params, - vd_set: VDSet, - count: F, - input: RawValue, - output: RawValue, - proof: ProofWithPublicInputs, - ) -> Result { - // verify the given proof in a PowPodTarget circuit + // Build the proof let (pow_pod_target, circuit_data) = &*STANDARD_POW_POD_DATA; - let statements = pub_self_statements(count, input, output) + let statements = pub_self_statements(hash, difficulty_f) .into_iter() .map(mainpod::Statement::from) .collect_vec(); let statements_hash: Hash = calculate_statements_hash(&statements, params); - // set targets - let pod_pow_input = PowPodVerifyInput { + + let pow_input = PowPodInput { vd_root: vd_set.root(), statements_hash, - proof, + hash, + difficulty: difficulty_f, }; + let mut pw = PartialWitness::::new(); - pow_pod_target.set_targets(&mut pw, &pod_pow_input)?; - let proof_with_pis = timed!( - "prove the pow-verification proof verification (PowPod proof)", - circuit_data.prove(pw)? - ); - // sanity check + pow_pod_target.set_targets(&mut pw, &pow_input)?; + + let proof_with_pis = timed!("prove Pow difficulty check", circuit_data.prove(pw)?); + circuit_data .verifier_data() .verify(proof_with_pis.clone())?; @@ -205,78 +140,19 @@ impl PowPod { Ok(PowPod { params: params.clone(), statements_hash, - count, - input, - output, + hash, + difficulty: difficulty_f, proof: proof_with_pis.proof, vd_set: vd_set.clone(), common_hash, }) } - - /// computes the PoW proof out of the RecursiveCircuit circuit. - fn get_pow_recursive_circuit_proof( - n_iters: usize, - starting_input: RawValue, - ) -> Result<(PowInnerCircuitInput, ProofWithPublicInputs)> { - if n_iters < 2 { - // this check is due the verifier_data_hash behaving differently for - // the first 2 iterations: - // - if n_iters=0, is [0,0,0,0] - // - if n_iters=1, is the one of the dummy_verifier_data - // in both cases, when verifying the proof out of the recursive - // chain in the PowPod circuit, the verifier_data_hash would not - // match the one expected (hardcoded as constant) at the PowPod - // circuit. - return Err(anyhow!("n_iters must be equal or greater than 2")); - } - - let mut inner_inputs = PowInnerCircuitInput { - prev_count: F::ZERO, - count: F::ONE, - input: starting_input, - midput: starting_input, // base case: midput==input - output: RawValue::from(pod2::middleware::hash_value(&starting_input)), - }; - - let (recursive_circuit, recursive_params) = &*POW_RECURSIVE_CIRCUIT; - - let (dummy_verifier_only_data, dummy_proof) = - dummy_recursive(recursive_params.common_data(), NUM_PUBLIC_INPUTS)?; - let mut recursive_proof = dummy_proof; - let mut recursive_verifier_only_data = dummy_verifier_only_data; - for i in 0..n_iters { - if i > 0 { - inner_inputs.prev_count = inner_inputs.count; - inner_inputs.count += F::ONE; - inner_inputs.midput = inner_inputs.output; - inner_inputs.output = - RawValue::from(pod2::middleware::hash_value(&inner_inputs.midput)); - - recursive_verifier_only_data = - recursive_params.verifier_data().verifier_only.clone(); - } - log::debug!("{inner_inputs:?}"); - log::debug!("{:?}", recursive_proof.public_inputs); - - recursive_proof = recursive_circuit.prove( - &inner_inputs, - vec![recursive_proof.clone()], - vec![recursive_verifier_only_data.clone()], - )?; - recursive_params - .verifier_data() - .verify(recursive_proof.clone())?; - } - Ok((inner_inputs, recursive_proof)) - } } #[derive(Serialize, Deserialize)] struct Data { - count: F, - input: RawValue, - output: RawValue, + hash: RawValue, + difficulty: F, proof: String, common_hash: String, } @@ -285,8 +161,9 @@ impl Pod for PowPod { fn params(&self) -> &Params { &self.params } + fn verify(&self) -> pod2::backends::plonky2::Result<()> { - let statements = pub_self_statements(self.count, self.input, self.output) + let statements = pub_self_statements(self.hash, self.difficulty) .into_iter() .map(mainpod::Statement::from) .collect_vec(); @@ -324,20 +201,19 @@ impl Pod for PowPod { } fn pub_self_statements(&self) -> Vec { - // exposed as a separate function for easier isolated testing - pub_self_statements(self.count, self.input, self.output) + pub_self_statements(self.hash, self.difficulty) } fn serialize_data(&self) -> serde_json::Value { serde_json::to_value(Data { - count: self.count, - input: self.input, - output: self.output, + hash: self.hash, + difficulty: self.difficulty, proof: serialize_proof(&self.proof), common_hash: self.common_hash.clone(), }) .expect("serialization to json") } + fn deserialize_data( params: Params, data: serde_json::Value, @@ -350,9 +226,8 @@ impl Pod for PowPod { let proof = deserialize_proof(common, &data.proof)?; Ok(Self { params, - count: data.count, - input: data.input, - output: data.output, + hash: data.hash, + difficulty: data.difficulty, vd_set, statements_hash, proof, @@ -371,43 +246,44 @@ impl Pod for PowPod { fn common_hash(&self) -> String { self.common_hash.clone() } + fn proof(&self) -> Proof { self.proof.clone() } + fn vd_set(&self) -> &VDSet { &self.vd_set } } -fn pub_self_statements(count: F, input: RawValue, output: RawValue) -> Vec { +fn pub_self_statements(hash: RawValue, difficulty: F) -> Vec { vec![middleware::Statement::Intro( IntroPredicateRef { name: POW_POD_TYPE.1.to_string(), - args_len: 3, + args_len: 2, verifier_data_hash: EMPTY_HASH, }, vec![ - RawValue([count, F::ZERO, F::ZERO, F::ZERO]).into(), - input.into(), - output.into(), + hash.into(), + RawValue([difficulty, F::ZERO, F::ZERO, F::ZERO]).into(), ], )] } + fn pub_self_statements_target( builder: &mut CircuitBuilder, params: &Params, - count: Target, - input: &[Target], - output: &[Target], + hash: &[Target], + difficulty: Target, ) -> Vec { let zero = builder.zero(); - let st_arg_0 = StatementArgTarget::literal( + let st_arg_0 = StatementArgTarget::literal(builder, &ValueTarget::from_slice(hash)); + let st_arg_1 = StatementArgTarget::literal( builder, - &ValueTarget::from_slice(&[count, zero, zero, zero]), + &ValueTarget::from_slice(&[difficulty, zero, zero, zero]), ); - let st_arg_1 = StatementArgTarget::literal(builder, &ValueTarget::from_slice(input)); - let st_arg_2 = StatementArgTarget::literal(builder, &ValueTarget::from_slice(output)); - let args = [st_arg_0, st_arg_1, st_arg_2] + + let args = [st_arg_0, st_arg_1] .into_iter() .chain(core::iter::repeat_with(|| { StatementArgTarget::none(builder) @@ -426,58 +302,86 @@ fn pub_self_statements_target( struct PowPodTarget { vd_root: HashOutTarget, statements_hash: HashOutTarget, - proof: ProofWithPublicInputsTarget, + hash: ValueTarget, + difficulty: Target, } -struct PowPodVerifyInput { + +struct PowPodInput { vd_root: Hash, statements_hash: Hash, - proof: ProofWithPublicInputs, + hash: RawValue, + difficulty: F, } + impl PowPodTarget { fn add_targets(builder: &mut CircuitBuilder, params: &Params) -> Result { let measure = measure_gates_begin!(builder, "PowPodTarget"); - // Verify RecursiveCircuit's proof (with verifier_data hardcoded as constant) - let (_, recursive_params) = &*POW_RECURSIVE_CIRCUIT; - let verifier_data_targ = - builder.constant_verifier_data(&recursive_params.verifier_data().verifier_only); - let proof = builder.add_virtual_proof_with_pis(recursive_params.common_data()); - builder.verify_proof::(&proof, &verifier_data_targ, recursive_params.common_data()); - - // ensure that the verifier_data_hash that appears at the public inputs - // of the proof being verified matches the one that is constant - let pi_verifier_data_hash = &proof.public_inputs[9..13]; - let constant_verifier_data_hash = hash_verifier_data_gadget(builder, &verifier_data_targ); - #[allow(clippy::needless_range_loop)] // to use same syntax as in other similar circuits - for i in 0..HASH_SIZE { - builder.connect( - pi_verifier_data_hash[i], - constant_verifier_data_hash.elements[i], - ); - } - - // calculate statements_hash - let count = proof.public_inputs[0]; - let input = &proof.public_inputs[1..5]; - let output = &proof.public_inputs[5..9]; - let statements = pub_self_statements_target(builder, params, count, input, output); + // Add virtual inputs + let hash = builder.add_virtual_value(); + let difficulty = builder.add_virtual_target(); + + // Check that hash[0] <= difficulty IN-CIRCUIT + // We need to prove hash[0] <= difficulty in a way that handles field arithmetic + + let hash_first = hash.elements[0]; + + // Strategy: Prove that difficulty - hash_first is non-negative in u64 space + // 1. Compute diff = difficulty - hash_first (in field arithmetic) + // 2. Split both into low/high 32-bit limbs to ensure they're valid u64s + // 3. Prove the subtraction is valid in u64 space (no underflow) + + // Split hash_first into two 32-bit limbs: hash_lo + hash_hi * 2^32 + let hash_bits = builder.split_le(hash_first, 64); + let hash_lo_bits = &hash_bits[0..32]; + let hash_hi_bits = &hash_bits[32..64]; + + // Reconstruct to verify decomposition + let two_32 = builder.constant(F::from_canonical_u64(1u64 << 32)); + let hash_lo = builder.le_sum(hash_lo_bits.iter().copied()); + let hash_hi = builder.le_sum(hash_hi_bits.iter().copied()); + let hash_reconstructed = builder.mul_add(hash_hi, two_32, hash_lo); + builder.connect(hash_first, hash_reconstructed); + + // Split difficulty into two 32-bit limbs: diff_lo + diff_hi * 2^32 + let diff_bits = builder.split_le(difficulty, 64); + let diff_lo_bits = &diff_bits[0..32]; + let diff_hi_bits = &diff_bits[32..64]; + + let diff_lo = builder.le_sum(diff_lo_bits.iter().copied()); + let diff_hi = builder.le_sum(diff_hi_bits.iter().copied()); + let diff_reconstructed = builder.mul_add(diff_hi, two_32, diff_lo); + builder.connect(difficulty, diff_reconstructed); + + // Prove difficulty >= hash_first in-circuit + // Strategy: Show that (difficulty - hash_first) fits in 64 bits + // If hash_first > difficulty, the difference would be negative, + // which wraps to a huge number (> 2^64) and split_le will fail + let diff_full = builder.sub(difficulty, hash_first); + let _diff_bits = builder.split_le(diff_full, 64); + + // Calculate statements_hash + let statements = pub_self_statements_target(builder, params, &hash.elements, difficulty); let statements_hash = calculate_statements_hash_circuit(params, builder, &statements); - // register the public inputs + // Register public inputs let vd_root = builder.add_virtual_hash(); builder.register_public_inputs(&statements_hash.elements); builder.register_public_inputs(&vd_root.elements); measure_gates_end!(builder, measure); + Ok(PowPodTarget { vd_root, statements_hash, - proof, + hash, + difficulty, }) } - fn set_targets(&self, pw: &mut PartialWitness, input: &PowPodVerifyInput) -> Result<()> { - pw.set_proof_with_pis_target(&self.proof, &input.proof)?; + fn set_targets(&self, pw: &mut PartialWitness, input: &PowPodInput) -> Result<()> { + pw.set_target_arr(&self.hash.elements, &input.hash.0)?; + pw.set_target(self.difficulty, input.difficulty)?; pw.set_hash_target( self.statements_hash, HashOut::from_vec(input.statements_hash.0.to_vec()), @@ -488,346 +392,63 @@ impl PowPodTarget { } } -#[derive(Clone, Debug)] -struct PowInnerCircuit { - prev_count: Target, - count: Target, // count contains the amount of recursive steps done - input: ValueTarget, // input that is bounded into the recursive chain - midput: ValueTarget, // midput is the 'input' used for the last step of the recursion - output: ValueTarget, // output of the recursive chain -} -#[derive(Debug)] -struct PowInnerCircuitInput { - prev_count: F, - count: F, - input: RawValue, - midput: RawValue, - output: RawValue, -} -impl InnerCircuit for PowInnerCircuit { - type Input = PowInnerCircuitInput; - type Params = (); - fn build( - builder: &mut CircuitBuilder, - _params: &Self::Params, - verified_proofs: &[VerifiedProofTarget], - ) -> BResult { - let prev_count = builder.add_virtual_target(); - let input = builder.add_virtual_value(); - let midput = builder.add_virtual_value(); - - let output_h = builder.hash_n_to_hash_no_pad::(midput.elements.to_vec()); - let output = ValueTarget::from_slice(output_h.elements.as_ref()); - - let zero = builder.zero(); - let one = builder.one(); - - let is_basecase = builder.is_equal(prev_count, zero); // case 0 - let is_not_basecase = builder.not(is_basecase); - let is_case_1 = builder.is_equal(prev_count, one); // case 1 - let case_0_or_1 = builder.or(is_basecase, is_case_1); - let after_case_1 = builder.not(case_0_or_1); - - // if we're at the prev_count==0, ensure that - // input==midput - for i in 0..HASH_SIZE { - builder.conditional_assert_eq( - is_basecase.target, - input.elements[i], - midput.elements[i], - ); - } - - // if we're at case prev_count>0, assert that the public_inputs of the - // proof being verified match with the prev_count, input and midput. - // For prev_count>1, we also check that the verifier_data_hash being - // used matches the one at the public_inputs of the previous proof. - builder.connect(verified_proofs[0].public_inputs[0], prev_count); - for i in 0..HASH_SIZE { - // if prev_count>0: - builder.conditional_assert_eq( - is_not_basecase.target, - verified_proofs[0].public_inputs[1 + i], - input.elements[i], - ); - builder.conditional_assert_eq( - is_not_basecase.target, - verified_proofs[0].public_inputs[5 + i], - midput.elements[i], - ); - - // if we're at case prev_count>1: - // check that the verifier_data's hash used to verify the current - // proof is the same as in the public_inputs. Notice that at case 0, - // this verifier_data_hash is [0,0,0,0], and at case 1 is the hash - // of the dummy_verifier_data; hence we do this check when - // prev_count>1. - builder.conditional_assert_eq( - after_case_1.target, - verified_proofs[0].public_inputs[9 + i], - verified_proofs[0].verifier_data_hash.elements[i], - ); - } - - // increment count - let count = builder.add(prev_count, one); - - // register public inputs: count, input, output - builder.register_public_input(count); - builder.register_public_inputs(&input.elements); - builder.register_public_inputs(&output.elements); - builder.register_public_inputs(&verified_proofs[0].verifier_data_hash.elements); - - Ok(Self { - prev_count, - count, - input, - midput, - output, - }) - } - fn set_targets(&self, pw: &mut PartialWitness, input: &Self::Input) -> BResult<()> { - pw.set_target(self.prev_count, input.prev_count)?; - pw.set_target(self.count, input.count)?; - pw.set_target_arr(&self.input.elements, &input.input.0)?; - pw.set_target_arr(&self.midput.elements, &input.midput.0)?; - pw.set_target_arr(&self.output.elements, &input.output.0)?; - Ok(()) - } -} - #[cfg(test)] mod tests { - use plonky2::plonk::circuit_data::CircuitConfig; - use pod2::{ - backends::plonky2::basetypes::DEFAULT_VD_SET, - frontend, measure_gates_print, - middleware::{Value, hash_str}, - }; + use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::hash_str}; use super::*; - // For tests only. Returns a valid VerifiedProofTarget filled with the - // public_inputs from the given PowInnerCircuitInput, in order to run some - // tests. - fn empty_verified_proof_target( - builder: &mut CircuitBuilder, - inp: &PowInnerCircuitInput, - ) -> VerifiedProofTarget { - let count = builder.constant(inp.prev_count); - let input = builder.constants(&inp.input.0); - let midput = if inp.prev_count.is_zero() { - builder.constants(&inp.output.0) - } else { - builder.constants(&inp.midput.0) - }; - let verifier_data_hash = HashOutTarget::from_partial(&[builder.zero()], builder.zero()); - VerifiedProofTarget { - public_inputs: [ - vec![count], - input, - midput, - verifier_data_hash.elements.to_vec(), - ] - .concat(), - verifier_data_hash, - } - } - #[test] - fn test_inner_circuit() -> Result<()> { - let inner_params = (); - - let starting_input = RawValue::from(hash_str("starting input")); - - // circuit - let config = CircuitConfig::standard_recursion_zk_config(); - let mut builder = CircuitBuilder::::new(config.clone()); - - let inner_inputs = PowInnerCircuitInput { - prev_count: F::ZERO, - count: F::ONE, - input: starting_input, - midput: starting_input, // base case: midput==input - output: RawValue::from(pod2::middleware::hash_value(&starting_input)), - }; - - // build circuit - let measure = measure_gates_begin!(&builder, format!("PowInnerCircuit gates")); - let verified_proof_target = empty_verified_proof_target(&mut builder, &inner_inputs); - let targets = - PowInnerCircuit::build(&mut builder, &inner_params, &[verified_proof_target])?; - measure_gates_end!(&builder, measure); - measure_gates_print!(); - let data = builder.build::(); - - // set witness - let mut pw = PartialWitness::::new(); - targets.set_targets(&mut pw, &inner_inputs)?; - - // generate & verify proof - let proof = data.prove(pw)?; - data.verify(proof.clone())?; - - // Second iteration - let inner_inputs = PowInnerCircuitInput { - prev_count: F::ONE, - count: F::from_canonical_u64(2u64), - input: starting_input, - midput: inner_inputs.output, // base case: midput==input - output: RawValue::from(pod2::middleware::hash_value(&inner_inputs.output)), - }; - let mut builder = CircuitBuilder::::new(config); - let mut pw = PartialWitness::::new(); - let verified_proof_target = empty_verified_proof_target(&mut builder, &inner_inputs); - let targets = - PowInnerCircuit::build(&mut builder, &inner_params, &[verified_proof_target])?; - targets.set_targets(&mut pw, &inner_inputs)?; - let data = builder.build::(); - let proof = data.prove(pw)?; - data.verify(proof.clone())?; - - Ok(()) - } - #[test] - fn test_recursion_on_inner_circuit() -> Result<()> { - let starting_input = RawValue::from(hash_str("starting input")); - let _ = PowPod::get_pow_recursive_circuit_proof(3, starting_input)?; - Ok(()) - } + fn test_pow_pod() -> Result<()> { + let params = Params::default(); + let vd_set = &*DEFAULT_VD_SET; - /// test to ensure that the pub_self_statements methods match between the - /// in-circuit and the out-circuit implementations - #[test] - fn test_pub_self_statements_target() -> Result<()> { - // first generate all the circuits data so that it does not need to be - // computed at further stages of the test (affecting the time reports) - timed!( - "generate POW_RECURSIVE_CIRCUIT, STANDARD_POW_POD_DATA, STANDARD_REC_MAIN_POD_CIRCUIT", - { - let (_, _) = &*POW_RECURSIVE_CIRCUIT; - let (_, _) = &*STANDARD_POW_POD_DATA; - let _ = - &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data( - ); + // Find a valid input by brute force (for testing) + let difficulty = 0x0020_0000_0000_0000u64; + let mut found_input = None; + + for i in 0..10000 { + let test_input = RawValue::from(i as i64); + let hash_output = RawValue::from(pod2::middleware::hash_value(&test_input)); + if hash_output.0[0].0 <= difficulty { + found_input = Some(test_input); + println!( + "Found valid input at i={}: hash={:#x}", + i, hash_output.0[0].0 + ); + break; } - ); - - let params = &Default::default(); - - let count = F::ONE; - let input = RawValue::from(hash_str("starting input")); - let output = RawValue::from(pod2::middleware::hash_value(&input)); - - let st = pub_self_statements(count, input, output) - .into_iter() - .map(mainpod::Statement::from) - .collect_vec(); - let statements_hash: HashOut = - HashOut::::from_vec(calculate_statements_hash(&st, params).0.to_vec()); - - // circuit - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::new(config); - let mut pw = PartialWitness::::new(); + } - // add targets - let count_targ = builder.add_virtual_target(); - let input_targ = builder.add_virtual_value(); - let output_targ = builder.add_virtual_value(); - let expected_statements_hash_targ = builder.add_virtual_hash(); + let ingredients = found_input.expect("Should find valid input"); - // set values to targets - pw.set_target(count_targ, count)?; - pw.set_target_arr(&input_targ.elements, &input.0)?; - pw.set_target_arr(&output_targ.elements, &output.0)?; - pw.set_hash_target(expected_statements_hash_targ, statements_hash)?; + // This should succeed + let pow_pod = PowPod::new(¶ms, vd_set.clone(), ingredients, difficulty)?; + pow_pod.verify()?; - let st_targ = pub_self_statements_target( - &mut builder, - params, - count_targ, - &input_targ.elements, - &output_targ.elements, + println!( + "pow_pod.verifier_data_hash(): {:#} . To be used in predicates.", + pow_pod.verifier_data_hash() ); - let statements_hash_targ = - calculate_statements_hash_circuit(params, &mut builder, &st_targ); - builder.connect_hashes(expected_statements_hash_targ, statements_hash_targ); - - // generate & verify proof - let data = builder.build::(); - let proof = data.prove(pw)?; - data.verify(proof.clone())?; + // Verify hash is computed correctly and meets difficulty + let hash_output = RawValue::from(pod2::middleware::hash_value(&ingredients)); + assert!(hash_output.0[0].0 <= difficulty); Ok(()) } #[test] - fn test_pow_pod() -> Result<()> { - // for this test, first generate all the circuits data so that it does - // not need to be computed at further stages of the test (affecting the - // time reports) - timed!( - "generate POW_RECURSIVE_CIRCUIT, STANDARD_POW_POD_DATA, standard_rec_main_pod_common_circuit_data", - { - let (_, _) = &*POW_RECURSIVE_CIRCUIT; - let (_, _) = &*STANDARD_POW_POD_DATA; - let _ = - &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data( - ); - } - ); - + fn test_pow_pod_fails_above_difficulty() -> Result<()> { let params = Params::default(); - let n_iters: usize = 2; - let input = RawValue::from(hash_str("starting input")); - let vd_set = &*DEFAULT_VD_SET; - let pow_pod = timed!( - "PowPod::new", - PowPod::new(¶ms, vd_set.clone(), n_iters, input)? - ); - pow_pod.verify()?; - - println!( - "pow_pod.verifier_data_hash(): {:#} . To be used when importing the PowPod as introduction pod to define new predicates.", - pow_pod.verifier_data_hash() - ); - - // wrap the pow_pod in a 'MainPod' - let main_pow_pod = frontend::MainPod { - pod: Box::new(pow_pod.clone()), - public_statements: pow_pod.pub_statements(), - params: params.clone(), - }; - - let expected_count = Value::from(n_iters as i64); - let expected_input = input; - - // now generate a new MainPod from the pow_pod - let mut main_pod_builder = frontend::MainPodBuilder::new(¶ms, vd_set); - main_pod_builder.add_pod(main_pow_pod.clone()); - - main_pod_builder.reveal(&main_pow_pod.public_statements[0]); - - let prover = pod2::backends::plonky2::mock::mainpod::MockProver {}; - let pod = main_pod_builder.prove(&prover)?; - assert!(pod.pod.verify().is_ok()); - println!("going to prove the main_pod"); - let prover = mainpod::Prover {}; - let main_pod = timed!("main_pod_builder.prove", main_pod_builder.prove(&prover)?); - let pod: Box = (main_pod.pod as Box) - .downcast::() - .unwrap(); - pod.verify()?; + let input = RawValue::from(hash_str("definitely above difficulty")); + let difficulty = 1u64; // Very strict difficulty - let st_pow = pod.pub_statements()[0].clone(); - let count = st_pow.args()[0].literal()?; - let input = st_pow.args()[1].literal()?; - assert_eq!(count, expected_count); - assert_eq!(input, Value::from(expected_input)); + // This should fail + let result = PowPod::new(¶ms, vd_set.clone(), input, difficulty); + assert!(result.is_err()); Ok(()) } diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 29eaeb7..28230d7 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -1,9 +1,21 @@ use std::slice; use commitlib::predicates::CommitPredicates; -use pod2::middleware::{CustomPredicateRef, Params}; +use plonky2::field::types::Field; +use pod2::middleware::{CustomPredicateRef, F, Params}; use pod2utils::PredicateDefs; +use crate::constants::{AXE_MINING_MAX, STONE_MINING_MAX, WOOD_MINING_MAX}; + +/// Convert a u64 difficulty to RawValue format for use in predicates (little-endian) +fn difficulty_to_raw_string(difficulty: u64) -> String { + let difficulty_f = F::from_canonical_u64(difficulty); + format!( + "Raw(0x{:016x}{:016x}{:016x}{:016x})", + 0u64, 0u64, 0u64, difficulty_f.0 + ) +} + pub struct ItemPredicates { pub defs: PredicateDefs, @@ -17,58 +29,74 @@ impl ItemPredicates { // 4 predicates per batch // 8 arguments per predicate, at most 5 of which are public // 5 statements per predicate - let batch_defs = [ + + // Convert mining difficulties to RawValue format for predicates + let stone_difficulty_raw = difficulty_to_raw_string(STONE_MINING_MAX); + let wood_difficulty_raw = difficulty_to_raw_string(WOOD_MINING_MAX); + let axe_difficulty_raw = difficulty_to_raw_string(AXE_MINING_MAX); + + let batch_def_1 = format!( r#" - use intro Pow(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 // powpod vd hash - - // Example of a mined item with no inputs or sequential work. - // Stone requires working in a stone mine (blueprint="stone") and - // 10 leading 0s. + use intro Vdf(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 // vdfpod vd hash + use intro Pow(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod vd hash + + // Example of a mined item with mining difficulty check and VDF work. + // Stone requires: + // - blueprint="stone" + // - hash(ingredients) meets difficulty (Pow mining) + // - sequential work via VDF IsStone(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) - Equal(inputs, {}) + Equal(inputs, {{}}) DictContains(ingredients, "blueprint", "stone") - Pow(3, ingredients, work) + Pow(ingredients, {stone_difficulty_raw}) // Proves ingredients <= STONE_MINING_MAX + Vdf(3, ingredients, work) // Proves 3 iterations of sequential hashing ) - - // Example of a mined item which is more common but takes more work to - // extract. + + // Example of a mined item with just Pow (no VDF work). + // Wood requires: + // - blueprint="wood" + // - hash(ingredients) meets difficulty (Pow mining) IsWood(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) - Equal(inputs, {}) + Equal(inputs, {{}}) DictContains(ingredients, "blueprint", "wood") - Equal(work, {}) - // TODO input POD: SequentialWork(ingredients, work, 5) - // TODO input POD: HashInRange(0, 1<<5, ingredients) + Pow(ingredients, {wood_difficulty_raw}) // Proves ingredients <= WOOD_MINING_MAX + Equal(work, {{}}) // No VDF work required ) - "#, + "# + ); + + let batch_def_2 = format!( r#" + use intro Pow(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod vd hash + AxeInputs(inputs, private: s1, wood, stone) = AND( // 2 ingredients - SetInsert(s1, {}, wood) + SetInsert(s1, {{}}, wood) SetInsert(inputs, s1, stone) - + // prove the ingredients are correct. IsWood(wood) IsStone(stone) ) - - // Combining Stone and Wood to get Axe is easy (no sequential work). - // TODO: Require a smelter as a tool + + // Combining Stone and Wood to get Axe requires mining (no sequential work). IsAxe(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "axe") - Equal(work, {}) - + Pow(ingredients, {axe_difficulty_raw}) // Proves ingredients <= AXE_MINING_MAX + Equal(work, {{}}) + AxeInputs(inputs) ) - + // Wooden Axe: WoodenAxeInputs(inputs, private: s1, wood1, wood2) = AND( // 2 ingredients - SetInsert(s1, {}, wood1) + SetInsert(s1, {{}}, wood1) SetInsert(inputs, s1, wood2) - + // prove the ingredients are correct. IsWood(wood1) IsWood(wood2) @@ -78,13 +106,20 @@ impl ItemPredicates { IsWoodenAxe(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "wooden-axe") - Equal(work, {}) - + Equal(work, {{}}) + WoodenAxeInputs(inputs) ) - "#, - ]; - let defs = PredicateDefs::new(params, &batch_defs, slice::from_ref(&commit_preds.defs)); + "# + ); + + let batch_defs = [batch_def_1, batch_def_2]; + let batch_defs_refs: Vec<&str> = batch_defs.iter().map(|s| s.as_str()).collect(); + let defs = PredicateDefs::new( + params, + &batch_defs_refs, + slice::from_ref(&commit_preds.defs), + ); ItemPredicates { is_stone: defs.predicate_ref_by_name("IsStone").unwrap(), @@ -108,8 +143,8 @@ mod tests { use super::*; use crate::{ constants::STONE_BLUEPRINT, - powpod::PowPod, test_util::test::{check_matched_wildcards, mock_vd_set}, + vdfpod::VdfPod, }; #[test] @@ -145,22 +180,22 @@ mod tests { }; let ingredients_dict = ingredients_def.dict(¶ms)?; let inputs_set = ingredients_def.inputs_set(¶ms)?; - // compute the PowPod + // compute the VdfPod let vd_set = &mock_vd_set(); - let pow_pod = PowPod::new( + let vdf_pod = VdfPod::new( ¶ms, vd_set.clone(), 3, RawValue::from(ingredients_def.dict(¶ms)?.commitment()), )?; - let main_pow_pod = MainPod { - pod: Box::new(pow_pod.clone()), - public_statements: pow_pod.pub_statements(), + let main_vdf_pod = MainPod { + pod: Box::new(vdf_pod.clone()), + public_statements: vdf_pod.pub_statements(), params: params.clone(), }; - let work: RawValue = pow_pod.output; - let st_pow = main_pow_pod.public_statements[0].clone(); - builder.add_pod(main_pow_pod); + let work: RawValue = vdf_pod.output; + let st_vdf = main_vdf_pod.public_statements[0].clone(); + builder.add_pod(main_vdf_pod); let item_def = ItemDef { ingredients: ingredients_def.clone(), work, @@ -248,7 +283,7 @@ mod tests { st_item_def, st_inputs_eq_empty, st_contains_blueprint, - st_pow, + st_vdf, ], ))?; diff --git a/craftlib/src/vdfpod.rs b/craftlib/src/vdfpod.rs new file mode 100644 index 0000000..ad2bd0a --- /dev/null +++ b/craftlib/src/vdfpod.rs @@ -0,0 +1,831 @@ +//! VdfPod: Introduction Pod that implements a Verifiable Delay Function (VDF). +//! - takes as input a custom value, which will be bounded into the recursive chain +//! - counts how many recursions have been performed +//! +//! The 'delay' comes from the sequential nature of the computation - each hash must +//! be computed after the previous one, preventing parallelization. +//! +//! Circuits structure: +//! 1. RecursiveCircuit, where for each recursive step: +//! +//! VdfInnerCircuit contains the logic of: +//! - output = hash(input) +//! - count+1 +//! +//! And the RecursiveCircuit does the logic of: +//! - verify previous proof of itself +//! +//! 2. VdfPod: +//! - satisfies in the pod2's Pod trait interface +//! - verifies the proof from RecursiveCircuit +//! +//! +//! Usage: +//! ```rust +//! use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::{Params, RawValue, hash_str}}; +//! use craftlib::vdfpod::VdfPod; +//! +//! let params = Params::default(); +//! let vd_set = &*DEFAULT_VD_SET; +//! let n_iters: usize = 2; +//! let input = RawValue::from(hash_str("starting input")); +//! let vdf_pod = VdfPod::new(¶ms, vd_set.clone(), n_iters, input).unwrap(); +//! ``` +//! An complete example of usage can be found at the test `test_vdf_pod` (bottom +//! of this file). + +use anyhow::{Result, anyhow}; +use itertools::Itertools; +use plonky2::{ + field::types::Field, + hash::{ + hash_types::{HashOut, HashOutTarget}, + poseidon::PoseidonHash, + }, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + circuit_data::{CircuitData, VerifierOnlyCircuitData}, + proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}, + }, +}; +use pod2::{ + backends::plonky2::{ + Error, Result as BResult, + circuits::{ + common::{ + CircuitBuilderPod, PredicateTarget, StatementArgTarget, StatementTarget, + ValueTarget, + }, + mainpod::calculate_statements_hash_circuit, + }, + deserialize_proof, mainpod, + mainpod::calculate_statements_hash, + recursion::{ + InnerCircuit, RecursiveCircuit, RecursiveParams, VerifiedProofTarget, + circuit::{dummy as dummy_recursive, hash_verifier_data_gadget}, + new_params as new_recursive_params, + }, + serialize_proof, + }, + measure_gates_begin, measure_gates_end, middleware, + middleware::{ + C, D, EMPTY_HASH, F, HASH_SIZE, Hash, IntroPredicateRef, Params, Pod, Proof, RawValue, + ToFields, VDSet, + }, + timed, +}; +use serde::{Deserialize, Serialize}; + +// ARITY is assumed to be one, this also assumed at the VdfInnerCircuit. +const ARITY: usize = 1; +const NUM_PUBLIC_INPUTS: usize = 13; // 13: count + input + output + verified_data_hash +const VDF_POD_TYPE: (usize, &str) = (2001, "Vdf"); + +static STANDARD_VDF_POD_DATA: std::sync::LazyLock<(VdfPodTarget, CircuitData)> = + std::sync::LazyLock::new(|| build().expect("successful build")); +fn build() -> Result<(VdfPodTarget, CircuitData)> { + let params = Params::default(); + + // use pod2's recursion config as config for the introduction pod; which if + // the zk feature enabled, it will have the zk property enabled + let rec_circuit_data = + &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data(); + + let common_data = rec_circuit_data.0.clone(); + let config = common_data.config.clone(); + + let mut builder = CircuitBuilder::::new(config); + let vdf_pod_verify_target = VdfPodTarget::add_targets(&mut builder, ¶ms)?; + pod2::backends::plonky2::recursion::pad_circuit(&mut builder, &common_data); + + let data = timed!("VdfPod build", builder.build::()); + assert_eq!(common_data, data.common); + Ok((vdf_pod_verify_target, data)) +} +static VDF_RECURSIVE_CIRCUIT: std::sync::LazyLock<( + RecursiveCircuit, + RecursiveParams, +)> = std::sync::LazyLock::new(|| build_vdf_recursive_circuit().expect("successful build")); +fn build_vdf_recursive_circuit() -> Result<(RecursiveCircuit, RecursiveParams)> { + let recursive_params: RecursiveParams = + new_recursive_params::(ARITY, NUM_PUBLIC_INPUTS, &())?; + + let recursive_circuit = RecursiveCircuit::::build(&recursive_params, &())?; + + Ok((recursive_circuit, recursive_params)) +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct VdfPod { + pub params: Params, + pub count: F, + pub input: RawValue, + pub output: RawValue, // output = H(H(H( ...H(input) ))) (count times) + + pub vd_set: VDSet, + pub statements_hash: Hash, + pub proof: Proof, + + pub common_hash: String, +} + +#[allow(dead_code)] +impl VdfPod { + /// returns a VdfPod for the given n_iters and input. + pub fn new(params: &Params, vd_set: VDSet, n_iters: usize, input: RawValue) -> Result { + let (last_iteration_values, proof_with_pis): ( + VdfInnerCircuitInput, + ProofWithPublicInputs, + ) = timed!( + "VdfPod::gen_vdf_recursive_circuit_proof", + VdfPod::get_vdf_recursive_circuit_proof(n_iters, input)? + ); + + // generate a new VdfPod from the given count, input, output + let (count, input, output) = ( + last_iteration_values.count, + last_iteration_values.input, + last_iteration_values.output, + ); + let vdf_pod = timed!( + "VdfPod::construct", + VdfPod::construct(params, vd_set, count, input, output, proof_with_pis)? + ); + + #[cfg(test)] // sanity check + vdf_pod.verify()?; + + Ok(vdf_pod) + } + + /// given the proof from RecursiveCircuit, constructs the + /// VdfPod which verifies it. + fn construct( + params: &Params, + vd_set: VDSet, + count: F, + input: RawValue, + output: RawValue, + proof: ProofWithPublicInputs, + ) -> Result { + // verify the given proof in a VdfPodTarget circuit + let (vdf_pod_target, circuit_data) = &*STANDARD_VDF_POD_DATA; + let statements = pub_self_statements(count, input, output) + .into_iter() + .map(mainpod::Statement::from) + .collect_vec(); + let statements_hash: Hash = calculate_statements_hash(&statements, params); + // set targets + let pod_vdf_input = VdfPodVerifyInput { + vd_root: vd_set.root(), + statements_hash, + proof, + }; + let mut pw = PartialWitness::::new(); + vdf_pod_target.set_targets(&mut pw, &pod_vdf_input)?; + let proof_with_pis = timed!( + "prove the vdf-verification proof verification (VdfPod proof)", + circuit_data.prove(pw)? + ); + // sanity check + circuit_data + .verifier_data() + .verify(proof_with_pis.clone())?; + + let common_hash: String = + pod2::backends::plonky2::mainpod::cache_get_rec_main_pod_common_hash(params).clone(); + + Ok(VdfPod { + params: params.clone(), + statements_hash, + count, + input, + output, + proof: proof_with_pis.proof, + vd_set: vd_set.clone(), + common_hash, + }) + } + + /// computes the VDF proof out of the RecursiveCircuit circuit. + fn get_vdf_recursive_circuit_proof( + n_iters: usize, + starting_input: RawValue, + ) -> Result<(VdfInnerCircuitInput, ProofWithPublicInputs)> { + if n_iters < 2 { + // this check is due the verifier_data_hash behaving differently for + // the first 2 iterations: + // - if n_iters=0, is [0,0,0,0] + // - if n_iters=1, is the one of the dummy_verifier_data + // in both cases, when verifying the proof out of the recursive + // chain in the VdfPod circuit, the verifier_data_hash would not + // match the one expected (hardcoded as constant) at the VdfPod + // circuit. + return Err(anyhow!("n_iters must be equal or greater than 2")); + } + + let mut inner_inputs = VdfInnerCircuitInput { + prev_count: F::ZERO, + count: F::ONE, + input: starting_input, + midput: starting_input, // base case: midput==input + output: RawValue::from(pod2::middleware::hash_value(&starting_input)), + }; + + let (recursive_circuit, recursive_params) = &*VDF_RECURSIVE_CIRCUIT; + + let (dummy_verifier_only_data, dummy_proof) = + dummy_recursive(recursive_params.common_data(), NUM_PUBLIC_INPUTS)?; + let mut recursive_proof = dummy_proof; + let mut recursive_verifier_only_data = dummy_verifier_only_data; + for i in 0..n_iters { + if i > 0 { + inner_inputs.prev_count = inner_inputs.count; + inner_inputs.count += F::ONE; + inner_inputs.midput = inner_inputs.output; + inner_inputs.output = + RawValue::from(pod2::middleware::hash_value(&inner_inputs.midput)); + + recursive_verifier_only_data = + recursive_params.verifier_data().verifier_only.clone(); + } + log::debug!("{inner_inputs:?}"); + log::debug!("{:?}", recursive_proof.public_inputs); + + recursive_proof = recursive_circuit.prove( + &inner_inputs, + vec![recursive_proof.clone()], + vec![recursive_verifier_only_data.clone()], + )?; + recursive_params + .verifier_data() + .verify(recursive_proof.clone())?; + } + Ok((inner_inputs, recursive_proof)) + } +} + +#[derive(Serialize, Deserialize)] +struct Data { + count: F, + input: RawValue, + output: RawValue, + proof: String, + common_hash: String, +} + +impl Pod for VdfPod { + fn params(&self) -> &Params { + &self.params + } + fn verify(&self) -> pod2::backends::plonky2::Result<()> { + let statements = pub_self_statements(self.count, self.input, self.output) + .into_iter() + .map(mainpod::Statement::from) + .collect_vec(); + let statements_hash: Hash = calculate_statements_hash(&statements, &self.params); + if statements_hash != self.statements_hash { + return Err(Error::statements_hash_not_equal( + self.statements_hash, + statements_hash, + )); + } + + let (_, circuit_data) = &*STANDARD_VDF_POD_DATA; + + let public_inputs = statements_hash + .to_fields(&self.params) + .iter() + .chain(self.vd_set().root().0.iter()) + .cloned() + .collect_vec(); + + circuit_data + .verify(ProofWithPublicInputs { + proof: self.proof.clone(), + public_inputs, + }) + .map_err(|e| Error::custom(format!("VdfPod proof verification failure: {e:?}"))) + } + + fn statements_hash(&self) -> Hash { + self.statements_hash + } + + fn pod_type(&self) -> (usize, &'static str) { + VDF_POD_TYPE + } + + fn pub_self_statements(&self) -> Vec { + // exposed as a separate function for easier isolated testing + pub_self_statements(self.count, self.input, self.output) + } + + fn serialize_data(&self) -> serde_json::Value { + serde_json::to_value(Data { + count: self.count, + input: self.input, + output: self.output, + proof: serialize_proof(&self.proof), + common_hash: self.common_hash.clone(), + }) + .expect("serialization to json") + } + fn deserialize_data( + params: Params, + data: serde_json::Value, + vd_set: VDSet, + statements_hash: Hash, + ) -> BResult { + let data: Data = serde_json::from_value(data)?; + let common = + &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data(); + let proof = deserialize_proof(common, &data.proof)?; + Ok(Self { + params, + count: data.count, + input: data.input, + output: data.output, + vd_set, + statements_hash, + proof, + common_hash: data.common_hash, + }) + } + + fn verifier_data(&self) -> VerifierOnlyCircuitData { + STANDARD_VDF_POD_DATA + .1 + .verifier_data() + .verifier_only + .clone() + } + + fn common_hash(&self) -> String { + self.common_hash.clone() + } + fn proof(&self) -> Proof { + self.proof.clone() + } + fn vd_set(&self) -> &VDSet { + &self.vd_set + } +} + +fn pub_self_statements(count: F, input: RawValue, output: RawValue) -> Vec { + vec![middleware::Statement::Intro( + IntroPredicateRef { + name: VDF_POD_TYPE.1.to_string(), + args_len: 3, + verifier_data_hash: EMPTY_HASH, + }, + vec![ + RawValue([count, F::ZERO, F::ZERO, F::ZERO]).into(), + input.into(), + output.into(), + ], + )] +} +fn pub_self_statements_target( + builder: &mut CircuitBuilder, + params: &Params, + count: Target, + input: &[Target], + output: &[Target], +) -> Vec { + let zero = builder.zero(); + let st_arg_0 = StatementArgTarget::literal( + builder, + &ValueTarget::from_slice(&[count, zero, zero, zero]), + ); + let st_arg_1 = StatementArgTarget::literal(builder, &ValueTarget::from_slice(input)); + let st_arg_2 = StatementArgTarget::literal(builder, &ValueTarget::from_slice(output)); + let args = [st_arg_0, st_arg_1, st_arg_2] + .into_iter() + .chain(core::iter::repeat_with(|| { + StatementArgTarget::none(builder) + })) + .take(params.max_statement_args) + .collect(); + + let verifier_data_hash = builder.constant_hash(HashOut { + elements: EMPTY_HASH.0, + }); + let predicate = PredicateTarget::new_intro(builder, verifier_data_hash); + vec![StatementTarget { predicate, args }] +} + +#[derive(Clone, Debug)] +struct VdfPodTarget { + vd_root: HashOutTarget, + statements_hash: HashOutTarget, + proof: ProofWithPublicInputsTarget, +} +struct VdfPodVerifyInput { + vd_root: Hash, + statements_hash: Hash, + proof: ProofWithPublicInputs, +} +impl VdfPodTarget { + fn add_targets(builder: &mut CircuitBuilder, params: &Params) -> Result { + let measure: () = measure_gates_begin!(builder, "VdfPodTarget"); + + // Verify RecursiveCircuit's proof (with verifier_data hardcoded as constant) + let (_, recursive_params) = &*VDF_RECURSIVE_CIRCUIT; + let verifier_data_targ = + builder.constant_verifier_data(&recursive_params.verifier_data().verifier_only); + let proof = builder.add_virtual_proof_with_pis(recursive_params.common_data()); + builder.verify_proof::(&proof, &verifier_data_targ, recursive_params.common_data()); + + // ensure that the verifier_data_hash that appears at the public inputs + // of the proof being verified matches the one that is constant + let pi_verifier_data_hash = &proof.public_inputs[9..13]; + let constant_verifier_data_hash = hash_verifier_data_gadget(builder, &verifier_data_targ); + #[allow(clippy::needless_range_loop)] // to use same syntax as in other similar circuits + for i in 0..HASH_SIZE { + builder.connect( + pi_verifier_data_hash[i], + constant_verifier_data_hash.elements[i], + ); + } + + // calculate statements_hash + let count = proof.public_inputs[0]; + let input = &proof.public_inputs[1..5]; + let output = &proof.public_inputs[5..9]; + let statements = pub_self_statements_target(builder, params, count, input, output); + let statements_hash = calculate_statements_hash_circuit(params, builder, &statements); + + // register the public inputs + let vd_root = builder.add_virtual_hash(); + builder.register_public_inputs(&statements_hash.elements); + builder.register_public_inputs(&vd_root.elements); + + measure_gates_end!(builder, measure); + Ok(VdfPodTarget { + vd_root, + statements_hash, + proof, + }) + } + + fn set_targets(&self, pw: &mut PartialWitness, input: &VdfPodVerifyInput) -> Result<()> { + pw.set_proof_with_pis_target(&self.proof, &input.proof)?; + pw.set_hash_target( + self.statements_hash, + HashOut::from_vec(input.statements_hash.0.to_vec()), + )?; + pw.set_target_arr(&self.vd_root.elements, &input.vd_root.0)?; + + Ok(()) + } +} + +#[derive(Clone, Debug)] +struct VdfInnerCircuit { + prev_count: Target, + count: Target, // count contains the amount of recursive steps done + input: ValueTarget, // input that is bounded into the recursive chain + midput: ValueTarget, // midput is the 'input' used for the last step of the recursion + output: ValueTarget, // output of the recursive chain +} +#[derive(Debug)] +struct VdfInnerCircuitInput { + prev_count: F, + count: F, + input: RawValue, + midput: RawValue, + output: RawValue, +} +impl InnerCircuit for VdfInnerCircuit { + type Input = VdfInnerCircuitInput; + type Params = (); + fn build( + builder: &mut CircuitBuilder, + _params: &Self::Params, + verified_proofs: &[VerifiedProofTarget], + ) -> BResult { + let prev_count = builder.add_virtual_target(); + let input = builder.add_virtual_value(); + let midput = builder.add_virtual_value(); + + let output_h = builder.hash_n_to_hash_no_pad::(midput.elements.to_vec()); + let output = ValueTarget::from_slice(output_h.elements.as_ref()); + + let zero = builder.zero(); + let one = builder.one(); + + let is_basecase = builder.is_equal(prev_count, zero); // case 0 + let is_not_basecase = builder.not(is_basecase); + let is_case_1 = builder.is_equal(prev_count, one); // case 1 + let case_0_or_1 = builder.or(is_basecase, is_case_1); + let after_case_1 = builder.not(case_0_or_1); + + // if we're at the prev_count==0, ensure that + // input==midput + for i in 0..HASH_SIZE { + builder.conditional_assert_eq( + is_basecase.target, + input.elements[i], + midput.elements[i], + ); + } + + // if we're at case prev_count>0, assert that the public_inputs of the + // proof being verified match with the prev_count, input and midput. + // For prev_count>1, we also check that the verifier_data_hash being + // used matches the one at the public_inputs of the previous proof. + builder.connect(verified_proofs[0].public_inputs[0], prev_count); + for i in 0..HASH_SIZE { + // if prev_count>0: + builder.conditional_assert_eq( + is_not_basecase.target, + verified_proofs[0].public_inputs[1 + i], + input.elements[i], + ); + builder.conditional_assert_eq( + is_not_basecase.target, + verified_proofs[0].public_inputs[5 + i], + midput.elements[i], + ); + + // if we're at case prev_count>1: + // check that the verifier_data's hash used to verify the current + // proof is the same as in the public_inputs. Notice that at case 0, + // this verifier_data_hash is [0,0,0,0], and at case 1 is the hash + // of the dummy_verifier_data; hence we do this check when + // prev_count>1. + builder.conditional_assert_eq( + after_case_1.target, + verified_proofs[0].public_inputs[9 + i], + verified_proofs[0].verifier_data_hash.elements[i], + ); + } + + // increment count + let count = builder.add(prev_count, one); + + // register public inputs: count, input, output + builder.register_public_input(count); + builder.register_public_inputs(&input.elements); + builder.register_public_inputs(&output.elements); + builder.register_public_inputs(&verified_proofs[0].verifier_data_hash.elements); + + Ok(Self { + prev_count, + count, + input, + midput, + output, + }) + } + fn set_targets(&self, pw: &mut PartialWitness, input: &Self::Input) -> BResult<()> { + pw.set_target(self.prev_count, input.prev_count)?; + pw.set_target(self.count, input.count)?; + pw.set_target_arr(&self.input.elements, &input.input.0)?; + pw.set_target_arr(&self.midput.elements, &input.midput.0)?; + pw.set_target_arr(&self.output.elements, &input.output.0)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use plonky2::plonk::circuit_data::CircuitConfig; + use pod2::{ + backends::plonky2::basetypes::DEFAULT_VD_SET, + frontend, measure_gates_print, + middleware::{Value, hash_str}, + }; + + use super::*; + + // For tests only. Returns a valid VerifiedProofTarget filled with the + // public_inputs from the given VdfInnerCircuitInput, in order to run some + // tests. + fn empty_verified_proof_target( + builder: &mut CircuitBuilder, + inp: &VdfInnerCircuitInput, + ) -> VerifiedProofTarget { + let count = builder.constant(inp.prev_count); + let input = builder.constants(&inp.input.0); + let midput = if inp.prev_count.is_zero() { + builder.constants(&inp.output.0) + } else { + builder.constants(&inp.midput.0) + }; + let verifier_data_hash = HashOutTarget::from_partial(&[builder.zero()], builder.zero()); + VerifiedProofTarget { + public_inputs: [ + vec![count], + input, + midput, + verifier_data_hash.elements.to_vec(), + ] + .concat(), + verifier_data_hash, + } + } + #[test] + fn test_inner_circuit() -> Result<()> { + let inner_params = (); + + let starting_input = RawValue::from(hash_str("starting input")); + + // circuit + let config = CircuitConfig::standard_recursion_zk_config(); + let mut builder = CircuitBuilder::::new(config.clone()); + + let inner_inputs = VdfInnerCircuitInput { + prev_count: F::ZERO, + count: F::ONE, + input: starting_input, + midput: starting_input, // base case: midput==input + output: RawValue::from(pod2::middleware::hash_value(&starting_input)), + }; + + // build circuit + let measure = measure_gates_begin!(&builder, format!("VdfInnerCircuit gates")); + let verified_proof_target = empty_verified_proof_target(&mut builder, &inner_inputs); + let targets = + VdfInnerCircuit::build(&mut builder, &inner_params, &[verified_proof_target])?; + measure_gates_end!(&builder, measure); + measure_gates_print!(); + let data = builder.build::(); + + // set witness + let mut pw = PartialWitness::::new(); + targets.set_targets(&mut pw, &inner_inputs)?; + + // generate & verify proof + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + // Second iteration + let inner_inputs = VdfInnerCircuitInput { + prev_count: F::ONE, + count: F::from_canonical_u64(2u64), + input: starting_input, + midput: inner_inputs.output, // base case: midput==input + output: RawValue::from(pod2::middleware::hash_value(&inner_inputs.output)), + }; + let mut builder = CircuitBuilder::::new(config); + let mut pw = PartialWitness::::new(); + let verified_proof_target = empty_verified_proof_target(&mut builder, &inner_inputs); + let targets = + VdfInnerCircuit::build(&mut builder, &inner_params, &[verified_proof_target])?; + targets.set_targets(&mut pw, &inner_inputs)?; + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + Ok(()) + } + + #[test] + fn test_recursion_on_inner_circuit() -> Result<()> { + let starting_input = RawValue::from(hash_str("starting input")); + let _ = VdfPod::get_vdf_recursive_circuit_proof(3, starting_input)?; + Ok(()) + } + + /// test to ensure that the pub_self_statements methods match between the + /// in-circuit and the out-circuit implementations + #[test] + fn test_pub_self_statements_target() -> Result<()> { + // first generate all the circuits data so that it does not need to be + // computed at further stages of the test (affecting the time reports) + timed!( + "generate VDF_RECURSIVE_CIRCUIT, STANDARD_VDF_POD_DATA, STANDARD_REC_MAIN_POD_CIRCUIT", + { + let (_, _) = &*VDF_RECURSIVE_CIRCUIT; + let (_, _) = &*STANDARD_VDF_POD_DATA; + let _ = + &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data( + ); + } + ); + + let params = &Default::default(); + + let count = F::ONE; + let input = RawValue::from(hash_str("starting input")); + let output = RawValue::from(pod2::middleware::hash_value(&input)); + + let st = pub_self_statements(count, input, output) + .into_iter() + .map(mainpod::Statement::from) + .collect_vec(); + let statements_hash: HashOut = + HashOut::::from_vec(calculate_statements_hash(&st, params).0.to_vec()); + + // circuit + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let mut pw = PartialWitness::::new(); + + // add targets + let count_targ = builder.add_virtual_target(); + let input_targ = builder.add_virtual_value(); + let output_targ = builder.add_virtual_value(); + let expected_statements_hash_targ = builder.add_virtual_hash(); + + // set values to targets + pw.set_target(count_targ, count)?; + pw.set_target_arr(&input_targ.elements, &input.0)?; + pw.set_target_arr(&output_targ.elements, &output.0)?; + pw.set_hash_target(expected_statements_hash_targ, statements_hash)?; + + let st_targ = pub_self_statements_target( + &mut builder, + params, + count_targ, + &input_targ.elements, + &output_targ.elements, + ); + let statements_hash_targ = + calculate_statements_hash_circuit(params, &mut builder, &st_targ); + + builder.connect_hashes(expected_statements_hash_targ, statements_hash_targ); + + // generate & verify proof + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + Ok(()) + } + + #[test] + fn test_vdf_pod() -> Result<()> { + // for this test, first generate all the circuits data so that it does + // not need to be computed at further stages of the test (affecting the + // time reports) + timed!( + "generate VDF_RECURSIVE_CIRCUIT, STANDARD_VDF_POD_DATA, standard_rec_main_pod_common_circuit_data", + { + let (_, _) = &*VDF_RECURSIVE_CIRCUIT; + let (_, _) = &*STANDARD_VDF_POD_DATA; + let _ = + &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data( + ); + } + ); + + let params = Params::default(); + let n_iters: usize = 2; + let input = RawValue::from(hash_str("starting input")); + + let vd_set = &*DEFAULT_VD_SET; + let vdf_pod = timed!( + "VdfPod::new", + VdfPod::new(¶ms, vd_set.clone(), n_iters, input)? + ); + vdf_pod.verify()?; + + println!( + "vdf_pod.verifier_data_hash(): {:#} . To be used when importing the VdfPod as introduction pod to define new predicates.", + vdf_pod.verifier_data_hash() + ); + + // wrap the vdf_pod in a 'MainPod' + let main_vdf_pod = frontend::MainPod { + pod: Box::new(vdf_pod.clone()), + public_statements: vdf_pod.pub_statements(), + params: params.clone(), + }; + + let expected_count = Value::from(n_iters as i64); + let expected_input = input; + + // now generate a new MainPod from the vdf_pod + let mut main_pod_builder = frontend::MainPodBuilder::new(¶ms, vd_set); + main_pod_builder.add_pod(main_vdf_pod.clone()); + + main_pod_builder.reveal(&main_vdf_pod.public_statements[0]); + + let prover = pod2::backends::plonky2::mock::mainpod::MockProver {}; + let pod = main_pod_builder.prove(&prover)?; + assert!(pod.pod.verify().is_ok()); + + println!("going to prove the main_pod"); + let prover = mainpod::Prover {}; + let main_pod = timed!("main_pod_builder.prove", main_pod_builder.prove(&prover)?); + let pod: Box = (main_pod.pod as Box) + .downcast::() + .unwrap(); + pod.verify()?; + + let st_vdf = pod.pub_statements()[0].clone(); + let count = st_vdf.args()[0].literal()?; + let input = st_vdf.args()[1].literal()?; + assert_eq!(count, expected_count); + assert_eq!(input, Value::from(expected_input)); + + Ok(()) + } +}