From eabc428ee2ffe14f45bcadd676bf5c7860d128e1 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 22 Jan 2026 15:34:39 -0800 Subject: [PATCH 1/6] rename powpod to vdfpod --- app_cli/src/lib.rs | 32 ++-- app_gui/src/crafting.rs | 12 +- craftlib/src/item.rs | 28 ++-- craftlib/src/lib.rs | 2 +- craftlib/src/predicates.rs | 40 ++--- craftlib/src/{powpod.rs => vdfpod.rs} | 205 +++++++++++++------------- 6 files changed, 158 insertions(+), 161 deletions(-) rename craftlib/src/{powpod.rs => vdfpod.rs} (82%) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index c1a90b9..54046ef 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -19,8 +19,8 @@ use craftlib::{ WOOD_MINING_MAX, WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, }, item::{CraftBuilder, MiningRecipe}, - powpod::PowPod, predicates::ItemPredicates, + vdfpod::VdfPod, }; use plonky2::field::types::Field; use pod2::{ @@ -180,7 +180,7 @@ impl Helper { recipe: Recipe, item_def: ItemDef, input_item_pods: Vec, - pow_pod: Option, + vdf_pod: Option, ) -> anyhow::Result { let prover = &Prover {}; let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); @@ -229,16 +229,16 @@ 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 - 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(), + // unwrap safe since if we're at Stone, vdf_pod is Some + 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_pow_pod); - craft_builder.st_is_stone(item_def, st_item_def.clone(), st_pow)? + craft_builder.ctx.builder.add_pod(main_vdf_pod); + craft_builder.st_is_stone(item_def, st_item_def.clone(), st_vdf)? } Recipe::Wood => craft_builder.st_is_wood(item_def, st_item_def.clone())?, Recipe::Axe => craft_builder.st_is_axe( @@ -306,7 +306,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) = match recipe { Recipe::Stone => { if !inputs.is_empty() { bail!("{recipe} takes 0 inputs"); @@ -317,20 +317,20 @@ pub fn craft_item( .unwrap(); let start = std::time::Instant::now(); - let pow_pod = PowPod::new( + let vdf_pod = VdfPod::new( params, vd_set.clone(), 3, // num_iters RawValue::from(ingredients_def.dict(params)?.commitment()), )?; - log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); + log::info!("[TIME] VdfPod proving time: {:?}", start.elapsed()); ( ItemDef { ingredients: ingredients_def.clone(), - work: pow_pod.output, + work: vdf_pod.output, }, vec![], - Some(pow_pod), + Some(vdf_pod), ) } Recipe::Wood => { @@ -398,7 +398,7 @@ pub fn craft_item( 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)?; 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..c36f17b 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -39,13 +39,13 @@ 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 IsStone(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) Equal(inputs, {}) DictContains(ingredients, "blueprint", "stone") - Pow(3, ingredients, work) + Vdf(3, ingredients, work) )"#, ..Default::default() }; @@ -136,7 +136,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 +164,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 +215,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 +233,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..5e58760 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -75,7 +75,7 @@ impl<'a> CraftBuilder<'a> { &mut self, 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 +83,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", STONE_BLUEPRINT), - st_pow + st_vdf ))?) } @@ -200,9 +200,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 +212,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 +227,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 +318,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 +335,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 +343,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 +389,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..86281bb 100644 --- a/craftlib/src/lib.rs +++ b/craftlib/src/lib.rs @@ -1,5 +1,5 @@ pub mod constants; pub mod item; -pub mod powpod; +pub mod vdfpod; pub mod predicates; mod test_util; diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 29eaeb7..fbec02d 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -19,8 +19,8 @@ impl ItemPredicates { // 5 statements per predicate let batch_defs = [ r#" - use intro Pow(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 // powpod vd hash - + use intro Vdf(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 // vdfpod 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. @@ -28,9 +28,9 @@ impl ItemPredicates { ItemDef(item, ingredients, inputs, key, work) Equal(inputs, {}) DictContains(ingredients, "blueprint", "stone") - Pow(3, ingredients, work) + Vdf(3, ingredients, work) ) - + // Example of a mined item which is more common but takes more work to // extract. IsWood(item, private: ingredients, inputs, key, work) = AND( @@ -47,28 +47,28 @@ impl ItemPredicates { // 2 ingredients 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 IsAxe(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "axe") Equal(work, {}) - + AxeInputs(inputs) ) - + // Wooden Axe: WoodenAxeInputs(inputs, private: s1, wood1, wood2) = AND( // 2 ingredients SetInsert(s1, {}, wood1) SetInsert(inputs, s1, wood2) - + // prove the ingredients are correct. IsWood(wood1) IsWood(wood2) @@ -79,7 +79,7 @@ impl ItemPredicates { ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "wooden-axe") Equal(work, {}) - + WoodenAxeInputs(inputs) ) "#, @@ -108,8 +108,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 +145,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 +248,7 @@ mod tests { st_item_def, st_inputs_eq_empty, st_contains_blueprint, - st_pow, + st_vdf, ], ))?; diff --git a/craftlib/src/powpod.rs b/craftlib/src/vdfpod.rs similarity index 82% rename from craftlib/src/powpod.rs rename to craftlib/src/vdfpod.rs index 53c61ab..ad2bd0a 100644 --- a/craftlib/src/powpod.rs +++ b/craftlib/src/vdfpod.rs @@ -1,40 +1,37 @@ -//! PowPod: Introduction Pod that used as a "Proof of Work". +//! 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 'work' comes from the proof computation cost at the each recursive step. -//! -//! 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. +//! 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: +//! 1. RecursiveCircuit, where for each recursive step: //! -//! PowInnerCircuit contains the logic of: +//! VdfInnerCircuit contains the logic of: //! - output = hash(input) //! - count+1 //! //! And the RecursiveCircuit does the logic of: //! - verify previous proof of itself //! -//! 2. PowPod: +//! 2. VdfPod: //! - satisfies in the pod2's Pod trait interface -//! - verifies the proof from RecursiveCircuit +//! - verifies the proof from RecursiveCircuit //! //! //! Usage: //! ```rust //! use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::{Params, RawValue, hash_str}}; -//! use craftlib::powpod::PowPod; +//! 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 pow_pod = PowPod::new(¶ms, vd_set.clone(), n_iters, input).unwrap(); +//! 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_pow_pod` (bottom +//! An complete example of usage can be found at the test `test_vdf_pod` (bottom //! of this file). use anyhow::{Result, anyhow}; @@ -83,14 +80,14 @@ use pod2::{ }; use serde::{Deserialize, Serialize}; -// ARITY is assumed to be one, this also assumed at the PowInnerCircuit. +// 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 POW_POD_TYPE: (usize, &str) = (2001, "Pow"); +const VDF_POD_TYPE: (usize, &str) = (2001, "Vdf"); -static STANDARD_POW_POD_DATA: std::sync::LazyLock<(PowPodTarget, CircuitData)> = +static STANDARD_VDF_POD_DATA: std::sync::LazyLock<(VdfPodTarget, CircuitData)> = std::sync::LazyLock::new(|| build().expect("successful build")); -fn build() -> Result<(PowPodTarget, CircuitData)> { +fn build() -> Result<(VdfPodTarget, CircuitData)> { let params = Params::default(); // use pod2's recursion config as config for the introduction pod; which if @@ -102,28 +99,28 @@ 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 vdf_pod_verify_target = VdfPodTarget::add_targets(&mut builder, ¶ms)?; pod2::backends::plonky2::recursion::pad_circuit(&mut builder, &common_data); - let data = timed!("PowPod build", builder.build::()); + let data = timed!("VdfPod build", builder.build::()); assert_eq!(common_data, data.common); - Ok((pow_pod_verify_target, data)) + Ok((vdf_pod_verify_target, data)) } -static POW_RECURSIVE_CIRCUIT: std::sync::LazyLock<( - RecursiveCircuit, +static VDF_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)> { +)> = 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, &())?; + new_recursive_params::(ARITY, NUM_PUBLIC_INPUTS, &())?; - let recursive_circuit = RecursiveCircuit::::build(&recursive_params, &())?; + let recursive_circuit = RecursiveCircuit::::build(&recursive_params, &())?; Ok((recursive_circuit, recursive_params)) } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct PowPod { +pub struct VdfPod { pub params: Params, pub count: F, pub input: RawValue, @@ -137,36 +134,36 @@ 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 { +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): ( - PowInnerCircuitInput, + VdfInnerCircuitInput, ProofWithPublicInputs, ) = timed!( - "PowPod::gen_pow_recursive_circuit_proof", - PowPod::get_pow_recursive_circuit_proof(n_iters, input)? + "VdfPod::gen_vdf_recursive_circuit_proof", + VdfPod::get_vdf_recursive_circuit_proof(n_iters, input)? ); - // generate a new PowPod from the given count, input, output + // 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 pow_pod = timed!( - "PowPod::construct", - PowPod::construct(params, vd_set, count, input, output, proof_with_pis)? + let vdf_pod = timed!( + "VdfPod::construct", + VdfPod::construct(params, vd_set, count, input, output, proof_with_pis)? ); #[cfg(test)] // sanity check - pow_pod.verify()?; + vdf_pod.verify()?; - Ok(pow_pod) + Ok(vdf_pod) } - /// given the proof from RecursiveCircuit, constructs the - /// PowPod which verifies it. + /// given the proof from RecursiveCircuit, constructs the + /// VdfPod which verifies it. fn construct( params: &Params, vd_set: VDSet, @@ -174,24 +171,24 @@ impl PowPod { input: RawValue, output: RawValue, proof: ProofWithPublicInputs, - ) -> Result { - // verify the given proof in a PowPodTarget circuit - let (pow_pod_target, circuit_data) = &*STANDARD_POW_POD_DATA; + ) -> 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_pow_input = PowPodVerifyInput { + let pod_vdf_input = VdfPodVerifyInput { vd_root: vd_set.root(), statements_hash, proof, }; let mut pw = PartialWitness::::new(); - pow_pod_target.set_targets(&mut pw, &pod_pow_input)?; + vdf_pod_target.set_targets(&mut pw, &pod_vdf_input)?; let proof_with_pis = timed!( - "prove the pow-verification proof verification (PowPod proof)", + "prove the vdf-verification proof verification (VdfPod proof)", circuit_data.prove(pw)? ); // sanity check @@ -202,7 +199,7 @@ impl PowPod { let common_hash: String = pod2::backends::plonky2::mainpod::cache_get_rec_main_pod_common_hash(params).clone(); - Ok(PowPod { + Ok(VdfPod { params: params.clone(), statements_hash, count, @@ -214,24 +211,24 @@ impl PowPod { }) } - /// computes the PoW proof out of the RecursiveCircuit circuit. - fn get_pow_recursive_circuit_proof( + /// computes the VDF proof out of the RecursiveCircuit circuit. + fn get_vdf_recursive_circuit_proof( n_iters: usize, starting_input: RawValue, - ) -> Result<(PowInnerCircuitInput, ProofWithPublicInputs)> { + ) -> 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 PowPod circuit, the verifier_data_hash would not - // match the one expected (hardcoded as constant) at the PowPod + // 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 = PowInnerCircuitInput { + let mut inner_inputs = VdfInnerCircuitInput { prev_count: F::ZERO, count: F::ONE, input: starting_input, @@ -239,7 +236,7 @@ impl PowPod { output: RawValue::from(pod2::middleware::hash_value(&starting_input)), }; - let (recursive_circuit, recursive_params) = &*POW_RECURSIVE_CIRCUIT; + let (recursive_circuit, recursive_params) = &*VDF_RECURSIVE_CIRCUIT; let (dummy_verifier_only_data, dummy_proof) = dummy_recursive(recursive_params.common_data(), NUM_PUBLIC_INPUTS)?; @@ -281,7 +278,7 @@ struct Data { common_hash: String, } -impl Pod for PowPod { +impl Pod for VdfPod { fn params(&self) -> &Params { &self.params } @@ -298,7 +295,7 @@ impl Pod for PowPod { )); } - let (_, circuit_data) = &*STANDARD_POW_POD_DATA; + let (_, circuit_data) = &*STANDARD_VDF_POD_DATA; let public_inputs = statements_hash .to_fields(&self.params) @@ -312,7 +309,7 @@ impl Pod for PowPod { proof: self.proof.clone(), public_inputs, }) - .map_err(|e| Error::custom(format!("PowPod proof verification failure: {e:?}"))) + .map_err(|e| Error::custom(format!("VdfPod proof verification failure: {e:?}"))) } fn statements_hash(&self) -> Hash { @@ -320,7 +317,7 @@ impl Pod for PowPod { } fn pod_type(&self) -> (usize, &'static str) { - POW_POD_TYPE + VDF_POD_TYPE } fn pub_self_statements(&self) -> Vec { @@ -361,7 +358,7 @@ impl Pod for PowPod { } fn verifier_data(&self) -> VerifierOnlyCircuitData { - STANDARD_POW_POD_DATA + STANDARD_VDF_POD_DATA .1 .verifier_data() .verifier_only @@ -382,7 +379,7 @@ impl Pod for PowPod { fn pub_self_statements(count: F, input: RawValue, output: RawValue) -> Vec { vec![middleware::Statement::Intro( IntroPredicateRef { - name: POW_POD_TYPE.1.to_string(), + name: VDF_POD_TYPE.1.to_string(), args_len: 3, verifier_data_hash: EMPTY_HASH, }, @@ -423,22 +420,22 @@ fn pub_self_statements_target( } #[derive(Clone, Debug)] -struct PowPodTarget { +struct VdfPodTarget { vd_root: HashOutTarget, statements_hash: HashOutTarget, proof: ProofWithPublicInputsTarget, } -struct PowPodVerifyInput { +struct VdfPodVerifyInput { vd_root: Hash, statements_hash: Hash, proof: ProofWithPublicInputs, } -impl PowPodTarget { +impl VdfPodTarget { fn add_targets(builder: &mut CircuitBuilder, params: &Params) -> Result { - let measure = measure_gates_begin!(builder, "PowPodTarget"); + let measure: () = measure_gates_begin!(builder, "VdfPodTarget"); - // Verify RecursiveCircuit's proof (with verifier_data hardcoded as constant) - let (_, recursive_params) = &*POW_RECURSIVE_CIRCUIT; + // 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()); @@ -469,14 +466,14 @@ impl PowPodTarget { builder.register_public_inputs(&vd_root.elements); measure_gates_end!(builder, measure); - Ok(PowPodTarget { + Ok(VdfPodTarget { vd_root, statements_hash, proof, }) } - fn set_targets(&self, pw: &mut PartialWitness, input: &PowPodVerifyInput) -> Result<()> { + 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, @@ -489,7 +486,7 @@ impl PowPodTarget { } #[derive(Clone, Debug)] -struct PowInnerCircuit { +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 @@ -497,15 +494,15 @@ struct PowInnerCircuit { output: ValueTarget, // output of the recursive chain } #[derive(Debug)] -struct PowInnerCircuitInput { +struct VdfInnerCircuitInput { prev_count: F, count: F, input: RawValue, midput: RawValue, output: RawValue, } -impl InnerCircuit for PowInnerCircuit { - type Input = PowInnerCircuitInput; +impl InnerCircuit for VdfInnerCircuit { + type Input = VdfInnerCircuitInput; type Params = (); fn build( builder: &mut CircuitBuilder, @@ -608,11 +605,11 @@ mod tests { use super::*; // For tests only. Returns a valid VerifiedProofTarget filled with the - // public_inputs from the given PowInnerCircuitInput, in order to run some + // public_inputs from the given VdfInnerCircuitInput, in order to run some // tests. fn empty_verified_proof_target( builder: &mut CircuitBuilder, - inp: &PowInnerCircuitInput, + inp: &VdfInnerCircuitInput, ) -> VerifiedProofTarget { let count = builder.constant(inp.prev_count); let input = builder.constants(&inp.input.0); @@ -643,7 +640,7 @@ mod tests { let config = CircuitConfig::standard_recursion_zk_config(); let mut builder = CircuitBuilder::::new(config.clone()); - let inner_inputs = PowInnerCircuitInput { + let inner_inputs = VdfInnerCircuitInput { prev_count: F::ZERO, count: F::ONE, input: starting_input, @@ -652,10 +649,10 @@ mod tests { }; // build circuit - let measure = measure_gates_begin!(&builder, format!("PowInnerCircuit gates")); + let measure = measure_gates_begin!(&builder, format!("VdfInnerCircuit gates")); let verified_proof_target = empty_verified_proof_target(&mut builder, &inner_inputs); let targets = - PowInnerCircuit::build(&mut builder, &inner_params, &[verified_proof_target])?; + VdfInnerCircuit::build(&mut builder, &inner_params, &[verified_proof_target])?; measure_gates_end!(&builder, measure); measure_gates_print!(); let data = builder.build::(); @@ -669,7 +666,7 @@ mod tests { data.verify(proof.clone())?; // Second iteration - let inner_inputs = PowInnerCircuitInput { + let inner_inputs = VdfInnerCircuitInput { prev_count: F::ONE, count: F::from_canonical_u64(2u64), input: starting_input, @@ -680,7 +677,7 @@ mod tests { 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])?; + 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)?; @@ -692,7 +689,7 @@ mod tests { #[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)?; + let _ = VdfPod::get_vdf_recursive_circuit_proof(3, starting_input)?; Ok(()) } @@ -703,10 +700,10 @@ mod tests { // 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", + "generate VDF_RECURSIVE_CIRCUIT, STANDARD_VDF_POD_DATA, STANDARD_REC_MAIN_POD_CIRCUIT", { - let (_, _) = &*POW_RECURSIVE_CIRCUIT; - let (_, _) = &*STANDARD_POW_POD_DATA; + let (_, _) = &*VDF_RECURSIVE_CIRCUIT; + let (_, _) = &*STANDARD_VDF_POD_DATA; let _ = &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data( ); @@ -764,15 +761,15 @@ mod tests { } #[test] - fn test_pow_pod() -> Result<()> { + 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 POW_RECURSIVE_CIRCUIT, STANDARD_POW_POD_DATA, standard_rec_main_pod_common_circuit_data", + "generate VDF_RECURSIVE_CIRCUIT, STANDARD_VDF_POD_DATA, standard_rec_main_pod_common_circuit_data", { - let (_, _) = &*POW_RECURSIVE_CIRCUIT; - let (_, _) = &*STANDARD_POW_POD_DATA; + let (_, _) = &*VDF_RECURSIVE_CIRCUIT; + let (_, _) = &*STANDARD_VDF_POD_DATA; let _ = &*pod2::backends::plonky2::cache_get_standard_rec_main_pod_common_circuit_data( ); @@ -784,32 +781,32 @@ mod tests { 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)? + let vdf_pod = timed!( + "VdfPod::new", + VdfPod::new(¶ms, vd_set.clone(), n_iters, input)? ); - pow_pod.verify()?; + vdf_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() + "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 pow_pod in a 'MainPod' - let main_pow_pod = frontend::MainPod { - pod: Box::new(pow_pod.clone()), - public_statements: pow_pod.pub_statements(), + // 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 pow_pod + // 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_pow_pod.clone()); + main_pod_builder.add_pod(main_vdf_pod.clone()); - main_pod_builder.reveal(&main_pow_pod.public_statements[0]); + 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)?; @@ -823,9 +820,9 @@ mod tests { .unwrap(); pod.verify()?; - let st_pow = pod.pub_statements()[0].clone(); - let count = st_pow.args()[0].literal()?; - let input = st_pow.args()[1].literal()?; + 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)); From f804411da2c422f403d01f2e3135594c4e0fd092 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 22 Jan 2026 16:21:39 -0800 Subject: [PATCH 2/6] add powpod; update wood to use it --- app_cli/src/lib.rs | 32 ++- app_gui/src/crafting.rs | 4 +- craftlib/src/item.rs | 2 + craftlib/src/lib.rs | 1 + craftlib/src/powpod.rs | 469 +++++++++++++++++++++++++++++++++++++ craftlib/src/predicates.rs | 45 +++- 6 files changed, 536 insertions(+), 17 deletions(-) create mode 100644 craftlib/src/powpod.rs diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 54046ef..7136148 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -21,6 +21,7 @@ use craftlib::{ item::{CraftBuilder, MiningRecipe}, predicates::ItemPredicates, vdfpod::VdfPod, + powpod::PoWPod, }; use plonky2::field::types::Field; use pod2::{ @@ -181,6 +182,7 @@ impl Helper { item_def: ItemDef, input_item_pods: Vec, vdf_pod: Option, + pow_pod: Option, ) -> anyhow::Result { let prover = &Prover {}; let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); @@ -240,7 +242,18 @@ impl Helper { craft_builder.ctx.builder.add_pod(main_vdf_pod); craft_builder.st_is_stone(item_def, st_item_def.clone(), st_vdf)? } - Recipe::Wood => craft_builder.st_is_wood(item_def, st_item_def.clone())?, + 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 => craft_builder.st_is_axe( item_def, st_item_def.clone(), @@ -306,7 +319,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, vdf_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"); @@ -331,6 +344,7 @@ pub fn craft_item( }, vec![], Some(vdf_pod), + None, ) } Recipe::Wood => { @@ -341,6 +355,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 +371,7 @@ pub fn craft_item( }, vec![], None, + Some(pow_pod), ) } Recipe::Axe => { @@ -370,6 +394,7 @@ pub fn craft_item( }, vec![wood, stone], None, + None, ) } Recipe::WoodenAxe => { @@ -392,13 +417,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, vdf_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 c36f17b..d14ae73 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -40,11 +40,13 @@ lazy_static! { outputs: &["Stone"], predicate: r#" use intro Vdf(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 +use intro PoW(input, difficulty, hash_output) from 0x8088c0d4f95988793df8a203412f36df4d82cfd98f33a0f4287156eb29f84bd7 -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(ingredients, difficulty, ingredients_hash) Vdf(3, ingredients, work) )"#, ..Default::default() diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 5e58760..65a61f2 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -91,6 +91,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 +99,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) ))?) } diff --git a/craftlib/src/lib.rs b/craftlib/src/lib.rs index 86281bb..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 new file mode 100644 index 0000000..f3f5efb --- /dev/null +++ b/craftlib/src/powpod.rs @@ -0,0 +1,469 @@ +//! 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 +//! +//! This is used to prove that mining work was done to find a valid nonce/seed. +//! +//! 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 the pod2's Pod trait interface +//! - verifies the proof from PoWCircuit +//! +//! Usage: +//! ```rust +//! 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 hash = RawValue::from(...); // ingredients commitment/hash +//! let difficulty = 0x0020_0000_0000_0000u64; +//! let pow_pod = PoWPod::new(¶ms, vd_set.clone(), hash, difficulty).unwrap(); +//! ``` + +use anyhow::Result; +use itertools::Itertools; +use plonky2::{ + field::types::Field, + hash::hash_types::{HashOut, HashOutTarget}, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + circuit_data::{CircuitData, VerifierOnlyCircuitData}, + proof::ProofWithPublicInputs, + }, +}; +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, + serialize_proof, + }, + measure_gates_begin, measure_gates_end, middleware, + middleware::{ + C, D, EMPTY_HASH, F, Hash, IntroPredicateRef, Params, Pod, Proof, RawValue, + ToFields, VDSet, + }, + timed, +}; +use serde::{Deserialize, Serialize}; + +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(); + + 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 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_target, data)) +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct PoWPod { + pub params: Params, + 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, + pub proof: Proof, + + pub common_hash: String, +} + +#[allow(dead_code)] +impl PoWPod { + /// 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"); + } + + let difficulty_f = F::from_canonical_u64(difficulty); + + // Build the proof + let (pow_pod_target, circuit_data) = &*STANDARD_POW_POD_DATA; + 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); + + let pow_input = PoWPodInput { + vd_root: vd_set.root(), + statements_hash, + hash, + difficulty: difficulty_f, + }; + + let mut pw = PartialWitness::::new(); + 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())?; + + let common_hash: String = + pod2::backends::plonky2::mainpod::cache_get_rec_main_pod_common_hash(params).clone(); + + Ok(PoWPod { + params: params.clone(), + statements_hash, + hash, + difficulty: difficulty_f, + proof: proof_with_pis.proof, + vd_set: vd_set.clone(), + common_hash, + }) + } +} + +#[derive(Serialize, Deserialize)] +struct Data { + hash: RawValue, + difficulty: F, + proof: String, + common_hash: String, +} + +impl Pod for PoWPod { + fn params(&self) -> &Params { + &self.params + } + + fn verify(&self) -> pod2::backends::plonky2::Result<()> { + let statements = pub_self_statements(self.hash, self.difficulty) + .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_POW_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!("PoWPod proof verification failure: {e:?}"))) + } + + fn statements_hash(&self) -> Hash { + self.statements_hash + } + + fn pod_type(&self) -> (usize, &'static str) { + POW_POD_TYPE + } + + fn pub_self_statements(&self) -> Vec { + pub_self_statements(self.hash, self.difficulty) + } + + fn serialize_data(&self) -> serde_json::Value { + serde_json::to_value(Data { + 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, + 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, + hash: data.hash, + difficulty: data.difficulty, + vd_set, + statements_hash, + proof, + common_hash: data.common_hash, + }) + } + + fn verifier_data(&self) -> VerifierOnlyCircuitData { + STANDARD_POW_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(hash: RawValue, difficulty: F) -> Vec { + vec![middleware::Statement::Intro( + IntroPredicateRef { + name: POW_POD_TYPE.1.to_string(), + args_len: 2, + verifier_data_hash: EMPTY_HASH, + }, + vec![ + hash.into(), + RawValue([difficulty, F::ZERO, F::ZERO, F::ZERO]).into(), + ], + )] +} + +fn pub_self_statements_target( + builder: &mut CircuitBuilder, + params: &Params, + hash: &[Target], + difficulty: Target, +) -> Vec { + let zero = builder.zero(); + let st_arg_0 = StatementArgTarget::literal(builder, &ValueTarget::from_slice(hash)); + let st_arg_1 = StatementArgTarget::literal( + builder, + &ValueTarget::from_slice(&[difficulty, zero, zero, zero]), + ); + + let args = [st_arg_0, st_arg_1] + .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 PoWPodTarget { + vd_root: HashOutTarget, + statements_hash: HashOutTarget, + hash: ValueTarget, + difficulty: Target, +} + +struct PoWPodInput { + vd_root: Hash, + statements_hash: Hash, + hash: RawValue, + difficulty: F, +} + +impl PoWPodTarget { + fn add_targets(builder: &mut CircuitBuilder, params: &Params) -> Result { + let measure = measure_gates_begin!(builder, "PoWPodTarget"); + + // 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 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, + hash, + difficulty, + }) + } + + 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()), + )?; + pw.set_target_arr(&self.vd_root.elements, &input.vd_root.0)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use pod2::{ + backends::plonky2::basetypes::DEFAULT_VD_SET, + middleware::hash_str, + }; + + use super::*; + + #[test] + fn test_pow_pod() -> Result<()> { + let params = Params::default(); + let vd_set = &*DEFAULT_VD_SET; + + // 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 ingredients = found_input.expect("Should find valid input"); + + // This should succeed + let pow_pod = PoWPod::new(¶ms, vd_set.clone(), ingredients, difficulty)?; + pow_pod.verify()?; + + println!( + "pow_pod.verifier_data_hash(): {:#} . To be used in predicates.", + pow_pod.verifier_data_hash() + ); + + // 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_fails_above_difficulty() -> Result<()> { + let params = Params::default(); + let vd_set = &*DEFAULT_VD_SET; + + let input = RawValue::from(hash_str("definitely above difficulty")); + let difficulty = 1u64; // Very strict difficulty + + // 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 fbec02d..9ed1b31 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -1,9 +1,12 @@ 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::WOOD_MINING_MAX; + pub struct ItemPredicates { pub defs: PredicateDefs, @@ -17,9 +20,17 @@ impl ItemPredicates { // 4 predicates per batch // 8 arguments per predicate, at most 5 of which are public // 5 statements per predicate - let batch_defs = [ - r#" + + // Convert WOOD_MINING_MAX to RawValue format for predicate (little-endian) + let wood_difficulty_f = F::from_canonical_u64(WOOD_MINING_MAX); + let wood_difficulty_raw = format!( + "Raw(0x{:016x}{:016x}{:016x}{:016x})", + 0u64, 0u64, 0u64, wood_difficulty_f.0 + ); + + let batch_def_1 = r#" 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 no inputs or sequential work. // Stone requires working in a stone mine (blueprint="stone") and @@ -31,18 +42,20 @@ impl ItemPredicates { Vdf(3, ingredients, work) ) - // 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, {}) DictContains(ingredients, "blueprint", "wood") - Equal(work, {}) - // TODO input POD: SequentialWork(ingredients, work, 5) - // TODO input POD: HashInRange(0, 1<<5, ingredients) + PoW(ingredients, "#.to_string() + &wood_difficulty_raw + r#") // Proves ingredients <= WOOD_MINING_MAX + Equal(work, {}) // No VDF work required ) - "#, - r#" + "#; + + let batch_def_2 = r#" AxeInputs(inputs, private: s1, wood, stone) = AND( // 2 ingredients SetInsert(s1, {}, wood) @@ -82,9 +95,15 @@ impl ItemPredicates { WoodenAxeInputs(inputs) ) - "#, - ]; - let defs = PredicateDefs::new(params, &batch_defs, slice::from_ref(&commit_preds.defs)); + "#; + + let batch_defs = [batch_def_1, batch_def_2.to_string()]; + 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(), From 7b02312ce28a761addd178f059fedd030f27c1b1 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 22 Jan 2026 16:37:52 -0800 Subject: [PATCH 3/6] update stone and axe to use powpod --- app_cli/src/lib.rs | 61 ++++++++++++++++++++++++++++------ craftlib/src/constants.rs | 2 +- craftlib/src/item.rs | 4 +++ craftlib/src/predicates.rs | 68 +++++++++++++++++++++++--------------- 4 files changed, 97 insertions(+), 38 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 7136148..1a877ca 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -19,9 +19,9 @@ use craftlib::{ WOOD_MINING_MAX, WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, }, item::{CraftBuilder, MiningRecipe}, + powpod::PoWPod, predicates::ItemPredicates, vdfpod::VdfPod, - powpod::PoWPod, }; use plonky2::field::types::Field; use pod2::{ @@ -231,7 +231,16 @@ 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, vdf_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 { + 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); + let vdf_pod = vdf_pod.unwrap(); let st_vdf = vdf_pod.pub_statements()[0].clone(); let main_vdf_pod = MainPod { @@ -240,7 +249,7 @@ impl Helper { 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_vdf)? + 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 @@ -254,12 +263,24 @@ impl Helper { craft_builder.ctx.builder.add_pod(main_pow_pod); craft_builder.st_is_wood(item_def, st_item_def.clone(), st_pow)? } - Recipe::Axe => craft_builder.st_is_axe( - item_def, - st_item_def.clone(), - sts_input_craft[0].clone(), - sts_input_craft[1].clone(), - )?, + 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::WoodenAxe => craft_builder.st_is_wooden_axe( item_def, st_item_def.clone(), @@ -329,6 +350,15 @@ pub fn craft_item( .do_mining(params, key, 0, STONE_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()), + STONE_MINING_MAX, + )?; + log::info!("[TIME] PoWPod proving time: {:?}", start.elapsed()); + let start = std::time::Instant::now(); let vdf_pod = VdfPod::new( params, @@ -344,7 +374,7 @@ pub fn craft_item( }, vec![], Some(vdf_pod), - None, + Some(pow_pod), ) } Recipe::Wood => { @@ -387,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(), @@ -394,7 +433,7 @@ pub fn craft_item( }, vec![wood, stone], None, - None, + Some(pow_pod), ) } Recipe::WoodenAxe => { diff --git a/craftlib/src/constants.rs b/craftlib/src/constants.rs index f5df968..cb6c188 100644 --- a/craftlib/src/constants.rs +++ b/craftlib/src/constants.rs @@ -5,7 +5,7 @@ pub const STONE_MINING_MAX: u64 = 0x0020_0000_0000_0000; pub const STONE_WORK: RawValue = EMPTY_VALUE; pub const WOOD_BLUEPRINT: &str = "wood"; -pub const WOOD_MINING_MAX: u64 = 0x0020_0000_0000_0000; +pub const WOOD_MINING_MAX: u64 = 0x0010_0000_0000_0000; pub const WOOD_WORK: RawValue = EMPTY_VALUE; pub const AXE_BLUEPRINT: &str = "axe"; diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 65a61f2..e24ce70 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -75,6 +75,7 @@ impl<'a> CraftBuilder<'a> { &mut self, item_def: ItemDef, st_item_def: Statement, + st_pow: Statement, st_vdf: Statement, ) -> anyhow::Result { // Build IsStone(item) @@ -83,6 +84,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", STONE_BLUEPRINT), + st_pow, st_vdf ))?) } @@ -129,6 +131,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 { @@ -138,6 +141,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 ))?) diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 9ed1b31..ddb9d8e 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -5,7 +5,16 @@ use plonky2::field::types::Field; use pod2::middleware::{CustomPredicateRef, F, Params}; use pod2utils::PredicateDefs; -use crate::constants::WOOD_MINING_MAX; +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, @@ -21,25 +30,27 @@ impl ItemPredicates { // 8 arguments per predicate, at most 5 of which are public // 5 statements per predicate - // Convert WOOD_MINING_MAX to RawValue format for predicate (little-endian) - let wood_difficulty_f = F::from_canonical_u64(WOOD_MINING_MAX); - let wood_difficulty_raw = format!( - "Raw(0x{:016x}{:016x}{:016x}{:016x})", - 0u64, 0u64, 0u64, wood_difficulty_f.0 - ); + // 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 = r#" + let batch_def_1 = format!( + r#" 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 no inputs or sequential work. - // Stone requires working in a stone mine (blueprint="stone") and - // 10 leading 0s. + // 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") - Vdf(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 with just PoW (no VDF work). @@ -48,17 +59,21 @@ impl ItemPredicates { // - 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") - PoW(ingredients, "#.to_string() + &wood_difficulty_raw + r#") // Proves ingredients <= WOOD_MINING_MAX - Equal(work, {}) // No VDF work required + 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 - let batch_def_2 = r#" AxeInputs(inputs, private: s1, wood, stone) = AND( // 2 ingredients - SetInsert(s1, {}, wood) + SetInsert(s1, {{}}, wood) SetInsert(inputs, s1, stone) // prove the ingredients are correct. @@ -66,12 +81,12 @@ impl ItemPredicates { 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) ) @@ -79,7 +94,7 @@ impl ItemPredicates { // 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. @@ -91,13 +106,14 @@ 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 batch_defs = [batch_def_1, batch_def_2.to_string()]; + 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, From 2361dc981be7c31c7133f8fd0bfb425f74be70fb Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 22 Jan 2026 16:38:10 -0800 Subject: [PATCH 4/6] fix wood mining max --- craftlib/src/constants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/craftlib/src/constants.rs b/craftlib/src/constants.rs index cb6c188..f5df968 100644 --- a/craftlib/src/constants.rs +++ b/craftlib/src/constants.rs @@ -5,7 +5,7 @@ pub const STONE_MINING_MAX: u64 = 0x0020_0000_0000_0000; pub const STONE_WORK: RawValue = EMPTY_VALUE; pub const WOOD_BLUEPRINT: &str = "wood"; -pub const WOOD_MINING_MAX: u64 = 0x0010_0000_0000_0000; +pub const WOOD_MINING_MAX: u64 = 0x0020_0000_0000_0000; pub const WOOD_WORK: RawValue = EMPTY_VALUE; pub const AXE_BLUEPRINT: &str = "axe"; From 1748bf3da43edde44e105db11bd96490288c949c Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 22 Jan 2026 16:42:56 -0800 Subject: [PATCH 5/6] add timing info for mining op --- craftlib/src/item.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index e24ce70..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)); } } From 4f2aeb6888656d617819661ca1b768058a706500 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 22 Jan 2026 16:51:21 -0800 Subject: [PATCH 6/6] fix --- app_cli/src/lib.rs | 16 +++--- app_gui/src/crafting.rs | 6 +- craftlib/src/powpod.rs | 112 ++++++++++++++++--------------------- craftlib/src/predicates.rs | 16 +++--- 4 files changed, 69 insertions(+), 81 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 1a877ca..a254088 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -19,7 +19,7 @@ use craftlib::{ WOOD_MINING_MAX, WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, }, item::{CraftBuilder, MiningRecipe}, - powpod::PoWPod, + powpod::PowPod, predicates::ItemPredicates, vdfpod::VdfPod, }; @@ -182,7 +182,7 @@ impl Helper { item_def: ItemDef, input_item_pods: Vec, vdf_pod: Option, - pow_pod: Option, + pow_pod: Option, ) -> anyhow::Result { let prover = &Prover {}; let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); @@ -351,13 +351,13 @@ pub fn craft_item( .unwrap(); let start = std::time::Instant::now(); - let pow_pod = PoWPod::new( + let pow_pod = PowPod::new( params, vd_set.clone(), RawValue::from(ingredients_def.dict(params)?.commitment()), STONE_MINING_MAX, )?; - log::info!("[TIME] PoWPod proving time: {:?}", start.elapsed()); + log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); let start = std::time::Instant::now(); let vdf_pod = VdfPod::new( @@ -387,13 +387,13 @@ pub fn craft_item( .unwrap(); let start = std::time::Instant::now(); - let pow_pod = PoWPod::new( + 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()); + log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); ( ItemDef { ingredients: ingredients_def.clone(), @@ -419,13 +419,13 @@ pub fn craft_item( .unwrap(); let start = std::time::Instant::now(); - let pow_pod = PoWPod::new( + 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()); + log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); ( ItemDef { ingredients: ingredients_def.clone(), diff --git a/app_gui/src/crafting.rs b/app_gui/src/crafting.rs index d14ae73..cbd1499 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -40,13 +40,13 @@ lazy_static! { outputs: &["Stone"], predicate: r#" use intro Vdf(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 -use intro PoW(input, difficulty, hash_output) from 0x8088c0d4f95988793df8a203412f36df4d82cfd98f33a0f4287156eb29f84bd7 +use intro Pow(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod vd hash IsStone(item, private: ingredients, inputs, key, work, difficulty, ingredients_hash) = AND( ItemDef(item, ingredients, inputs, key, work) Equal(inputs, {}) DictContains(ingredients, "blueprint", "stone") - PoW(ingredients, difficulty, ingredients_hash) + Pow(ingredients, difficulty) Vdf(3, ingredients, work) )"#, ..Default::default() @@ -59,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() }; @@ -70,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 diff --git a/craftlib/src/powpod.rs b/craftlib/src/powpod.rs index f3f5efb..f09d958 100644 --- a/craftlib/src/powpod.rs +++ b/craftlib/src/powpod.rs @@ -1,29 +1,29 @@ -//! PoWPod: Introduction Pod that proves Proof of Work (mining difficulty). +//! 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 //! //! This is used to prove that mining work was done to find a valid nonce/seed. //! //! Circuit structure: -//! 1. PoWCircuit: +//! 1. PowCircuit: //! - hash: RawValue (4 field elements - already a hash/commitment) //! - difficulty_target: u64 constant //! - proves: hash[0] <= difficulty_target //! -//! 2. PoWPod: +//! 2. PowPod: //! - satisfies the pod2's Pod trait interface -//! - verifies the proof from PoWCircuit +//! - verifies the proof from PowCircuit //! //! Usage: //! ```rust //! use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::{Params, RawValue}}; -//! use craftlib::powpod::PoWPod; +//! use craftlib::powpod::PowPod; //! //! let params = Params::default(); //! let vd_set = &*DEFAULT_VD_SET; //! 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(); +//! let pow_pod = PowPod::new(¶ms, vd_set.clone(), hash, difficulty).unwrap(); //! ``` use anyhow::Result; @@ -57,19 +57,18 @@ use pod2::{ }, measure_gates_begin, measure_gates_end, middleware, middleware::{ - C, D, EMPTY_HASH, F, 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}; -const POW_POD_TYPE: (usize, &str) = (2002, "PoW"); +const POW_POD_TYPE: (usize, &str) = (2002, "Pow"); -static STANDARD_POW_POD_DATA: std::sync::LazyLock<(PoWPodTarget, CircuitData)> = +static STANDARD_POW_POD_DATA: std::sync::LazyLock<(PowPodTarget, CircuitData)> = std::sync::LazyLock::new(|| build().expect("successful build")); -fn build() -> Result<(PoWPodTarget, CircuitData)> { +fn build() -> Result<(PowPodTarget, CircuitData)> { let params = Params::default(); let rec_circuit_data = @@ -79,18 +78,18 @@ fn build() -> Result<(PoWPodTarget, CircuitData)> { let config = common_data.config.clone(); let mut builder = CircuitBuilder::::new(config); - let pow_pod_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::()); + let data = timed!("PowPod build", builder.build::()); assert_eq!(common_data, data.common); Ok((pow_pod_target, data)) } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct PoWPod { +pub struct PowPod { pub params: Params, - pub hash: RawValue, // The hash to check (e.g., dict commitment) + pub hash: RawValue, // The hash to check (e.g., dict commitment) pub difficulty: F, // difficulty target as a field element pub vd_set: VDSet, @@ -101,14 +100,9 @@ pub struct PoWPod { } #[allow(dead_code)] -impl PoWPod { - /// Creates a PoWPod proving that hash[0] <= difficulty - pub fn new( - params: &Params, - vd_set: VDSet, - hash: RawValue, - difficulty: u64, - ) -> Result { +impl PowPod { + /// 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"); @@ -124,7 +118,7 @@ impl PoWPod { .collect_vec(); let statements_hash: Hash = calculate_statements_hash(&statements, params); - let pow_input = PoWPodInput { + let pow_input = PowPodInput { vd_root: vd_set.root(), statements_hash, hash, @@ -133,11 +127,8 @@ impl PoWPod { let mut pw = PartialWitness::::new(); pow_pod_target.set_targets(&mut pw, &pow_input)?; - - let proof_with_pis = timed!( - "prove PoW difficulty check", - circuit_data.prove(pw)? - ); + + let proof_with_pis = timed!("prove Pow difficulty check", circuit_data.prove(pw)?); circuit_data .verifier_data() @@ -146,7 +137,7 @@ impl PoWPod { let common_hash: String = pod2::backends::plonky2::mainpod::cache_get_rec_main_pod_common_hash(params).clone(); - Ok(PoWPod { + Ok(PowPod { params: params.clone(), statements_hash, hash, @@ -166,7 +157,7 @@ struct Data { common_hash: String, } -impl Pod for PoWPod { +impl Pod for PowPod { fn params(&self) -> &Params { &self.params } @@ -198,7 +189,7 @@ impl Pod for PoWPod { proof: self.proof.clone(), public_inputs, }) - .map_err(|e| Error::custom(format!("PoWPod proof verification failure: {e:?}"))) + .map_err(|e| Error::custom(format!("PowPod proof verification failure: {e:?}"))) } fn statements_hash(&self) -> Hash { @@ -291,7 +282,7 @@ fn pub_self_statements_target( builder, &ValueTarget::from_slice(&[difficulty, zero, zero, zero]), ); - + let args = [st_arg_0, st_arg_1] .into_iter() .chain(core::iter::repeat_with(|| { @@ -308,23 +299,23 @@ fn pub_self_statements_target( } #[derive(Clone, Debug)] -struct PoWPodTarget { +struct PowPodTarget { vd_root: HashOutTarget, statements_hash: HashOutTarget, hash: ValueTarget, difficulty: Target, } -struct PoWPodInput { +struct PowPodInput { vd_root: Hash, statements_hash: Hash, hash: RawValue, difficulty: F, } -impl PoWPodTarget { +impl PowPodTarget { fn add_targets(builder: &mut CircuitBuilder, params: &Params) -> Result { - let measure = measure_gates_begin!(builder, "PoWPodTarget"); + let measure = measure_gates_begin!(builder, "PowPodTarget"); // Add virtual inputs let hash = builder.add_virtual_value(); @@ -332,36 +323,36 @@ impl PoWPodTarget { // 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 + + // 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, @@ -370,12 +361,7 @@ impl PoWPodTarget { 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 = pub_self_statements_target(builder, params, &hash.elements, difficulty); let statements_hash = calculate_statements_hash_circuit(params, builder, &statements); // Register public inputs @@ -384,8 +370,8 @@ impl PoWPodTarget { builder.register_public_inputs(&vd_root.elements); measure_gates_end!(builder, measure); - - Ok(PoWPodTarget { + + Ok(PowPodTarget { vd_root, statements_hash, hash, @@ -393,7 +379,7 @@ impl PoWPodTarget { }) } - fn set_targets(&self, pw: &mut PartialWitness, input: &PoWPodInput) -> Result<()> { + 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( @@ -408,10 +394,7 @@ impl PoWPodTarget { #[cfg(test)] mod tests { - use pod2::{ - backends::plonky2::basetypes::DEFAULT_VD_SET, - middleware::hash_str, - }; + use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::hash_str}; use super::*; @@ -423,21 +406,24 @@ mod tests { // 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); + println!( + "Found valid input at i={}: hash={:#x}", + i, hash_output.0[0].0 + ); break; } } - + let ingredients = found_input.expect("Should find valid input"); // This should succeed - let pow_pod = PoWPod::new(¶ms, vd_set.clone(), ingredients, difficulty)?; + let pow_pod = PowPod::new(¶ms, vd_set.clone(), ingredients, difficulty)?; pow_pod.verify()?; println!( @@ -461,7 +447,7 @@ mod tests { let difficulty = 1u64; // Very strict difficulty // This should fail - let result = PoWPod::new(¶ms, vd_set.clone(), input, difficulty); + 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 ddb9d8e..28230d7 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -38,30 +38,30 @@ impl ItemPredicates { let batch_def_1 = format!( r#" use intro Vdf(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 // vdfpod vd hash - use intro PoW(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod 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) + // - 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, {{}}) DictContains(ingredients, "blueprint", "stone") - PoW(ingredients, {stone_difficulty_raw}) // Proves ingredients <= STONE_MINING_MAX + 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 with just PoW (no VDF work). + // Example of a mined item with just Pow (no VDF work). // Wood requires: // - blueprint="wood" - // - hash(ingredients) meets difficulty (PoW mining) + // - hash(ingredients) meets difficulty (Pow mining) IsWood(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) Equal(inputs, {{}}) DictContains(ingredients, "blueprint", "wood") - PoW(ingredients, {wood_difficulty_raw}) // Proves ingredients <= WOOD_MINING_MAX + Pow(ingredients, {wood_difficulty_raw}) // Proves ingredients <= WOOD_MINING_MAX Equal(work, {{}}) // No VDF work required ) "# @@ -69,7 +69,7 @@ impl ItemPredicates { let batch_def_2 = format!( r#" - use intro PoW(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod vd hash + use intro Pow(hash, difficulty) from 0x42fed42704533123de144a9e820c9d6bdf4c8616f29664111469bd696b628686 // powpod vd hash AxeInputs(inputs, private: s1, wood, stone) = AND( // 2 ingredients @@ -85,7 +85,7 @@ impl ItemPredicates { IsAxe(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "axe") - PoW(ingredients, {axe_difficulty_raw}) // Proves ingredients <= AXE_MINING_MAX + Pow(ingredients, {axe_difficulty_raw}) // Proves ingredients <= AXE_MINING_MAX Equal(work, {{}}) AxeInputs(inputs)