From 9f707aaaf5803ef5cdb01ff2fb135c0e90c696e9 Mon Sep 17 00:00:00 2001 From: Arthur Paulino Date: Mon, 14 Aug 2023 22:01:44 -0300 Subject: [PATCH] slot allocation for is_diff_neg --- src/lem/circuit.rs | 157 +++++++++++++++++++++++++++-------------- src/lem/eval.rs | 5 +- src/lem/interpreter.rs | 48 ++++++++++--- src/lem/mod.rs | 8 +-- src/lem/slot.rs | 22 ++++-- 5 files changed, 169 insertions(+), 71 deletions(-) diff --git a/src/lem/circuit.rs b/src/lem/circuit.rs index 9cb4c87b20..fb734fb51b 100644 --- a/src/lem/circuit.rs +++ b/src/lem/circuit.rs @@ -41,11 +41,13 @@ use crate::circuit::gadgets::{ pointer::AllocatedPtr, }; -use crate::field::{FWrap, LurkField}; -use crate::tag::ExprTag::*; +use crate::{ + field::{FWrap, LurkField}, + tag::ExprTag::*, +}; use super::{ - interpreter::Frame, + interpreter::{Frame, PreimageData}, pointers::{Ptr, ZPtr}, slot::*, store::Store, @@ -174,7 +176,7 @@ impl Func { preallocated_preimg: Vec>, store: &mut Store, ) -> Result> { - let cs = &mut cs.namespace(|| format!("poseidon for slot {slot}")); + let cs = &mut cs.namespace(|| format!("image for slot {slot}")); let preallocated_img = { match slot.typ { SlotType::Hash2 => { @@ -186,6 +188,25 @@ impl Func { SlotType::Hash4 => { hash_poseidon(cs, preallocated_preimg, store.poseidon_cache.constants.c8())? } + SlotType::IsDiffNeg => { + let a_num = &preallocated_preimg[0]; + let b_num = &preallocated_preimg[1]; + let diff = sub( + &mut cs.namespace(|| format!("diff for slot {slot}")), + a_num, + b_num, + ) + .unwrap(); + let diff_is_negative = allocate_is_negative( + &mut cs.namespace(|| format!("diff_is_negative for slot {slot}")), + &diff, + )?; + + boolean_to_num( + &mut cs.namespace(|| format!("boolean_to_num for slot {slot}")), + &diff_is_negative, + )? + } } }; Ok(preallocated_img) @@ -194,13 +215,13 @@ impl Func { /// Allocates unconstrained slots fn allocate_slots>( cs: &mut CS, - preimgs: &[Option>>], + preimg_data: &[Option>], slot_type: SlotType, num_slots: usize, store: &mut Store, ) -> Result>, AllocatedNum)>> { assert!( - preimgs.len() == num_slots, + preimg_data.len() == num_slots, "collected preimages not equal to the number of available slots" ); @@ -208,38 +229,52 @@ impl Func { // We must perform the allocations for the slots containing data collected // by the interpreter. The `None` cases must be filled with dummy values - for (slot_idx, maybe_preimg) in preimgs.iter().enumerate() { - if let Some(preimg) = maybe_preimg { + for (slot_idx, maybe_preimg_data) in preimg_data.iter().enumerate() { + if let Some(preimg_data) = maybe_preimg_data { let slot = Slot { idx: slot_idx, typ: slot_type, }; + // Allocate the preimage because the image depends on it - let mut preallocated_preimg = Vec::with_capacity(2 * preimg.len()); - - let mut component_idx = 0; - for ptr in preimg { - let z_ptr = store.hash_ptr(ptr)?; - - // allocate pointer tag - preallocated_preimg.push(Self::allocate_preimg_component_for_slot( - cs, - &slot, - component_idx, - z_ptr.tag.to_field(), - )?); - - component_idx += 1; - - // allocate pointer hash - preallocated_preimg.push(Self::allocate_preimg_component_for_slot( - cs, - &slot, - component_idx, - z_ptr.hash, - )?); - - component_idx += 1; + let mut preallocated_preimg = Vec::with_capacity(slot_type.preimg_size()); + + match preimg_data { + PreimageData::PtrVec(ptr_vec) => { + let mut component_idx = 0; + for ptr in ptr_vec { + let z_ptr = store.hash_ptr(ptr)?; + + // allocate pointer tag + preallocated_preimg.push(Self::allocate_preimg_component_for_slot( + cs, + &slot, + component_idx, + z_ptr.tag.to_field(), + )?); + + component_idx += 1; + + // allocate pointer hash + preallocated_preimg.push(Self::allocate_preimg_component_for_slot( + cs, + &slot, + component_idx, + z_ptr.hash, + )?); + + component_idx += 1; + } + } + PreimageData::FPair(a, b) => { + // allocate first element + preallocated_preimg + .push(Self::allocate_preimg_component_for_slot(cs, &slot, 0, *a)?); + + // allocate second element + preallocated_preimg + .push(Self::allocate_preimg_component_for_slot(cs, &slot, 1, *b)?); + } } // Allocate the image by calling the arithmetic function according @@ -318,12 +353,21 @@ impl Func { store, )?; + let preallocated_is_diff_neg_slots = Func::allocate_slots( + cs, + &frame.preimages.is_diff_neg, + SlotType::IsDiffNeg, + self.slot.is_diff_neg, + store, + )?; + struct Globals<'a, F: LurkField> { store: &'a mut Store, global_allocator: &'a mut GlobalAllocator, preallocated_hash2_slots: Vec<(Vec>, AllocatedNum)>, preallocated_hash3_slots: Vec<(Vec>, AllocatedNum)>, preallocated_hash4_slots: Vec<(Vec>, AllocatedNum)>, + preallocated_is_diff_neg_slots: Vec<(Vec>, AllocatedNum)>, call_outputs: VecDeque>>, call_count: usize, } @@ -354,6 +398,7 @@ impl Func { SlotType::Hash4 => { &g.preallocated_hash4_slots[next_slot.consume_hash4()] } + _ => panic!("Invalid slot type for hash_helper macro"), }; // For each component of the preimage, add implication constraints @@ -363,10 +408,7 @@ impl Func { let ptr_idx = 2 * i; implies_equal( &mut cs.namespace(|| { - format!( - "implies equal for {var}'s tag (LEMOP {:?}, pos {i})", - &op - ) + format!("implies equal for {var}'s tag (OP {:?}, pos {i})", &op) }), not_dummy, allocated_ptr.tag(), @@ -375,7 +417,7 @@ impl Func { implies_equal( &mut cs.namespace(|| { format!( - "implies equal for {var}'s hash (LEMOP {:?}, pos {i})", + "implies equal for {var}'s hash (OP {:?}, pos {i})", &op ) }), @@ -410,12 +452,13 @@ impl Func { SlotType::Hash4 => { &g.preallocated_hash4_slots[next_slot.consume_hash4()] } + _ => panic!("Invalid slot type for unhash_helper macro"), }; // Add the implication constraint for the image implies_equal( &mut cs.namespace(|| { - format!("implies equal for {}'s hash (LEMOP {:?})", $img, &op) + format!("implies equal for {}'s hash (OP {:?})", $img, &op) }), not_dummy, allocated_img.hash(), @@ -581,20 +624,28 @@ impl Func { let a = bound_allocations.get(a)?; let b = bound_allocations.get(b)?; // TODO check that the tags are correct - let a_num = a.hash(); - let b_num = b.hash(); - let diff = sub(&mut cs.namespace(|| "diff"), a_num, b_num).unwrap(); - let diff_is_negative = - allocate_is_negative(&mut cs.namespace(|| "diff_is_negative"), &diff)?; - let tag = g .global_allocator .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; - let lt = boolean_to_num( - &mut cs.namespace(|| "boolean_to_num"), - &diff_is_negative, + let (preallocated_preimg, lt) = + &g.preallocated_is_diff_neg_slots[next_slot.consume_is_diff_neg()]; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for first component (OP {:?})", &op) + }), + not_dummy, + a.hash(), + &preallocated_preimg[0], + )?; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for second component (OP {:?})", &op) + }), + not_dummy, + b.hash(), + &preallocated_preimg[1], )?; - let c = AllocatedPtr::from_parts(tag, lt); + let c = AllocatedPtr::from_parts(tag, lt.clone()); bound_allocations.insert(tgt.clone(), c); } Op::Emit(_) => (), @@ -845,6 +896,7 @@ impl Func { preallocated_hash2_slots, preallocated_hash3_slots, preallocated_hash4_slots, + preallocated_is_diff_neg_slots, call_outputs, call_count: 0, }, @@ -892,7 +944,6 @@ impl Func { Op::Lt(_, _, _) => { globals.insert(FWrap(Tag::Expr(Num).to_field())); num_constraints += 2; - num_constraints += 389; } Op::Emit(_) => (), Op::Hash2(_, tag, _) => { @@ -973,8 +1024,10 @@ impl Func { } let globals = &mut HashSet::default(); // fixed cost for each slot - let slot_constraints = - 289 * self.slot.hash2 + 337 * self.slot.hash3 + 388 * self.slot.hash4; + let slot_constraints = 289 * self.slot.hash2 + + 337 * self.slot.hash3 + + 388 * self.slot.hash4 + + 391 * self.slot.is_diff_neg; let num_constraints = recurse::(&self.body, false, globals, store); slot_constraints + num_constraints + globals.len() } diff --git a/src/lem/eval.rs b/src/lem/eval.rs index 0bd520cc3a..a9445cae9f 100644 --- a/src/lem/eval.rs +++ b/src/lem/eval.rs @@ -838,12 +838,13 @@ mod tests { use blstrs::Scalar as Fr; const NUM_INPUTS: usize = 1; - const NUM_AUX: usize = 9680; - const NUM_CONSTRAINTS: usize = 11762; + const NUM_AUX: usize = 8512; + const NUM_CONSTRAINTS: usize = 10597; const NUM_SLOTS: SlotsCounter = SlotsCounter { hash2: 16, hash3: 4, hash4: 2, + is_diff_neg: 1, }; fn test_eval_and_constrain_aux(store: &mut Store, pairs: Vec<(Ptr, Ptr)>) { diff --git a/src/lem/interpreter.rs b/src/lem/interpreter.rs index 68cafc39a0..e3d82d40a0 100644 --- a/src/lem/interpreter.rs +++ b/src/lem/interpreter.rs @@ -9,15 +9,22 @@ use super::{ use crate::tag::ExprTag::*; +#[derive(Clone)] +pub enum PreimageData { + PtrVec(Vec>), + FPair(F, F), +} + #[derive(Clone, Default)] /// `Preimages` hold the non-deterministic advices for hashes and `Func` calls. /// The hash preimages must have the same shape as the allocated slots for the /// `Func`, and the `None` values are used to fill the unused slots, which are /// later filled by dummy values. pub struct Preimages { - pub hash2_ptrs: Vec>>>, - pub hash3_ptrs: Vec>>>, - pub hash4_ptrs: Vec>>>, + pub hash2_ptrs: Vec>>, + pub hash3_ptrs: Vec>>, + pub hash4_ptrs: Vec>>, + pub is_diff_neg: Vec>>, pub call_outputs: VecDeque>>, } @@ -27,11 +34,13 @@ impl Preimages { let hash2_ptrs = Vec::with_capacity(slot.hash2); let hash3_ptrs = Vec::with_capacity(slot.hash3); let hash4_ptrs = Vec::with_capacity(slot.hash4); + let is_diff_neg = Vec::with_capacity(slot.is_diff_neg); let call_outputs = VecDeque::new(); Preimages { hash2_ptrs, hash3_ptrs, hash4_ptrs, + is_diff_neg, call_outputs, } } @@ -147,6 +156,9 @@ impl Block { let b = bindings.get(b)?; let c = match (a, b) { (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { + preimages + .is_diff_neg + .push(Some(PreimageData::FPair(*f, *g))); let f = Num::Scalar(*f); let g = Num::Scalar(*g); let b = if f < g { F::ONE } else { F::ZERO }; @@ -164,14 +176,18 @@ impl Block { let preimg_ptrs = bindings.get_many_cloned(preimg)?; let tgt_ptr = store.intern_2_ptrs(*tag, preimg_ptrs[0], preimg_ptrs[1]); bindings.insert(img.clone(), tgt_ptr); - preimages.hash2_ptrs.push(Some(preimg_ptrs)); + preimages + .hash2_ptrs + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } Op::Hash3(img, tag, preimg) => { let preimg_ptrs = bindings.get_many_cloned(preimg)?; let tgt_ptr = store.intern_3_ptrs(*tag, preimg_ptrs[0], preimg_ptrs[1], preimg_ptrs[2]); bindings.insert(img.clone(), tgt_ptr); - preimages.hash3_ptrs.push(Some(preimg_ptrs)); + preimages + .hash3_ptrs + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } Op::Hash4(img, tag, preimg) => { let preimg_ptrs = bindings.get_many_cloned(preimg)?; @@ -183,7 +199,9 @@ impl Block { preimg_ptrs[3], ); bindings.insert(img.clone(), tgt_ptr); - preimages.hash4_ptrs.push(Some(preimg_ptrs)); + preimages + .hash4_ptrs + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } Op::Unhash2(preimg, img) => { let img_ptr = bindings.get(img)?; @@ -197,7 +215,9 @@ impl Block { for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { bindings.insert(var.clone(), *ptr); } - preimages.hash2_ptrs.push(Some(preimg_ptrs.to_vec())); + preimages + .hash2_ptrs + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Unhash3(preimg, img) => { let img_ptr = bindings.get(img)?; @@ -211,7 +231,9 @@ impl Block { for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { bindings.insert(var.clone(), *ptr); } - preimages.hash3_ptrs.push(Some(preimg_ptrs.to_vec())); + preimages + .hash3_ptrs + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Unhash4(preimg, img) => { let img_ptr = bindings.get(img)?; @@ -225,7 +247,9 @@ impl Block { for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { bindings.insert(var.clone(), *ptr); } - preimages.hash4_ptrs.push(Some(preimg_ptrs.to_vec())); + preimages + .hash4_ptrs + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Hide(tgt, sec, src) => { let src_ptr = bindings.get(src)?; @@ -344,6 +368,7 @@ impl Func { let hash2_init = preimages.hash2_ptrs.len(); let hash3_init = preimages.hash3_ptrs.len(); let hash4_init = preimages.hash4_ptrs.len(); + let is_diff_neg_init = preimages.is_diff_neg.len(); let mut res = self .body @@ -353,6 +378,8 @@ impl Func { let hash2_used = preimages.hash2_ptrs.len() - hash2_init; let hash3_used = preimages.hash3_ptrs.len() - hash3_init; let hash4_used = preimages.hash4_ptrs.len() - hash4_init; + let is_diff_neg_used = preimages.is_diff_neg.len() - is_diff_neg_init; + for _ in hash2_used..self.slot.hash2 { preimages.hash2_ptrs.push(None); } @@ -362,6 +389,9 @@ impl Func { for _ in hash4_used..self.slot.hash4 { preimages.hash4_ptrs.push(None); } + for _ in is_diff_neg_used..self.slot.is_diff_neg { + preimages.is_diff_neg.push(None); + } Ok(res) } diff --git a/src/lem/mod.rs b/src/lem/mod.rs index e048b669ca..863c3e3df4 100644 --- a/src/lem/mod.rs +++ b/src/lem/mod.rs @@ -802,7 +802,7 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42))]; - synthesize_test_helper(&func, inputs, SlotsCounter::new((2, 0, 0))); + synthesize_test_helper(&func, inputs, SlotsCounter::new((2, 0, 0, 0))); } #[test] @@ -863,7 +863,7 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42)), Ptr::char('c')]; - synthesize_test_helper(&lem, inputs, SlotsCounter::new((2, 2, 2))); + synthesize_test_helper(&lem, inputs, SlotsCounter::new((2, 2, 2, 0))); } #[test] @@ -897,7 +897,7 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42)), Ptr::char('c')]; - synthesize_test_helper(&lem, inputs, SlotsCounter::new((3, 3, 3))); + synthesize_test_helper(&lem, inputs, SlotsCounter::new((3, 3, 3, 0))); } #[test] @@ -944,6 +944,6 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42)), Ptr::char('c')]; - synthesize_test_helper(&lem, inputs, SlotsCounter::new((4, 4, 4))); + synthesize_test_helper(&lem, inputs, SlotsCounter::new((4, 4, 4, 0))); } } diff --git a/src/lem/slot.rs b/src/lem/slot.rs index 06333b5319..ae497d979d 100644 --- a/src/lem/slot.rs +++ b/src/lem/slot.rs @@ -110,16 +110,18 @@ pub struct SlotsCounter { pub hash2: usize, pub hash3: usize, pub hash4: usize, + pub is_diff_neg: usize, } impl SlotsCounter { /// This interface is mostly for testing #[inline] - pub fn new(num_slots: (usize, usize, usize)) -> Self { + pub fn new(num_slots: (usize, usize, usize, usize)) -> Self { Self { hash2: num_slots.0, hash3: num_slots.1, hash4: num_slots.2, + is_diff_neg: num_slots.3, } } @@ -141,6 +143,12 @@ impl SlotsCounter { self.hash4 - 1 } + #[inline] + pub fn consume_is_diff_neg(&mut self) -> usize { + self.is_diff_neg += 1; + self.is_diff_neg - 1 + } + #[inline] pub fn max(&self, other: Self) -> Self { use std::cmp::max; @@ -148,6 +156,7 @@ impl SlotsCounter { hash2: max(self.hash2, other.hash2), hash3: max(self.hash3, other.hash3), hash4: max(self.hash4, other.hash4), + is_diff_neg: max(self.is_diff_neg, other.is_diff_neg), } } @@ -157,6 +166,7 @@ impl SlotsCounter { hash2: self.hash2 + other.hash2, hash3: self.hash3 + other.hash3, hash4: self.hash4 + other.hash4, + is_diff_neg: self.is_diff_neg + other.is_diff_neg, } } } @@ -165,9 +175,10 @@ impl Block { pub fn count_slots(&self) -> SlotsCounter { let ops_slots = self.ops.iter().fold(SlotsCounter::default(), |acc, op| { let val = match op { - Op::Hash2(..) | Op::Unhash2(..) => SlotsCounter::new((1, 0, 0)), - Op::Hash3(..) | Op::Unhash3(..) => SlotsCounter::new((0, 1, 0)), - Op::Hash4(..) | Op::Unhash4(..) => SlotsCounter::new((0, 0, 1)), + Op::Hash2(..) | Op::Unhash2(..) => SlotsCounter::new((1, 0, 0, 0)), + Op::Hash3(..) | Op::Unhash3(..) => SlotsCounter::new((0, 1, 0, 0)), + Op::Hash4(..) | Op::Unhash4(..) => SlotsCounter::new((0, 0, 1, 0)), + Op::Lt(..) => SlotsCounter::new((0, 0, 0, 1)), Op::Call(_, func, _) => func.slot, _ => SlotsCounter::default(), }; @@ -205,6 +216,7 @@ pub(crate) enum SlotType { Hash2, Hash3, Hash4, + IsDiffNeg, } impl SlotType { @@ -213,6 +225,7 @@ impl SlotType { Self::Hash2 => 4, Self::Hash3 => 6, Self::Hash4 => 8, + Self::IsDiffNeg => 2, } } } @@ -223,6 +236,7 @@ impl std::fmt::Display for SlotType { Self::Hash2 => write!(f, "Hash2"), Self::Hash3 => write!(f, "Hash3"), Self::Hash4 => write!(f, "Hash4"), + Self::IsDiffNeg => write!(f, "IsDiffNeg"), } } }