From 1a2dbdd3674aac8acaa804aae0688426f64fd33b Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 26 Nov 2025 17:18:24 +0000 Subject: [PATCH 01/29] Port @artwyman's BatchDef, ItemInBatch, AllItemsInBatch predicates Co-authored-by: Andrew Twyman --- commitlib/src/predicates.rs | 101 +++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/commitlib/src/predicates.rs b/commitlib/src/predicates.rs index 1687c63..939ec37 100644 --- a/commitlib/src/predicates.rs +++ b/commitlib/src/predicates.rs @@ -8,12 +8,19 @@ pub struct CommitPredicates { pub subset_of: CustomPredicateRef, pub subset_of_recursive: CustomPredicateRef, + pub batch_def: CustomPredicateRef, + pub item_in_batch: CustomPredicateRef, + pub item_def: CustomPredicateRef, - pub item_key: CustomPredicateRef, + pub all_items_in_batch: CustomPredicateRef, + pub all_items_in_batch_empty: CustomPredicateRef, + pub all_items_in_batch_recursive: CustomPredicateRef, + pub item_key: CustomPredicateRef, pub nullifiers: CustomPredicateRef, pub nullifiers_empty: CustomPredicateRef, pub nullifiers_recursive: CustomPredicateRef, + pub commit_creation: CustomPredicateRef, } @@ -25,6 +32,7 @@ impl CommitPredicates { // 8 arguments per predicate, at most 5 of which are public // 5 statements per predicate let batch_defs = [ + // 1 r#" // Generic recursive construction confirming subset. Relies on the Merkle // tree already requiring unique keys (so no inserts on super) @@ -39,23 +47,75 @@ impl CommitPredicates { SubsetOf(smaller, super) ) - // Prove proper derivation of item ID from defined inputs - // The ingredients dict is explicitly allowed to contain more fields - // for use in item predicates. - ItemDef(item, ingredients, inputs, key, work) = AND( + // Core commitment to a crafting operation: + // batch = a single hash representing all outputs + // ingredients = dict with 2 required fields (inputs, keys), + // but allowing other fields usable by the item layer + // keys = root of a dict containing one key per item + // inputs = root of a set of item IDs of inputs consumed + // work = opaque value (hash) used by item layer for sequential work + BatchDef(batch, ingredients, inputs, keys, work) = AND( DictContains(ingredients, "inputs", inputs) - DictContains(ingredients, "key", key) + DictContains(ingredients, "keys", keys) HashOf(item, ingredients, work) ) + // Each item in a batch has an index (likely 0..N, but could be any + // value) which must correspond to its key. + // It confirms that the item ID and keys use the same indexes, for + // consistent nullifiers. + ItemInBatch(item, batch, private: index, key) = AND( + HashOf(item, batch, index) + SetContains(keys, index, key) + ) + "#, + // 2 + r#" + // Predicate constructing the ID of one item from a batch without + // any reference to the batch,. + // Each item in a batch has an index (possibly 0..N, but could be + // any value) which must correspond to the index of its key. + ItemDef(item, ingredients, inputs, key, work, private: batch, index) = AND( + BatchDef(batch, ingredients, inputs, keys, work) + ItemInBatch(item, batch, index) + ) + + // Recursive construction to extract all the individual item IDs + // from a batch. They must be 1:1 with the input keys. The `All` in + // the name means this is a strict predicate, which doesn't allow + // for extra values in sets. Note that `keys` is public here, + // because CommitCrafting needs to match it up against the + // CraftingDef. Note this allows for empty batches. + AllItemsInBatch(items, batch, keys) = OR( + AllItemsInBatchEmpty(items, batch, keys) + AllItemsInBatchRecursive(items, batch, keys) + ) + + AllItemsInBatchEmpty(items, batch, keys) = AND( + Equal(items, #{}) + Equal(keys, #{}) + // batch is intentionally unconstrained + ) + + AllItemsInBatchRecursive(items, batch, keys) = AND( + SetInsert(items, prev_items, item) + DictInsert(keys, prev_keys, index, key) + + // Inlined version of ItemInBatch. Here we need to explicitly + // set all values, and don't want to pay for an extra statement + // to do so. + HashOf(item, batch, index) + SetContains(keys, index, key) + ) + "#, + // 3 + r#" // Helper to expose just the item and key from ItemId calculation. // This is just the CreatedItem pattern with some of inupts private. ItemKey(item, key, private: ingredients, inputs, work) = AND( ItemDef(item, ingredients, inputs, key, work) ) - "#, - &format!( - r#" + // Derive nullifiers from items (using a recursive foreach construction) // This proves the relationship between an item and its key before using // the key to calculate a nullifier. @@ -77,7 +137,10 @@ impl CommitPredicates { SetInsert(inputs, inputs_prev, input) Nullifiers(nullifiers_prev, inputs_prev) ) - + "#, + // 4 + &format!( + r#" // ZK version of CreatedItem for committing on-chain. // Validator/Logger/Archiver needs to maintain 2 append-only // sets of items and nullifiers. New creating is @@ -88,7 +151,10 @@ impl CommitPredicates { CommitCreation(item, nullifiers, created_items, private: ingredients, inputs, key, work) = AND( // Prove the item hash includes all of its committed properties - ItemDef(item, ingredients, inputs, key, work) + BatchDef(batch, ingredients, inputs, keys, work) + + // Prove that the item set represents all outputs of this batch. + AllItemsInBatch(items, batch, keys) // Prove all inputs are in the created set SubsetOf(inputs, created_items) @@ -105,12 +171,23 @@ impl CommitPredicates { CommitPredicates { subset_of: defs.predicate_ref_by_name("SubsetOf").unwrap(), subset_of_recursive: defs.predicate_ref_by_name("SubsetOfRecursive").unwrap(), + batch_def: defs.predicate_ref_by_name("BatchDef").unwrap(), + item_in_batch: defs.predicate_ref_by_name("ItemInBatch").unwrap(), + item_def: defs.predicate_ref_by_name("ItemDef").unwrap(), + all_items_in_batch: defs.predicate_ref_by_name("AllItemsInBatch").unwrap(), + all_items_in_batch_empty: defs.predicate_ref_by_name("AllItemsInBatchEmpty").unwrap(), + all_items_in_batch_recursive: defs + .predicate_ref_by_name("AllItemsInBatchRecursive") + .unwrap(), + item_key: defs.predicate_ref_by_name("ItemKey").unwrap(), nullifiers: defs.predicate_ref_by_name("Nullifiers").unwrap(), nullifiers_empty: defs.predicate_ref_by_name("NullifiersEmpty").unwrap(), nullifiers_recursive: defs.predicate_ref_by_name("NullifiersRecursive").unwrap(), + commit_creation: defs.predicate_ref_by_name("CommitCreation").unwrap(), + defs, } } @@ -124,6 +201,6 @@ mod tests { fn test_compile_custom_predicates() { let params = Params::default(); let commit_preds = CommitPredicates::compile(¶ms); - assert!(commit_preds.defs.batches.len() == 2); + assert!(commit_preds.defs.batches.len() == 4); } } From e8af4c6ad67a1a4e0ff57fed3da19f58a0bae8d1 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 26 Nov 2025 17:21:22 +0000 Subject: [PATCH 02/29] add st_{batch/item}_def, st_item_in_batch methods; pending update macro --- commitlib/src/lib.rs | 83 +++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 2a15d40..e08c832 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -91,31 +91,6 @@ impl<'a> ItemBuilder<'a> { Self { ctx, params } } - // Adds statements to MainPodBilder to represent a generic item based on the - // ItemDef. Includes the following public predicates: ItemDef, ItemKey - // Returns the Statement object for ItemDef for use in further statements. - pub fn st_item_def(&mut self, item_def: ItemDef) -> anyhow::Result { - let ingredients_dict = item_def.ingredients.dict(self.params)?; - let inputs_set = item_def.ingredients.inputs_set(self.params)?; - let item_hash = item_def.item_hash(self.params)?; - - // Build ItemDef(item, ingredients, inputs, key, work) - Ok(st_custom!(self.ctx, - ItemDef() = ( - DictContains(ingredients_dict, "inputs", inputs_set), - DictContains(ingredients_dict, "key", item_def.ingredients.key), - HashOf(item_hash, ingredients_dict, item_def.work) - ))?) - } - - pub fn st_item_key(&mut self, st_item_def: Statement) -> anyhow::Result { - // Build ItemKey(item, key) - Ok(st_custom!(self.ctx, - ItemKey() = ( - st_item_def - ))?) - } - fn st_super_sub_set_recursive( &mut self, inputs_set: Set, @@ -166,6 +141,63 @@ impl<'a> ItemBuilder<'a> { } } + pub fn st_batch_def(&mut self, index: usize, batch: Vec) -> anyhow::Result { + let ingredients_dict = batch[index].ingredients.dict(self.params)?; + let inputs_set = batch[index].ingredients.inputs_set(self.params)?; + let item_hash = batch[index].item_hash(self.params)?; + + // Build BatchDef(item, ingredients, inputs, key, work) + Ok(st_custom!(self.ctx, + BatchDef() = ( + DictContains(ingredients_dict, "inputs", inputs_set), + DictContains(ingredients_dict, "key", batch[index].ingredients.key), + HashOf(item_hash, ingredients_dict, batch[index].work) + ))?) + } + + pub fn st_item_in_batch( + &mut self, + item_def: ItemDef, + batch: Vec, + ) -> anyhow::Result { + let ingredients_dict = item_def.ingredients.dict(self.params)?; + let inputs_set = item_def.ingredients.inputs_set(self.params)?; + let item_hash = item_def.item_hash(self.params)?; + + // Build ItemInBatch(item, batch) + Ok(st_custom!(self.ctx, + ItemInBatch() = ( + HashOf(item_hash, ingredients_dict, batch[0].work), + SetContains(ingredients_dict, "key", batch[0].ingredients.key), + ))?) + } + + // Adds statements to MainPodBilder to represent a generic item based on the + // ItemDef. Includes the following public predicates: ItemDef, ItemKey + // Returns the Statement object for ItemDef for use in further statements. + pub fn st_item_def(&mut self, item_def: ItemDef) -> anyhow::Result { + let ingredients_dict = item_def.ingredients.dict(self.params)?; + let inputs_set = item_def.ingredients.inputs_set(self.params)?; + let item_hash = item_def.item_hash(self.params)?; + + // Build ItemDef(item, ingredients, inputs, key, work) + Ok(st_custom!(self.ctx, + ItemDef() = ( + BatchDef(batch, ingredients, inputs, keys, work), + ItemInBatch(item, batch, index), + ))?) + } + + // TODO st_all_items_in_batch{, _empty, _recursive} + + pub fn st_item_key(&mut self, st_item_def: Statement) -> anyhow::Result { + // Build ItemKey(item, key) + Ok(st_custom!(self.ctx, + ItemKey() = ( + st_item_def + ))?) + } + // Adds statements to MainPodBilder to prove correct nullifiers for a set of // inputs. Returns the private Nullifiers. pub fn st_nullifiers( @@ -226,6 +258,7 @@ impl<'a> ItemBuilder<'a> { // the root to prove that inputs were previously created. pub fn st_commit_creation( &mut self, + // TODO update to multi-output (batch_def & st_all_items_in_batch) item_def: ItemDef, st_nullifiers: Statement, created_items: Set, From e73940cd7bafbe51c68e3f091d21bfba0e719d48 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 27 Nov 2025 11:58:08 +1000 Subject: [PATCH 03/29] Adjust predicates --- commitlib/src/predicates.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/commitlib/src/predicates.rs b/commitlib/src/predicates.rs index 939ec37..7ec620c 100644 --- a/commitlib/src/predicates.rs +++ b/commitlib/src/predicates.rs @@ -57,27 +57,28 @@ impl CommitPredicates { BatchDef(batch, ingredients, inputs, keys, work) = AND( DictContains(ingredients, "inputs", inputs) DictContains(ingredients, "keys", keys) - HashOf(item, ingredients, work) + HashOf(batch, ingredients, work) ) // Each item in a batch has an index (likely 0..N, but could be any // value) which must correspond to its key. // It confirms that the item ID and keys use the same indexes, for // consistent nullifiers. - ItemInBatch(item, batch, private: index, key) = AND( + ItemInBatch(item, batch, index, private: key, keys) = AND( HashOf(item, batch, index) - SetContains(keys, index, key) + DictContains(keys, index, key) ) "#, // 2 r#" // Predicate constructing the ID of one item from a batch without - // any reference to the batch,. + // any reference to the batch. // Each item in a batch has an index (possibly 0..N, but could be // any value) which must correspond to the index of its key. - ItemDef(item, ingredients, inputs, key, work, private: batch, index) = AND( + ItemDef(item, ingredients, inputs, key, work, private: batch, index, keys) = AND( BatchDef(batch, ingredients, inputs, keys, work) ItemInBatch(item, batch, index) + DictContains(keys, index, key) ) // Recursive construction to extract all the individual item IDs @@ -97,7 +98,7 @@ impl CommitPredicates { // batch is intentionally unconstrained ) - AllItemsInBatchRecursive(items, batch, keys) = AND( + AllItemsInBatchRecursive(items, batch, keys, private: prev_items, prev_keys, item, index, key) = AND( SetInsert(items, prev_items, item) DictInsert(keys, prev_keys, index, key) @@ -105,11 +106,11 @@ impl CommitPredicates { // set all values, and don't want to pay for an extra statement // to do so. HashOf(item, batch, index) - SetContains(keys, index, key) + DictContains(keys, index, key) ) "#, // 3 - r#" + &format!(r#" // Helper to expose just the item and key from ItemId calculation. // This is just the CreatedItem pattern with some of inupts private. ItemKey(item, key, private: ingredients, inputs, work) = AND( @@ -137,7 +138,7 @@ impl CommitPredicates { SetInsert(inputs, inputs_prev, input) Nullifiers(nullifiers_prev, inputs_prev) ) - "#, + "#), // 4 &format!( r#" @@ -148,9 +149,10 @@ impl CommitPredicates { // - item is not already in item set // - all nullifiers are not already in nullifier set // - createdItems is one of the historical item set roots - CommitCreation(item, nullifiers, created_items, - private: ingredients, inputs, key, work) = AND( - // Prove the item hash includes all of its committed properties + CommitCreation(items, nullifiers, created_items, + private: batch, ingredients, inputs, keys, work) = AND( + // Prove the core crafting operation. + // The batch hash includes all of its committed properties BatchDef(batch, ingredients, inputs, keys, work) // Prove that the item set represents all outputs of this batch. From 2288bb7f7bde86f145c53d05041a6bae2731a5be Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 27 Nov 2025 16:03:30 +1000 Subject: [PATCH 04/29] WIP --- commitlib/src/lib.rs | 97 +++++++++++++++++++++++++------------ commitlib/src/predicates.rs | 14 +++--- 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index e08c832..066c646 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -21,7 +21,7 @@ pub const CONSUMED_ITEM_EXTERNAL_NULLIFIER: &str = "consumed item external nulli pub struct IngredientsDef { // These properties are committed on-chain pub inputs: HashSet, - pub key: RawValue, + pub keys: Vec, // These properties are used only by the application layer pub app_layer: HashMap, @@ -31,7 +31,10 @@ impl IngredientsDef { pub fn dict(&self, params: &Params) -> pod2::middleware::Result { let mut map = HashMap::new(); map.insert(Key::from("inputs"), Value::from(self.inputs_set(params)?)); - map.insert(Key::from("key"), Value::from(self.key)); + map.insert(Key::from("keys"), Value::from( + Dictionary::new(params.max_depth_mt_containers, + self.keys.iter().enumerate().map(|(i,k)| (format!("{i}").into(), (*k).into())).collect() + )?)); for (key, value) in &self.app_layer { map.insert(Key::from(key), value.clone()); } @@ -47,21 +50,45 @@ impl IngredientsDef { } } -// Rust-level definition of an item, used to derive its ID (hash). +// Rust-level definition of a batch, used to derive its ID (hash). #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ItemDef { +pub struct BatchDef { pub ingredients: IngredientsDef, pub work: RawValue, } -impl ItemDef { - pub fn item_hash(&self, params: &Params) -> pod2::middleware::Result { +impl BatchDef { + pub fn batch_hash(&self, params: &Params) -> pod2::middleware::Result { Ok(hash_values(&[ Value::from(self.ingredients.dict(params)?), Value::from(self.work), ])) } + pub fn new(ingredients: IngredientsDef, work: RawValue) -> Self { + Self { ingredients, work } + } +} + +// Rust-level definition of an item, used to derive its ID (hash). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ItemDef { + pub batch: BatchDef, + pub index: usize +} + +impl ItemDef { + pub fn item_key(&self) -> RawValue { + self.batch.ingredients.keys[self.index] + } + + pub fn item_hash(&self, params: &Params) -> pod2::middleware::Result { + Ok(hash_values(&[ + Value::from(self.batch.batch_hash(params)?), + Value::from(self.index as i64), + ])) + } + pub fn nullifier(&self, params: &Params) -> pod2::middleware::Result { Ok(hash_values(&[ Value::from(self.item_hash(params)?), @@ -69,8 +96,8 @@ impl ItemDef { ])) } - pub fn new(ingredients: IngredientsDef, work: RawValue) -> Self { - Self { ingredients, work } + pub fn new(batch: BatchDef, index: usize) -> Self { + Self { batch, index } } } @@ -141,34 +168,41 @@ impl<'a> ItemBuilder<'a> { } } - pub fn st_batch_def(&mut self, index: usize, batch: Vec) -> anyhow::Result { - let ingredients_dict = batch[index].ingredients.dict(self.params)?; - let inputs_set = batch[index].ingredients.inputs_set(self.params)?; - let item_hash = batch[index].item_hash(self.params)?; + pub fn st_batch_def(&mut self, batch: BatchDef) -> anyhow::Result { + let ingredients_dict = batch.ingredients.dict(self.params)?; + let inputs_set = batch.ingredients.inputs_set(self.params)?; + let batch_hash = batch.batch_hash(self.params)?; + let keys_dict = Dictionary::new(self.params.max_depth_mt_containers, + batch.ingredients.keys.iter().enumerate().map(|(i,k)| (format!("{i}").into(), (*k).into())).collect() + )?; + // Build BatchDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, BatchDef() = ( DictContains(ingredients_dict, "inputs", inputs_set), - DictContains(ingredients_dict, "key", batch[index].ingredients.key), - HashOf(item_hash, ingredients_dict, batch[index].work) + DictContains(ingredients_dict, "keys", keys_dict), + HashOf(batch_hash, ingredients_dict, batch.work) ))?) } pub fn st_item_in_batch( &mut self, - item_def: ItemDef, - batch: Vec, + item_def: ItemDef ) -> anyhow::Result { - let ingredients_dict = item_def.ingredients.dict(self.params)?; - let inputs_set = item_def.ingredients.inputs_set(self.params)?; let item_hash = item_def.item_hash(self.params)?; + let batch_hash = item_def.batch.batch_hash(self.params)?; + // TODO + let keys_dict = Dictionary::new(self.params.max_depth_mt_containers, + item_def.batch.ingredients.keys.iter().enumerate().map(|(i,k)| (format!("{i}").into(), (*k).into())).collect() + )?; + let index_str = format!("{}", item_def.index); // Build ItemInBatch(item, batch) Ok(st_custom!(self.ctx, ItemInBatch() = ( - HashOf(item_hash, ingredients_dict, batch[0].work), - SetContains(ingredients_dict, "key", batch[0].ingredients.key), + HashOf(item_hash, batch_hash, item_def.index as i64), + DictContains(keys_dict, index_str, item_def.item_key()) ))?) } @@ -176,15 +210,14 @@ impl<'a> ItemBuilder<'a> { // ItemDef. Includes the following public predicates: ItemDef, ItemKey // Returns the Statement object for ItemDef for use in further statements. pub fn st_item_def(&mut self, item_def: ItemDef) -> anyhow::Result { - let ingredients_dict = item_def.ingredients.dict(self.params)?; - let inputs_set = item_def.ingredients.inputs_set(self.params)?; - let item_hash = item_def.item_hash(self.params)?; + let batch_def = self.st_batch_def(item_def.batch.clone())?; + let item_in_batch = self.st_item_in_batch(item_def.clone())?; // Build ItemDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, - ItemDef() = ( - BatchDef(batch, ingredients, inputs, keys, work), - ItemInBatch(item, batch, index), + ItemDef() = ( + batch_def, + item_in_batch ))?) } @@ -265,7 +298,7 @@ impl<'a> ItemBuilder<'a> { st_item_def: Statement, ) -> anyhow::Result { let st_inputs_subset = - self.st_super_sub_set(item_def.ingredients.inputs_set(self.params)?, created_items)?; + self.st_super_sub_set(item_def.batch.ingredients.inputs_set(self.params)?, created_items)?; // Build CommitCreation(item, nullifiers, created_items) let st_commit_creation = st_custom!(self.ctx, @@ -320,13 +353,13 @@ mod tests { let key = Value::from(key).raw(); let ingredients_def = IngredientsDef { inputs: input_item_hashes, - key, + keys: vec![key], app_layer: HashMap::from([("blueprint".to_string(), Value::from(blueprint))]), }; - let item_def = ItemDef { - ingredients: ingredients_def, - work: Value::from(42).raw(), - }; + + let batch_def = BatchDef::new(ingredients_def, Value::from(42).raw()); + let item_def = ItemDef::new(batch_def, 0); + let (st_nullifiers, _nullifiers) = if sts_item_key.is_empty() { item_builder.st_nullifiers(sts_item_key).unwrap() } else { diff --git a/commitlib/src/predicates.rs b/commitlib/src/predicates.rs index 7ec620c..033d2c7 100644 --- a/commitlib/src/predicates.rs +++ b/commitlib/src/predicates.rs @@ -64,7 +64,7 @@ impl CommitPredicates { // value) which must correspond to its key. // It confirms that the item ID and keys use the same indexes, for // consistent nullifiers. - ItemInBatch(item, batch, index, private: key, keys) = AND( + ItemInBatch(item, batch, index, keys, private: key) = AND( HashOf(item, batch, index) DictContains(keys, index, key) ) @@ -77,7 +77,7 @@ impl CommitPredicates { // any value) which must correspond to the index of its key. ItemDef(item, ingredients, inputs, key, work, private: batch, index, keys) = AND( BatchDef(batch, ingredients, inputs, keys, work) - ItemInBatch(item, batch, index) + ItemInBatch(item, batch, index, keys) DictContains(keys, index, key) ) @@ -93,8 +93,8 @@ impl CommitPredicates { ) AllItemsInBatchEmpty(items, batch, keys) = AND( - Equal(items, #{}) - Equal(keys, #{}) + Equal(items, {}) + Equal(keys, {}) // batch is intentionally unconstrained ) @@ -140,8 +140,7 @@ impl CommitPredicates { ) "#), // 4 - &format!( - r#" + r#" // ZK version of CreatedItem for committing on-chain. // Validator/Logger/Archiver needs to maintain 2 append-only // sets of items and nullifiers. New creating is @@ -164,8 +163,7 @@ impl CommitPredicates { // Expose nullifiers for all inputs Nullifiers(nullifiers, inputs) ) - "# - ), + "#, ]; let defs = PredicateDefs::new(params, &batch_defs, &[]); From de016a648448d23e314fd4603af9b3305728668e Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 1 Dec 2025 12:10:21 +1000 Subject: [PATCH 05/29] Fix commitlib tests --- commitlib/src/lib.rs | 155 ++++++++++++++++++++++++++++-------- commitlib/src/predicates.rs | 8 +- 2 files changed, 129 insertions(+), 34 deletions(-) diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 066c646..d8a00a5 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -31,10 +31,17 @@ impl IngredientsDef { pub fn dict(&self, params: &Params) -> pod2::middleware::Result { let mut map = HashMap::new(); map.insert(Key::from("inputs"), Value::from(self.inputs_set(params)?)); - map.insert(Key::from("keys"), Value::from( - Dictionary::new(params.max_depth_mt_containers, - self.keys.iter().enumerate().map(|(i,k)| (format!("{i}").into(), (*k).into())).collect() - )?)); + map.insert( + Key::from("keys"), + Value::from(Dictionary::new( + params.max_depth_mt_containers, + self.keys + .iter() + .enumerate() + .map(|(i, k)| (format!("{i}").into(), (*k).into())) + .collect(), + )?), + ); for (key, value) in &self.app_layer { map.insert(Key::from(key), value.clone()); } @@ -74,18 +81,19 @@ impl BatchDef { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ItemDef { pub batch: BatchDef, - pub index: usize + pub index: usize, } impl ItemDef { pub fn item_key(&self) -> RawValue { self.batch.ingredients.keys[self.index] } - + pub fn item_hash(&self, params: &Params) -> pod2::middleware::Result { + let index_key_hash = Key::new(format!("{}", self.index)).hash(); Ok(hash_values(&[ Value::from(self.batch.batch_hash(params)?), - Value::from(self.index as i64), + Value::from(index_key_hash), ])) } @@ -172,10 +180,16 @@ impl<'a> ItemBuilder<'a> { let ingredients_dict = batch.ingredients.dict(self.params)?; let inputs_set = batch.ingredients.inputs_set(self.params)?; let batch_hash = batch.batch_hash(self.params)?; - let keys_dict = Dictionary::new(self.params.max_depth_mt_containers, - batch.ingredients.keys.iter().enumerate().map(|(i,k)| (format!("{i}").into(), (*k).into())).collect() - )?; - + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + batch + .ingredients + .keys + .iter() + .enumerate() + .map(|(i, k)| (format!("{i}").into(), (*k).into())) + .collect(), + )?; // Build BatchDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, @@ -186,22 +200,27 @@ impl<'a> ItemBuilder<'a> { ))?) } - pub fn st_item_in_batch( - &mut self, - item_def: ItemDef - ) -> anyhow::Result { + pub fn st_item_in_batch(&mut self, item_def: ItemDef) -> anyhow::Result { let item_hash = item_def.item_hash(self.params)?; let batch_hash = item_def.batch.batch_hash(self.params)?; // TODO - let keys_dict = Dictionary::new(self.params.max_depth_mt_containers, - item_def.batch.ingredients.keys.iter().enumerate().map(|(i,k)| (format!("{i}").into(), (*k).into())).collect() + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + item_def + .batch + .ingredients + .keys + .iter() + .enumerate() + .map(|(i, k)| (format!("{i}").into(), (*k).into())) + .collect(), )?; let index_str = format!("{}", item_def.index); // Build ItemInBatch(item, batch) Ok(st_custom!(self.ctx, ItemInBatch() = ( - HashOf(item_hash, batch_hash, item_def.index as i64), + HashOf(item_hash, batch_hash, index_str), DictContains(keys_dict, index_str, item_def.item_key()) ))?) } @@ -213,15 +232,84 @@ impl<'a> ItemBuilder<'a> { let batch_def = self.st_batch_def(item_def.batch.clone())?; let item_in_batch = self.st_item_in_batch(item_def.clone())?; + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + item_def + .batch + .ingredients + .keys + .iter() + .enumerate() + .map(|(i, k)| (format!("{i}").into(), (*k).into())) + .collect(), + )?; + let index_str = format!("{}", item_def.index); + // Build ItemDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, ItemDef() = ( batch_def, - item_in_batch + item_in_batch, + DictContains(keys_dict, index_str, item_def.item_key()) ))?) } - // TODO st_all_items_in_batch{, _empty, _recursive} + pub fn st_all_items_in_batch(&mut self, batch_def: BatchDef) -> anyhow::Result { + let batch_hash = batch_def.batch_hash(self.params)?; + + let empty_set = set!(self.params.max_depth_mt_containers)?; + let empty_dict = Dictionary::new(self.params.max_depth_mt_containers, HashMap::new())?; + + // Build AllItemsInBatch(items, batch, keys) + let st_all_items_in_batch_empty = st_custom!(self.ctx, + AllItemsInBatchEmpty(batch = batch_hash) = ( + Equal(&empty_set, EMPTY_VALUE), + Equal(&empty_dict, EMPTY_VALUE) + ))?; + let init_st = st_custom!(self.ctx, + AllItemsInBatch() = ( + st_all_items_in_batch_empty, + Statement::None + ))?; + + let (st_all_items_in_batch, _, _) = batch_def + .ingredients + .keys + .iter() + .enumerate() + .try_fold::<_, _, anyhow::Result<_>>( + (init_st, empty_set.clone(), empty_dict.clone()), + |(st_all_items_in_batch_prev, items_prev, keys_prev), (index, key)| { + let index_str = format!("{index}"); + let item_hash = + hash_values(&[Value::from(batch_hash), Value::from(index_str.clone())]); + + let mut keys = keys_prev.clone(); + keys.insert(&Key::new(index_str.clone()), &(*key).into())?; + + let mut items = items_prev.clone(); + items.insert(&item_hash.into())?; + + let st_all_items_in_batch_recursive = st_custom!(self.ctx, + AllItemsInBatchRecursive() = ( + st_all_items_in_batch_prev, + SetInsert(items, items_prev, item_hash), + DictInsert(keys, keys_prev, index_str, key), + HashOf(item_hash, batch_hash, index_str) + ))?; + + let st_all_items_in_batch = st_custom!(self.ctx, + AllItemsInBatch() = ( + Statement::None, + st_all_items_in_batch_recursive + ))?; + + Ok((st_all_items_in_batch, items, keys)) + }, + )?; + + Ok(st_all_items_in_batch) + } pub fn st_item_key(&mut self, st_item_def: Statement) -> anyhow::Result { // Build ItemKey(item, key) @@ -292,18 +380,23 @@ impl<'a> ItemBuilder<'a> { pub fn st_commit_creation( &mut self, // TODO update to multi-output (batch_def & st_all_items_in_batch) - item_def: ItemDef, + batch_def: BatchDef, st_nullifiers: Statement, created_items: Set, - st_item_def: Statement, + st_batch_def: Statement, ) -> anyhow::Result { - let st_inputs_subset = - self.st_super_sub_set(item_def.batch.ingredients.inputs_set(self.params)?, created_items)?; + let st_inputs_subset = self.st_super_sub_set( + batch_def.ingredients.inputs_set(self.params)?, + created_items, + )?; + + let st_all_items_in_batch = self.st_all_items_in_batch(batch_def)?; // Build CommitCreation(item, nullifiers, created_items) let st_commit_creation = st_custom!(self.ctx, - CommitCreation() = ( - st_item_def.clone(), + CommitCreation() = ( + st_batch_def, + st_all_items_in_batch, st_inputs_subset, st_nullifiers ))?; @@ -358,8 +451,8 @@ mod tests { }; let batch_def = BatchDef::new(ingredients_def, Value::from(42).raw()); - let item_def = ItemDef::new(batch_def, 0); - + let item_def = ItemDef::new(batch_def.clone(), 0); + let (st_nullifiers, _nullifiers) = if sts_item_key.is_empty() { item_builder.st_nullifiers(sts_item_key).unwrap() } else { @@ -379,14 +472,14 @@ mod tests { let item_hash = item_def.item_hash(params).unwrap(); created_items.insert(&Value::from(item_hash)).unwrap(); - let st_item_def = item_builder.st_item_def(item_def.clone()).unwrap(); + let st_batch_def = item_builder.st_batch_def(batch_def.clone()).unwrap(); let _st_commit_creation = item_builder .st_commit_creation( - item_def.clone(), + batch_def.clone(), st_nullifiers, created_items.clone(), - st_item_def, + st_batch_def, ) .unwrap(); diff --git a/commitlib/src/predicates.rs b/commitlib/src/predicates.rs index 033d2c7..1b116b3 100644 --- a/commitlib/src/predicates.rs +++ b/commitlib/src/predicates.rs @@ -99,6 +99,7 @@ impl CommitPredicates { ) AllItemsInBatchRecursive(items, batch, keys, private: prev_items, prev_keys, item, index, key) = AND( + AllItemsInBatch(prev_items, batch, prev_keys) SetInsert(items, prev_items, item) DictInsert(keys, prev_keys, index, key) @@ -106,11 +107,11 @@ impl CommitPredicates { // set all values, and don't want to pay for an extra statement // to do so. HashOf(item, batch, index) - DictContains(keys, index, key) ) "#, // 3 - &format!(r#" + &format!( + r#" // Helper to expose just the item and key from ItemId calculation. // This is just the CreatedItem pattern with some of inupts private. ItemKey(item, key, private: ingredients, inputs, work) = AND( @@ -138,7 +139,8 @@ impl CommitPredicates { SetInsert(inputs, inputs_prev, input) Nullifiers(nullifiers_prev, inputs_prev) ) - "#), + "# + ), // 4 r#" // ZK version of CreatedItem for committing on-chain. From e60812776d35696d275c4960a0635e6c2cb6e720 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 1 Dec 2025 16:20:37 +1000 Subject: [PATCH 06/29] Change itemdef statement method signature --- commitlib/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index d8a00a5..3c9d4ee 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -228,8 +228,11 @@ impl<'a> ItemBuilder<'a> { // Adds statements to MainPodBilder to represent a generic item based on the // ItemDef. Includes the following public predicates: ItemDef, ItemKey // Returns the Statement object for ItemDef for use in further statements. - pub fn st_item_def(&mut self, item_def: ItemDef) -> anyhow::Result { - let batch_def = self.st_batch_def(item_def.batch.clone())?; + pub fn st_item_def( + &mut self, + item_def: ItemDef, + st_batch_def: Statement, + ) -> anyhow::Result { let item_in_batch = self.st_item_in_batch(item_def.clone())?; let keys_dict = Dictionary::new( @@ -248,7 +251,7 @@ impl<'a> ItemBuilder<'a> { // Build ItemDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, ItemDef() = ( - batch_def, + st_batch_def, item_in_batch, DictContains(keys_dict, index_str, item_def.item_key()) ))?) @@ -489,7 +492,8 @@ mod tests { let mut builder = MainPodBuilder::new(params, vd_set); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, batches), params); - let st_item_def = item_builder.st_item_def(item_def).unwrap(); + let st_batch_def = item_builder.st_batch_def(batch_def.clone()).unwrap(); + let st_item_def = item_builder.st_item_def(item_def, st_batch_def).unwrap(); let st_item_key = item_builder.st_item_key(st_item_def).unwrap(); item_builder.ctx.builder.reveal(&st_item_key); From 3269e084ad34813dd114cbe65168ec4a3ab3fdbd Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 2 Dec 2025 12:37:34 +1000 Subject: [PATCH 07/29] Update craftlib --- craftlib/src/item.rs | 85 ++++++++++++++++---------- craftlib/src/predicates.rs | 122 +++++++++++++++++++++++++++++++------ 2 files changed, 157 insertions(+), 50 deletions(-) diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 4b967ca..b93ee23 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -16,14 +16,14 @@ pub struct MiningRecipe { } impl MiningRecipe { - pub fn prep_ingredients(&self, key: RawValue, seed: i64) -> IngredientsDef { + pub fn prep_ingredients(&self, keys: &[RawValue], seed: i64) -> IngredientsDef { let app_layer = HashMap::from([ ("blueprint".to_string(), Value::from(self.blueprint.clone())), ("seed".to_string(), Value::from(seed)), ]); IngredientsDef { inputs: self.inputs.clone(), - key, + keys: keys.to_vec(), app_layer, } } @@ -31,13 +31,13 @@ impl MiningRecipe { pub fn do_mining( &self, params: &Params, - key: RawValue, + keys: &[RawValue], start_seed: i64, mine_max: u64, ) -> pod2::middleware::Result> { log::info!("Mining..."); for seed in start_seed..=i64::MAX { - let ingredients = self.prep_ingredients(key, seed); + let ingredients = self.prep_ingredients(keys, seed); let ingredients_hash = ingredients.hash(params)?; let mining_val = ingredients_hash.to_fields(params)[0]; if mining_val.0 <= mine_max { @@ -81,8 +81,8 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, IsStone() = ( st_item_def, - Equal(item_def.ingredients.inputs_set(self.params)?, EMPTY_VALUE), - DictContains(item_def.ingredients.dict(self.params)?, "blueprint", STONE_BLUEPRINT), + Equal(item_def.batch.ingredients.inputs_set(self.params)?, EMPTY_VALUE), + DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", STONE_BLUEPRINT), st_pow ))?) } @@ -96,9 +96,9 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, IsWood() = ( st_item_def, - Equal(item_def.ingredients.inputs_set(self.params)?, EMPTY_VALUE), - DictContains(item_def.ingredients.dict(self.params)?, "blueprint", WOOD_BLUEPRINT), - Equal(item_def.work, EMPTY_VALUE) + Equal(item_def.batch.ingredients.inputs_set(self.params)?, EMPTY_VALUE), + DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", WOOD_BLUEPRINT), + Equal(item_def.batch.work, EMPTY_VALUE) ))?) } @@ -135,8 +135,8 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, IsAxe() = ( st_item_def, - DictContains(item_def.ingredients.dict(self.params)?, "blueprint", AXE_BLUEPRINT), - Equal(item_def.work, EMPTY_VALUE), + DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", AXE_BLUEPRINT), + Equal(item_def.batch.work, EMPTY_VALUE), st_axe_inputs ))?) } @@ -174,8 +174,8 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, IsWoodenAxe() = ( st_item_def, - DictContains(item_def.ingredients.dict(self.params)?, "blueprint", WOODEN_AXE_BLUEPRINT), - Equal(item_def.work, EMPTY_VALUE), + DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", WOODEN_AXE_BLUEPRINT), + Equal(item_def.batch.work, EMPTY_VALUE), st_wooden_axe_inputs ))?) } @@ -186,14 +186,17 @@ mod tests { use std::{collections::HashMap, sync::Arc}; - use commitlib::{ItemBuilder, ItemDef, predicates::CommitPredicates, util::set_from_hashes}; + use commitlib::{ + BatchDef, ItemBuilder, ItemDef, predicates::CommitPredicates, util::set_from_hashes, + }; use pod2::{ backends::plonky2::mock::mainpod::MockProver, frontend::{MainPod, MainPodBuilder}, lang::parse, middleware::{ CustomPredicateBatch, EMPTY_VALUE, MainPodProver, Params, Pod, RawValue, VDSet, Value, - containers::Set, hash_value, + containers::{Dictionary, Set}, + hash_value, }, }; @@ -222,7 +225,9 @@ mod tests { ) -> anyhow::Result { let mut builder = MainPodBuilder::new(&Default::default(), vd_set); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, batches), params); - let st_item_def = item_builder.st_item_def(item_def.clone())?; + let st_batch_def = item_builder.st_batch_def(item_def.batch.clone())?; + let st_item_def = item_builder.st_item_def(item_def.clone(), st_batch_def.clone())?; + item_builder.ctx.builder.reveal(&st_batch_def); item_builder.ctx.builder.reveal(&st_item_def); let st_item_key = item_builder.st_item_key(st_item_def.clone())?; item_builder.ctx.builder.reveal(&st_item_key); @@ -255,13 +260,17 @@ mod tests { let mut builder = MainPodBuilder::new(&Default::default(), vd_set); // TODO: Consider a more robust lookup for this which doesn't depend on index. - let st_item_def = item_main_pod.public_statements[0].clone(); + let st_batch_def = item_main_pod.public_statements[0].clone(); builder.add_pod(item_main_pod); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, batches), params); let (st_nullifier, _) = item_builder.st_nullifiers(vec![])?; - let st_commit_creation = - item_builder.st_commit_creation(item_def, st_nullifier, created_items, st_item_def)?; + let st_commit_creation = item_builder.st_commit_creation( + item_def.batch, + st_nullifier, + created_items, + st_batch_def, + )?; builder.reveal(&st_commit_creation); // Prove MainPOD @@ -285,11 +294,12 @@ mod tests { // TODO: This test is going to get slower (~2s) whenever the ingredient // dict definition changes. Need a better approach to testing mining. let mine_success = - mining_recipe.do_mining(¶ms, key, STONE_START_SEED, STONE_MINING_MAX)?; + mining_recipe.do_mining(¶ms, &[key], STONE_START_SEED, STONE_MINING_MAX)?; assert!(mine_success.is_some()); let ingredients_def = mine_success.unwrap(); - let item_def = ItemDef::new(ingredients_def.clone(), STONE_WORK); + let batch_def = BatchDef::new(ingredients_def.clone(), STONE_WORK); + let item_def = ItemDef::new(batch_def, 0); let item_hash = item_def.item_hash(¶ms)?; println!( "Mined stone {:?} from ingredients {:?}", @@ -315,7 +325,7 @@ mod tests { let key = RawValue::from(0xBADC0DE); let mining_recipe = MiningRecipe::new(STONE_BLUEPRINT.to_string(), &[]); let ingredients_def = mining_recipe - .do_mining(¶ms, key, STONE_START_SEED, STONE_MINING_MAX)? + .do_mining(¶ms, &[key], STONE_START_SEED, STONE_MINING_MAX)? .unwrap(); let pow_pod = PowPod::new( @@ -333,10 +343,8 @@ mod tests { // Pre-calculate hashes and intermediate values. let ingredients_dict = ingredients_def.dict(¶ms)?; let inputs_set = ingredients_def.inputs_set(¶ms)?; - let item_def = ItemDef { - ingredients: ingredients_def.clone(), - work: pow_pod.output, - }; + let batch_def = BatchDef::new(ingredients_def.clone(), pow_pod.output); + let item_def = ItemDef::new(batch_def.clone(), 0); let item_hash = item_def.item_hash(¶ms)?; // Prove a stone POD. This is the private POD for the player to store @@ -351,8 +359,8 @@ mod tests { )?; stone_main_pod.pod.verify()?; - assert_eq!(stone_main_pod.public_statements.len(), 3); - //println!("Stone POD: {:?}", stone_main_pod.pod); + assert_eq!(stone_main_pod.public_statements.len(), 4); + println!("Stone POD: {:?}", stone_main_pod.pod); // PODLang query to check the final statements. let stone_query = format!( @@ -361,6 +369,7 @@ mod tests { {} REQUEST( + BatchDef(batch, ingredients, inputs, keys, work) ItemDef(item, ingredients, inputs, key, work) ItemKey(item, key) IsStone(item) @@ -385,10 +394,18 @@ mod tests { check_matched_wildcards( matched_wildcards, HashMap::from([ + ("batch".into(), Value::from(batch_def.batch_hash(¶ms)?)), ("item".to_string(), Value::from(item_hash)), ("ingredients".to_string(), Value::from(ingredients_dict)), ("inputs".to_string(), Value::from(inputs_set)), ("key".to_string(), Value::from(key)), + ( + "keys".into(), + Value::from(Dictionary::new( + params.max_depth_mt_containers, + [("0".into(), key.into())].into_iter().collect(), + )?), + ), ("work".to_string(), Value::from(pow_pod.output)), ]), ); @@ -416,7 +433,7 @@ mod tests { commit_main_pod.pod.verify()?; assert_eq!(commit_main_pod.public_statements.len(), 1); - //println!("Commit POD: {:?}", stone_main_pod.pod); + println!("Commit POD: {:?}", commit_main_pod.pod); // PODLang query to check the final statement. let commit_query = format!( @@ -424,7 +441,7 @@ mod tests { {} REQUEST( - CommitCreation(item, nullifiers, created_items) + CommitCreation(items, nullifiers, created_items) ) "#, &commit_preds.defs.imports, @@ -446,7 +463,13 @@ mod tests { check_matched_wildcards( matched_wildcards, HashMap::from([ - ("item".to_string(), Value::from(item_hash)), + ( + "items".to_string(), + Value::from(Set::new( + params.max_depth_mt_containers, + [item_hash.into()].into_iter().collect(), + )?), + ), ("created_items".to_string(), Value::from(created_items)), ("nullifiers".to_string(), Value::from(EMPTY_VALUE)), ]), diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 29eaeb7..cf094c0 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -97,12 +97,16 @@ impl ItemPredicates { mod tests { use std::collections::{HashMap, HashSet}; - use commitlib::{IngredientsDef, ItemDef, util::set_from_hashes}; + use commitlib::{BatchDef, IngredientsDef, ItemDef, util::set_from_hashes}; use pod2::{ backends::plonky2::mock::mainpod::MockProver, frontend::{MainPod, MainPodBuilder, Operation}, lang::parse, - middleware::{EMPTY_VALUE, Pod, RawValue, Statement, Value, hash_value}, + middleware::{ + EMPTY_VALUE, Pod, RawValue, Statement, Value, + containers::{Dictionary, Set}, + hash_value, + }, }; use super::*; @@ -137,7 +141,7 @@ mod tests { // Pre-calculate hashes and intermediate values. let ingredients_def: IngredientsDef = IngredientsDef { inputs: HashSet::new(), - key: RawValue::from(key), + keys: vec![RawValue::from(key)], app_layer: HashMap::from([ ("blueprint".to_string(), Value::from(STONE_BLUEPRINT)), ("seed".to_string(), Value::from(seed)), @@ -161,11 +165,20 @@ mod tests { let work: RawValue = pow_pod.output; let st_pow = main_pow_pod.public_statements[0].clone(); builder.add_pod(main_pow_pod); - let item_def = ItemDef { - ingredients: ingredients_def.clone(), - work, - }; + let batch_def = BatchDef::new(ingredients_def.clone(), work); + let batch_hash = batch_def.batch_hash(¶ms)?; + let item_def = ItemDef::new(batch_def.clone(), 0); let item_hash = item_def.item_hash(¶ms)?; + let key_dict = Dictionary::new( + params.max_depth_mt_containers, + ingredients_def + .keys + .clone() + .into_iter() + .enumerate() + .map(|(i, key)| (format!("{i}").into(), key.into())) + .collect(), + )?; // Sets for on-chain commitment let nullifiers = set_from_hashes(¶ms, &HashSet::new())?; @@ -177,25 +190,43 @@ mod tests { ]), )?; - // Build ItemDef(item, ingredients, inputs, key, work) + // Build BatchDef(batch, ingredients, inputs, keys, work) let st_contains_inputs = builder.priv_op(Operation::dict_contains( ingredients_dict.clone(), "inputs", inputs_set.clone(), ))?; - let st_contains_key = builder.priv_op(Operation::dict_contains( + let st_contains_keys = builder.priv_op(Operation::dict_contains( ingredients_dict.clone(), - "key", - ingredients_def.key, + "keys", + key_dict.clone(), ))?; - let st_item_hash = builder.priv_op(Operation::hash_of( - item_hash, + let st_batch_hash = builder.priv_op(Operation::hash_of( + batch_hash, ingredients_dict.clone(), - item_def.work, + batch_def.work, ))?; - let st_item_def = builder.pub_op(Operation::custom( + let st_batch_def = builder.pub_op(Operation::custom( + commit_preds.batch_def.clone(), + [st_contains_inputs, st_contains_keys, st_batch_hash], + ))?; + + // Build ItemInBatch(item, batch, index, keys) + let st_item_hash = builder.priv_op(Operation::hash_of(item_hash, batch_hash, "0"))?; + let st_contains_key = builder.priv_op(Operation::dict_contains( + key_dict.clone(), + "0", + ingredients_def.keys[0], + ))?; + let st_item_in_batch = builder.pub_op(Operation::custom( + commit_preds.item_in_batch.clone(), + [st_item_hash.clone(), st_contains_key.clone()], + ))?; + + // Build ItemDef(item, ingredients, inputs, key, work) + let st_item_def = builder.priv_op(Operation::custom( commit_preds.item_def.clone(), - [st_contains_inputs, st_contains_key, st_item_hash], + [st_batch_def.clone(), st_item_in_batch, st_contains_key], ))?; // Build ItemKey(item, key) @@ -230,10 +261,60 @@ mod tests { [st_nullifiers_empty, Statement::None], ))?; + // Build AllItemsInBatch(items, batch, keys) + let empty_set = Set::new(params.max_depth_mt_containers, HashSet::new())?; + let empty_dict = Dictionary::new(params.max_depth_mt_containers, HashMap::new())?; + let st_all_items_in_batch_empty = builder.op( + false, + vec![(1, batch_hash.into())], + Operation::custom( + commit_preds.all_items_in_batch_empty.clone(), + [ + Statement::Equal(empty_set.clone().into(), EMPTY_VALUE.into()), + Statement::Equal(empty_dict.clone().into(), EMPTY_VALUE.into()), + ], + ), + )?; + let st_all_items_in_batch1 = builder.priv_op(Operation::custom( + commit_preds.all_items_in_batch.clone(), + [st_all_items_in_batch_empty, Statement::None], + ))?; + let items = { + let mut items = empty_set.clone(); + items.insert(&item_hash.into())?; + items + }; + let st_set_insert = + builder.priv_op(Operation::set_insert(items.clone(), empty_set, item_hash))?; + let st_dict_insert = builder.priv_op(Operation::dict_insert( + key_dict.clone(), + empty_dict, + "0", + key, + ))?; + let st_all_items_in_batch_recursive = builder.priv_op(Operation::custom( + commit_preds.all_items_in_batch_recursive.clone(), + [ + st_all_items_in_batch1, + st_set_insert, + st_dict_insert, + st_item_hash, + ], + ))?; + let st_all_items_in_batch = builder.priv_op(Operation::custom( + commit_preds.all_items_in_batch.clone(), + [Statement::None, st_all_items_in_batch_recursive], + ))?; + // Build CommitCreation(item, nullifiers, created_items) let _st_commit_crafting = builder.pub_op(Operation::custom( commit_preds.commit_creation.clone(), - [st_item_def.clone(), st_inputs_subset, st_nullifiers], + [ + st_batch_def, + st_all_items_in_batch, + st_inputs_subset, + st_nullifiers, + ], ))?; // Build IsStone(item) @@ -266,11 +347,11 @@ mod tests { {} REQUEST( - ItemDef(item, ingredients, inputs, key, work) + BatchDef(batch, ingredients, inputs, keys, work) ItemKey(item, key) SubsetOf(inputs, created_items) Nullifiers(nullifiers, inputs) - CommitCreation(item, nullifiers, created_items) + CommitCreation(items, nullifiers, created_items) IsStone(item) ) "#, @@ -289,10 +370,13 @@ mod tests { check_matched_wildcards( matched_wildcards, HashMap::from([ + ("batch".into(), batch_hash.into()), ("item".to_string(), Value::from(item_hash)), + ("items".into(), items.into()), ("ingredients".to_string(), Value::from(ingredients_dict)), ("inputs".to_string(), Value::from(inputs_set)), ("key".to_string(), Value::from(key)), + ("keys".into(), key_dict.into()), ("work".to_string(), Value::from(work)), ("created_items".to_string(), Value::from(created_items)), ("nullifiers".to_string(), Value::from(nullifiers)), From 936c403fb412568db55e402ac4ef2fc6720a334b Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 2 Dec 2025 13:34:03 +1000 Subject: [PATCH 08/29] Use HashMap for keys --- commitlib/src/lib.rs | 94 +++++++++++++------------------------- craftlib/src/item.rs | 33 ++++++------- craftlib/src/predicates.rs | 30 +++++------- 3 files changed, 61 insertions(+), 96 deletions(-) diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 3c9d4ee..35c7039 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -21,7 +21,8 @@ pub const CONSUMED_ITEM_EXTERNAL_NULLIFIER: &str = "consumed item external nulli pub struct IngredientsDef { // These properties are committed on-chain pub inputs: HashSet, - pub keys: Vec, + // TODO: Maybe replace this with a Value -> Value map? + pub keys: HashMap, // These properties are used only by the application layer pub app_layer: HashMap, @@ -35,11 +36,7 @@ impl IngredientsDef { Key::from("keys"), Value::from(Dictionary::new( params.max_depth_mt_containers, - self.keys - .iter() - .enumerate() - .map(|(i, k)| (format!("{i}").into(), (*k).into())) - .collect(), + self.keys.clone(), )?), ); for (key, value) in &self.app_layer { @@ -81,19 +78,18 @@ impl BatchDef { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ItemDef { pub batch: BatchDef, - pub index: usize, + pub index: Key, } impl ItemDef { - pub fn item_key(&self) -> RawValue { - self.batch.ingredients.keys[self.index] + pub fn item_key(&self) -> Value { + self.batch.ingredients.keys[&self.index].clone() } pub fn item_hash(&self, params: &Params) -> pod2::middleware::Result { - let index_key_hash = Key::new(format!("{}", self.index)).hash(); Ok(hash_values(&[ Value::from(self.batch.batch_hash(params)?), - Value::from(index_key_hash), + Value::from(self.index.hash()), ])) } @@ -104,7 +100,7 @@ impl ItemDef { ])) } - pub fn new(batch: BatchDef, index: usize) -> Self { + pub fn new(batch: BatchDef, index: Key) -> Self { Self { batch, index } } } @@ -182,13 +178,7 @@ impl<'a> ItemBuilder<'a> { let batch_hash = batch.batch_hash(self.params)?; let keys_dict = Dictionary::new( self.params.max_depth_mt_containers, - batch - .ingredients - .keys - .iter() - .enumerate() - .map(|(i, k)| (format!("{i}").into(), (*k).into())) - .collect(), + batch.ingredients.keys.clone(), )?; // Build BatchDef(item, ingredients, inputs, key, work) @@ -203,25 +193,16 @@ impl<'a> ItemBuilder<'a> { pub fn st_item_in_batch(&mut self, item_def: ItemDef) -> anyhow::Result { let item_hash = item_def.item_hash(self.params)?; let batch_hash = item_def.batch.batch_hash(self.params)?; - // TODO let keys_dict = Dictionary::new( self.params.max_depth_mt_containers, - item_def - .batch - .ingredients - .keys - .iter() - .enumerate() - .map(|(i, k)| (format!("{i}").into(), (*k).into())) - .collect(), + item_def.batch.ingredients.keys.clone(), )?; - let index_str = format!("{}", item_def.index); // Build ItemInBatch(item, batch) Ok(st_custom!(self.ctx, ItemInBatch() = ( - HashOf(item_hash, batch_hash, index_str), - DictContains(keys_dict, index_str, item_def.item_key()) + HashOf(item_hash, batch_hash, item_def.index.hash()), + DictContains(keys_dict, item_def.index.name(), item_def.item_key()) ))?) } @@ -237,23 +218,15 @@ impl<'a> ItemBuilder<'a> { let keys_dict = Dictionary::new( self.params.max_depth_mt_containers, - item_def - .batch - .ingredients - .keys - .iter() - .enumerate() - .map(|(i, k)| (format!("{i}").into(), (*k).into())) - .collect(), + item_def.batch.ingredients.keys.clone(), )?; - let index_str = format!("{}", item_def.index); // Build ItemDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, ItemDef() = ( st_batch_def, item_in_batch, - DictContains(keys_dict, index_str, item_def.item_key()) + DictContains(keys_dict, item_def.index.name(), item_def.item_key()) ))?) } @@ -279,37 +252,34 @@ impl<'a> ItemBuilder<'a> { .ingredients .keys .iter() - .enumerate() .try_fold::<_, _, anyhow::Result<_>>( - (init_st, empty_set.clone(), empty_dict.clone()), - |(st_all_items_in_batch_prev, items_prev, keys_prev), (index, key)| { - let index_str = format!("{index}"); - let item_hash = - hash_values(&[Value::from(batch_hash), Value::from(index_str.clone())]); + (init_st, empty_set.clone(), empty_dict.clone()), + |(st_all_items_in_batch_prev, items_prev, keys_prev), (index, key)| { + let item_hash = hash_values(&[batch_hash.into(), index.raw().into()]); - let mut keys = keys_prev.clone(); - keys.insert(&Key::new(index_str.clone()), &(*key).into())?; + let mut keys = keys_prev.clone(); + keys.insert(index, key)?; - let mut items = items_prev.clone(); - items.insert(&item_hash.into())?; + let mut items = items_prev.clone(); + items.insert(&item_hash.into())?; - let st_all_items_in_batch_recursive = st_custom!(self.ctx, + let st_all_items_in_batch_recursive = st_custom!(self.ctx, AllItemsInBatchRecursive() = ( st_all_items_in_batch_prev, SetInsert(items, items_prev, item_hash), - DictInsert(keys, keys_prev, index_str, key), - HashOf(item_hash, batch_hash, index_str) + DictInsert(keys, keys_prev, index.name(), key), + HashOf(item_hash, batch_hash, index.hash()) ))?; - let st_all_items_in_batch = st_custom!(self.ctx, + let st_all_items_in_batch = st_custom!(self.ctx, AllItemsInBatch() = ( Statement::None, st_all_items_in_batch_recursive ))?; - Ok((st_all_items_in_batch, items, keys)) - }, - )?; + Ok((st_all_items_in_batch, items, keys)) + }, + )?; Ok(st_all_items_in_batch) } @@ -382,7 +352,6 @@ impl<'a> ItemBuilder<'a> { // the root to prove that inputs were previously created. pub fn st_commit_creation( &mut self, - // TODO update to multi-output (batch_def & st_all_items_in_batch) batch_def: BatchDef, st_nullifiers: Statement, created_items: Set, @@ -446,15 +415,16 @@ mod tests { item_builder.ctx.builder.add_pod(input_item_key_pod); } - let key = Value::from(key).raw(); + let index: Key = "0".into(); + let key = Value::from(key); let ingredients_def = IngredientsDef { inputs: input_item_hashes, - keys: vec![key], + keys: [(index.clone(), key)].into_iter().collect(), app_layer: HashMap::from([("blueprint".to_string(), Value::from(blueprint))]), }; let batch_def = BatchDef::new(ingredients_def, Value::from(42).raw()); - let item_def = ItemDef::new(batch_def.clone(), 0); + let item_def = ItemDef::new(batch_def.clone(), index); let (st_nullifiers, _nullifiers) = if sts_item_key.is_empty() { item_builder.st_nullifiers(sts_item_key).unwrap() diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index b93ee23..dfa9cf1 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use commitlib::{IngredientsDef, ItemDef}; use log; -use pod2::middleware::{EMPTY_VALUE, Hash, Params, RawValue, Statement, ToFields, Value}; +use pod2::middleware::{EMPTY_VALUE, Hash, Key, Params, Statement, ToFields, Value}; use pod2utils::{macros::BuildContext, set, st_custom}; use crate::constants::{AXE_BLUEPRINT, STONE_BLUEPRINT, WOOD_BLUEPRINT, WOODEN_AXE_BLUEPRINT}; @@ -16,14 +16,14 @@ pub struct MiningRecipe { } impl MiningRecipe { - pub fn prep_ingredients(&self, keys: &[RawValue], seed: i64) -> IngredientsDef { + pub fn prep_ingredients(&self, keys: HashMap, seed: i64) -> IngredientsDef { let app_layer = HashMap::from([ ("blueprint".to_string(), Value::from(self.blueprint.clone())), ("seed".to_string(), Value::from(seed)), ]); IngredientsDef { inputs: self.inputs.clone(), - keys: keys.to_vec(), + keys, app_layer, } } @@ -31,13 +31,13 @@ impl MiningRecipe { pub fn do_mining( &self, params: &Params, - keys: &[RawValue], + keys: HashMap, start_seed: i64, mine_max: u64, ) -> pod2::middleware::Result> { log::info!("Mining..."); for seed in start_seed..=i64::MAX { - let ingredients = self.prep_ingredients(keys, seed); + let ingredients = self.prep_ingredients(keys.clone(), seed); let ingredients_hash = ingredients.hash(params)?; let mining_val = ingredients_hash.to_fields(params)[0]; if mining_val.0 <= mine_max { @@ -288,18 +288,20 @@ mod tests { fn test_mine_stone() -> anyhow::Result<()> { let params = Params::default(); let mining_recipe = MiningRecipe::new(STONE_BLUEPRINT.to_string(), &[]); - let key = RawValue::from(0xBADC0DE); + let index: Key = "stone".into(); + let key = Value::from(0xBADC0DE); + let keys: HashMap<_, _> = [(index.clone(), key.clone())].into_iter().collect(); // Seed of 2612=0xA34 is a match with hash 6647892930992163=0x000A7EE9D427E832. // TODO: This test is going to get slower (~2s) whenever the ingredient // dict definition changes. Need a better approach to testing mining. let mine_success = - mining_recipe.do_mining(¶ms, &[key], STONE_START_SEED, STONE_MINING_MAX)?; + mining_recipe.do_mining(¶ms, keys, STONE_START_SEED, STONE_MINING_MAX)?; assert!(mine_success.is_some()); let ingredients_def = mine_success.unwrap(); let batch_def = BatchDef::new(ingredients_def.clone(), STONE_WORK); - let item_def = ItemDef::new(batch_def, 0); + let item_def = ItemDef::new(batch_def, index); let item_hash = item_def.item_hash(¶ms)?; println!( "Mined stone {:?} from ingredients {:?}", @@ -322,10 +324,12 @@ mod tests { let vd_set = &mock_vd_set(); // Mine stone with a selected key. - let key = RawValue::from(0xBADC0DE); + let index: Key = "stone".into(); + let key = Value::from(0xBADC0DE); + let keys: HashMap<_, _> = [(index.clone(), key.clone())].into_iter().collect(); let mining_recipe = MiningRecipe::new(STONE_BLUEPRINT.to_string(), &[]); let ingredients_def = mining_recipe - .do_mining(¶ms, &[key], STONE_START_SEED, STONE_MINING_MAX)? + .do_mining(¶ms, keys.clone(), STONE_START_SEED, STONE_MINING_MAX)? .unwrap(); let pow_pod = PowPod::new( @@ -344,7 +348,7 @@ mod tests { let ingredients_dict = ingredients_def.dict(¶ms)?; let inputs_set = ingredients_def.inputs_set(¶ms)?; let batch_def = BatchDef::new(ingredients_def.clone(), pow_pod.output); - let item_def = ItemDef::new(batch_def.clone(), 0); + let item_def = ItemDef::new(batch_def.clone(), index); let item_hash = item_def.item_hash(¶ms)?; // Prove a stone POD. This is the private POD for the player to store @@ -398,13 +402,10 @@ mod tests { ("item".to_string(), Value::from(item_hash)), ("ingredients".to_string(), Value::from(ingredients_dict)), ("inputs".to_string(), Value::from(inputs_set)), - ("key".to_string(), Value::from(key)), + ("key".to_string(), key.clone()), ( "keys".into(), - Value::from(Dictionary::new( - params.max_depth_mt_containers, - [("0".into(), key.into())].into_iter().collect(), - )?), + Value::from(Dictionary::new(params.max_depth_mt_containers, keys)?), ), ("work".to_string(), Value::from(pow_pod.output)), ]), diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index cf094c0..98ebe08 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -103,7 +103,7 @@ mod tests { frontend::{MainPod, MainPodBuilder, Operation}, lang::parse, middleware::{ - EMPTY_VALUE, Pod, RawValue, Statement, Value, + EMPTY_VALUE, Key, Pod, RawValue, Statement, Value, containers::{Dictionary, Set}, hash_value, }, @@ -120,7 +120,7 @@ mod tests { fn test_compile_custom_predicates() { let params = Params::default(); let commit_preds = CommitPredicates::compile(¶ms); - assert!(commit_preds.defs.batches.len() == 2); + assert!(commit_preds.defs.batches.len() == 4); let item_preds = ItemPredicates::compile(¶ms, &commit_preds); assert!(item_preds.defs.batches.len() == 2); @@ -136,12 +136,13 @@ mod tests { // Item recipe constants let seed: i64 = 0xA34; + let index: Key = "0".into(); let key = 0xBADC0DE; // Pre-calculate hashes and intermediate values. let ingredients_def: IngredientsDef = IngredientsDef { inputs: HashSet::new(), - keys: vec![RawValue::from(key)], + keys: [(index.clone(), Value::from(key))].into_iter().collect(), app_layer: HashMap::from([ ("blueprint".to_string(), Value::from(STONE_BLUEPRINT)), ("seed".to_string(), Value::from(seed)), @@ -167,18 +168,10 @@ mod tests { builder.add_pod(main_pow_pod); let batch_def = BatchDef::new(ingredients_def.clone(), work); let batch_hash = batch_def.batch_hash(¶ms)?; - let item_def = ItemDef::new(batch_def.clone(), 0); + let item_def = ItemDef::new(batch_def.clone(), index.clone()); let item_hash = item_def.item_hash(¶ms)?; - let key_dict = Dictionary::new( - params.max_depth_mt_containers, - ingredients_def - .keys - .clone() - .into_iter() - .enumerate() - .map(|(i, key)| (format!("{i}").into(), key.into())) - .collect(), - )?; + let key_dict = + Dictionary::new(params.max_depth_mt_containers, ingredients_def.keys.clone())?; // Sets for on-chain commitment let nullifiers = set_from_hashes(¶ms, &HashSet::new())?; @@ -212,11 +205,12 @@ mod tests { ))?; // Build ItemInBatch(item, batch, index, keys) - let st_item_hash = builder.priv_op(Operation::hash_of(item_hash, batch_hash, "0"))?; + let st_item_hash = + builder.priv_op(Operation::hash_of(item_hash, batch_hash, index.hash()))?; let st_contains_key = builder.priv_op(Operation::dict_contains( key_dict.clone(), - "0", - ingredients_def.keys[0], + index.name(), + ingredients_def.keys[&index].clone(), ))?; let st_item_in_batch = builder.pub_op(Operation::custom( commit_preds.item_in_batch.clone(), @@ -289,7 +283,7 @@ mod tests { let st_dict_insert = builder.priv_op(Operation::dict_insert( key_dict.clone(), empty_dict, - "0", + index.name(), key, ))?; let st_all_items_in_batch_recursive = builder.priv_op(Operation::custom( From ecb3cc94171fa3d70ca172dea392f60c3a928665 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 2 Dec 2025 15:48:24 +1000 Subject: [PATCH 09/29] Update app --- app_cli/src/lib.rs | 74 ++++++++++++++------------------------ commitlib/src/lib.rs | 19 ++++++---- craftlib/src/item.rs | 10 ++++-- craftlib/src/predicates.rs | 2 +- 4 files changed, 47 insertions(+), 58 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index c1a90b9..a9fbf74 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -7,7 +7,7 @@ use std::{ use alloy::primitives::Address; use anyhow::{Context as _, Result, anyhow, bail}; -use commitlib::{ItemBuilder, ItemDef, predicates::CommitPredicates}; +use commitlib::{BatchDef, ItemBuilder, ItemDef, predicates::CommitPredicates}; use common::{ payload::{Payload, PayloadProof}, set_from_value, @@ -27,7 +27,7 @@ use pod2::{ backends::plonky2::mainpod::Prover, frontend::{MainPod, MainPodBuilder}, middleware::{ - CustomPredicateBatch, DEFAULT_VD_SET, F, Params, Pod, RawValue, VDSet, containers::Set, + CustomPredicateBatch, DEFAULT_VD_SET, F, Key, Params, Pod, RawValue, VDSet, containers::Set, }, }; use pod2utils::macros::BuildContext; @@ -192,7 +192,7 @@ impl Helper { for input_item_pod in input_item_pods { let st_item_key = input_item_pod.pod.pub_statements()[0].clone(); sts_input_item_key.push(st_item_key); - let st_craft = input_item_pod.pod.pub_statements()[3].clone(); + let st_craft = input_item_pod.pod.pub_statements()[4].clone(); sts_input_craft.push(st_craft); item_builder.ctx.builder.add_pod(input_item_pod); } @@ -222,7 +222,8 @@ impl Helper { let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); - let st_item_def = item_builder.st_item_def(item_def.clone()).unwrap(); + let st_batch_def = item_builder.st_batch_def(item_def.batch.clone())?; + let st_item_def = item_builder.st_item_def(item_def.clone(), st_batch_def.clone())?; let st_item_key = item_builder.st_item_key(st_item_def.clone()).unwrap(); let mut craft_builder = @@ -256,9 +257,10 @@ impl Helper { }; builder.reveal(&st_item_key); // 0: Required for consuming via Nullifiers - builder.reveal(&st_item_def); // 1: Required for committing via CommitCreation - builder.reveal(&st_nullifiers); // 2: Required for committing via CommitCreation - builder.reveal(&st_craft); // 3: App layer predicate + builder.reveal(&st_batch_def); // 1: Required for committing via CommitCreation + builder.reveal(&st_item_def); // 2: Explicit item predicate + builder.reveal(&st_nullifiers); // 3: Required for committing via CommitCreation + builder.reveal(&st_craft); // 4: App layer predicate info!("Proving item_pod"); let start = std::time::Instant::now(); @@ -279,13 +281,13 @@ impl Helper { let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); - let st_item_def = crafted_item.pod.public_statements[1].clone(); - let st_nullifiers = crafted_item.pod.public_statements[2].clone(); + let st_batch_def = crafted_item.pod.public_statements[1].clone(); + let st_nullifiers = crafted_item.pod.public_statements[3].clone(); let st_commit_creation = item_builder.st_commit_creation( - crafted_item.def.clone(), + crafted_item.def.batch.clone(), st_nullifiers, created_items.clone(), - st_item_def, + st_batch_def, )?; builder.reveal(&st_commit_creation); let prover = &Prover {}; @@ -305,6 +307,8 @@ pub fn craft_item( ) -> anyhow::Result<()> { let vd_set = DEFAULT_VD_SET.clone(); let key = rand_raw_value(); + let index = Key::new(format!("{recipe}")); + let keys = [(index.clone(), key.into())].into_iter().collect(); info!("About to craft \"{recipe}\" with key {key:#}"); let (item_def, input_items, pow_pod) = match recipe { Recipe::Stone => { @@ -313,7 +317,7 @@ pub fn craft_item( } let mining_recipe = MiningRecipe::new(STONE_BLUEPRINT.to_string(), &[]); let ingredients_def = mining_recipe - .do_mining(params, key, 0, STONE_MINING_MAX)? + .do_mining(params, keys, 0, STONE_MINING_MAX)? .unwrap(); let start = std::time::Instant::now(); @@ -324,14 +328,8 @@ pub fn craft_item( RawValue::from(ingredients_def.dict(params)?.commitment()), )?; log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); - ( - ItemDef { - ingredients: ingredients_def.clone(), - work: pow_pod.output, - }, - vec![], - Some(pow_pod), - ) + let batch_def = BatchDef::new(ingredients_def.clone(), pow_pod.output); + (ItemDef::new(batch_def, index)?, vec![], Some(pow_pod)) } Recipe::Wood => { if !inputs.is_empty() { @@ -339,16 +337,10 @@ pub fn craft_item( } let mining_recipe = MiningRecipe::new(WOOD_BLUEPRINT.to_string(), &[]); let ingredients_def = mining_recipe - .do_mining(params, key, 0, WOOD_MINING_MAX)? + .do_mining(params, keys, 0, WOOD_MINING_MAX)? .unwrap(); - ( - ItemDef { - ingredients: ingredients_def.clone(), - work: WOOD_WORK, - }, - vec![], - None, - ) + let batch_def = BatchDef::new(ingredients_def.clone(), WOOD_WORK); + (ItemDef::new(batch_def, index)?, vec![], None) } Recipe::Axe => { if inputs.len() != 2 { @@ -361,16 +353,10 @@ pub fn craft_item( &[wood.def.item_hash(params)?, stone.def.item_hash(params)?], ); let ingredients_def = mining_recipe - .do_mining(params, key, 0, AXE_MINING_MAX)? + .do_mining(params, keys, 0, AXE_MINING_MAX)? .unwrap(); - ( - ItemDef { - ingredients: ingredients_def.clone(), - work: AXE_WORK, - }, - vec![wood, stone], - None, - ) + let batch_def = BatchDef::new(ingredients_def.clone(), AXE_WORK); + (ItemDef::new(batch_def, index)?, vec![wood, stone], None) } Recipe::WoodenAxe => { if inputs.len() != 2 { @@ -383,16 +369,10 @@ pub fn craft_item( &[wood1.def.item_hash(params)?, wood2.def.item_hash(params)?], ); let ingredients_def = mining_recipe - .do_mining(params, key, 0, WOODEN_AXE_MINING_MAX)? + .do_mining(params, keys, 0, WOODEN_AXE_MINING_MAX)? .unwrap(); - ( - ItemDef { - ingredients: ingredients_def.clone(), - work: WOODEN_AXE_WORK, - }, - vec![wood1, wood2], - None, - ) + let batch_def = BatchDef::new(ingredients_def.clone(), WOODEN_AXE_WORK); + (ItemDef::new(batch_def, index)?, vec![wood1, wood2], None) } }; diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 35c7039..551ab5e 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -3,6 +3,7 @@ pub mod util; use std::collections::{HashMap, HashSet}; +use anyhow::anyhow; use pod2::middleware::{ EMPTY_HASH, EMPTY_VALUE, Hash, Key, Params, RawValue, Statement, Value, containers::{Dictionary, Set}, @@ -100,8 +101,12 @@ impl ItemDef { ])) } - pub fn new(batch: BatchDef, index: Key) -> Self { - Self { batch, index } + pub fn new(batch: BatchDef, index: Key) -> anyhow::Result { + if batch.ingredients.keys.get(&index).is_some() { + Ok(Self { batch, index }) + } else { + Err(anyhow!("Invalid index: {index}")) + } } } @@ -223,10 +228,10 @@ impl<'a> ItemBuilder<'a> { // Build ItemDef(item, ingredients, inputs, key, work) Ok(st_custom!(self.ctx, - ItemDef() = ( - st_batch_def, - item_in_batch, - DictContains(keys_dict, item_def.index.name(), item_def.item_key()) + ItemDef() = ( + st_batch_def, + item_in_batch, + DictContains(keys_dict, item_def.index.name(), item_def.item_key()) ))?) } @@ -424,7 +429,7 @@ mod tests { }; let batch_def = BatchDef::new(ingredients_def, Value::from(42).raw()); - let item_def = ItemDef::new(batch_def.clone(), index); + let item_def = ItemDef::new(batch_def.clone(), index).unwrap(); let (st_nullifiers, _nullifiers) = if sts_item_key.is_empty() { item_builder.st_nullifiers(sts_item_key).unwrap() diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index dfa9cf1..5ddb54b 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -113,7 +113,11 @@ impl<'a> CraftBuilder<'a> { let mut s1 = empty_set.clone(); s1.insert(&wood).unwrap(); let mut inputs = s1.clone(); + println!("1"); + println!("{wood}"); + println!("{stone}"); inputs.insert(&stone).unwrap(); + println!("2"); Ok(st_custom!(self.ctx, AxeInputs() = ( SetInsert(s1, empty_set, wood), @@ -301,7 +305,7 @@ mod tests { let ingredients_def = mine_success.unwrap(); let batch_def = BatchDef::new(ingredients_def.clone(), STONE_WORK); - let item_def = ItemDef::new(batch_def, index); + let item_def = ItemDef::new(batch_def, index)?; let item_hash = item_def.item_hash(¶ms)?; println!( "Mined stone {:?} from ingredients {:?}", @@ -348,7 +352,7 @@ mod tests { let ingredients_dict = ingredients_def.dict(¶ms)?; let inputs_set = ingredients_def.inputs_set(¶ms)?; let batch_def = BatchDef::new(ingredients_def.clone(), pow_pod.output); - let item_def = ItemDef::new(batch_def.clone(), index); + let item_def = ItemDef::new(batch_def.clone(), index)?; let item_hash = item_def.item_hash(¶ms)?; // Prove a stone POD. This is the private POD for the player to store @@ -374,7 +378,7 @@ mod tests { REQUEST( BatchDef(batch, ingredients, inputs, keys, work) - ItemDef(item, ingredients, inputs, key, work) + ItemDef (item, ingredients, inputs, key, work) ItemKey(item, key) IsStone(item) ) diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 98ebe08..cd560ca 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -168,7 +168,7 @@ mod tests { builder.add_pod(main_pow_pod); let batch_def = BatchDef::new(ingredients_def.clone(), work); let batch_hash = batch_def.batch_hash(¶ms)?; - let item_def = ItemDef::new(batch_def.clone(), index.clone()); + let item_def = ItemDef::new(batch_def.clone(), index.clone())?; let item_hash = item_def.item_hash(¶ms)?; let key_dict = Dictionary::new(params.max_depth_mt_containers, ingredients_def.keys.clone())?; From e9532518da1833f01d812803264ad56acf688359 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 2 Dec 2025 13:18:27 +0000 Subject: [PATCH 10/29] small fixes, identify the cause of the error at the Synchronizer verifying the proof from the blobs --- app_cli/src/lib.rs | 7 +++++++ commitlib/src/lib.rs | 6 +++--- common/src/shrink.rs | 1 + full-flow.sh | 2 +- synchronizer/src/main.rs | 20 ++++++++++++-------- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index a9fbf74..b5eb5ee 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -376,6 +376,13 @@ pub fn craft_item( } }; + // create output dir (if there is a parent dir), in case it does not exist + // yet, so that later when creating the file we don't get an error if the + // directory does not exist + if let Some(dir) = output.parent() { + std::fs::create_dir_all(dir)?; + } + 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)?; diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 551ab5e..812ca98 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -371,9 +371,9 @@ impl<'a> ItemBuilder<'a> { // Build CommitCreation(item, nullifiers, created_items) let st_commit_creation = st_custom!(self.ctx, - CommitCreation() = ( - st_batch_def, - st_all_items_in_batch, + CommitCreation() = ( + st_batch_def, + st_all_items_in_batch, st_inputs_subset, st_nullifiers ))?; diff --git a/common/src/shrink.rs b/common/src/shrink.rs index 7d4ec9d..1ee5b83 100644 --- a/common/src/shrink.rs +++ b/common/src/shrink.rs @@ -175,5 +175,6 @@ pub fn shrink_compress_pod( &indices, &shrunk_main_pod_build.circuit_data.common.fri_params, ); + println!("PIS: {:?}", proof_with_pis.public_inputs); Ok(compressed_proof) } diff --git a/full-flow.sh b/full-flow.sh index fe50166..ecfdc81 100755 --- a/full-flow.sh +++ b/full-flow.sh @@ -27,7 +27,7 @@ $tmux new-session -d -s fullflow $tmux split-window -v # run the Synchronizer server -$tmux send-keys -t fullflow:0.0 'RUST_LOG=synchronizer=debug cargo run --release -p synchronizer' C-m +$tmux send-keys -t fullflow:0.0 'RUST_LOG=synchronizer=debug,info cargo run --release -p synchronizer' C-m # app command line: diff --git a/synchronizer/src/main.rs b/synchronizer/src/main.rs index 793731c..2990893 100644 --- a/synchronizer/src/main.rs +++ b/synchronizer/src/main.rs @@ -51,7 +51,7 @@ use synchronizer::{ }, }; use tokio::{runtime::Runtime, time::sleep}; -use tracing::{debug, info, trace}; +use tracing::{debug, error, info, trace}; pub mod endpoints; @@ -380,7 +380,7 @@ impl Node { info!("Valid do_blob at slot {}, blob_index {}!", slot, blob.index); } Err(e) => { - info!("Invalid do_blob: {:?}", e); + error!("Ignoring blob due: Invalid do_blob: {:?}", e); continue; } }; @@ -426,6 +426,11 @@ impl Node { ) .unwrap(), ); + // WIP: + // TODO update st_commit_creation to match the new statement after batch + // instead of single item. This will make the st_hash match the expected + // one, which in turn will make the public_inputs be valid when the + // Synchronizer decompresses & verifies the proof. let st_commit_creation = Statement::Custom( self.pred_commit_creation.clone(), vec![ @@ -470,16 +475,15 @@ impl Node { PayloadProof::Plonky2(proof) => proof, PayloadProof::Groth16(_) => todo!(), }; + println!("PIS: {:?}", public_inputs); let proof_with_pis = CompressedProofWithPublicInputs { proof: *shrunk_main_pod_proof, public_inputs, }; - let proof = proof_with_pis - .decompress( - &self.verifier_circuit_data.verifier_only.circuit_digest, - &self.common_circuit_data, - ) - .unwrap(); + let proof = proof_with_pis.decompress( + &self.verifier_circuit_data.verifier_only.circuit_digest, + &self.common_circuit_data, + )?; self.verifier_circuit_data.verify(proof) } } From c777b15ff39c86cd54cdad87cb440719e50650ef Mon Sep 17 00:00:00 2001 From: Ahmad Date: Wed, 3 Dec 2025 15:51:25 +1000 Subject: [PATCH 11/29] Fix committing & verifying --- app_cli/src/lib.rs | 7 +++++-- app_cli/src/main.rs | 7 +++++-- commitlib/src/lib.rs | 2 +- common/src/payload.rs | 45 ++++++++++++++++++++++++++++++++-------- synchronizer/src/main.rs | 24 +++++++++++++-------- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index b5eb5ee..4bd1638 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -27,7 +27,8 @@ use pod2::{ backends::plonky2::mainpod::Prover, frontend::{MainPod, MainPodBuilder}, middleware::{ - CustomPredicateBatch, DEFAULT_VD_SET, F, Key, Params, Pod, RawValue, VDSet, containers::Set, + CustomPredicateBatch, DEFAULT_VD_SET, F, Key, Params, Pod, RawValue, VDSet, Value, + containers::Set, }, }; use pod2utils::macros::BuildContext; @@ -414,9 +415,11 @@ pub async fn commit_item(params: &Params, cfg: &Config, input: &Path) -> anyhow: let st_commit_creation = pod.public_statements[0].clone(); let nullifier_set = set_from_value(&st_commit_creation.args()[1].literal()?)?; let nullifiers: Vec = nullifier_set.set().iter().map(|v| v.raw()).collect(); + // Single item => set containing one element + let items = vec![Value::from(crafted_item.def.item_hash(params)?).raw()]; let payload_bytes = Payload { proof: PayloadProof::Plonky2(Box::new(shrunk_main_pod_proof.clone())), - item: RawValue::from(crafted_item.def.item_hash(params)?), + items, created_items_root: RawValue::from(created_items.commitment()), nullifiers, } diff --git a/app_cli/src/main.rs b/app_cli/src/main.rs index 1aeef6e..a9c3319 100644 --- a/app_cli/src/main.rs +++ b/app_cli/src/main.rs @@ -74,14 +74,17 @@ async fn main() -> anyhow::Result<()> { // Verify that the item exists on-blob-space: // first get the merkle proof of item existence from the Synchronizer let item = RawValue::from(crafted_item.def.item_hash(¶ms)?); - let item_hex: String = format!("{item:#}"); + + // Single item => set containing one element + // TODO: Generalise. + let item_set_hex: String = format!("{item:#}"); let (epoch, _): (u64, RawValue) = reqwest::blocking::get(format!("{}/created_items_root", cfg.sync_url,))?.json()?; info!("Verifying commitment of item {item:#} via synchronizer at epoch {epoch}"); let (epoch, mtp): (u64, MerkleProof) = reqwest::blocking::get(format!( "{}/created_item/{}", cfg.sync_url, - &item_hex[2..] + &item_set_hex[2..] ))? .json()?; info!("mtp at epoch {epoch}: {mtp:?}"); diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 812ca98..74a7e1b 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -102,7 +102,7 @@ impl ItemDef { } pub fn new(batch: BatchDef, index: Key) -> anyhow::Result { - if batch.ingredients.keys.get(&index).is_some() { + if batch.ingredients.keys.contains_key(&index) { Ok(Self { batch, index }) } else { Err(anyhow!("Invalid index: {index}")) diff --git a/common/src/payload.rs b/common/src/payload.rs index 440192f..ba34ba2 100644 --- a/common/src/payload.rs +++ b/common/src/payload.rs @@ -37,7 +37,7 @@ pub fn read_elems(bytes: &mut impl Read) -> Result<[F; N]> { #[allow(clippy::large_enum_variant)] pub struct Payload { pub proof: PayloadProof, - pub item: RawValue, + pub items: Vec, pub created_items_root: RawValue, pub nullifiers: Vec, } @@ -51,9 +51,16 @@ impl Payload { .write_all(&PAYLOAD_MAGIC.to_le_bytes()) .expect("vec write"); self.proof.write_bytes(&mut buffer); - write_elems(&mut buffer, &self.item.0); write_elems(&mut buffer, &self.created_items_root.0); - assert!(self.nullifiers.len() <= 255); + // TODO: What are the constraints on these lengths apart from the type casts below? + assert!(self.items.len() < 256); + buffer + .write_all(&(self.items.len() as u8).to_le_bytes()) + .expect("vec write"); + for item in &self.items { + write_elems(&mut buffer, &item.0); + } + assert!(self.nullifiers.len() < 256); buffer .write_all(&(self.nullifiers.len() as u8).to_le_bytes()) .expect("vec write"); @@ -76,8 +83,16 @@ impl Payload { let (proof, len) = PayloadProof::from_bytes(bytes, common_data)?; bytes = &bytes[len..]; - let item = RawValue(read_elems(&mut bytes)?); let created_items_root = RawValue(read_elems(&mut bytes)?); + let items_len = { + let mut buffer = [0; 1]; + bytes.read_exact(&mut buffer)?; + u8::from_le_bytes(buffer) + }; + let mut items = Vec::with_capacity(items_len as usize); + for _ in 0..items_len { + items.push(RawValue(read_elems(&mut bytes)?)); + } let nullifiers_len = { let mut buffer = [0; 1]; bytes.read_exact(&mut buffer)?; @@ -89,7 +104,7 @@ impl Payload { } Ok(Self { proof, - item, + items, created_items_root, nullifiers, }) @@ -194,7 +209,14 @@ mod tests { let payload = { let mut builder = MainPodBuilder::new(¶ms, vd_set); - let item = Value::from("dummy_item"); + let items = vec![Value::from("dummy_item").raw()]; + let item_set = Value::from( + Set::new( + params.max_depth_mt_containers, + items.iter().map(|rv| (*rv).into()).collect(), + ) + .unwrap(), + ); let nullifiers = vec![ Value::from(1).raw(), Value::from(2).raw(), @@ -213,7 +235,7 @@ mod tests { .op( true, vec![ - (0, item.clone()), + (0, item_set.clone()), (1, nullifiers_set.clone()), (2, created_items.clone()), ], @@ -237,7 +259,7 @@ mod tests { Payload { proof: PayloadProof::Plonky2(Box::new(shrunk_main_pod_proof.clone())), - item: item.raw(), + items, created_items_root: created_items.raw(), nullifiers, } @@ -257,10 +279,15 @@ mod tests { ) .unwrap(), ); + let item_set = Set::new( + params.max_depth_mt_containers, + payload.items.iter().map(|rv| (*rv).into()).collect(), + ) + .unwrap(); let st = Statement::Custom( pred, vec![ - Value::from(payload.item), + item_set.into(), nullifiers_set, Value::from(payload.created_items_root), ], diff --git a/synchronizer/src/main.rs b/synchronizer/src/main.rs index 2990893..91b6ce3 100644 --- a/synchronizer/src/main.rs +++ b/synchronizer/src/main.rs @@ -407,9 +407,11 @@ impl Node { ); } - // Check that output is unique - if state.created_items.contains(&Value::from(payload.item)) { - bail!("item {} exists in created_items", payload.item); + // Check that outputs are unique + for item in &payload.items { + if state.created_items.contains(&(*item).into()) { + bail!("item {} exists in created_items", item); + } } // Check that inputs are unique @@ -431,10 +433,14 @@ impl Node { // instead of single item. This will make the st_hash match the expected // one, which in turn will make the public_inputs be valid when the // Synchronizer decompresses & verifies the proof. + let item_set = Set::new( + self.params.max_depth_mt_containers, + payload.items.iter().map(|rv| (*rv).into()).collect(), + )?; let st_commit_creation = Statement::Custom( self.pred_commit_creation.clone(), vec![ - Value::from(payload.item), + item_set.into(), nullifiers_set, Value::from(payload.created_items_root), ], @@ -447,11 +453,11 @@ impl Node { for nullifier in &payload.nullifiers { state.nullifiers.insert(*nullifier); } - // Register item - state - .created_items - .insert(&Value::from(payload.item)) - .unwrap(); + + // Register items + for item in payload.items { + state.created_items.insert(&item.into())?; + } state.epoch += 1; let created_items_root = state.created_items.commitment(); From 94d2a49d2f52e3c32719acd636965ecf87c3b38c Mon Sep 17 00:00:00 2001 From: Ahmad Date: Wed, 3 Dec 2025 21:31:32 +1000 Subject: [PATCH 12/29] Form item POD in steps --- app_cli/src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++------ commitlib/src/lib.rs | 3 +-- craftlib/src/item.rs | 12 ++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 4bd1638..76d726f 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -25,7 +25,7 @@ use craftlib::{ use plonky2::field::types::Field; use pod2::{ backends::plonky2::mainpod::Prover, - frontend::{MainPod, MainPodBuilder}, + frontend::{MainPod, MainPodBuilder, Operation}, middleware::{ CustomPredicateBatch, DEFAULT_VD_SET, F, Key, Params, Pod, RawValue, VDSet, Value, containers::Set, @@ -240,17 +240,17 @@ 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)? + craft_builder.st_is_stone(item_def.clone(), st_item_def.clone(), st_pow)? } - Recipe::Wood => craft_builder.st_is_wood(item_def, st_item_def.clone())?, + Recipe::Wood => craft_builder.st_is_wood(item_def.clone(), st_item_def.clone())?, Recipe::Axe => craft_builder.st_is_axe( - item_def, + item_def.clone(), st_item_def.clone(), sts_input_craft[0].clone(), sts_input_craft[1].clone(), )?, Recipe::WoodenAxe => craft_builder.st_is_wooden_axe( - item_def, + item_def.clone(), st_item_def.clone(), sts_input_craft[0].clone(), sts_input_craft[1].clone(), @@ -263,12 +263,41 @@ impl Helper { builder.reveal(&st_nullifiers); // 3: Required for committing via CommitCreation builder.reveal(&st_craft); // 4: App layer predicate - info!("Proving item_pod"); + info!("Proving preliminary item_pod"); let start = std::time::Instant::now(); let item_key_pod = builder.prove(prover).unwrap(); log::info!("[TIME] pod proving time: {:?}", start.elapsed()); item_key_pod.pod.verify().unwrap(); + // Now construct a POD with the same revealed statements plus an AllItemsInBatch statement. + let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); + let mut item_builder = + ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); + + item_builder.ctx.builder.add_pod(item_key_pod.clone()); + + println!("{}", item_key_pod.public_statements.len()); + item_key_pod + .public_statements + .into_iter() + .try_for_each(|st| { + item_builder + .ctx + .builder + .pub_op(Operation::copy(st)) + .map(|_| ()) + })?; + + let st_all_items_in_batch = item_builder.st_all_items_in_batch(item_def.batch)?; + + item_builder.ctx.builder.reveal(&st_all_items_in_batch); // 5: Required for committing via CommitCreation + + info!("Proving actual item_pod"); + let start = std::time::Instant::now(); + let item_key_pod = item_builder.ctx.builder.prove(prover)?; + log::info!("[TIME] pod proving time: {:?}", start.elapsed()); + item_key_pod.pod.verify()?; + Ok(item_key_pod) } @@ -284,11 +313,13 @@ impl Helper { ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); let st_batch_def = crafted_item.pod.public_statements[1].clone(); let st_nullifiers = crafted_item.pod.public_statements[3].clone(); + let st_all_items_in_batch = crafted_item.pod.public_statements[5].clone(); let st_commit_creation = item_builder.st_commit_creation( crafted_item.def.batch.clone(), st_nullifiers, created_items.clone(), st_batch_def, + st_all_items_in_batch, )?; builder.reveal(&st_commit_creation); let prover = &Prover {}; diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 74a7e1b..68cc952 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -361,14 +361,13 @@ impl<'a> ItemBuilder<'a> { st_nullifiers: Statement, created_items: Set, st_batch_def: Statement, + st_all_items_in_batch: Statement, ) -> anyhow::Result { let st_inputs_subset = self.st_super_sub_set( batch_def.ingredients.inputs_set(self.params)?, created_items, )?; - let st_all_items_in_batch = self.st_all_items_in_batch(batch_def)?; - // Build CommitCreation(item, nullifiers, created_items) let st_commit_creation = st_custom!(self.ctx, CommitCreation() = ( diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 5ddb54b..0ab3dbe 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -227,6 +227,13 @@ mod tests { prover: &dyn MainPodProver, vd_set: &VDSet, ) -> anyhow::Result { + // Prove AllItemsInBatch + let mut builder = MainPodBuilder::new(&Default::default(), vd_set); + let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, batches), params); + let st_all_items_in_batch = item_builder.st_all_items_in_batch(item_def.batch.clone())?; + item_builder.ctx.builder.reveal(&st_all_items_in_batch); + let all_items_in_batch_pod = item_builder.ctx.builder.prove(prover)?; + let mut builder = MainPodBuilder::new(&Default::default(), vd_set); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, batches), params); let st_batch_def = item_builder.st_batch_def(item_def.batch.clone())?; @@ -235,11 +242,14 @@ mod tests { item_builder.ctx.builder.reveal(&st_item_def); let st_item_key = item_builder.st_item_key(st_item_def.clone())?; item_builder.ctx.builder.reveal(&st_item_key); + let st_all_items_in_batch = all_items_in_batch_pod.public_statements[0].clone(); + item_builder.ctx.builder.reveal(&st_all_items_in_batch); let st_pow = pow_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); + craft_builder.ctx.builder.add_pod(all_items_in_batch_pod); let st_is_stone = craft_builder.st_is_stone(item_def, st_item_def, st_pow)?; craft_builder.ctx.builder.reveal(&st_is_stone); @@ -265,6 +275,7 @@ mod tests { // TODO: Consider a more robust lookup for this which doesn't depend on index. let st_batch_def = item_main_pod.public_statements[0].clone(); + let st_all_items_in_batch = item_main_pod.public_statements[3].clone(); builder.add_pod(item_main_pod); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, batches), params); @@ -274,6 +285,7 @@ mod tests { st_nullifier, created_items, st_batch_def, + st_all_items_in_batch, )?; builder.reveal(&st_commit_creation); From 345a8ad9dd79a632563cb132bf52dc3afa7fae96 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Wed, 3 Dec 2025 22:03:11 +1000 Subject: [PATCH 13/29] Clean-up --- app_cli/src/lib.rs | 48 ++++++++++++++++---------------------------- craftlib/src/item.rs | 2 +- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 76d726f..8069094 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -25,7 +25,7 @@ use craftlib::{ use plonky2::field::types::Field; use pod2::{ backends::plonky2::mainpod::Prover, - frontend::{MainPod, MainPodBuilder, Operation}, + frontend::{MainPod, MainPodBuilder}, middleware::{ CustomPredicateBatch, DEFAULT_VD_SET, F, Key, Params, Pod, RawValue, VDSet, Value, containers::Set, @@ -184,6 +184,18 @@ impl Helper { pow_pod: Option, ) -> anyhow::Result { let prover = &Prover {}; + + // First take care of AllItemsInBatch statement. + let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); + let mut item_builder = + ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); + + let st_all_items_in_batch = item_builder.st_all_items_in_batch(item_def.batch.clone())?; + + item_builder.ctx.builder.reveal(&st_all_items_in_batch); // 5: Required for committing via CommitCreation + + let all_items_in_batch_pod = item_builder.ctx.builder.prove(prover)?; + let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); @@ -257,47 +269,21 @@ impl Helper { )?, }; + builder.add_pod(all_items_in_batch_pod); + builder.reveal(&st_item_key); // 0: Required for consuming via Nullifiers builder.reveal(&st_batch_def); // 1: Required for committing via CommitCreation builder.reveal(&st_item_def); // 2: Explicit item predicate builder.reveal(&st_nullifiers); // 3: Required for committing via CommitCreation builder.reveal(&st_craft); // 4: App layer predicate + builder.reveal(&st_all_items_in_batch); // 5: Required for committing via CommitCreation - info!("Proving preliminary item_pod"); + info!("Proving item_pod"); let start = std::time::Instant::now(); let item_key_pod = builder.prove(prover).unwrap(); log::info!("[TIME] pod proving time: {:?}", start.elapsed()); item_key_pod.pod.verify().unwrap(); - // Now construct a POD with the same revealed statements plus an AllItemsInBatch statement. - let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); - let mut item_builder = - ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); - - item_builder.ctx.builder.add_pod(item_key_pod.clone()); - - println!("{}", item_key_pod.public_statements.len()); - item_key_pod - .public_statements - .into_iter() - .try_for_each(|st| { - item_builder - .ctx - .builder - .pub_op(Operation::copy(st)) - .map(|_| ()) - })?; - - let st_all_items_in_batch = item_builder.st_all_items_in_batch(item_def.batch)?; - - item_builder.ctx.builder.reveal(&st_all_items_in_batch); // 5: Required for committing via CommitCreation - - info!("Proving actual item_pod"); - let start = std::time::Instant::now(); - let item_key_pod = item_builder.ctx.builder.prove(prover)?; - log::info!("[TIME] pod proving time: {:?}", start.elapsed()); - item_key_pod.pod.verify()?; - Ok(item_key_pod) } diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 0ab3dbe..e92a814 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -379,7 +379,7 @@ mod tests { )?; stone_main_pod.pod.verify()?; - assert_eq!(stone_main_pod.public_statements.len(), 4); + assert_eq!(stone_main_pod.public_statements.len(), 5); println!("Stone POD: {:?}", stone_main_pod.pod); // PODLang query to check the final statements. From 85242b7bd596a64f385b0d746f4884b361996ba7 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 3 Dec 2025 17:43:54 +0000 Subject: [PATCH 14/29] wip draft DisassembleStone into 2 outputs predicates --- craftlib/src/predicates.rs | 54 ++++++++++++++++++++++++++++++++++++++ synchronizer/src/main.rs | 5 ---- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index cd560ca..6b1f66d 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -83,6 +83,60 @@ impl ItemPredicates { WoodenAxeInputs(inputs) ) "#, + // multi-output related predicates: + // (simplified version without tools & durability) + r#" + // disassemble 2 Stones into 2 outputs: Dust,Gravel. + + // inputs: 2 Stones + StoneDisassembleInputs(inputs, private: s1, stone1, stone2) = AND( + SetInsert(s1, {}, stone1) + SetInsert(inputs, s1, stone2) + + // prove the ingredients are correct + IsStone(stone1) + IsStone(stone2) + ) + // outputs: 1 Dust, 1 Gravel + StoneDisassembleOutputs(inputs, private: batch, k1, _dust_key, _gravel_key) = AND( + HashOf(dust, batch, "dust") + HashOf(gravel, batch, "gravel") + DictInsert(k1, {}, "dust", _dust_key) + DictInsert(keys, k1, "gravel", _gravel_key) + ) + // helper to have a single predicate for the inputs & outputs + StoneDisassembleInputsOutputs(inputs, private: batch, k1, s1, stone1, stone2, _dust_key, _gravel_key) = AND ( + StoneDisassembleInputs(inputs) + StoneDisassembleOutputs(inputs) + ) + StoneDisassemble(dust, gravel, stone1, stone2, private: batch, ingredients, inputs, work, _dust_key, _gravel_key) = AND( + BatchDef(batch, ingredients, inputs, keys, work) + DictContains(ingredients, "blueprint", "dust") + DictContains(ingredients, "blueprint", "gravel") + + StoneDisassembleInputsOutputs(inputs) + ) + "#, + // WIP, NOTE: for the next 2 predicates I'm not sure on the + // approach; might be missing stuff. + r#" + // can only obtain Dust from disassembling 2 stones + IsDust(item, private: ingredients, inputs, key, work) = AND( + ItemDef(item, ingredients, inputs, key, work) + DictContains(ingredients, "blueprint", "dust") + Equal(work, {}) + + StoneDisassemble(inputs) + ) + // can only obtain Gravel from disassembling 2 stones + IsGravel(item, private: ingredients, inputs, key, work) = AND( + ItemDef(item, ingredients, inputs, key, work) + DictContains(ingredients, "blueprint", "gravel") + Equal(work, {}) + + StoneDisassemble(inputs) + ) + "#, ]; let defs = PredicateDefs::new(params, &batch_defs, slice::from_ref(&commit_preds.defs)); diff --git a/synchronizer/src/main.rs b/synchronizer/src/main.rs index 91b6ce3..a42b5c0 100644 --- a/synchronizer/src/main.rs +++ b/synchronizer/src/main.rs @@ -428,11 +428,6 @@ impl Node { ) .unwrap(), ); - // WIP: - // TODO update st_commit_creation to match the new statement after batch - // instead of single item. This will make the st_hash match the expected - // one, which in turn will make the public_inputs be valid when the - // Synchronizer decompresses & verifies the proof. let item_set = Set::new( self.params.max_depth_mt_containers, payload.items.iter().map(|rv| (*rv).into()).collect(), From 2c1bf8cc77a8a1058543a8efc7e341a5369773ad Mon Sep 17 00:00:00 2001 From: arnaucube Date: Thu, 4 Dec 2025 11:15:13 +0000 Subject: [PATCH 15/29] fix DisassembleStone related predicates --- craftlib/src/predicates.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 6b1f66d..d6bb7f9 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -97,20 +97,25 @@ impl ItemPredicates { IsStone(stone1) IsStone(stone2) ) + // outputs: 1 Dust, 1 Gravel - StoneDisassembleOutputs(inputs, private: batch, k1, _dust_key, _gravel_key) = AND( + StoneDisassembleOutputs(inputs, + private: batch, keys, k1, dust, gravel, _dust_key, _gravel_key) = AND( HashOf(dust, batch, "dust") HashOf(gravel, batch, "gravel") DictInsert(k1, {}, "dust", _dust_key) DictInsert(keys, k1, "gravel", _gravel_key) ) + // helper to have a single predicate for the inputs & outputs - StoneDisassembleInputsOutputs(inputs, private: batch, k1, s1, stone1, stone2, _dust_key, _gravel_key) = AND ( + StoneDisassembleInputsOutputs(inputs) = AND ( StoneDisassembleInputs(inputs) StoneDisassembleOutputs(inputs) ) - StoneDisassemble(dust, gravel, stone1, stone2, private: batch, ingredients, inputs, work, _dust_key, _gravel_key) = AND( - BatchDef(batch, ingredients, inputs, keys, work) + + StoneDisassemble(inputs, + private: batch, keys, ingredients) = AND( + BatchDef(batch, ingredients, inputs, keys, {}) // no work DictContains(ingredients, "blueprint", "dust") DictContains(ingredients, "blueprint", "gravel") @@ -128,8 +133,9 @@ impl ItemPredicates { StoneDisassemble(inputs) ) + // can only obtain Gravel from disassembling 2 stones - IsGravel(item, private: ingredients, inputs, key, work) = AND( + IsGravel(item, private: ingredients, inputs, key, work, dust, gravel) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "gravel") Equal(work, {}) @@ -177,7 +183,7 @@ mod tests { assert!(commit_preds.defs.batches.len() == 4); let item_preds = ItemPredicates::compile(¶ms, &commit_preds); - assert!(item_preds.defs.batches.len() == 2); + assert!(item_preds.defs.batches.len() == 4); } #[test] From c5e01089376ffc3aea5de61bb45b12e3c4355def Mon Sep 17 00:00:00 2001 From: arnaucube Date: Thu, 4 Dec 2025 17:07:59 +0000 Subject: [PATCH 16/29] draft multi-outputs DisassembleStone rust statements methods --- craftlib/src/constants.rs | 8 +++ craftlib/src/item.rs | 113 ++++++++++++++++++++++++++++++++++++- craftlib/src/predicates.rs | 22 ++++---- 3 files changed, 129 insertions(+), 14 deletions(-) diff --git a/craftlib/src/constants.rs b/craftlib/src/constants.rs index f5df968..e8bb18e 100644 --- a/craftlib/src/constants.rs +++ b/craftlib/src/constants.rs @@ -16,3 +16,11 @@ pub const AXE_WORK: RawValue = EMPTY_VALUE; pub const WOODEN_AXE_BLUEPRINT: &str = "wooden-axe"; pub const WOODEN_AXE_MINING_MAX: u64 = 0x0020_0000_0000_0000; pub const WOODEN_AXE_WORK: RawValue = EMPTY_VALUE; + +pub const DUST_BLUEPRINT: &str = "dust"; +pub const DUST_MINING_MAX: u64 = 0x0020_0000_0000_0000; +pub const DUST_WORK: RawValue = EMPTY_VALUE; + +pub const GRAVEL_BLUEPRINT: &str = "gravel"; +pub const GRAVEL_MINING_MAX: u64 = 0x0020_0000_0000_0000; +pub const GRAVEL_WORK: RawValue = EMPTY_VALUE; diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index e92a814..072da23 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -1,11 +1,16 @@ use std::collections::{HashMap, HashSet}; -use commitlib::{IngredientsDef, ItemDef}; +use commitlib::{BatchDef, IngredientsDef, ItemDef}; use log; -use pod2::middleware::{EMPTY_VALUE, Hash, Key, Params, Statement, ToFields, Value}; +use pod2::middleware::{ + EMPTY_VALUE, Hash, Key, Params, Statement, ToFields, Value, containers::Dictionary, +}; use pod2utils::{macros::BuildContext, set, st_custom}; -use crate::constants::{AXE_BLUEPRINT, STONE_BLUEPRINT, WOOD_BLUEPRINT, WOODEN_AXE_BLUEPRINT}; +use crate::constants::{ + AXE_BLUEPRINT, DUST_BLUEPRINT, GRAVEL_BLUEPRINT, STONE_BLUEPRINT, WOOD_BLUEPRINT, + WOODEN_AXE_BLUEPRINT, +}; // Reusable recipe for an item to be mined, not including the variable // cryptographic values. @@ -183,6 +188,108 @@ impl<'a> CraftBuilder<'a> { st_wooden_axe_inputs ))?) } + + fn st_stone_disassemble_inputs( + &mut self, + st_is_stone1: Statement, + st_is_stone2: Statement, + ) -> anyhow::Result { + let stone1 = st_is_stone1.args()[0].literal().unwrap(); + let stone2 = st_is_stone2.args()[0].literal().unwrap(); + let empty_set = set!(self.params.max_depth_mt_containers).unwrap(); + let mut s1 = empty_set.clone(); + s1.insert(&stone1).unwrap(); + let mut inputs = s1.clone(); + inputs.insert(&stone2).unwrap(); + Ok(st_custom!(self.ctx, + StoneDisassembleInputs() = ( + SetInsert(s1, empty_set, stone1), + SetInsert(inputs, s1, stone2), + st_is_stone1, + st_is_stone2 + ))?) + } + pub fn st_stone_disassemble_outputs( + &mut self, + batch_def: BatchDef, + ) -> anyhow::Result { + let batch_hash = batch_def.batch_hash(self.params)?; + + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + batch_def.ingredients.keys.clone(), + )?; + + Ok(st_custom!(self.ctx, + StoneDisassembleOutputs() = ( + // TODO WIP + todo!(), + HashOf(dust_hash, batch_hash, item_def.index.hash()), + HashOf(gravel_hash, batch_hash, item_def.index.hash()), + DictContains(keys_dict, item_def.index.name(), item_def.item_key()) + DictContains(keys_dict, item_def.index.name(), item_def.item_key()) + ))?) + } + fn st_stone_disassemble_inputs_outputs( + &mut self, + st_is_stone1: Statement, + st_is_stone2: Statement, + batch_def: BatchDef, + ) -> anyhow::Result { + let st_stone_disassemble_inputs = + self.st_stone_disassemble_inputs(st_is_stone1, st_is_stone2)?; + let st_stone_disassemble_outputs = self.st_stone_disassemble_outputs(batch_def)?; + + Ok(st_custom!(self.ctx, + StoneDisassembleInputsOutputs() = ( + st_stone_disassemble_inputs, + st_stone_disassemble_outputs, + ))?) + } + fn st_stone_disassemble( + &mut self, + st_stone_disassemble_inputs_outputs: Statement, + st_batch_def: Statement, + ) -> anyhow::Result { + Ok(st_custom!(self.ctx, + StoneDisassemble() = ( + st_batch_def, + // TODO + todo!(), + st_stone_disassemble_inputs_outputs, + ))?) + } + + pub fn st_is_dust( + &mut self, + item_def: ItemDef, + st_item_def: Statement, + st_stone_disassemble_inputs_outputs: Statement, + st_is_dust: Statement, + ) -> anyhow::Result { + Ok(st_custom!(self.ctx, + IsAxe() = ( + st_item_def, + DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", DUST_BLUEPRINT), + Equal(item_def.batch.work, EMPTY_VALUE), + st_stone_disassemble_inputs_outputs, + ))?) + } + pub fn st_is_gravel( + &mut self, + item_def: ItemDef, + st_item_def: Statement, + st_stone_disassemble_inputs_outputs: Statement, + st_is_gravel: Statement, + ) -> anyhow::Result { + Ok(st_custom!(self.ctx, + IsAxe() = ( + st_item_def, + DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", GRAVEL_BLUEPRINT), + Equal(item_def.batch.work, EMPTY_VALUE), + st_stone_disassemble_inputs_outputs, + ))?) + } } #[cfg(test)] diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index d6bb7f9..31cc46c 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -41,8 +41,7 @@ impl ItemPredicates { // TODO input POD: SequentialWork(ingredients, work, 5) // TODO input POD: HashInRange(0, 1<<5, ingredients) ) - "#, - r#" + AxeInputs(inputs, private: s1, wood, stone) = AND( // 2 ingredients SetInsert(s1, {}, wood) @@ -62,6 +61,8 @@ impl ItemPredicates { AxeInputs(inputs) ) + "#, + r#" // Wooden Axe: WoodenAxeInputs(inputs, private: s1, wood1, wood2) = AND( @@ -82,10 +83,10 @@ impl ItemPredicates { WoodenAxeInputs(inputs) ) - "#, + + // multi-output related predicates: // (simplified version without tools & durability) - r#" // disassemble 2 Stones into 2 outputs: Dust,Gravel. // inputs: 2 Stones @@ -106,6 +107,8 @@ impl ItemPredicates { DictInsert(k1, {}, "dust", _dust_key) DictInsert(keys, k1, "gravel", _gravel_key) ) + "#, + r#" // helper to have a single predicate for the inputs & outputs StoneDisassembleInputsOutputs(inputs) = AND ( @@ -114,17 +117,14 @@ impl ItemPredicates { ) StoneDisassemble(inputs, - private: batch, keys, ingredients) = AND( - BatchDef(batch, ingredients, inputs, keys, {}) // no work + private: batch, keys, ingredients, work) = AND( + BatchDef(batch, ingredients, inputs, keys, work) DictContains(ingredients, "blueprint", "dust") DictContains(ingredients, "blueprint", "gravel") StoneDisassembleInputsOutputs(inputs) ) - "#, - // WIP, NOTE: for the next 2 predicates I'm not sure on the - // approach; might be missing stuff. - r#" + // can only obtain Dust from disassembling 2 stones IsDust(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) @@ -183,7 +183,7 @@ mod tests { assert!(commit_preds.defs.batches.len() == 4); let item_preds = ItemPredicates::compile(¶ms, &commit_preds); - assert!(item_preds.defs.batches.len() == 4); + assert!(item_preds.defs.batches.len() == 3); } #[test] From 7cde69678ef8b84085c75e24e408b0f818f55cdb Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 5 Dec 2025 11:17:25 +1000 Subject: [PATCH 17/29] Complete statement methods --- craftlib/src/item.rs | 40 +++++++++++++++++++++----------------- craftlib/src/predicates.rs | 3 +-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 072da23..fd29b36 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use commitlib::{BatchDef, IngredientsDef, ItemDef}; use log; use pod2::middleware::{ - EMPTY_VALUE, Hash, Key, Params, Statement, ToFields, Value, containers::Dictionary, + EMPTY_VALUE, Hash, Key, Params, Statement, ToFields, Value, containers::Dictionary, hash_values, }; use pod2utils::{macros::BuildContext, set, st_custom}; @@ -214,20 +214,26 @@ impl<'a> CraftBuilder<'a> { batch_def: BatchDef, ) -> anyhow::Result { let batch_hash = batch_def.batch_hash(self.params)?; + let dust_hash = hash_values(&[batch_hash.into(), DUST_BLUEPRINT.into()]); + let gravel_hash = hash_values(&[batch_hash.into(), GRAVEL_BLUEPRINT.into()]); + let dust_key = batch_def.ingredients.keys[&DUST_BLUEPRINT.into()].clone(); + let gravel_key = batch_def.ingredients.keys[&GRAVEL_BLUEPRINT.into()].clone(); let keys_dict = Dictionary::new( self.params.max_depth_mt_containers, batch_def.ingredients.keys.clone(), )?; + let empty_dict = Dictionary::new(self.params.max_depth_mt_containers, HashMap::new())?; + let mut k1_dict = empty_dict.clone(); + k1_dict.insert(&DUST_BLUEPRINT.into(), &dust_key)?; + Ok(st_custom!(self.ctx, - StoneDisassembleOutputs() = ( - // TODO WIP - todo!(), - HashOf(dust_hash, batch_hash, item_def.index.hash()), - HashOf(gravel_hash, batch_hash, item_def.index.hash()), - DictContains(keys_dict, item_def.index.name(), item_def.item_key()) - DictContains(keys_dict, item_def.index.name(), item_def.item_key()) + StoneDisassembleOutputs() = ( + HashOf(dust_hash, batch_hash, DUST_BLUEPRINT), + HashOf(gravel_hash, batch_hash, GRAVEL_BLUEPRINT), + DictInsert(k1_dict, empty_dict, DUST_BLUEPRINT, dust_key), + DictInsert(keys_dict, k1_dict, GRAVEL_BLUEPRINT, gravel_key) ))?) } fn st_stone_disassemble_inputs_outputs( @@ -243,20 +249,20 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, StoneDisassembleInputsOutputs() = ( st_stone_disassemble_inputs, - st_stone_disassemble_outputs, + st_stone_disassemble_outputs ))?) } fn st_stone_disassemble( &mut self, st_stone_disassemble_inputs_outputs: Statement, st_batch_def: Statement, + batch_def: BatchDef, ) -> anyhow::Result { Ok(st_custom!(self.ctx, StoneDisassemble() = ( st_batch_def, - // TODO - todo!(), - st_stone_disassemble_inputs_outputs, + DictContains(batch_def.ingredients.dict(self.params)?, "blueprint", format!("{DUST_BLUEPRINT}+{GRAVEL_BLUEPRINT}")), + st_stone_disassemble_inputs_outputs ))?) } @@ -265,14 +271,13 @@ impl<'a> CraftBuilder<'a> { item_def: ItemDef, st_item_def: Statement, st_stone_disassemble_inputs_outputs: Statement, - st_is_dust: Statement, ) -> anyhow::Result { Ok(st_custom!(self.ctx, - IsAxe() = ( + IsDust() = ( st_item_def, DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", DUST_BLUEPRINT), Equal(item_def.batch.work, EMPTY_VALUE), - st_stone_disassemble_inputs_outputs, + st_stone_disassemble_inputs_outputs ))?) } pub fn st_is_gravel( @@ -280,14 +285,13 @@ impl<'a> CraftBuilder<'a> { item_def: ItemDef, st_item_def: Statement, st_stone_disassemble_inputs_outputs: Statement, - st_is_gravel: Statement, ) -> anyhow::Result { Ok(st_custom!(self.ctx, - IsAxe() = ( + IsGravel() = ( st_item_def, DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", GRAVEL_BLUEPRINT), Equal(item_def.batch.work, EMPTY_VALUE), - st_stone_disassemble_inputs_outputs, + st_stone_disassemble_inputs_outputs ))?) } } diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 31cc46c..d0fe3e3 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -119,8 +119,7 @@ impl ItemPredicates { StoneDisassemble(inputs, private: batch, keys, ingredients, work) = AND( BatchDef(batch, ingredients, inputs, keys, work) - DictContains(ingredients, "blueprint", "dust") - DictContains(ingredients, "blueprint", "gravel") + DictContains(ingredients, "blueprint", "dust+gravel") StoneDisassembleInputsOutputs(inputs) ) From 161b2cec84ca84246af5967dd670c6ceffce47e3 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Fri, 5 Dec 2025 15:14:16 +0100 Subject: [PATCH 18/29] (wip) integrate multi-outputs crafting into the app_cli Joint work with @ax0 . Integrate multi-outputs crafting into the app_cli, update craftlib predicates & statements building. Co-authored-by: Ahmad --- app_cli/src/lib.rs | 135 ++++++++++++++++++++++++++++++++----- craftlib/src/constants.rs | 6 +- craftlib/src/item.rs | 58 ++++++++++------ craftlib/src/predicates.rs | 42 ++++++------ 4 files changed, 179 insertions(+), 62 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 8069094..a596a78 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -15,8 +15,9 @@ use common::{ }; use craftlib::{ constants::{ - AXE_BLUEPRINT, AXE_MINING_MAX, AXE_WORK, STONE_BLUEPRINT, STONE_MINING_MAX, WOOD_BLUEPRINT, - WOOD_MINING_MAX, WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, + AXE_BLUEPRINT, AXE_MINING_MAX, AXE_WORK, DUST_BLUEPRINT, DUST_MINING_MAX, DUST_WORK, + GEM_BLUEPRINT, STONE_BLUEPRINT, STONE_MINING_MAX, WOOD_BLUEPRINT, WOOD_MINING_MAX, + WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, }, item::{CraftBuilder, MiningRecipe}, powpod::PowPod, @@ -95,6 +96,7 @@ pub enum Recipe { Wood, Axe, WoodenAxe, + DustGem, } impl Recipe { pub fn list() -> Vec { @@ -106,12 +108,14 @@ impl Recipe { pub enum ProductionType { Mine, Craft, + Disassemble, } impl fmt::Display for ProductionType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let text = match self { ProductionType::Mine => "Mine", ProductionType::Craft => "Craft", + ProductionType::Disassemble => "Disassemble", }; write!(f, "{text}") } @@ -124,6 +128,7 @@ impl Recipe { Self::Wood => ProductionType::Mine, Self::Axe => ProductionType::Craft, Self::WoodenAxe => ProductionType::Craft, + Self::DustGem => ProductionType::Disassemble, } } } @@ -136,6 +141,7 @@ impl FromStr for Recipe { "wood" => Ok(Self::Wood), "axe" => Ok(Self::Axe), "wooden-axe" => Ok(Self::WoodenAxe), + "dust-gem" => Ok(Self::DustGem), _ => Err(anyhow!("unknown recipe {s}")), } } @@ -148,6 +154,7 @@ impl fmt::Display for Recipe { Self::Wood => write!(f, "wood"), Self::Axe => write!(f, "axe"), Self::WoodenAxe => write!(f, "wooden-axe"), + Self::DustGem => write!(f, "dust-gem"), } } } @@ -239,6 +246,23 @@ impl Helper { let st_item_def = item_builder.st_item_def(item_def.clone(), st_batch_def.clone())?; let st_item_key = item_builder.st_item_key(st_item_def.clone()).unwrap(); + builder.reveal(&st_item_key); // 0: Required for consuming via Nullifiers + builder.reveal(&st_batch_def); // 1: Required for committing via CommitCreation + builder.reveal(&st_item_def); // 2: Explicit item predicate + builder.reveal(&st_nullifiers); // 3: Required for committing via CommitCreation + + info!("Proving item_pod"); + let start = std::time::Instant::now(); + let item_key_pod = builder.prove(prover).unwrap(); + log::info!("[TIME] pod proving time: {:?}", start.elapsed()); + item_key_pod.pod.verify().unwrap(); + + // new pod + let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); + + builder.add_pod(all_items_in_batch_pod); + builder.add_pod(item_key_pod); + let mut craft_builder = CraftBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); let st_craft = match recipe { @@ -267,10 +291,22 @@ impl Helper { sts_input_craft[0].clone(), sts_input_craft[1].clone(), )?, + Recipe::DustGem => { + let st_stone_disassemble_inputs_outputs = craft_builder + .st_stone_disassemble_inputs_outputs( + sts_input_craft[0].clone(), + sts_input_craft[1].clone(), + item_def.batch.clone(), + )?; + let st_stone_disassemble = craft_builder.st_stone_disassemble( + st_stone_disassemble_inputs_outputs, + st_batch_def.clone(), + item_def.batch.clone(), + )?; + st_stone_disassemble + } }; - builder.add_pod(all_items_in_batch_pod); - builder.reveal(&st_item_key); // 0: Required for consuming via Nullifiers builder.reveal(&st_batch_def); // 1: Required for committing via CommitCreation builder.reveal(&st_item_def); // 2: Explicit item predicate @@ -278,13 +314,13 @@ impl Helper { builder.reveal(&st_craft); // 4: App layer predicate builder.reveal(&st_all_items_in_batch); // 5: Required for committing via CommitCreation - info!("Proving item_pod"); + info!("Proving final_pod"); let start = std::time::Instant::now(); - let item_key_pod = builder.prove(prover).unwrap(); + let final_pod = builder.prove(prover).unwrap(); log::info!("[TIME] pod proving time: {:?}", start.elapsed()); - item_key_pod.pod.verify().unwrap(); + final_pod.pod.verify().unwrap(); - Ok(item_key_pod) + Ok(final_pod) } fn make_commitment_pod( @@ -347,7 +383,7 @@ pub fn craft_item( )?; log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); let batch_def = BatchDef::new(ingredients_def.clone(), pow_pod.output); - (ItemDef::new(batch_def, index)?, vec![], Some(pow_pod)) + (vec![ItemDef::new(batch_def, index)?], vec![], Some(pow_pod)) } Recipe::Wood => { if !inputs.is_empty() { @@ -358,7 +394,7 @@ pub fn craft_item( .do_mining(params, keys, 0, WOOD_MINING_MAX)? .unwrap(); let batch_def = BatchDef::new(ingredients_def.clone(), WOOD_WORK); - (ItemDef::new(batch_def, index)?, vec![], None) + (vec![ItemDef::new(batch_def, index)?], vec![], None) } Recipe::Axe => { if inputs.len() != 2 { @@ -374,7 +410,11 @@ pub fn craft_item( .do_mining(params, keys, 0, AXE_MINING_MAX)? .unwrap(); let batch_def = BatchDef::new(ingredients_def.clone(), AXE_WORK); - (ItemDef::new(batch_def, index)?, vec![wood, stone], None) + ( + vec![ItemDef::new(batch_def, index)?], + vec![wood, stone], + None, + ) } Recipe::WoodenAxe => { if inputs.len() != 2 { @@ -390,7 +430,42 @@ pub fn craft_item( .do_mining(params, keys, 0, WOODEN_AXE_MINING_MAX)? .unwrap(); let batch_def = BatchDef::new(ingredients_def.clone(), WOODEN_AXE_WORK); - (ItemDef::new(batch_def, index)?, vec![wood1, wood2], None) + ( + vec![ItemDef::new(batch_def, index)?], + vec![wood1, wood2], + None, + ) + } + Recipe::DustGem => { + if inputs.len() != 2 { + bail!("{recipe} takes 2 inputs"); + } + let stone1 = load_item(&inputs[0])?; + let stone2 = load_item(&inputs[1])?; + let mining_recipe = MiningRecipe::new( + format!("{DUST_BLUEPRINT}+{GEM_BLUEPRINT}"), + &[stone1.def.item_hash(params)?, stone2.def.item_hash(params)?], + ); + let key_dust = rand_raw_value(); + let key_gem = rand_raw_value(); + let keys = [ + (DUST_BLUEPRINT.into(), key_dust.into()), + (GEM_BLUEPRINT.into(), key_gem.into()), + ] + .into_iter() + .collect(); + let ingredients_def = mining_recipe + .do_mining(params, keys, 0, DUST_MINING_MAX)? // NOTE: GEM_MINING_MAX unused + .unwrap(); + let batch_def = BatchDef::new(ingredients_def.clone(), DUST_WORK); // NOTE: GEM_WORK unused + ( + vec![ + ItemDef::new(batch_def.clone(), DUST_BLUEPRINT.into())?, + ItemDef::new(batch_def, GEM_BLUEPRINT.into())?, + ], + vec![stone1, stone2], + None, + ) } }; @@ -403,12 +478,36 @@ 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 crafted_item = CraftedItem { pod, def: item_def }; - let mut file = std::fs::File::create(output)?; - serde_json::to_writer(&mut file, &crafted_item)?; - info!("Stored crafted item mined with recipe {recipe} to {output:?}"); + // TODO: can optimize doing the loop inside 'make_item_pod' to reuse some + // batch computations + let pods: Vec<_> = item_def + .iter() + .map(|item_def_i| { + helper.make_item_pod( + recipe, + item_def_i.clone(), + input_item_pods.clone(), + pow_pod.clone(), + ) + }) + .collect::>>()?; + + // TODO instead of 'i', use 'index' (which is the blueprint (which takes + // values as 'dust', 'gem')) + for (i, pod) in pods.iter().enumerate() { + let crafted_item = CraftedItem { + pod: pod.clone(), + def: item_def[i].clone(), + }; + let sufix = if pods.len() > 1 { + format!("_{i}") + } else { + "".to_string() + }; + let mut file = std::fs::File::create(format! {"{}{sufix}", output.display()})?; + serde_json::to_writer(&mut file, &crafted_item)?; + info!("Stored crafted item mined with recipe {recipe} to {output:?}{sufix}"); + } Ok(()) } diff --git a/craftlib/src/constants.rs b/craftlib/src/constants.rs index e8bb18e..972f832 100644 --- a/craftlib/src/constants.rs +++ b/craftlib/src/constants.rs @@ -21,6 +21,6 @@ pub const DUST_BLUEPRINT: &str = "dust"; pub const DUST_MINING_MAX: u64 = 0x0020_0000_0000_0000; pub const DUST_WORK: RawValue = EMPTY_VALUE; -pub const GRAVEL_BLUEPRINT: &str = "gravel"; -pub const GRAVEL_MINING_MAX: u64 = 0x0020_0000_0000_0000; -pub const GRAVEL_WORK: RawValue = EMPTY_VALUE; +pub const GEM_BLUEPRINT: &str = "gem"; +pub const GEM_MINING_MAX: u64 = 0x0020_0000_0000_0000; +pub const GEM_WORK: RawValue = EMPTY_VALUE; diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index fd29b36..b5fd521 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -8,7 +8,7 @@ use pod2::middleware::{ use pod2utils::{macros::BuildContext, set, st_custom}; use crate::constants::{ - AXE_BLUEPRINT, DUST_BLUEPRINT, GRAVEL_BLUEPRINT, STONE_BLUEPRINT, WOOD_BLUEPRINT, + AXE_BLUEPRINT, DUST_BLUEPRINT, GEM_BLUEPRINT, STONE_BLUEPRINT, WOOD_BLUEPRINT, WOODEN_AXE_BLUEPRINT, }; @@ -17,6 +17,8 @@ use crate::constants::{ #[derive(Debug, Clone)] pub struct MiningRecipe { pub inputs: HashSet, + // Q: should it always be a string even for multiple outputs? for now we're + // joining the multiple strings with a '+' pub blueprint: String, } @@ -189,7 +191,7 @@ impl<'a> CraftBuilder<'a> { ))?) } - fn st_stone_disassemble_inputs( + pub fn st_stone_disassemble_inputs( &mut self, st_is_stone1: Statement, st_is_stone2: Statement, @@ -215,9 +217,10 @@ impl<'a> CraftBuilder<'a> { ) -> anyhow::Result { let batch_hash = batch_def.batch_hash(self.params)?; let dust_hash = hash_values(&[batch_hash.into(), DUST_BLUEPRINT.into()]); - let gravel_hash = hash_values(&[batch_hash.into(), GRAVEL_BLUEPRINT.into()]); + let gem_hash = hash_values(&[batch_hash.into(), GEM_BLUEPRINT.into()]); + dbg!(&batch_def.ingredients.keys); let dust_key = batch_def.ingredients.keys[&DUST_BLUEPRINT.into()].clone(); - let gravel_key = batch_def.ingredients.keys[&GRAVEL_BLUEPRINT.into()].clone(); + let gem_key = batch_def.ingredients.keys[&GEM_BLUEPRINT.into()].clone(); let keys_dict = Dictionary::new( self.params.max_depth_mt_containers, @@ -231,12 +234,12 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, StoneDisassembleOutputs() = ( HashOf(dust_hash, batch_hash, DUST_BLUEPRINT), - HashOf(gravel_hash, batch_hash, GRAVEL_BLUEPRINT), + HashOf(gem_hash, batch_hash, GEM_BLUEPRINT), DictInsert(k1_dict, empty_dict, DUST_BLUEPRINT, dust_key), - DictInsert(keys_dict, k1_dict, GRAVEL_BLUEPRINT, gravel_key) + DictInsert(keys_dict, k1_dict, GEM_BLUEPRINT, gem_key) ))?) } - fn st_stone_disassemble_inputs_outputs( + pub fn st_stone_disassemble_inputs_outputs( &mut self, st_is_stone1: Statement, st_is_stone2: Statement, @@ -252,7 +255,7 @@ impl<'a> CraftBuilder<'a> { st_stone_disassemble_outputs ))?) } - fn st_stone_disassemble( + pub fn st_stone_disassemble( &mut self, st_stone_disassemble_inputs_outputs: Statement, st_batch_def: Statement, @@ -261,7 +264,7 @@ impl<'a> CraftBuilder<'a> { Ok(st_custom!(self.ctx, StoneDisassemble() = ( st_batch_def, - DictContains(batch_def.ingredients.dict(self.params)?, "blueprint", format!("{DUST_BLUEPRINT}+{GRAVEL_BLUEPRINT}")), + DictContains(batch_def.ingredients.dict(self.params)?, "blueprint", format!("{DUST_BLUEPRINT}+{GEM_BLUEPRINT}")), st_stone_disassemble_inputs_outputs ))?) } @@ -269,29 +272,44 @@ impl<'a> CraftBuilder<'a> { pub fn st_is_dust( &mut self, item_def: ItemDef, - st_item_def: Statement, - st_stone_disassemble_inputs_outputs: Statement, + st_item_def: Statement, // TODO rm + st_stone_disassemble: Statement, ) -> anyhow::Result { + let batch_hash = item_def.batch.batch_hash(self.params)?; + let dust_hash = hash_values(&[batch_hash.into(), DUST_BLUEPRINT.into()]); + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + item_def.batch.ingredients.keys.clone(), + )?; + let dust_key = item_def.batch.ingredients.keys[&DUST_BLUEPRINT.into()].clone(); Ok(st_custom!(self.ctx, IsDust() = ( - st_item_def, - DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", DUST_BLUEPRINT), + HashOf(dust_hash, batch_hash, DUST_BLUEPRINT), + DictContains(keys_dict, DUST_BLUEPRINT, dust_key), Equal(item_def.batch.work, EMPTY_VALUE), - st_stone_disassemble_inputs_outputs + st_stone_disassemble ))?) } - pub fn st_is_gravel( + pub fn st_is_gem( &mut self, item_def: ItemDef, st_item_def: Statement, - st_stone_disassemble_inputs_outputs: Statement, + st_stone_disassemble: Statement, ) -> anyhow::Result { + let batch_hash = item_def.batch.batch_hash(self.params)?; + let gem_hash = hash_values(&[batch_hash.into(), GEM_BLUEPRINT.into()]); + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + item_def.batch.ingredients.keys.clone(), + )?; + let gem_key = item_def.batch.ingredients.keys[&GEM_BLUEPRINT.into()].clone(); + Ok(st_custom!(self.ctx, - IsGravel() = ( - st_item_def, - DictContains(item_def.batch.ingredients.dict(self.params)?, "blueprint", GRAVEL_BLUEPRINT), + IsGem() = ( + HashOf(gem_hash, batch_hash, GEM_BLUEPRINT), + DictContains(keys_dict, GEM_BLUEPRINT, gem_key), Equal(item_def.batch.work, EMPTY_VALUE), - st_stone_disassemble_inputs_outputs + st_stone_disassemble ))?) } } diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index d0fe3e3..637b6be 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -87,7 +87,7 @@ impl ItemPredicates { // multi-output related predicates: // (simplified version without tools & durability) - // disassemble 2 Stones into 2 outputs: Dust,Gravel. + // disassemble 2 Stones into 2 outputs: Dust,Gem. // inputs: 2 Stones StoneDisassembleInputs(inputs, private: s1, stone1, stone2) = AND( @@ -99,47 +99,47 @@ impl ItemPredicates { IsStone(stone2) ) - // outputs: 1 Dust, 1 Gravel - StoneDisassembleOutputs(inputs, - private: batch, keys, k1, dust, gravel, _dust_key, _gravel_key) = AND( + // outputs: 1 Dust, 1 Gem + StoneDisassembleOutputs(batch, keys, + private: k1, dust, gem, _dust_key, _gem_key) = AND( HashOf(dust, batch, "dust") - HashOf(gravel, batch, "gravel") + HashOf(gem, batch, "gem") DictInsert(k1, {}, "dust", _dust_key) - DictInsert(keys, k1, "gravel", _gravel_key) + DictInsert(keys, k1, "gem", _gem_key) ) "#, r#" // helper to have a single predicate for the inputs & outputs - StoneDisassembleInputsOutputs(inputs) = AND ( + StoneDisassembleInputsOutputs(inputs, batch, keys) = AND ( StoneDisassembleInputs(inputs) - StoneDisassembleOutputs(inputs) + StoneDisassembleOutputs(batch, keys) ) - StoneDisassemble(inputs, - private: batch, keys, ingredients, work) = AND( + StoneDisassemble(batch, keys, work, + private: inputs, ingredients) = AND( BatchDef(batch, ingredients, inputs, keys, work) - DictContains(ingredients, "blueprint", "dust+gravel") + DictContains(ingredients, "blueprint", "dust+gem") - StoneDisassembleInputsOutputs(inputs) + StoneDisassembleInputsOutputs(inputs, batch, keys) ) // can only obtain Dust from disassembling 2 stones - IsDust(item, private: ingredients, inputs, key, work) = AND( - ItemDef(item, ingredients, inputs, key, work) - DictContains(ingredients, "blueprint", "dust") + IsDust(item, private: batch, ingredients, inputs, keys, key, work) = AND( + HashOf(item, batch, "dust") + DictContains(keys, "dust", key) Equal(work, {}) - StoneDisassemble(inputs) + StoneDisassemble(batch, keys, work) ) - // can only obtain Gravel from disassembling 2 stones - IsGravel(item, private: ingredients, inputs, key, work, dust, gravel) = AND( - ItemDef(item, ingredients, inputs, key, work) - DictContains(ingredients, "blueprint", "gravel") + // can only obtain Gem from disassembling 2 stones + IsGem(item, private: batch, ingredients, inputs, keys, key, work) = AND( + HashOf(item, batch, "gem") + DictContains(keys, "gem", key) Equal(work, {}) - StoneDisassemble(inputs) + StoneDisassemble(batch, keys, work) ) "#, ]; From 1d6d4de5c271b3dc9d639a328c7cb506c8a0bdfb Mon Sep 17 00:00:00 2001 From: arnaucube Date: Fri, 5 Dec 2025 19:25:17 +0100 Subject: [PATCH 19/29] add ui elems for multi-outputs DisassembleStone into Dust+Gem (not yet connected to pod logic) --- README.md | 2 ++ app_gui/assets/dust.png | Bin 0 -> 644 bytes app_gui/assets/gem.png | Bin 0 -> 584 bytes app_gui/src/crafting.rs | 62 +++++++++++++++++++++++++++++++++++++++- app_gui/src/main.rs | 4 +++ shell.nix | 25 ++++++++++++++++ 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 app_gui/assets/dust.png create mode 100644 app_gui/assets/gem.png create mode 100644 shell.nix diff --git a/README.md b/README.md index 127f3a0..9ae0ee2 100644 --- a/README.md +++ b/README.md @@ -108,3 +108,5 @@ The files in `app_gui/assets` are distributed under the Flaticon License: - `uranium.png`: Uranium icons created by Freepik - Flaticon - `tomato.png`: Tomato icons created by PixelPerfect - Flaticon - `steel-sword.png`: Steel sword icons created by Assia Benkerroum - Flaticon +- `dust.png`: Dust icon created by Freepik +- `gem.png`: Dust icon created by Freepik diff --git a/app_gui/assets/dust.png b/app_gui/assets/dust.png new file mode 100644 index 0000000000000000000000000000000000000000..f6abc56f3fe48600a7c4b4c582f062bd2d56391d GIT binary patch literal 644 zcmV-~0(jf}fx_qSsmGy9OA7#O0FnSeHx4A72iZx^!mJE=tpNQD)XuFU!=o zcvkl%*3u<$r%a}{DwMC++;JtsGc|M&D__&w4_-xIeEPAmK=Fq;A#lt-*2S8Xx~~6O z&sM61vUnh$%MtxOdyV;`tXFIG{o$!W2sv3*R819S)i~m{4JU-{vjhMi0OmhI(*V2x z$^bHLnnVF`0hj@>8aVE8pFHARayxCqtIL<~euyT=17r55*DvtreJ)FgAP9FlTX_gz z0>A)(H_b25r>6&>L}tC=QU8!Zrx_$t+4!SpZ~LxAMob2pLJ!voVQUBXU;d1JiDeh# z>Ei2Vh$KmpS7*mPqZL_`B&DV_{pq!;Ue{CtZ77P0xS5yo0PeJF4giY)0suUXUbD_O ebWi)zF2`Tgt+{xXc>GiV0000|IK^(dS{{ck=JE()BgV;!m*rrLEnB<<%A-T=X4~4$)!FkU) z@AG|!Bl`5^r5&Zz_1{aq=a=tlJLI6g)baVQLJRU!Zg zz7`qj*j+r%$Na-QMeVTQ$HXz5;1qy7O0@u-BME?G<3y5_Gkjp&jMdnYQSQj`9+3b} z0_E}4e$!-P3&NiD27YP`zcWt5gGKLbTA!T9b-)3Xw@KKr_XE`yA=)J7r*rz~0ZgW~ z2HXb-0%G;*AfVBLz_r&h?#*QN3L@WlCe1_O1weUc5KwD&!ti=J%cF3AA0WvpaTX*# z)T+>GcEf>TjR1i4z~_b}8vp=YxT$(e%po?TQ`8$sP;_E$_O|w-nH~VZxdm-H$Hl!} z%GT~s$X literal 0 HcmV?d00001 diff --git a/app_gui/src/crafting.rs b/app_gui/src/crafting.rs index d6f6b28..aa4af9c 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -20,6 +20,7 @@ pub enum Process { Wood, Axe, WoodenAxe, + DisassembleStone, Mock(&'static str), } @@ -97,6 +98,63 @@ IsWoodenAxe(item, private: ingredients, inputs, key, work, s1, wood1, wood2) = A // prove the ingredients are correct. IsWood(wood1) IsWood(wood2) +)"#, + ..Default::default() + }; + static ref DISASSEMBLE_STONE_DATA: ProcessData = ProcessData { + description: "Disassemble Stone into Dust+Gem.", + input_ingredients: &["Stone", "Stone"], + outputs: &["Dust", "Gem"], + predicate: r#" +// inputs: 2 Stones +StoneDisassembleInputs(inputs, private: s1, stone1, stone2) = AND( + SetInsert(s1, {}, stone1) + SetInsert(inputs, s1, stone2) + + // prove the ingredients are correct + IsStone(stone1) + IsStone(stone2) +) + +// outputs: 1 Dust, 1 Gem +StoneDisassembleOutputs(batch, keys, + private: k1, dust, gem, _dust_key, _gem_key) = AND( + HashOf(dust, batch, "dust") + HashOf(gem, batch, "gem") + DictInsert(k1, {}, "dust", _dust_key) + DictInsert(keys, k1, "gem", _gem_key) +) + +// helper to have a single predicate for the inputs & outputs +StoneDisassembleInputsOutputs(inputs, batch, keys) = AND ( + StoneDisassembleInputs(inputs) + StoneDisassembleOutputs(batch, keys) +) + +StoneDisassemble(batch, keys, work, + private: inputs, ingredients) = AND( + BatchDef(batch, ingredients, inputs, keys, work) + DictContains(ingredients, "blueprint", "dust+gem") + + StoneDisassembleInputsOutputs(inputs, batch, keys) +) + +// can only obtain Dust from disassembling 2 stones +IsDust(item, private: batch, ingredients, inputs, keys, key, work) = AND( + HashOf(item, batch, "dust") + DictContains(keys, "dust", key) + Equal(work, {}) + + StoneDisassemble(batch, keys, work) +) + +// can only obtain Gem from disassembling 2 stones +IsGem(item, private: batch, ingredients, inputs, keys, key, work) = AND( + HashOf(item, batch, "gem") + DictContains(keys, "gem", key) + Equal(work, {}) + + StoneDisassemble(batch, keys, work) )"#, ..Default::default() }; @@ -400,6 +458,7 @@ impl Process { Self::Wood => Some(Recipe::Wood), Self::Axe => Some(Recipe::Axe), Self::WoodenAxe => Some(Recipe::WoodenAxe), + Self::DisassembleStone => Some(Recipe::DustGem), Self::Mock(_) => None, } } @@ -410,6 +469,7 @@ impl Process { Self::Wood => &WOOD_DATA, Self::Axe => &AXE_DATA, Self::WoodenAxe => &WOODEN_AXE_DATA, + Self::DisassembleStone => &DISASSEMBLE_STONE_DATA, Self::Mock("Destroy") => &DESTROY_DATA, Self::Mock("Tomato") => &TOMATO_DATA, Self::Mock("Steel Sword") => &STEEL_SWORD_DATA, @@ -461,7 +521,7 @@ impl Verb { ], Self::Craft => vec![Axe, WoodenAxe, Mock("Tree House")], Self::Produce => vec![Mock("Tomato"), Mock("Steel Sword")], - Self::Disassemble => vec![Mock("Disassemble-H2O")], + Self::Disassemble => vec![DisassembleStone, Mock("Disassemble-H2O")], Self::Destroy => vec![Mock("Destroy")], } } diff --git a/app_gui/src/main.rs b/app_gui/src/main.rs index 68c6457..62fb284 100644 --- a/app_gui/src/main.rs +++ b/app_gui/src/main.rs @@ -280,6 +280,10 @@ impl App { egui::include_image!("../assets/tomato.png") } else if name.starts_with("Steel Sword") { egui::include_image!("../assets/steel-sword.png") + } else if name.starts_with("Dust") { + egui::include_image!("../assets/dust.png") + } else if name.starts_with("Gem") { + egui::include_image!("../assets/gem.png") } else { egui::include_image!("../assets/empty.png") }) diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..bb2fa72 --- /dev/null +++ b/shell.nix @@ -0,0 +1,25 @@ +{ pkgs ? import { } }: + +let + dlopenLibraries = with pkgs; [ + libxkbcommon + libxkbcommon.dev + + wayland + wayland.dev + wayland-protocols + sway + mesa + mesa-gl-headers + egl-wayland + libGL + ]; +in pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + rustup + mesa + mesa-gl-headers + ]; + + env.RUSTFLAGS = "-C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath dlopenLibraries}"; +} From 1bb36f3e9ea91f5b5557ca5435919ac5b32b380d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 8 Dec 2025 16:04:54 +1000 Subject: [PATCH 20/29] Fix CLI --- app_cli/src/lib.rs | 60 +++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index a596a78..d4c58e4 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -209,36 +209,46 @@ impl Helper { let mut sts_input_item_key = Vec::new(); let mut sts_input_craft = Vec::new(); + + // TODO: Use recursion here to be able to make use of more than 2 input PODs. for input_item_pod in input_item_pods { let st_item_key = input_item_pod.pod.pub_statements()[0].clone(); sts_input_item_key.push(st_item_key); let st_craft = input_item_pod.pod.pub_statements()[4].clone(); sts_input_craft.push(st_craft); - item_builder.ctx.builder.add_pod(input_item_pod); + item_builder.ctx.builder.add_pod(input_item_pod); } - let (st_nullifiers, _nullifiers) = if sts_input_item_key.is_empty() { - item_builder.st_nullifiers(sts_input_item_key).unwrap() - } else { - // The default params don't have enough custom statement verifications to fit - // everything in a single pod, so we split it in two. - let (st_nullifiers, nullifiers) = - item_builder.st_nullifiers(sts_input_item_key).unwrap(); - item_builder.ctx.builder.reveal(&st_nullifiers); - // Propagate sts_input_craft for use in st_craft - for st_input_craft in &sts_input_craft { - item_builder.ctx.builder.reveal(st_input_craft); - } + // Prove and proceed. + sts_input_item_key.iter().chain(sts_input_craft.iter()).for_each(|st| item_builder.ctx.builder.reveal(st)); + info!("Proving input_item_pod..."); + let input_item_pod = item_builder.ctx.builder.prove(prover)?; - info!("Proving nullifiers_pod..."); - let nullifiers_pod = builder.prove(prover).unwrap(); - nullifiers_pod.pod.verify().unwrap(); - builder = MainPodBuilder::new(&self.params, &self.vd_set); - item_builder = - ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); - item_builder.ctx.builder.add_pod(nullifiers_pod); - (st_nullifiers, nullifiers) - }; + // Take care of nullifiers. + builder = MainPodBuilder::new(&self.params, &self.vd_set); + item_builder = + ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); + + item_builder.ctx.builder.add_pod(input_item_pod.clone()); + item_builder.ctx.builder.add_pod(all_items_in_batch_pod.clone()); + + // By the way, the default params don't have enough custom statement verifications + // to fit everything in a single pod, hence all the splits. + let (st_nullifiers, _nullifiers) = + item_builder.st_nullifiers(sts_input_item_key).unwrap(); + item_builder.ctx.builder.reveal(&st_nullifiers); + all_items_in_batch_pod.public_statements.iter().for_each(|st| item_builder.ctx.builder.reveal(st)); + + info!("Proving nullifiers_et_al_pod..."); + let nullifiers_et_al_pod = builder.prove(prover).unwrap(); + nullifiers_et_al_pod.pod.verify().unwrap(); + + // Start afresh for item POD. + builder = MainPodBuilder::new(&self.params, &self.vd_set); + item_builder = + ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); + item_builder.ctx.builder.add_pod(nullifiers_et_al_pod); + item_builder.ctx.builder.add_pod(input_item_pod.clone()); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); @@ -250,7 +260,8 @@ impl Helper { builder.reveal(&st_batch_def); // 1: Required for committing via CommitCreation builder.reveal(&st_item_def); // 2: Explicit item predicate builder.reveal(&st_nullifiers); // 3: Required for committing via CommitCreation - + builder.reveal(&st_all_items_in_batch); // 4: Required for committing via CommitCreation + info!("Proving item_pod"); let start = std::time::Instant::now(); let item_key_pod = builder.prove(prover).unwrap(); @@ -260,13 +271,14 @@ impl Helper { // new pod let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); - builder.add_pod(all_items_in_batch_pod); builder.add_pod(item_key_pod); + builder.add_pod(input_item_pod); let mut craft_builder = CraftBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); let st_craft = match recipe { Recipe::Stone => { + craft_builder.ctx.builder.input_pods.pop(); // 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(); From 9631c5c9a2192e85c61833ec1e1426622d3eec73 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 8 Dec 2025 16:05:21 +1000 Subject: [PATCH 21/29] Fix mock mode toggle --- app_gui/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_gui/src/main.rs b/app_gui/src/main.rs index 62fb284..14af0b5 100644 --- a/app_gui/src/main.rs +++ b/app_gui/src/main.rs @@ -229,7 +229,7 @@ impl App { // Mock toggle taken into account. for verb in Verb::list() .into_iter() - .filter(|v| self.mock_mode || v == &Verb::Gather || v == &Verb::Craft) + .filter(|v| self.mock_mode || v == &Verb::Gather || v == &Verb::Craft || v == &Verb::Disassemble) { if ui .selectable_label(Some(verb) == self.crafting.selected_verb, verb.as_str()) From f853b360161de003add5c5ae4148093c488e6836 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 9 Dec 2025 10:13:19 +1000 Subject: [PATCH 22/29] Clippy --- app_cli/src/lib.rs | 31 +++++++++++++++++++------------ app_gui/src/main.rs | 10 ++++++---- commitlib/src/lib.rs | 5 ++++- craftlib/src/item.rs | 44 -------------------------------------------- 4 files changed, 29 insertions(+), 61 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index d4c58e4..a37cb89 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -216,11 +216,14 @@ impl Helper { sts_input_item_key.push(st_item_key); let st_craft = input_item_pod.pod.pub_statements()[4].clone(); sts_input_craft.push(st_craft); - item_builder.ctx.builder.add_pod(input_item_pod); + item_builder.ctx.builder.add_pod(input_item_pod); } // Prove and proceed. - sts_input_item_key.iter().chain(sts_input_craft.iter()).for_each(|st| item_builder.ctx.builder.reveal(st)); + sts_input_item_key + .iter() + .chain(sts_input_craft.iter()) + .for_each(|st| item_builder.ctx.builder.reveal(st)); info!("Proving input_item_pod..."); let input_item_pod = item_builder.ctx.builder.prove(prover)?; @@ -230,15 +233,20 @@ impl Helper { ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); item_builder.ctx.builder.add_pod(input_item_pod.clone()); - item_builder.ctx.builder.add_pod(all_items_in_batch_pod.clone()); - + item_builder + .ctx + .builder + .add_pod(all_items_in_batch_pod.clone()); + // By the way, the default params don't have enough custom statement verifications // to fit everything in a single pod, hence all the splits. - let (st_nullifiers, _nullifiers) = - item_builder.st_nullifiers(sts_input_item_key).unwrap(); + let (st_nullifiers, _nullifiers) = item_builder.st_nullifiers(sts_input_item_key).unwrap(); item_builder.ctx.builder.reveal(&st_nullifiers); - all_items_in_batch_pod.public_statements.iter().for_each(|st| item_builder.ctx.builder.reveal(st)); - + all_items_in_batch_pod + .public_statements + .iter() + .for_each(|st| item_builder.ctx.builder.reveal(st)); + info!("Proving nullifiers_et_al_pod..."); let nullifiers_et_al_pod = builder.prove(prover).unwrap(); nullifiers_et_al_pod.pod.verify().unwrap(); @@ -261,7 +269,7 @@ impl Helper { builder.reveal(&st_item_def); // 2: Explicit item predicate builder.reveal(&st_nullifiers); // 3: Required for committing via CommitCreation builder.reveal(&st_all_items_in_batch); // 4: Required for committing via CommitCreation - + info!("Proving item_pod"); let start = std::time::Instant::now(); let item_key_pod = builder.prove(prover).unwrap(); @@ -310,12 +318,11 @@ impl Helper { sts_input_craft[1].clone(), item_def.batch.clone(), )?; - let st_stone_disassemble = craft_builder.st_stone_disassemble( + craft_builder.st_stone_disassemble( st_stone_disassemble_inputs_outputs, st_batch_def.clone(), item_def.batch.clone(), - )?; - st_stone_disassemble + )? } }; diff --git a/app_gui/src/main.rs b/app_gui/src/main.rs index 14af0b5..b2a3b88 100644 --- a/app_gui/src/main.rs +++ b/app_gui/src/main.rs @@ -227,10 +227,12 @@ impl App { ui.horizontal(|ui| { ui.set_min_height(32.0); // Mock toggle taken into account. - for verb in Verb::list() - .into_iter() - .filter(|v| self.mock_mode || v == &Verb::Gather || v == &Verb::Craft || v == &Verb::Disassemble) - { + for verb in Verb::list().into_iter().filter(|v| { + self.mock_mode + || v == &Verb::Gather + || v == &Verb::Craft + || v == &Verb::Disassemble + }) { if ui .selectable_label(Some(verb) == self.crafting.selected_verb, verb.as_str()) .clicked() diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index 68cc952..e7bdea9 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -450,13 +450,16 @@ mod tests { let item_hash = item_def.item_hash(params).unwrap(); created_items.insert(&Value::from(item_hash)).unwrap(); let st_batch_def = item_builder.st_batch_def(batch_def.clone()).unwrap(); - + let st_all_items_in_batch = item_builder + .st_all_items_in_batch(batch_def.clone()) + .unwrap(); let _st_commit_creation = item_builder .st_commit_creation( batch_def.clone(), st_nullifiers, created_items.clone(), st_batch_def, + st_all_items_in_batch, ) .unwrap(); diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index b5fd521..f7e3108 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -268,50 +268,6 @@ impl<'a> CraftBuilder<'a> { st_stone_disassemble_inputs_outputs ))?) } - - pub fn st_is_dust( - &mut self, - item_def: ItemDef, - st_item_def: Statement, // TODO rm - st_stone_disassemble: Statement, - ) -> anyhow::Result { - let batch_hash = item_def.batch.batch_hash(self.params)?; - let dust_hash = hash_values(&[batch_hash.into(), DUST_BLUEPRINT.into()]); - let keys_dict = Dictionary::new( - self.params.max_depth_mt_containers, - item_def.batch.ingredients.keys.clone(), - )?; - let dust_key = item_def.batch.ingredients.keys[&DUST_BLUEPRINT.into()].clone(); - Ok(st_custom!(self.ctx, - IsDust() = ( - HashOf(dust_hash, batch_hash, DUST_BLUEPRINT), - DictContains(keys_dict, DUST_BLUEPRINT, dust_key), - Equal(item_def.batch.work, EMPTY_VALUE), - st_stone_disassemble - ))?) - } - pub fn st_is_gem( - &mut self, - item_def: ItemDef, - st_item_def: Statement, - st_stone_disassemble: Statement, - ) -> anyhow::Result { - let batch_hash = item_def.batch.batch_hash(self.params)?; - let gem_hash = hash_values(&[batch_hash.into(), GEM_BLUEPRINT.into()]); - let keys_dict = Dictionary::new( - self.params.max_depth_mt_containers, - item_def.batch.ingredients.keys.clone(), - )?; - let gem_key = item_def.batch.ingredients.keys[&GEM_BLUEPRINT.into()].clone(); - - Ok(st_custom!(self.ctx, - IsGem() = ( - HashOf(gem_hash, batch_hash, GEM_BLUEPRINT), - DictContains(keys_dict, GEM_BLUEPRINT, gem_key), - Equal(item_def.batch.work, EMPTY_VALUE), - st_stone_disassemble - ))?) - } } #[cfg(test)] From 6a1dbbc2d98f083655e9acebeb9ee52a5bf03cde Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 9 Dec 2025 11:41:13 +1000 Subject: [PATCH 23/29] Code review --- app_cli/src/lib.rs | 14 +++++++----- commitlib/src/lib.rs | 6 ++--- common/src/payload.rs | 3 ++- craftlib/src/item.rs | 48 +++++++++++++++++++++++++++++++++++----- shell.nix | 2 +- synchronizer/src/main.rs | 2 +- 6 files changed, 58 insertions(+), 17 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index a37cb89..2a4a714 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -513,19 +513,21 @@ pub fn craft_item( // TODO instead of 'i', use 'index' (which is the blueprint (which takes // values as 'dust', 'gem')) - for (i, pod) in pods.iter().enumerate() { + for (def, pod) in std::iter::zip(item_def, pods.iter()) { + let i = def.index.clone(); let crafted_item = CraftedItem { pod: pod.clone(), - def: item_def[i].clone(), + def, }; - let sufix = if pods.len() > 1 { - format!("_{i}") + let suffix = if pods.len() > 1 { + format!("_{}", i.name()) } else { "".to_string() }; - let mut file = std::fs::File::create(format! {"{}{sufix}", output.display()})?; + let filename = format! {"{}{suffix}", output.display()}; + let mut file = std::fs::File::create(&filename)?; serde_json::to_writer(&mut file, &crafted_item)?; - info!("Stored crafted item mined with recipe {recipe} to {output:?}{sufix}"); + info!("Stored crafted item mined with recipe {recipe} to {filename}"); } Ok(()) diff --git a/commitlib/src/lib.rs b/commitlib/src/lib.rs index e7bdea9..e61433f 100644 --- a/commitlib/src/lib.rs +++ b/commitlib/src/lib.rs @@ -205,9 +205,9 @@ impl<'a> ItemBuilder<'a> { // Build ItemInBatch(item, batch) Ok(st_custom!(self.ctx, - ItemInBatch() = ( - HashOf(item_hash, batch_hash, item_def.index.hash()), - DictContains(keys_dict, item_def.index.name(), item_def.item_key()) + ItemInBatch() = ( + HashOf(item_hash, batch_hash, item_def.index.hash()), + DictContains(keys_dict, item_def.index.name(), item_def.item_key()) ))?) } diff --git a/common/src/payload.rs b/common/src/payload.rs index ba34ba2..f54c046 100644 --- a/common/src/payload.rs +++ b/common/src/payload.rs @@ -52,7 +52,7 @@ impl Payload { .expect("vec write"); self.proof.write_bytes(&mut buffer); write_elems(&mut buffer, &self.created_items_root.0); - // TODO: What are the constraints on these lengths apart from the type casts below? + assert!(self.items.len() < 256); buffer .write_all(&(self.items.len() as u8).to_le_bytes()) @@ -60,6 +60,7 @@ impl Payload { for item in &self.items { write_elems(&mut buffer, &item.0); } + assert!(self.nullifiers.len() < 256); buffer .write_all(&(self.nullifiers.len() as u8).to_le_bytes()) diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index f7e3108..238d41b 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -120,11 +120,7 @@ impl<'a> CraftBuilder<'a> { let mut s1 = empty_set.clone(); s1.insert(&wood).unwrap(); let mut inputs = s1.clone(); - println!("1"); - println!("{wood}"); - println!("{stone}"); inputs.insert(&stone).unwrap(); - println!("2"); Ok(st_custom!(self.ctx, AxeInputs() = ( SetInsert(s1, empty_set, wood), @@ -218,7 +214,6 @@ impl<'a> CraftBuilder<'a> { let batch_hash = batch_def.batch_hash(self.params)?; let dust_hash = hash_values(&[batch_hash.into(), DUST_BLUEPRINT.into()]); let gem_hash = hash_values(&[batch_hash.into(), GEM_BLUEPRINT.into()]); - dbg!(&batch_def.ingredients.keys); let dust_key = batch_def.ingredients.keys[&DUST_BLUEPRINT.into()].clone(); let gem_key = batch_def.ingredients.keys[&GEM_BLUEPRINT.into()].clone(); @@ -255,6 +250,49 @@ impl<'a> CraftBuilder<'a> { st_stone_disassemble_outputs ))?) } + pub fn st_is_dust( + &mut self, + item_def: ItemDef, + st_stone_disassemble: Statement, + ) -> anyhow::Result { + let batch_hash = item_def.batch.batch_hash(self.params)?; + let dust_hash = hash_values(&[batch_hash.into(), DUST_BLUEPRINT.into()]); + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + item_def.batch.ingredients.keys.clone(), + )?; + let dust_key = item_def.batch.ingredients.keys[&DUST_BLUEPRINT.into()].clone(); + Ok(st_custom!(self.ctx, + IsDust() = ( + HashOf(dust_hash, batch_hash, DUST_BLUEPRINT), + DictContains(keys_dict, DUST_BLUEPRINT, dust_key), + Equal(item_def.batch.work, EMPTY_VALUE), + st_stone_disassemble + ))?) + } + + pub fn st_is_gem( + &mut self, + item_def: ItemDef, + st_stone_disassemble: Statement, + ) -> anyhow::Result { + let batch_hash = item_def.batch.batch_hash(self.params)?; + let gem_hash = hash_values(&[batch_hash.into(), GEM_BLUEPRINT.into()]); + let keys_dict = Dictionary::new( + self.params.max_depth_mt_containers, + item_def.batch.ingredients.keys.clone(), + )?; + let gem_key = item_def.batch.ingredients.keys[&GEM_BLUEPRINT.into()].clone(); + + Ok(st_custom!(self.ctx, + IsGem() = ( + HashOf(gem_hash, batch_hash, GEM_BLUEPRINT), + DictContains(keys_dict, GEM_BLUEPRINT, gem_key), + Equal(item_def.batch.work, EMPTY_VALUE), + st_stone_disassemble + ))?) + } + pub fn st_stone_disassemble( &mut self, st_stone_disassemble_inputs_outputs: Statement, diff --git a/shell.nix b/shell.nix index bb2fa72..9f1036a 100644 --- a/shell.nix +++ b/shell.nix @@ -8,7 +8,7 @@ let wayland wayland.dev wayland-protocols - sway + emacs mesa mesa-gl-headers egl-wayland diff --git a/synchronizer/src/main.rs b/synchronizer/src/main.rs index a42b5c0..9b934d4 100644 --- a/synchronizer/src/main.rs +++ b/synchronizer/src/main.rs @@ -380,7 +380,7 @@ impl Node { info!("Valid do_blob at slot {}, blob_index {}!", slot, blob.index); } Err(e) => { - error!("Ignoring blob due: Invalid do_blob: {:?}", e); + info!("Ignoring blob due to invalid do_blob: {:?}", e); continue; } }; From 4c60ce2550aa010e8cc061dc47416babc8c402de Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 9 Dec 2025 14:38:24 +1000 Subject: [PATCH 24/29] Fix GUI flow --- app_cli/src/lib.rs | 36 ++++++++++++++++++++++-------------- app_gui/src/crafting.rs | 17 +++++++++++++++-- app_gui/src/main.rs | 14 +++++++++----- app_gui/src/task_system.rs | 31 ++++++++++++++++++------------- synchronizer/src/main.rs | 2 +- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 2a4a714..4edba14 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -377,7 +377,7 @@ pub fn craft_item( recipe: Recipe, output: &Path, inputs: &[PathBuf], -) -> anyhow::Result<()> { +) -> anyhow::Result> { let vd_set = DEFAULT_VD_SET.clone(); let key = rand_raw_value(); let index = Key::new(format!("{recipe}")); @@ -511,26 +511,34 @@ pub fn craft_item( }) .collect::>>()?; - // TODO instead of 'i', use 'index' (which is the blueprint (which takes - // values as 'dust', 'gem')) - for (def, pod) in std::iter::zip(item_def, pods.iter()) { - let i = def.index.clone(); + let filenames: Vec = item_def + .iter() + .map(|ItemDef { index, batch: _ }| { + let suffix = if pods.len() > 1 { + format!("_{}", index.name()) + } else { + "".to_string() + }; + format! {"{}{suffix}", output.display()}.into() + }) + .collect(); + + for (filename, (def, pod)) in + std::iter::zip(filenames.iter(), std::iter::zip(item_def, pods.iter())) + { let crafted_item = CraftedItem { pod: pod.clone(), def, }; - let suffix = if pods.len() > 1 { - format!("_{}", i.name()) - } else { - "".to_string() - }; - let filename = format! {"{}{suffix}", output.display()}; - let mut file = std::fs::File::create(&filename)?; + let mut file = std::fs::File::create(filename)?; serde_json::to_writer(&mut file, &crafted_item)?; - info!("Stored crafted item mined with recipe {recipe} to {filename}"); + info!( + "Stored crafted item mined with recipe {recipe} to {}", + filename.display() + ); } - Ok(()) + Ok(filenames) } pub async fn commit_item(params: &Params, cfg: &Config, input: &Path) -> anyhow::Result<()> { diff --git a/app_gui/src/crafting.rs b/app_gui/src/crafting.rs index aa4af9c..ecd677a 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -550,7 +550,7 @@ pub struct Crafting { // Input index to item index pub input_items: HashMap, pub output_filename: String, - pub craft_result: Option>, + pub craft_result: Option>>, pub commit_result: Option>, } @@ -811,8 +811,21 @@ impl App { if button_commit_clicked { if self.crafting.output_filename.is_empty() { self.crafting.commit_result = Some(Err(anyhow!("Please enter a filename."))); + } else if self.crafting.craft_result.is_none() + || self.crafting.craft_result.as_ref().unwrap().is_err() + { + self.crafting.commit_result = Some(Err(anyhow!( + "The item(s) must first be successfully crafted." + ))); } else { - let input = Path::new(&self.cfg.pods_path).join(&self.crafting.output_filename); + let first_output_filename = &self + .crafting + .craft_result + .as_ref() + .unwrap() + .as_ref() + .unwrap()[0]; + let input = Path::new(&self.cfg.pods_path).join(first_output_filename); self.task_req_tx .send(Request::Commit { params: self.params.clone(), diff --git a/app_gui/src/main.rs b/app_gui/src/main.rs index b2a3b88..bb00afb 100644 --- a/app_gui/src/main.rs +++ b/app_gui/src/main.rs @@ -51,8 +51,10 @@ impl eframe::App for App { if let Ok(res) = self.task_res_rx.try_recv() { match res { Response::Craft(r) => { - if let Ok(entry) = &r { - self.load_item(entry, false).unwrap(); + if let Ok(entries) = &r { + entries + .iter() + .for_each(|entry| self.load_item(entry, false).unwrap()); } else { log::error!("{r:?}"); } @@ -70,8 +72,10 @@ impl eframe::App for App { self.crafting.commit_result = Some(r); } Response::CraftAndCommit(r) => { - if let Ok(entry) = &r { - self.load_item(entry, false).unwrap(); + if let Ok(entries) = &r { + entries + .iter() + .for_each(|entry| self.load_item(entry, false).unwrap()); } else { log::error!("{r:?}"); } @@ -80,7 +84,7 @@ impl eframe::App for App { // Reset filename self.crafting.output_filename = "".to_string(); self.crafting.craft_result = None; - self.crafting.commit_result = Some(r); + self.crafting.commit_result = r.map(|entries| Ok(entries[0].clone())).ok(); } Response::Null => {} } diff --git a/app_gui/src/task_system.rs b/app_gui/src/task_system.rs index c5a1b5b..cc980f7 100644 --- a/app_gui/src/task_system.rs +++ b/app_gui/src/task_system.rs @@ -39,9 +39,9 @@ pub enum Request { } pub enum Response { - Craft(Result), + Craft(Result>), Commit(Result), - CraftAndCommit(Result), + CraftAndCommit(Result>), Null, } @@ -67,22 +67,27 @@ pub fn handle_req(task_status: &RwLock, req: Request) -> Response { output, input_paths, } => { - if let Response::Craft(Result::Err(e)) = craft( + let craft_res = craft( task_status, ¶ms, pods_path, recipe, output.clone(), input_paths, - ) { - return Response::CraftAndCommit(Result::Err(e)); - }; - let res = commit(task_status, ¶ms, cfg, output.clone()); - let r = match res { - Response::Commit(result) => result, - _ => Err(anyhow!("unexpected response")), - }; - Response::CraftAndCommit(r) + ); + match craft_res { + Response::Craft(Result::Err(e)) => Response::CraftAndCommit(Result::Err(e)), + Response::Craft(Result::Ok(output_paths)) => { + // TODO: Maybe have a separate batch or commitment POD? + let res = commit(task_status, ¶ms, cfg, output_paths[0].clone()); + let r = match res { + Response::Commit(_) => Result::Ok(output_paths), + _ => Err(anyhow!("unexpected response")), + }; + Response::CraftAndCommit(r) + } + _ => Response::CraftAndCommit(Err(anyhow!("unexpected response"))), + } } Request::Exit => Response::Null, } @@ -123,7 +128,7 @@ fn craft( } task_status.write().unwrap().busy = None; - Response::Craft(r.map(|_| output)) + Response::Craft(r) } fn commit( task_status: &RwLock, diff --git a/synchronizer/src/main.rs b/synchronizer/src/main.rs index 9b934d4..e233644 100644 --- a/synchronizer/src/main.rs +++ b/synchronizer/src/main.rs @@ -51,7 +51,7 @@ use synchronizer::{ }, }; use tokio::{runtime::Runtime, time::sleep}; -use tracing::{debug, error, info, trace}; +use tracing::{debug, info, trace}; pub mod endpoints; From 8e222e11a561907ba932c163b3ec8c19d3358f5d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 9 Dec 2025 14:52:47 +1000 Subject: [PATCH 25/29] Remove print statements --- common/src/shrink.rs | 1 - synchronizer/src/main.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/common/src/shrink.rs b/common/src/shrink.rs index 1ee5b83..7d4ec9d 100644 --- a/common/src/shrink.rs +++ b/common/src/shrink.rs @@ -175,6 +175,5 @@ pub fn shrink_compress_pod( &indices, &shrunk_main_pod_build.circuit_data.common.fri_params, ); - println!("PIS: {:?}", proof_with_pis.public_inputs); Ok(compressed_proof) } diff --git a/synchronizer/src/main.rs b/synchronizer/src/main.rs index e233644..3bf61c0 100644 --- a/synchronizer/src/main.rs +++ b/synchronizer/src/main.rs @@ -476,7 +476,6 @@ impl Node { PayloadProof::Plonky2(proof) => proof, PayloadProof::Groth16(_) => todo!(), }; - println!("PIS: {:?}", public_inputs); let proof_with_pis = CompressedProofWithPublicInputs { proof: *shrunk_main_pod_proof, public_inputs, From 6a2ddf4630c1e49b2d621dc8d650aead8d16601d Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 9 Dec 2025 13:48:04 +0100 Subject: [PATCH 26/29] fix multi-output outputs file naming (& ui icons), parametrize & reduce PoW of Stone from 3 to 2 (since now proving in general already involves more recursive steps) --- app_cli/src/lib.rs | 20 ++++++---------- app_cli/src/main.rs | 8 +++---- app_gui/src/crafting.rs | 47 ++++++++++++++++++++++++++------------ app_gui/src/main.rs | 4 ++-- app_gui/src/task_system.rs | 23 ++++++++++++------- craftlib/src/constants.rs | 1 + craftlib/src/item.rs | 2 +- craftlib/src/predicates.rs | 22 ++++++++++-------- shell.nix | 14 +++++++----- 9 files changed, 83 insertions(+), 58 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index 4edba14..f5972d2 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -16,8 +16,8 @@ use common::{ use craftlib::{ constants::{ AXE_BLUEPRINT, AXE_MINING_MAX, AXE_WORK, DUST_BLUEPRINT, DUST_MINING_MAX, DUST_WORK, - GEM_BLUEPRINT, STONE_BLUEPRINT, STONE_MINING_MAX, WOOD_BLUEPRINT, WOOD_MINING_MAX, - WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, + GEM_BLUEPRINT, STONE_BLUEPRINT, STONE_MINING_MAX, STONE_WORK_COST, WOOD_BLUEPRINT, + WOOD_MINING_MAX, WOOD_WORK, WOODEN_AXE_BLUEPRINT, WOODEN_AXE_MINING_MAX, WOODEN_AXE_WORK, }, item::{CraftBuilder, MiningRecipe}, powpod::PowPod, @@ -375,7 +375,7 @@ impl Helper { pub fn craft_item( params: &Params, recipe: Recipe, - output: &Path, + outputs: &[PathBuf], inputs: &[PathBuf], ) -> anyhow::Result> { let vd_set = DEFAULT_VD_SET.clone(); @@ -397,7 +397,7 @@ pub fn craft_item( let pow_pod = PowPod::new( params, vd_set.clone(), - 3, // num_iters + STONE_WORK_COST, // num_iters RawValue::from(ingredients_def.dict(params)?.commitment()), )?; log::info!("[TIME] PowPod proving time: {:?}", start.elapsed()); @@ -491,7 +491,7 @@ pub fn craft_item( // create output dir (if there is a parent dir), in case it does not exist // yet, so that later when creating the file we don't get an error if the // directory does not exist - if let Some(dir) = output.parent() { + if let Some(dir) = outputs[0].parent() { std::fs::create_dir_all(dir)?; } @@ -513,14 +513,8 @@ pub fn craft_item( let filenames: Vec = item_def .iter() - .map(|ItemDef { index, batch: _ }| { - let suffix = if pods.len() > 1 { - format!("_{}", index.name()) - } else { - "".to_string() - }; - format! {"{}{suffix}", output.display()}.into() - }) + .enumerate() + .map(|(i, _)| format! {"{}", outputs[i].display()}.into()) .collect(); for (filename, (def, pod)) in diff --git a/app_cli/src/main.rs b/app_cli/src/main.rs index a9c3319..eff2cf9 100644 --- a/app_cli/src/main.rs +++ b/app_cli/src/main.rs @@ -28,8 +28,8 @@ enum Commands { Craft { #[arg(long, value_name = "RECIPE")] recipe: String, - #[arg(long, value_name = "FILE")] - output: PathBuf, + #[arg(long = "input", value_name = "FILE")] + outputs: Vec, #[arg(long = "input", value_name = "FILE")] inputs: Vec, }, @@ -59,11 +59,11 @@ async fn main() -> anyhow::Result<()> { match cli.command { Some(Commands::Craft { recipe, - output, + outputs, inputs, }) => { let recipe = Recipe::from_str(&recipe)?; - craft_item(¶ms, recipe, &output, &inputs)?; + craft_item(¶ms, recipe, &outputs, &inputs)?; } Some(Commands::Commit { input }) => { commit_item(¶ms, &cfg, &input).await?; diff --git a/app_gui/src/crafting.rs b/app_gui/src/crafting.rs index ecd677a..c8673ea 100644 --- a/app_gui/src/crafting.rs +++ b/app_gui/src/crafting.rs @@ -549,7 +549,7 @@ pub struct Crafting { pub selected_action: Option<&'static str>, // Input index to item index pub input_items: HashMap, - pub output_filename: String, + pub outputs_filename: Vec, pub craft_result: Option>>, pub commit_result: Option>, } @@ -737,11 +737,20 @@ impl App { self.crafting.selected_action = selected_action; - // NOTE: If we don't show filenames in the left panel, then we shouldn't ask for a - // filename either. - if self.crafting.output_filename.is_empty() { - self.crafting.output_filename = - format!("{:?}_{}", process, self.items.len() + self.used_items.len()); + // prepare the outputs names that will be used to store the outputs files + let process_outputs = process.data().outputs; + if self.crafting.outputs_filename.is_empty() { + self.crafting.outputs_filename = process_outputs + .iter() + .enumerate() + .map(|(i, process_output)| { + format!( + "{}_{}", + process_output, + self.items.len() + self.used_items.len() + i + ) + }) + .collect(); } ui.add_space(8.0); @@ -771,11 +780,15 @@ impl App { }); if button_craft_clicked { - if self.crafting.output_filename.is_empty() { + if self.crafting.outputs_filename.is_empty() { self.crafting.craft_result = Some(Err(anyhow!("Please enter a filename."))); } else { - let output = - Path::new(&self.cfg.pods_path).join(&self.crafting.output_filename); + let outputs_paths = self + .crafting + .outputs_filename + .iter() + .map(|output| Path::new(&self.cfg.pods_path).join(output)) + .collect(); let input_paths = (0..inputs.len()) .map(|i| { self.crafting @@ -798,7 +811,7 @@ impl App { params: self.params.clone(), pods_path: self.cfg.pods_path.clone(), recipe, - output, + outputs: outputs_paths, input_paths, }) .unwrap(); @@ -809,7 +822,7 @@ impl App { } if button_commit_clicked { - if self.crafting.output_filename.is_empty() { + if self.crafting.outputs_filename.is_empty() { self.crafting.commit_result = Some(Err(anyhow!("Please enter a filename."))); } else if self.crafting.craft_result.is_none() || self.crafting.craft_result.as_ref().unwrap().is_err() @@ -837,11 +850,15 @@ impl App { } if button_craft_and_commit_clicked { - if self.crafting.output_filename.is_empty() { + if self.crafting.outputs_filename.is_empty() { self.crafting.commit_result = Some(Err(anyhow!("Please enter a filename."))); } else { - let output = - Path::new(&self.cfg.pods_path).join(&self.crafting.output_filename); + let outputs_paths = self + .crafting + .outputs_filename + .iter() + .map(|output| Path::new(&self.cfg.pods_path).join(output)) + .collect(); let input_paths = (0..inputs.len()) .map(|i| { self.crafting @@ -865,7 +882,7 @@ impl App { cfg: self.cfg.clone(), pods_path: self.cfg.pods_path.clone(), recipe, - output, + outputs: outputs_paths, input_paths, }) .unwrap(); diff --git a/app_gui/src/main.rs b/app_gui/src/main.rs index bb00afb..c3b1f06 100644 --- a/app_gui/src/main.rs +++ b/app_gui/src/main.rs @@ -68,7 +68,7 @@ impl eframe::App for App { log::error!("{e:?}"); } // Reset filename - self.crafting.output_filename = "".to_string(); + self.crafting.outputs_filename = vec![]; self.crafting.commit_result = Some(r); } Response::CraftAndCommit(r) => { @@ -82,7 +82,7 @@ impl eframe::App for App { self.refresh_items().unwrap(); self.crafting.input_items = HashMap::new(); // Reset filename - self.crafting.output_filename = "".to_string(); + self.crafting.outputs_filename = vec![]; self.crafting.craft_result = None; self.crafting.commit_result = r.map(|entries| Ok(entries[0].clone())).ok(); } diff --git a/app_gui/src/task_system.rs b/app_gui/src/task_system.rs index cc980f7..5b06023 100644 --- a/app_gui/src/task_system.rs +++ b/app_gui/src/task_system.rs @@ -19,7 +19,7 @@ pub enum Request { params: Params, pods_path: String, recipe: Recipe, - output: PathBuf, + outputs: Vec, input_paths: Vec, }, Commit { @@ -32,7 +32,7 @@ pub enum Request { cfg: Config, pods_path: String, recipe: Recipe, - output: PathBuf, + outputs: Vec, input_paths: Vec, }, Exit, @@ -55,16 +55,23 @@ pub fn handle_req(task_status: &RwLock, req: Request) -> Response { params, pods_path, recipe, - output, + outputs, input_paths, - } => craft(task_status, ¶ms, pods_path, recipe, output, input_paths), + } => craft( + task_status, + ¶ms, + pods_path, + recipe, + outputs, + input_paths, + ), Request::Commit { params, cfg, input } => commit(task_status, ¶ms, cfg, input), Request::CraftAndCommit { params, cfg, pods_path, recipe, - output, + outputs, input_paths, } => { let craft_res = craft( @@ -72,7 +79,7 @@ pub fn handle_req(task_status: &RwLock, req: Request) -> Response { ¶ms, pods_path, recipe, - output.clone(), + outputs, input_paths, ); match craft_res { @@ -98,13 +105,13 @@ fn craft( params: &Params, pods_path: String, recipe: Recipe, - output: PathBuf, + outputs: Vec, input_paths: Vec, ) -> Response { set_busy_task(task_status, "Crafting"); let start = std::time::Instant::now(); - let r = craft_item(params, recipe, &output, &input_paths); + let r = craft_item(params, recipe, &outputs, &input_paths); log::info!("[TIME] total Craft Item time: {:?}", start.elapsed()); // move the files of the used inputs into the `used` subdir diff --git a/craftlib/src/constants.rs b/craftlib/src/constants.rs index 972f832..fdbb854 100644 --- a/craftlib/src/constants.rs +++ b/craftlib/src/constants.rs @@ -3,6 +3,7 @@ use pod2::middleware::{EMPTY_VALUE, RawValue}; pub const STONE_BLUEPRINT: &str = "stone"; pub const STONE_MINING_MAX: u64 = 0x0020_0000_0000_0000; pub const STONE_WORK: RawValue = EMPTY_VALUE; +pub const STONE_WORK_COST: usize = 2; pub const WOOD_BLUEPRINT: &str = "wood"; pub const WOOD_MINING_MAX: u64 = 0x0020_0000_0000_0000; diff --git a/craftlib/src/item.rs b/craftlib/src/item.rs index 238d41b..62d4320 100644 --- a/craftlib/src/item.rs +++ b/craftlib/src/item.rs @@ -474,7 +474,7 @@ mod tests { let pow_pod = PowPod::new( ¶ms, vd_set.clone(), - 3, // num_iters + crate::constants::STONE_WORK_COST, // num_iters RawValue::from(ingredients_def.dict(¶ms)?.commitment()), )?; let main_pow_pod = MainPod { diff --git a/craftlib/src/predicates.rs b/craftlib/src/predicates.rs index 637b6be..0668419 100644 --- a/craftlib/src/predicates.rs +++ b/craftlib/src/predicates.rs @@ -4,6 +4,8 @@ use commitlib::predicates::CommitPredicates; use pod2::middleware::{CustomPredicateRef, Params}; use pod2utils::PredicateDefs; +use crate::constants::STONE_WORK_COST; + pub struct ItemPredicates { pub defs: PredicateDefs, @@ -18,7 +20,8 @@ impl ItemPredicates { // 8 arguments per predicate, at most 5 of which are public // 5 statements per predicate let batch_defs = [ - r#" + &format!( + r#" use intro Pow(count, input, output) from 0x3493488bc23af15ac5fabe38c3cb6c4b66adb57e3898adf201ae50cc57183f65 // powpod vd hash // Example of a mined item with no inputs or sequential work. @@ -26,25 +29,25 @@ impl ItemPredicates { // 10 leading 0s. 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({STONE_WORK_COST}, 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( ItemDef(item, ingredients, inputs, key, work) - Equal(inputs, {}) + Equal(inputs, {{}}) DictContains(ingredients, "blueprint", "wood") - Equal(work, {}) + Equal(work, {{}}) // TODO input POD: SequentialWork(ingredients, work, 5) // TODO input POD: HashInRange(0, 1<<5, ingredients) ) AxeInputs(inputs, private: s1, wood, stone) = AND( // 2 ingredients - SetInsert(s1, {}, wood) + SetInsert(s1, {{}}, wood) SetInsert(inputs, s1, stone) // prove the ingredients are correct. @@ -57,11 +60,12 @@ impl ItemPredicates { IsAxe(item, private: ingredients, inputs, key, work) = AND( ItemDef(item, ingredients, inputs, key, work) DictContains(ingredients, "blueprint", "axe") - Equal(work, {}) + Equal(work, {{}}) AxeInputs(inputs) ) - "#, + "# + ), r#" // Wooden Axe: @@ -214,7 +218,7 @@ mod tests { let pow_pod = PowPod::new( ¶ms, vd_set.clone(), - 3, + STONE_WORK_COST, RawValue::from(ingredients_def.dict(¶ms)?.commitment()), )?; let main_pow_pod = MainPod { diff --git a/shell.nix b/shell.nix index 9f1036a..89709bc 100644 --- a/shell.nix +++ b/shell.nix @@ -3,12 +3,13 @@ let dlopenLibraries = with pkgs; [ libxkbcommon - libxkbcommon.dev + # libxkbcommon.dev + #vulkan-loader wayland - wayland.dev + # wayland.dev wayland-protocols - emacs + sway mesa mesa-gl-headers egl-wayland @@ -17,9 +18,10 @@ let in pkgs.mkShell { nativeBuildInputs = with pkgs; [ rustup - mesa - mesa-gl-headers + gcc + pkg-config ]; - env.RUSTFLAGS = "-C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath dlopenLibraries}"; + env.RUSTFLAGS = "-C linker=clang -C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath dlopenLibraries}"; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath dlopenLibraries; } From 38760412215b04ed97c3b21149b8df191a6cb603 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 11 Dec 2025 12:45:54 +1000 Subject: [PATCH 27/29] Fix CLI --- app_cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_cli/src/main.rs b/app_cli/src/main.rs index eff2cf9..ab739d5 100644 --- a/app_cli/src/main.rs +++ b/app_cli/src/main.rs @@ -28,7 +28,7 @@ enum Commands { Craft { #[arg(long, value_name = "RECIPE")] recipe: String, - #[arg(long = "input", value_name = "FILE")] + #[arg(long = "output", value_name = "FILE")] outputs: Vec, #[arg(long = "input", value_name = "FILE")] inputs: Vec, From d8075cb09ae525ce86c49dd9d45defdea3e2e42c Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 12 Dec 2025 18:48:00 +1000 Subject: [PATCH 28/29] Drop unnecessary PODs when crafting --- app_cli/src/lib.rs | 89 ++++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/app_cli/src/lib.rs b/app_cli/src/lib.rs index f5972d2..b8945f1 100644 --- a/app_cli/src/lib.rs +++ b/app_cli/src/lib.rs @@ -201,51 +201,58 @@ impl Helper { item_builder.ctx.builder.reveal(&st_all_items_in_batch); // 5: Required for committing via CommitCreation - let all_items_in_batch_pod = item_builder.ctx.builder.prove(prover)?; - - let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); - let mut item_builder = - ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); - let mut sts_input_item_key = Vec::new(); let mut sts_input_craft = Vec::new(); + let mut input_item_pod = None; + + // Need more intermediate PODs if there are input items. + if !input_item_pods.is_empty() { + info!("Proving all_items_in_batch_pod..."); + let all_items_in_batch_pod = item_builder.ctx.builder.prove(prover)?; + builder = MainPodBuilder::new(&self.params, &self.vd_set); + item_builder = + ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); + // TODO: Use recursion here to be able to make use of more than 2 input PODs. + for input_item_pod in input_item_pods { + let st_item_key = input_item_pod.pod.pub_statements()[0].clone(); + sts_input_item_key.push(st_item_key); + let st_craft = input_item_pod.pod.pub_statements()[4].clone(); + sts_input_craft.push(st_craft); + item_builder.ctx.builder.add_pod(input_item_pod); + } - // TODO: Use recursion here to be able to make use of more than 2 input PODs. - for input_item_pod in input_item_pods { - let st_item_key = input_item_pod.pod.pub_statements()[0].clone(); - sts_input_item_key.push(st_item_key); - let st_craft = input_item_pod.pod.pub_statements()[4].clone(); - sts_input_craft.push(st_craft); - item_builder.ctx.builder.add_pod(input_item_pod); + // Prove and proceed. + sts_input_item_key + .iter() + .chain(sts_input_craft.iter()) + .for_each(|st| item_builder.ctx.builder.reveal(st)); + info!("Proving input_item_pod..."); + input_item_pod = Some(item_builder.ctx.builder.prove(prover)?); + + // Take care of nullifiers. + builder = MainPodBuilder::new(&self.params, &self.vd_set); + item_builder = + ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); + + item_builder + .ctx + .builder + .add_pod(input_item_pod.clone().unwrap()); + item_builder + .ctx + .builder + .add_pod(all_items_in_batch_pod.clone()); + all_items_in_batch_pod + .public_statements + .iter() + .for_each(|st| item_builder.ctx.builder.reveal(st)); } - // Prove and proceed. - sts_input_item_key - .iter() - .chain(sts_input_craft.iter()) - .for_each(|st| item_builder.ctx.builder.reveal(st)); - info!("Proving input_item_pod..."); - let input_item_pod = item_builder.ctx.builder.prove(prover)?; - - // Take care of nullifiers. - builder = MainPodBuilder::new(&self.params, &self.vd_set); - item_builder = - ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); - - item_builder.ctx.builder.add_pod(input_item_pod.clone()); - item_builder - .ctx - .builder - .add_pod(all_items_in_batch_pod.clone()); - // By the way, the default params don't have enough custom statement verifications // to fit everything in a single pod, hence all the splits. let (st_nullifiers, _nullifiers) = item_builder.st_nullifiers(sts_input_item_key).unwrap(); item_builder.ctx.builder.reveal(&st_nullifiers); - all_items_in_batch_pod - .public_statements - .iter() - .for_each(|st| item_builder.ctx.builder.reveal(st)); + item_builder.ctx.builder.reveal(&st_all_items_in_batch); info!("Proving nullifiers_et_al_pod..."); let nullifiers_et_al_pod = builder.prove(prover).unwrap(); @@ -256,7 +263,10 @@ impl Helper { item_builder = ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); item_builder.ctx.builder.add_pod(nullifiers_et_al_pod); - item_builder.ctx.builder.add_pod(input_item_pod.clone()); + + input_item_pod + .iter() + .for_each(|p| item_builder.ctx.builder.add_pod(p.clone())); let mut item_builder = ItemBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); @@ -280,13 +290,14 @@ impl Helper { let mut builder = MainPodBuilder::new(&self.params, &self.vd_set); builder.add_pod(item_key_pod); - builder.add_pod(input_item_pod); + input_item_pod + .iter() + .for_each(|p| builder.add_pod(p.clone())); let mut craft_builder = CraftBuilder::new(BuildContext::new(&mut builder, &self.batches), &self.params); let st_craft = match recipe { Recipe::Stone => { - craft_builder.ctx.builder.input_pods.pop(); // 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(); From 7e2d79a04e63c5f1a2e55aa62750f25c01c542f0 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 7 Jan 2026 16:27:13 +0100 Subject: [PATCH 29/29] rm shell.nix file --- shell.nix | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 shell.nix diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 89709bc..0000000 --- a/shell.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ pkgs ? import { } }: - -let - dlopenLibraries = with pkgs; [ - libxkbcommon - # libxkbcommon.dev - - #vulkan-loader - wayland - # wayland.dev - wayland-protocols - sway - mesa - mesa-gl-headers - egl-wayland - libGL - ]; -in pkgs.mkShell { - nativeBuildInputs = with pkgs; [ - rustup - gcc - pkg-config - ]; - - env.RUSTFLAGS = "-C linker=clang -C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath dlopenLibraries}"; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath dlopenLibraries; -}