diff --git a/src/circuit/circuit_frame.rs b/src/circuit/circuit_frame.rs index c0b362f014..f2eaf2cbca 100644 --- a/src/circuit/circuit_frame.rs +++ b/src/circuit/circuit_frame.rs @@ -29,7 +29,7 @@ use super::gadgets::constraints::{ pick, pick_const, sub, }; use crate::circuit::circuit_frame::constraints::{ - add, allocate_is_negative, boolean_to_num, enforce_pack, linear, mul, + add, allocate_is_negative, boolean_to_num, enforce_pack, enforce_product_and_sum, mul, }; use crate::circuit::gadgets::hashes::{AllocatedConsWitness, AllocatedContWitness}; use crate::circuit::ToInputs; @@ -4908,7 +4908,7 @@ fn to_unsigned_integer_helper>( }; // field element = pow(2, size).q + r - linear( + enforce_product_and_sum( &mut cs, || "product(q,pow(2,size)) + r", &q_num, diff --git a/src/circuit/gadgets/constraints.rs b/src/circuit/gadgets/constraints.rs index fb738d5fdf..e289394c90 100644 --- a/src/circuit/gadgets/constraints.rs +++ b/src/circuit/gadgets/constraints.rs @@ -74,6 +74,7 @@ pub(crate) fn enforce_sum>( ); } +/// Compute sum and enforce it. pub(crate) fn add>( mut cs: CS, a: &AllocatedNum, @@ -149,30 +150,69 @@ pub(crate) fn add_to_lc>( Ok(v_lc) } -// Enforce v is the bit decomposition of num, therefore we have that 0 <= num < 2ˆ(sizeof(v)). -pub(crate) fn enforce_pack>( +/// If premise is true, enforce `a` fits into 64 bits. It shows a non-deterministic +/// partial bit decomposition in order to constraint correct behavior. +pub(crate) fn implies_u64>( + mut cs: CS, + premise: &Boolean, + a: &AllocatedNum, +) -> Result<(), SynthesisError> { + let mut a_u64 = a.get_value().and_then(|a| a.to_u64()).unwrap_or(0); + + let mut bits: Vec = Vec::with_capacity(64); + for i in 0..64 { + let b = a_u64 & 1; + let b_bool = Boolean::Is(AllocatedBit::alloc( + &mut cs.namespace(|| format!("b.{i}")), + Some(b == 1), + )?); + bits.push(b_bool); + + a_u64 /= 2; + } + + // premise -> a = sum(bits) + implies_pack( + &mut cs.namespace(|| "u64 bit decomposition check"), + premise, + &bits, + a, + )?; + + Ok(()) +} + +/// If premise is true, enforce v is the bit decomposition of num, therefore we have that 0 <= num < 2ˆ(sizeof(v)). +pub(crate) fn implies_pack>( mut cs: CS, + premise: &Boolean, v: &[Boolean], num: &AllocatedNum, ) -> Result<(), SynthesisError> { let mut coeff = F::ONE; - - let mut v_lc = LinearCombination::::zero(); + let mut pack = LinearCombination::::zero(); for b in v { - v_lc = add_to_lc::(b, v_lc, coeff)?; + pack = add_to_lc::(b, pack, coeff)?; coeff = coeff.double(); } + let diff = |_| pack - num.get_variable(); + let premise_lc = |_| premise.lc(CS::one(), F::ONE); + let zero = |lc| lc; - cs.enforce( - || "pack", - |_| v_lc, - |lc| lc + CS::one(), - |lc| lc + num.get_variable(), - ); + cs.enforce(|| "pack", diff, premise_lc, zero); Ok(()) } +/// Enforce v is the bit decomposition of num, therefore we have that 0 <= num < 2ˆ(sizeof(v)). +pub(crate) fn enforce_pack>( + cs: CS, + v: &[Boolean], + num: &AllocatedNum, +) -> Result<(), SynthesisError> { + implies_pack(cs, &Boolean::Constant(true), v, num) +} + /// Adds a constraint to CS, enforcing a difference relationship between the allocated numbers a, b, and difference. /// /// a - b = difference @@ -197,6 +237,7 @@ pub(crate) fn enforce_difference>( ); } +/// Compute difference and enforce it. pub(crate) fn sub>( mut cs: CS, a: &AllocatedNum, @@ -220,7 +261,7 @@ pub(crate) fn sub>( /// a * b + c = num is enforced. /// /// a * b = num - c -pub(crate) fn linear>( +pub(crate) fn enforce_product_and_sum>( cs: &mut CS, annotation: A, a: &AllocatedNum, @@ -262,6 +303,7 @@ pub(crate) fn product>( ); } +/// Compute product and enforce it. pub(crate) fn mul>( mut cs: CS, a: &AllocatedNum, @@ -402,7 +444,7 @@ where Ok(c) } -/// Convert from Boolean to AllocatedNum +/// Convert from Boolean to AllocatedNum. pub(crate) fn boolean_to_num>( mut cs: CS, bit: &Boolean, @@ -429,7 +471,7 @@ where Ok(num) } -// This could now use alloc_is_zero to avoid duplication. +/// This could now use alloc_is_zero to avoid duplication. pub fn alloc_equal, F: PrimeField>( mut cs: CS, a: &AllocatedNum, @@ -484,7 +526,7 @@ pub fn alloc_equal, F: PrimeField>( Ok(Boolean::Is(result)) } -// Like `alloc_equal`, but with second argument a constant. +/// Like `alloc_equal`, but with second argument a constant. pub(crate) fn alloc_equal_const, F: PrimeField>( mut cs: CS, a: &AllocatedNum, @@ -539,6 +581,7 @@ pub(crate) fn alloc_equal_const, F: PrimeField>( Ok(Boolean::Is(result)) } +/// Allocate a Boolean which is true if and only if `x` is zero. pub(crate) fn alloc_is_zero, F: PrimeField>( cs: CS, x: &AllocatedNum, @@ -546,6 +589,7 @@ pub(crate) fn alloc_is_zero, F: PrimeField>( alloc_num_is_zero(cs, &Num::from(x.clone())) } +/// Allocate a Boolean which is true if and only if `num` is zero. pub(crate) fn alloc_num_is_zero, F: PrimeField>( mut cs: CS, num: &Num, @@ -594,6 +638,7 @@ pub(crate) fn alloc_num_is_zero, F: PrimeField>( Ok(Boolean::Is(result)) } +/// Variadic or. pub(crate) fn or_v, F: PrimeField>( cs: CS, v: &[&Boolean], @@ -606,6 +651,7 @@ pub(crate) fn or_v, F: PrimeField>( or_v_unchecked_for_optimization(cs, v) } +/// Unchecked variadic or. pub(crate) fn or_v_unchecked_for_optimization, F: PrimeField>( mut cs: CS, v: &[&Boolean], @@ -622,6 +668,7 @@ pub(crate) fn or_v_unchecked_for_optimization, F: PrimeF Ok(nor.not()) } +/// Variadic and. pub(crate) fn and_v, F: PrimeField>( mut cs: CS, v: &[&Boolean], @@ -759,6 +806,7 @@ pub(crate) fn implies_equal_zero, F: PrimeField>( enforce_implication_lc_zero(cs, premise, |lc| lc + a.get_variable()) } +/// Use DeMorgan to constrain or. pub(crate) fn or, F: PrimeField>( mut cs: CS, a: &Boolean, @@ -780,13 +828,13 @@ pub(crate) fn must_be_simple_bit(x: &Boolean) -> AllocatedBit { } } -// Allocate Boolean for predicate "num is negative". -// We have that a number is defined to be negative if the parity bit (the -// least significant bit) is odd after doubling, meaning that the field element -// (after doubling) is larger than the underlying prime p that defines the -// field, then a modular reduction must have been carried out, changing the parity that -// should be even (since we multiplied by 2) to odd. In other words, we define -// negative numbers to be those field elements that are larger than p/2. +/// Allocate Boolean for predicate "num is negative". +/// We have that a number is defined to be negative if the parity bit (the +/// least significant bit) is odd after doubling, meaning that the field element +/// (after doubling) is larger than the underlying prime p that defines the +/// field, then a modular reduction must have been carried out, changing the parity that +/// should be even (since we multiplied by 2) to odd. In other words, we define +/// negative numbers to be those field elements that are larger than p/2. pub(crate) fn allocate_is_negative>( mut cs: CS, num: &AllocatedNum, @@ -1279,4 +1327,35 @@ mod tests { assert!(cs.is_satisfied()); } + + #[test] + fn test_implies_u64_negative_edge_case() { + let mut cs = TestConstraintSystem::::new(); + + let alloc_num = AllocatedNum::alloc(&mut cs.namespace(|| "num"), || { + // Edge case: 2ˆ64 = 18446744073709551616 + Ok(Fr::from_str_vartime("18446744073709551616").unwrap()) + }) + .unwrap(); + + let t = Boolean::Constant(true); + implies_u64(&mut cs.namespace(|| "enforce u64"), &t, &alloc_num).unwrap(); + assert!(!cs.is_satisfied()); + } + + proptest! { + #[test] + fn test_implies_u64(f in any::>()) { + let mut cs = TestConstraintSystem::::new(); + + let num = AllocatedNum::alloc(cs.namespace(|| "num"), || Ok(f.0)).unwrap(); + + let t = Boolean::Constant(true); + implies_u64(&mut cs.namespace(|| "enforce u64"), &t, &num).unwrap(); + + let f_u64_roundtrip: Fr = f.0.to_u64_unchecked().into(); + let was_u64 = f_u64_roundtrip == f.0; + prop_assert_eq!(was_u64, cs.is_satisfied()); + } + } } diff --git a/src/field.rs b/src/field.rs index c59aef2d52..742f2fb0df 100644 --- a/src/field.rs +++ b/src/field.rs @@ -141,6 +141,18 @@ pub trait LurkField: PrimeField + PrimeFieldBits { Some(u64::from_le_bytes(byte_array)) } + /// Attempts to convert the field element to a u64 + fn to_u128(&self) -> Option { + for x in &self.to_repr().as_ref()[16..] { + if *x != 0 { + return None; + } + } + let mut byte_array = [0u8; 16]; + byte_array.copy_from_slice(&self.to_repr().as_ref()[0..16]); + Some(u128::from_le_bytes(byte_array)) + } + /// Converts the first 4 bytes of the field element to a u32 fn to_u32_unchecked(&self) -> u32 { let mut byte_array = [0u8; 4]; @@ -155,6 +167,13 @@ pub trait LurkField: PrimeField + PrimeFieldBits { u64::from_le_bytes(byte_array) } + /// Converts the first 16 bytes of the field element to a u128 + fn to_u128_unchecked(&self) -> u128 { + let mut byte_array = [0u8; 16]; + byte_array.copy_from_slice(&self.to_repr().as_ref()[0..16]); + u128::from_le_bytes(byte_array) + } + /// Constructs a field element from a u64 fn from_u64(x: u64) -> Self { x.into() diff --git a/src/lem/circuit.rs b/src/lem/circuit.rs index 58a79362d6..4a0a72c5d2 100644 --- a/src/lem/circuit.rs +++ b/src/lem/circuit.rs @@ -25,7 +25,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use anyhow::{Context, Result}; use bellpepper_core::{ - ConstraintSystem, + ConstraintSystem, SynthesisError, { boolean::{AllocatedBit, Boolean}, num::AllocatedNum, @@ -34,18 +34,21 @@ use bellpepper_core::{ use crate::circuit::gadgets::{ constraints::{ - add, alloc_equal, alloc_equal_const, and, enforce_selector_with_premise, implies_equal, - mul, sub, + add, alloc_equal, alloc_equal_const, alloc_is_zero, allocate_is_negative, and, + boolean_to_num, div, enforce_pack, enforce_product_and_sum, enforce_selector_with_premise, + implies_equal, implies_u64, mul, pick, sub, }, data::{allocate_constant, hash_poseidon}, 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 +177,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 +189,26 @@ impl Func { SlotType::Hash4 => { hash_poseidon(cs, preallocated_preimg, store.poseidon_cache.constants.c8())? } + SlotType::Commitment => { + hash_poseidon(cs, preallocated_preimg, store.poseidon_cache.constants.c3())? + } + SlotType::LessThan => { + let a_num = &preallocated_preimg[0]; + let b_num = &preallocated_preimg[1]; + let diff = sub( + &mut cs.namespace(|| format!("sub for slot {slot}")), + a_num, + b_num, + )?; + let diff_is_negative = allocate_is_negative( + &mut cs.namespace(|| format!("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 +217,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 +231,69 @@ 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::FPtr(f, ptr) => { + let z_ptr = store.hash_ptr(ptr)?; + // allocate first component + preallocated_preimg + .push(Self::allocate_preimg_component_for_slot(cs, &slot, 0, *f)?); + // allocate second component + preallocated_preimg.push(Self::allocate_preimg_component_for_slot( + cs, + &slot, + 1, + z_ptr.tag.to_field(), + )?); + // allocate third component + preallocated_preimg.push(Self::allocate_preimg_component_for_slot( + cs, &slot, 2, z_ptr.hash, + )?); + } + PreimageData::FPair(a, b) => { + // allocate first component + preallocated_preimg + .push(Self::allocate_preimg_component_for_slot(cs, &slot, 0, *a)?); + + // allocate second component + preallocated_preimg + .push(Self::allocate_preimg_component_for_slot(cs, &slot, 1, *b)?); + } } // Allocate the image by calling the arithmetic function according @@ -296,7 +350,7 @@ impl Func { // that's why they are filled with dummies let preallocated_hash2_slots = Func::allocate_slots( cs, - &frame.preimages.hash2_ptrs, + &frame.preimages.hash2, SlotType::Hash2, self.slot.hash2, store, @@ -304,7 +358,7 @@ impl Func { let preallocated_hash3_slots = Func::allocate_slots( cs, - &frame.preimages.hash3_ptrs, + &frame.preimages.hash3, SlotType::Hash3, self.slot.hash3, store, @@ -312,18 +366,36 @@ impl Func { let preallocated_hash4_slots = Func::allocate_slots( cs, - &frame.preimages.hash4_ptrs, + &frame.preimages.hash4, SlotType::Hash4, self.slot.hash4, store, )?; + let preallocated_commitment_slots = Func::allocate_slots( + cs, + &frame.preimages.commitment, + SlotType::Commitment, + self.slot.commitment, + store, + )?; + + let preallocated_less_than_slots = Func::allocate_slots( + cs, + &frame.preimages.less_than, + SlotType::LessThan, + self.slot.less_than, + 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_commitment_slots: Vec<(Vec>, AllocatedNum)>, + preallocated_less_than_slots: Vec<(Vec>, AllocatedNum)>, call_outputs: VecDeque>>, call_count: usize, } @@ -354,6 +426,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 +436,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 +445,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 +480,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(), @@ -513,10 +584,35 @@ impl Func { let allocated_ptr = AllocatedPtr::from_parts(tag, src.hash().clone()); bound_allocations.insert(tgt.clone(), allocated_ptr); } + Op::EqTag(tgt, a, b) => { + let a = bound_allocations.get(a)?; + let b = bound_allocations.get(b)?; + let a_num = a.tag(); + let b_num = b.tag(); + let eq = alloc_equal(&mut cs.namespace(|| "equal_tag"), a_num, b_num)?; + let c_num = boolean_to_num(&mut cs.namespace(|| "equal_tag.to_num"), &eq)?; + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let c = AllocatedPtr::from_parts(tag, c_num); + bound_allocations.insert(tgt.clone(), c); + } + Op::EqVal(tgt, a, b) => { + let a = bound_allocations.get(a)?; + let b = bound_allocations.get(b)?; + let a_num = a.hash(); + let b_num = b.hash(); + let eq = alloc_equal(&mut cs.namespace(|| "equal_val"), a_num, b_num)?; + let c_num = boolean_to_num(&mut cs.namespace(|| "equal_val.to_num"), &eq)?; + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let c = AllocatedPtr::from_parts(tag, c_num); + bound_allocations.insert(tgt.clone(), c); + } Op::Add(tgt, a, b) => { 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 c_num = add(&mut cs.namespace(|| "add"), a_num, b_num)?; @@ -529,7 +625,6 @@ impl Func { Op::Sub(tgt, a, b) => { 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 c_num = sub(&mut cs.namespace(|| "sub"), a_num, b_num)?; @@ -542,7 +637,6 @@ impl Func { Op::Mul(tgt, a, b) => { 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 c_num = mul(&mut cs.namespace(|| "mul"), a_num, b_num)?; @@ -552,26 +646,193 @@ impl Func { let c = AllocatedPtr::from_parts(tag, c_num); bound_allocations.insert(tgt.clone(), c); } - Op::Div(_tgt, _a, _b) => { - // TODO + Op::Div(tgt, a, b) => { + let a = bound_allocations.get(a)?; + let b = bound_allocations.get(b)?; + let a_num = a.hash(); + let b_num = b.hash(); + + let b_is_zero = &alloc_is_zero(&mut cs.namespace(|| "b_is_zero"), b_num)?; + let one = g.global_allocator.get_or_alloc_const(cs, F::ONE)?; + + let divisor = pick( + &mut cs.namespace(|| "maybe-dummy divisor"), + b_is_zero, + &one, + b_num, + )?; + + let quotient = div(&mut cs.namespace(|| "quotient"), a_num, &divisor)?; + + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let c = AllocatedPtr::from_parts(tag, quotient); + bound_allocations.insert(tgt.clone(), c); } - Op::Emit(_) => (), - Op::Hide(tgt, _sec, _pay) => { - // TODO - let allocated_ptr = AllocatedPtr::from_parts( - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, + Op::Lt(tgt, a, b) => { + let a = bound_allocations.get(a)?; + let b = bound_allocations.get(b)?; + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let (preallocated_preimg, lt) = + &g.preallocated_less_than_slots[next_slot.consume_less_than()]; + for (i, n) in [a.hash(), b.hash()].into_iter().enumerate() { + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for component {i} (OP {:?})", &op) + }), + not_dummy, + n, + &preallocated_preimg[i], + )?; + } + let c = AllocatedPtr::from_parts(tag, lt.clone()); + bound_allocations.insert(tgt.clone(), c); + } + Op::Trunc(tgt, a, n) => { + assert!(*n <= 64); + let a = bound_allocations.get(a)?; + let mut trunc_bits = a + .hash() + .to_bits_le_strict(&mut cs.namespace(|| "to_bits_le"))?; + trunc_bits.truncate(*n as usize); + let trunc = AllocatedNum::alloc(cs.namespace(|| "trunc"), || { + let b = if *n < 64 { (1 << *n) - 1 } else { u64::MAX }; + a.hash() + .get_value() + .map(|a| F::from_u64(a.to_u64_unchecked() & b)) + .ok_or(SynthesisError::AssignmentMissing) + })?; + enforce_pack(&mut cs.namespace(|| "enforce_trunc"), &trunc_bits, &trunc)?; + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let c = AllocatedPtr::from_parts(tag, trunc); + bound_allocations.insert(tgt.clone(), c); + } + Op::DivRem64(tgt, a, b) => { + let a = bound_allocations.get(a)?.hash(); + let b = bound_allocations.get(b)?.hash(); + let div_rem = a.get_value().and_then(|a| { + b.get_value().map(|b| { + if not_dummy.get_value().unwrap() { + let a = a.to_u64_unchecked(); + let b = b.to_u64_unchecked(); + (F::from_u64(a / b), F::from_u64(a % b)) + } else { + (F::ZERO, a) + } + }) + }); + let div = + AllocatedNum::alloc(cs.namespace(|| "div"), || Ok(div_rem.unwrap().0))?; + let rem = + AllocatedNum::alloc(cs.namespace(|| "rem"), || Ok(div_rem.unwrap().1))?; + + let diff = sub(cs.namespace(|| "diff for slot {slot}"), b, &rem)?; + implies_u64(cs.namespace(|| "div_u64"), not_dummy, &div)?; + implies_u64(cs.namespace(|| "rem_u64"), not_dummy, &rem)?; + implies_u64(cs.namespace(|| "diff_u64"), not_dummy, &diff)?; + + enforce_product_and_sum( + cs, + || "enforce a = b * div + rem", + b, + &div, + &rem, + a, ); + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let div_ptr = AllocatedPtr::from_parts(tag.clone(), div); + let rem_ptr = AllocatedPtr::from_parts(tag, rem); + bound_allocations.insert(tgt[0].clone(), div_ptr); + bound_allocations.insert(tgt[1].clone(), rem_ptr); + } + Op::Emit(_) => (), + Op::Hide(tgt, sec, pay) => { + let sec = bound_allocations.get(sec)?; + let pay = bound_allocations.get(pay)?; + let sec_tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let (preallocated_preimg, hash) = + &g.preallocated_commitment_slots[next_slot.consume_commitment()]; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for the secret's tag (OP {:?})", &op) + }), + not_dummy, + sec.tag(), + &sec_tag, + )?; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for the secret's hash (OP {:?})", &op) + }), + not_dummy, + sec.hash(), + &preallocated_preimg[0], + )?; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for the payload's tag (OP {:?})", &op) + }), + not_dummy, + pay.tag(), + &preallocated_preimg[1], + )?; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for the payload's hash (OP {:?})", &op) + }), + not_dummy, + pay.hash(), + &preallocated_preimg[2], + )?; + let tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Comm).to_field())?; + let allocated_ptr = AllocatedPtr::from_parts(tag, hash.clone()); bound_allocations.insert(tgt.clone(), allocated_ptr); } - Op::Open(pay, sec, _comm_or_num) => { - // TODO - let allocated_ptr = AllocatedPtr::from_parts( - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, + Op::Open(sec, pay, comm) => { + let comm = bound_allocations.get(comm)?; + let (preallocated_preimg, com_hash) = + &g.preallocated_commitment_slots[next_slot.consume_commitment()]; + let comm_tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Comm).to_field())?; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for comm's tag (OP {:?})", &op) + }), + not_dummy, + comm.tag(), + &comm_tag, + )?; + implies_equal( + &mut cs.namespace(|| { + format!("implies equal for comm's hash (OP {:?})", &op) + }), + not_dummy, + comm.hash(), + com_hash, + )?; + let sec_tag = g + .global_allocator + .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + let allocated_sec_ptr = + AllocatedPtr::from_parts(sec_tag, preallocated_preimg[0].clone()); + let allocated_pay_ptr = AllocatedPtr::from_parts( + preallocated_preimg[1].clone(), + preallocated_preimg[2].clone(), ); - bound_allocations.insert(pay.clone(), allocated_ptr.clone()); - bound_allocations.insert(sec.clone(), allocated_ptr); + bound_allocations.insert(sec.clone(), allocated_sec_ptr); + bound_allocations.insert(pay.clone(), allocated_pay_ptr); } } } @@ -596,10 +857,8 @@ impl Func { Ctrl::IfEq(x, y, eq_block, else_block) => { let x = bound_allocations.get(x)?.hash(); let y = bound_allocations.get(y)?.hash(); - // TODO should we check whether the tags are equal too? let eq = alloc_equal(&mut cs.namespace(|| "if_eq.alloc_equal"), x, y)?; let not_eq = eq.not(); - // TODO is this the most efficient way of doing if statements? let not_dummy_and_eq = and(&mut cs.namespace(|| "if_eq.and"), not_dummy, &eq)?; let not_dummy_and_not_eq = and(&mut cs.namespace(|| "if_eq.and.2"), not_dummy, ¬_eq)?; @@ -709,18 +968,18 @@ impl Func { let allocated_lit = bound_allocations.get(match_var)?.hash().clone(); let mut selector = Vec::with_capacity(cases.len() + 1); let mut branch_slots = Vec::with_capacity(cases.len()); - for (lit, block) in cases { + for (i, (lit, block)) in cases.iter().enumerate() { let lit_ptr = lit.to_ptr(g.store); let lit_hash = g.store.hash_ptr(&lit_ptr)?.hash; let allocated_has_match = alloc_equal_const( - &mut cs.namespace(|| format!("{:?}.alloc_equal_const", lit)), + &mut cs.namespace(|| format!("{i}.alloc_equal_const")), &allocated_lit, lit_hash, ) .with_context(|| "couldn't allocate equal const")?; let not_dummy_and_has_match = and( - &mut cs.namespace(|| format!("{:?}.and", lit)), + &mut cs.namespace(|| format!("{i}.and")), not_dummy, &allocated_has_match, ) @@ -730,7 +989,7 @@ impl Func { let mut branch_slot = *next_slot; recurse( - &mut cs.namespace(|| format!("{:?}", lit)), + &mut cs.namespace(|| format!("{i}.case")), block, ¬_dummy_and_has_match, &mut branch_slot, @@ -803,6 +1062,8 @@ impl Func { preallocated_hash2_slots, preallocated_hash3_slots, preallocated_hash4_slots, + preallocated_commitment_slots, + preallocated_less_than_slots, call_outputs, call_count: 0, }, @@ -839,12 +1100,31 @@ impl Func { Op::Cast(_tgt, tag, _src) => { globals.insert(FWrap(tag.to_field())); } + Op::EqTag(_, _, _) | Op::EqVal(_, _, _) => { + globals.insert(FWrap(Tag::Expr(Num).to_field())); + num_constraints += 5; + } Op::Add(_, _, _) | Op::Sub(_, _, _) | Op::Mul(_, _, _) => { globals.insert(FWrap(Tag::Expr(Num).to_field())); num_constraints += 1; } Op::Div(_, _, _) => { - // TODO + globals.insert(FWrap(F::ONE)); + num_constraints += 5; + } + Op::Lt(_, _, _) => { + globals.insert(FWrap(Tag::Expr(Num).to_field())); + num_constraints += 2; + } + Op::Trunc(_, _, _) => { + globals.insert(FWrap(Tag::Expr(Num).to_field())); + // bit decomposition + enforce_pack + num_constraints += 389; + } + Op::DivRem64(_, _, _) => { + globals.insert(FWrap(Tag::Expr(Num).to_field())); + // three implies_u64, one sub and one linear + num_constraints += 197; } Op::Emit(_) => (), Op::Hash2(_, tag, _) => { @@ -869,9 +1149,15 @@ impl Func { // one constraint for the image's hash num_constraints += 1; } - Op::Hide(..) | Op::Open(..) => { - // TODO - globals.insert(FWrap(F::ZERO)); + Op::Hide(..) => { + num_constraints += 4; + globals.insert(FWrap(Tag::Expr(Num).to_field())); + globals.insert(FWrap(Tag::Expr(Comm).to_field())); + } + Op::Open(..) => { + num_constraints += 2; + globals.insert(FWrap(Tag::Expr(Num).to_field())); + globals.insert(FWrap(Tag::Expr(Comm).to_field())); } } } @@ -925,8 +1211,11 @@ 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 + + 265 * self.slot.commitment + + 391 * self.slot.less_than; 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 a82b177983..d47ca5aef7 100644 --- a/src/lem/eval.rs +++ b/src/lem/eval.rs @@ -19,7 +19,8 @@ pub(crate) fn eval_step() -> Func { fn safe_uncons() -> Func { func!(safe_uncons(xs): 2 => { - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let nilstr = Symbol(""); match xs.tag { Expr::Nil => { @@ -56,7 +57,8 @@ fn reduce() -> Func { match args.tag { Expr::Nil => { let dummy = Symbol("dummy"); - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); return (dummy, nil) } Expr::Cons => { @@ -88,7 +90,8 @@ fn reduce() -> Func { } }); let is_unop = func!(is_unop(head): 1 => { - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let t = Symbol("t"); match head.val { Symbol("car") @@ -109,7 +112,8 @@ fn reduce() -> Func { }); let is_binop = func!(is_binop(head): 1 => { - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let t = Symbol("t"); match head.val { Symbol("cons") @@ -118,8 +122,7 @@ fn reduce() -> Func { | Symbol("+") | Symbol("-") | Symbol("*") - // TODO: bellperson complains if we use "/" - | Symbol("div") + | Symbol("/") | Symbol("%") | Symbol("=") | Symbol("eq") @@ -134,7 +137,8 @@ fn reduce() -> Func { }); let is_potentially_fun = func!(is_potentially_fun(head): 1 => { let t = Symbol("t"); - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); match head.tag { Expr::Fun | Expr::Cons | Expr::Sym | Expr::Thunk => { return (t) @@ -149,7 +153,8 @@ fn reduce() -> Func { let apply: Ctrl::ApplyContinuation; let errctrl: Ctrl::Error; let err: Cont::Error; - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let t = Symbol("t"); match cont.tag { @@ -432,7 +437,8 @@ fn apply_cont() -> Func { match var_or_binding.tag { // It's a var, so we are extending a simple env with a recursive env. Expr::Sym | Expr::Nil => { - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let list: Expr::Cons = hash2(cons, nil); let res: Expr::Cons = hash2(list, env); return (res) @@ -446,14 +452,51 @@ fn apply_cont() -> Func { } } }); + // Returns 2 if both arguments are U64, 1 if the arguments are some kind of number (either U64 or Num), + // and 0 otherwise + let args_num_type = func!(args_num_type(arg1, arg2): 1 => { + let other = Num(0); + match arg1.tag { + Expr::Num => { + match arg2.tag { + Expr::Num => { + let ret = Num(1); + return (ret) + } + Expr::U64 => { + let ret = Num(1); + return (ret) + } + }; + return (other) + } + Expr::U64 => { + match arg2.tag { + Expr::Num => { + let ret = Num(1); + return (ret) + } + Expr::U64 => { + let ret = Num(2); + return (ret) + } + }; + return (other) + } + }; + return (other) + }); func!(apply_cont(result, env, cont, ctrl): 4 => { // Useful constants let ret: Ctrl::Return; let makethunk: Ctrl::MakeThunk; let errctrl: Ctrl::Error; let err: Cont::Error; - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let t = Symbol("t"); + let zero = Num(0); + let size_u64 = Num(18446744073709551616); match ctrl.tag { Ctrl::ApplyContinuation => { @@ -583,7 +626,6 @@ fn apply_cont() -> Func { return(secret, env, continuation, makethunk) } Symbol("commit") => { - let zero = Num(0); let comm = hide(zero, result); return(comm, env, continuation, makethunk) } @@ -599,11 +641,9 @@ fn apply_cont() -> Func { Symbol("u64") => { match result.tag { Expr::Num => { - // TODO we also need to use `Mod` to truncate - // But 2^64 is out-of-range of u64, so we will - // maybe use u128 - // let limit = Num(18446744073709551616); - let cast = cast(result, Expr::U64); + // The limit is 2**64 - 1 + let trunc = truncate(result, 64); + let cast = cast(trunc, Expr::U64); return(cast, env, continuation, makethunk) } Expr::U64 => { @@ -615,7 +655,7 @@ fn apply_cont() -> Func { Symbol("comm") => { match result.tag { Expr::Num | Expr::Comm => { - let cast = cast(result, Expr::Num); + let cast = cast(result, Expr::Comm); return(cast, env, continuation, makethunk) } }; @@ -623,12 +663,15 @@ fn apply_cont() -> Func { } Symbol("char") => { match result.tag { - Expr::Num | Expr::Char => { - // TODO we also need to use `Mod` to truncate - // let limit = Num(4294967296); - let cast = cast(result, Expr::Num); + Expr::Num => { + // The limit is 2**32 - 1 + let trunc = truncate(result, 32); + let cast = cast(trunc, Expr::Char); return(cast, env, continuation, makethunk) } + Expr::Char => { + return(result, env, continuation, makethunk) + } }; return(result, env, err, errctrl) } @@ -663,6 +706,7 @@ fn apply_cont() -> Func { } Cont::Binop2 => { let (operator, evaled_arg, continuation) = unhash3(cont); + let (args_num_type) = args_num_type(evaled_arg, result); match operator.val { Symbol("eval") => { return (evaled_arg, result, continuation, ret) @@ -691,53 +735,171 @@ fn apply_cont() -> Func { return(hidden, env, continuation, makethunk) } Symbol("eq") => { - // TODO should we check whether the tags are also equal? - if evaled_arg == result { - return (t, env, continuation, makethunk) + let eq_tag = eq_tag(evaled_arg, result); + let eq_val = eq_val(evaled_arg, result); + let eq = mul(eq_tag, eq_val); + match eq.val { + Num(0) => { + return (nil, env, continuation, makethunk) + } + Num(1) => { + return (t, env, continuation, makethunk) + } } - return (nil, env, continuation, makethunk) } Symbol("+") => { - // TODO deal with U64 - let val = add(evaled_arg, result); - return (val, env, continuation, makethunk) + match args_num_type.val { + Num(0) => { + return (result, env, err, errctrl) + } + Num(1) => { + let val = add(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Num(2) => { + let val = add(evaled_arg, result); + let not_overflow = lt(val, size_u64); + match not_overflow.val { + Num(0) => { + let val = sub(val, size_u64); + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + Num(1) => { + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + } + } + } } Symbol("-") => { - // TODO deal with U64 - let val = sub(evaled_arg, result); - return (val, env, continuation, makethunk) + match args_num_type.val { + Num(0) => { + return (result, env, err, errctrl) + } + Num(1) => { + let val = sub(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Num(2) => { + // Subtraction in U64 is almost the same as subtraction + // in the field. If the difference is negative, we need + // to add 2^64 to get back to U64 domain. + let val = sub(evaled_arg, result); + let is_neg = lt(val, zero); + match is_neg.val { + Num(0) => { + let val = add(val, size_u64); + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + Num(1) => { + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + } + } + } } Symbol("*") => { - // TODO deal with U64 - let val = mul(evaled_arg, result); - return (val, env, continuation, makethunk) + match args_num_type.val { + Num(0) => { + return (result, env, err, errctrl) + } + Num(1) => { + let val = mul(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Num(2) => { + let val = mul(evaled_arg, result); + // The limit is 2**64 - 1 + let trunc = truncate(val, 64); + let cast = cast(trunc, Expr::U64); + return (cast, env, continuation, makethunk) + } + } } - Symbol("div") => { - return (result, env, err, errctrl) + Symbol("/") => { + match args_num_type.val { + Num(0) => { + return (result, env, err, errctrl) + } + Num(1) => { + let val = div(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Num(2) => { + let (div, _rem) = div_rem64(evaled_arg, result); + let div = cast(div, Expr::U64); + return (div, env, continuation, makethunk) + } + } } Symbol("%") => { - // TODO + match args_num_type.val { + Num(2) => { + let (_div, rem) = div_rem64(evaled_arg, result); + let rem = cast(rem, Expr::U64); + return (rem, env, continuation, makethunk) + } + }; return (result, env, err, errctrl) } Symbol("=") => { - // TODO - return (result, env, err, errctrl) + match args_num_type.val { + Num(0) => { + return (result, env, err, errctrl) + } + }; + if evaled_arg == result { + return (t, env, continuation, makethunk) + } + return (nil, env, continuation, makethunk) } Symbol("<") => { - // TODO - return (result, env, err, errctrl) + let val = lt(evaled_arg, result); + match val.val { + Num(0) => { + return (nil, env, continuation, makethunk) + } + Num(1) => { + return (t, env, continuation, makethunk) + } + } } Symbol(">") => { - // TODO - return (result, env, err, errctrl) + let val = lt(result, evaled_arg); + match val.val { + Num(0) => { + return (nil, env, continuation, makethunk) + } + Num(1) => { + return (t, env, continuation, makethunk) + } + } } Symbol("<=") => { - // TODO - return (result, env, err, errctrl) + let val = lt(result, evaled_arg); + match val.val { + Num(0) => { + return (t, env, continuation, makethunk) + } + Num(1) => { + return (nil, env, continuation, makethunk) + } + } } Symbol(">=") => { - // TODO - return (result, env, err, errctrl) + let val = lt(evaled_arg, result); + match val.val { + Num(0) => { + return (t, env, continuation, makethunk) + } + Num(1) => { + return (nil, env, continuation, makethunk) + } + } } }; return (result, env, err, errctrl) @@ -809,12 +971,14 @@ mod tests { use blstrs::Scalar as Fr; const NUM_INPUTS: usize = 1; - const NUM_AUX: usize = 8092; - const NUM_CONSTRAINTS: usize = 10125; + const NUM_AUX: usize = 10442; + const NUM_CONSTRAINTS: usize = 12703; const NUM_SLOTS: SlotsCounter = SlotsCounter { hash2: 16, hash3: 4, hash4: 2, + commitment: 1, + less_than: 1, }; fn test_eval_and_constrain_aux(store: &mut Store, pairs: Vec<(Ptr, Ptr)>) { @@ -863,13 +1027,36 @@ mod tests { fn expr_in_expr_out_pairs(s: &mut Store) -> Vec<(Ptr, Ptr)> { let state = State::init_lurk_state().rccell(); let mut read = |code: &str| s.read(state.clone(), code).unwrap(); + let div = read("(/ 70u64 8u64)"); + let div_res = read("8u64"); + let rem = read("(% 70u64 8u64)"); + let rem_res = read("6u64"); + let u64_1 = read("(u64 100000000)"); + let u64_1_res = read("100000000u64"); + let u64_2 = read("(u64 1000000000000000000000000)"); + let u64_2_res = read("2003764205206896640u64"); + let mul_overflow = read("(* 1000000000000u64 100000000000000u64)"); + let mul_overflow_res = read("15908979783594147840u64"); + let char_conv = read("(char 97)"); + let char_conv_res = read("'a'"); + let char_overflow = read("(char 4294967393)"); + let char_overflow_res = read("'a'"); + let t = read("t"); + let nil = read("nil"); + let le1 = read("(<= 4 8)"); + let le2 = read("(<= 8 8)"); + let le3 = read("(<= 10 8)"); + let gt1 = read("(> 4 8)"); + let gt2 = read("(> 8 8)"); + let gt3 = read("(> 10 8)"); + let ltz = read("(< (- 0 10) 0)"); let sum = read("(+ 21 21)"); let sum_res = read("42"); let car = read("(car (cons 1 2))"); let car_res = read("1"); let let_ = read( "(let ((x (cons 1 2))) - (cons (car x) (cdr x)))", + (cons (car x) (cdr x)))", ); let let_res = read("(1 . 2)"); let lam0 = read("((lambda () 1))"); @@ -889,6 +1076,20 @@ mod tests { ); let fold_res = read("55"); vec![ + (div, div_res), + (rem, rem_res), + (u64_1, u64_1_res), + (u64_2, u64_2_res), + (mul_overflow, mul_overflow_res), + (char_conv, char_conv_res), + (char_overflow, char_overflow_res), + (le1, t), + (le2, t), + (le3, nil), + (gt1, nil), + (gt2, nil), + (gt3, t), + (ltz, t), (sum, sum_res), (car, car_res), (let_, let_res), diff --git a/src/lem/interpreter.rs b/src/lem/interpreter.rs index 8cc7a9dea7..555a1046a4 100644 --- a/src/lem/interpreter.rs +++ b/src/lem/interpreter.rs @@ -1,4 +1,5 @@ use crate::field::{FWrap, LurkField}; +use crate::num::Num; use anyhow::{bail, Result}; use std::collections::VecDeque; @@ -8,29 +9,42 @@ use super::{ use crate::tag::ExprTag::*; +#[derive(Clone)] +pub enum PreimageData { + PtrVec(Vec>), + FPtr(F, Ptr), + 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: Vec>>, + pub hash3: Vec>>, + pub hash4: Vec>>, + pub commitment: Vec>>, + pub less_than: Vec>>, pub call_outputs: VecDeque>>, } impl Preimages { pub fn new_from_func(func: &Func) -> Preimages { let slot = func.slot; - 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 hash2 = Vec::with_capacity(slot.hash2); + let hash3 = Vec::with_capacity(slot.hash3); + let hash4 = Vec::with_capacity(slot.hash4); + let commitment = Vec::with_capacity(slot.commitment); + let less_than = Vec::with_capacity(slot.less_than); let call_outputs = VecDeque::new(); Preimages { - hash2_ptrs, - hash3_ptrs, - hash4_ptrs, + hash2, + hash3, + hash4, + commitment, + less_than, call_outputs, } } @@ -97,66 +111,136 @@ impl Block { let tgt_ptr = src_ptr.cast(*tag); bindings.insert(tgt.clone(), tgt_ptr); } + Op::EqTag(tgt, a, b) => { + let a = bindings.get(a)?; + let b = bindings.get(b)?; + let c = if a.tag() == b.tag() { + Ptr::Leaf(Tag::Expr(Num), F::ONE) + } else { + Ptr::Leaf(Tag::Expr(Num), F::ZERO) + }; + bindings.insert(tgt.clone(), c); + } + Op::EqVal(tgt, a, b) => { + let a = bindings.get(a)?; + let b = bindings.get(b)?; + // In order to compare Ptrs, we *must* resolve the hashes. Otherwise, we risk failing to recognize equality of + // compound data with opaque data in either element's transitive closure. + let a_hash = store.hash_ptr(a)?.hash; + let b_hash = store.hash_ptr(b)?.hash; + let c = if a_hash == b_hash { + Ptr::Leaf(Tag::Expr(Num), F::ONE) + } else { + Ptr::Leaf(Tag::Expr(Num), F::ZERO) + }; + bindings.insert(tgt.clone(), c); + } Op::Add(tgt, a, b) => { let a = bindings.get(a)?; let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f + *g) - } - _ => bail!("Addition only works on numbers"), + let c = if let (Ptr::Leaf(_, f), Ptr::Leaf(_, g)) = (a, b) { + Ptr::Leaf(Tag::Expr(Num), *f + *g) + } else { + bail!("`Add` only works on leaves") }; bindings.insert(tgt.clone(), c); } Op::Sub(tgt, a, b) => { let a = bindings.get(a)?; let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f - *g) - } - _ => bail!("Addition only works on numbers"), + let c = if let (Ptr::Leaf(_, f), Ptr::Leaf(_, g)) = (a, b) { + Ptr::Leaf(Tag::Expr(Num), *f - *g) + } else { + bail!("`Sub` only works on leaves") }; bindings.insert(tgt.clone(), c); } Op::Mul(tgt, a, b) => { let a = bindings.get(a)?; let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f * *g) - } - _ => bail!("Addition only works on numbers"), + let c = if let (Ptr::Leaf(_, f), Ptr::Leaf(_, g)) = (a, b) { + Ptr::Leaf(Tag::Expr(Num), *f * *g) + } else { + bail!("`Mul` only works on leaves") }; bindings.insert(tgt.clone(), c); } Op::Div(tgt, a, b) => { let a = bindings.get(a)?; let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f * g.invert().unwrap()) + let c = if let (Ptr::Leaf(_, f), Ptr::Leaf(_, g)) = (a, b) { + if g == &F::ZERO { + bail!("Can't divide by zero") } - _ => bail!("Division only works on numbers"), + Ptr::Leaf(Tag::Expr(Num), *f * g.invert().expect("not zero")) + } else { + bail!("`Div` only works on numbers") + }; + bindings.insert(tgt.clone(), c); + } + Op::Lt(tgt, a, b) => { + let a = bindings.get(a)?; + let b = bindings.get(b)?; + let c = if let (Ptr::Leaf(_, f), Ptr::Leaf(_, g)) = (a, b) { + preimages.less_than.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 }; + Ptr::Leaf(Tag::Expr(Num), b) + } else { + bail!("`Lt` only works on leaves") + }; + bindings.insert(tgt.clone(), c); + } + Op::Trunc(tgt, a, n) => { + assert!(*n <= 64); + let a = bindings.get(a)?; + let c = if let Ptr::Leaf(_, f) = a { + let b = if *n < 64 { (1 << *n) - 1 } else { u64::MAX }; + Ptr::Leaf(Tag::Expr(Num), F::from_u64(f.to_u64_unchecked() & b)) + } else { + bail!("`Trunc` only works a leaf") }; bindings.insert(tgt.clone(), c); } + Op::DivRem64(tgt, a, b) => { + let a = bindings.get(a)?; + let b = bindings.get(b)?; + let (c1, c2) = if let (Ptr::Leaf(_, f), Ptr::Leaf(_, g)) = (a, b) { + if g == &F::ZERO { + bail!("Can't divide by zero") + } + let f = f.to_u64_unchecked(); + let g = g.to_u64_unchecked(); + let c1 = Ptr::Leaf(Tag::Expr(Num), F::from_u64(f / g)); + let c2 = Ptr::Leaf(Tag::Expr(Num), F::from_u64(f % g)); + (c1, c2) + } else { + bail!("`DivRem64` only works on leaves") + }; + bindings.insert(tgt[0].clone(), c1); + bindings.insert(tgt[1].clone(), c2); + } Op::Emit(a) => { let a = bindings.get(a)?; - println!("{}", a.to_string(store)) + println!("{}", a.dbg_display(store)) } Op::Hash2(img, tag, preimg) => { 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 + .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 + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } Op::Hash4(img, tag, preimg) => { let preimg_ptrs = bindings.get_many_cloned(preimg)?; @@ -168,7 +252,9 @@ impl Block { preimg_ptrs[3], ); bindings.insert(img.clone(), tgt_ptr); - preimages.hash4_ptrs.push(Some(preimg_ptrs)); + preimages + .hash4 + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } Op::Unhash2(preimg, img) => { let img_ptr = bindings.get(img)?; @@ -182,7 +268,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 + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Unhash3(preimg, img) => { let img_ptr = bindings.get(img)?; @@ -196,7 +284,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 + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Unhash4(preimg, img) => { let img_ptr = bindings.get(img)?; @@ -210,7 +300,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 + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Hide(tgt, sec, src) => { let src_ptr = bindings.get(src)?; @@ -224,20 +316,24 @@ impl Block { .hash3(&[*secret, z_ptr.tag.to_field(), z_ptr.hash]); let tgt_ptr = Ptr::comm(hash); store.comms.insert(FWrap::(hash), (*secret, *src_ptr)); + preimages + .commitment + .push(Some(PreimageData::FPtr(*secret, *src_ptr))); bindings.insert(tgt.clone(), tgt_ptr); } - Op::Open(tgt_secret, tgt_ptr, comm_or_num) => match bindings.get(comm_or_num)? { - Ptr::Leaf(Tag::Expr(Num), hash) | Ptr::Leaf(Tag::Expr(Comm), hash) => { - let Some((secret, ptr)) = store.comms.get(&FWrap::(*hash)) else { - bail!("No committed data for hash {}", &hash.hex_digits()) - }; - bindings.insert(tgt_ptr.clone(), *ptr); - bindings.insert(tgt_secret.clone(), Ptr::Leaf(Tag::Expr(Num), *secret)); - } - _ => { - bail!("{comm_or_num} is not a num/comm pointer") - } - }, + Op::Open(tgt_secret, tgt_ptr, comm) => { + let Ptr::Leaf(Tag::Expr(Comm), hash) = bindings.get(comm)? else { + bail!("{comm} is not a comm pointer") + }; + let Some((secret, ptr)) = store.comms.get(&FWrap::(*hash)) else { + bail!("No committed data for hash {}", &hash.hex_digits()) + }; + bindings.insert(tgt_ptr.clone(), *ptr); + bindings.insert(tgt_secret.clone(), Ptr::Leaf(Tag::Expr(Num), *secret)); + preimages + .commitment + .push(Some(PreimageData::FPtr(*secret, *ptr))) + } } } match &self.ctrl { @@ -326,26 +422,37 @@ impl Func { // We must fill any unused slots with `None` values so we save // the initial size of preimages, which might not be zero - let hash2_init = preimages.hash2_ptrs.len(); - let hash3_init = preimages.hash3_ptrs.len(); - let hash4_init = preimages.hash4_ptrs.len(); + let hash2_init = preimages.hash2.len(); + let hash3_init = preimages.hash3.len(); + let hash4_init = preimages.hash4.len(); + let commitment_init = preimages.commitment.len(); + let less_than_init = preimages.less_than.len(); let mut res = self .body .run(args, store, bindings, preimages, Path::default())?; let preimages = &mut res.0.preimages; - 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 hash2_used = preimages.hash2.len() - hash2_init; + let hash3_used = preimages.hash3.len() - hash3_init; + let hash4_used = preimages.hash4.len() - hash4_init; + let commitment_used = preimages.commitment.len() - commitment_init; + let less_than_used = preimages.less_than.len() - less_than_init; + for _ in hash2_used..self.slot.hash2 { - preimages.hash2_ptrs.push(None); + preimages.hash2.push(None); } for _ in hash3_used..self.slot.hash3 { - preimages.hash3_ptrs.push(None); + preimages.hash3.push(None); } for _ in hash4_used..self.slot.hash4 { - preimages.hash4_ptrs.push(None); + preimages.hash4.push(None); + } + for _ in commitment_used..self.slot.commitment { + preimages.commitment.push(None); + } + for _ in less_than_used..self.slot.less_than { + preimages.less_than.push(None); } Ok(res) diff --git a/src/lem/macros.rs b/src/lem/macros.rs index 35c904fddf..10869a34be 100644 --- a/src/lem/macros.rs +++ b/src/lem/macros.rs @@ -58,6 +58,20 @@ macro_rules! op { $crate::var!($src), ) }; + ( let $tgt:ident = eq_tag($a:ident, $b:ident) ) => { + $crate::lem::Op::EqTag( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; + ( let $tgt:ident = eq_val($a:ident, $b:ident) ) => { + $crate::lem::Op::EqVal( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; ( let $tgt:ident = add($a:ident, $b:ident) ) => { $crate::lem::Op::Add( $crate::var!($tgt), @@ -86,6 +100,27 @@ macro_rules! op { $crate::var!($b), ) }; + ( let $tgt:ident = lt($a:ident, $b:ident) ) => { + $crate::lem::Op::Lt( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; + ( let $tgt:ident = truncate($a:ident, $b:literal) ) => { + $crate::lem::Op::Trunc( + $crate::var!($tgt), + $crate::var!($a), + $b, + ) + }; + ( let ($tgt1:ident, $tgt2:ident) = div_rem64($a:ident, $b:ident) ) => { + $crate::lem::Op::DivRem64( + $crate::vars!($tgt1, $tgt2), + $crate::var!($a), + $crate::var!($b), + ) + }; ( emit($v:ident) ) => { $crate::lem::Op::Emit($crate::var!($v)) }; @@ -243,6 +278,26 @@ macro_rules! block { $($tail)* ) }; + (@seq {$($limbs:expr)*}, let $tgt:ident = eq_tag($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = eq_tag($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = eq_val($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = eq_val($a, $b)) + }, + $($tail)* + ) + }; (@seq {$($limbs:expr)*}, let $tgt:ident = add($a:ident, $b:ident) ; $($tail:tt)*) => { $crate::block! ( @seq @@ -283,6 +338,36 @@ macro_rules! block { $($tail)* ) }; + (@seq {$($limbs:expr)*}, let $tgt:ident = lt($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = lt($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = truncate($a:ident, $b:literal) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = truncate($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident) = div_rem64($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let ($tgt1, $tgt2) = div_rem64($a, $b)) + }, + $($tail)* + ) + }; (@seq {$($limbs:expr)*}, emit($v:ident) ; $($tail:tt)*) => { $crate::block! ( @seq diff --git a/src/lem/mod.rs b/src/lem/mod.rs index 900c588d6c..6ce5278a9e 100644 --- a/src/lem/mod.rs +++ b/src/lem/mod.rs @@ -155,8 +155,8 @@ impl std::fmt::Display for Tag { /// LEM literals #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum Lit { - // TODO maybe it should be a LurkField instead of u64 - Num(u64), + // TODO maybe it should be a LurkField instead of u128 + Num(u128), String(String), Symbol(Symbol), } @@ -166,7 +166,7 @@ impl Lit { match self { Self::Symbol(s) => store.intern_symbol(s), Self::String(s) => store.intern_string(s), - Self::Num(num) => Ptr::num((*num).into()), + Self::Num(num) => Ptr::num(F::from_u128(*num)), } } pub fn from_ptr(ptr: &Ptr, store: &Store) -> Option { @@ -175,7 +175,7 @@ impl Lit { match ptr.tag() { Expr(Num) => match ptr { Ptr::Leaf(_, f) => { - let num = LurkField::to_u64_unchecked(f); + let num = LurkField::to_u128_unchecked(f); Some(Self::Num(num)) } _ => unreachable!(), @@ -238,6 +238,10 @@ pub enum Op { /// `Cast(y, t, x)` binds `y` to a pointer with tag `t` and the hash of `x` Cast(Var, Tag, Var), /// `Add(y, a, b)` binds `y` to the sum of `a` and `b` + EqTag(Var, Var, Var), + /// `EqVal(y, a, b)` binds `y` to `1` if `a.val != b.val`, or to `0` otherwise + EqVal(Var, Var, Var), + /// `Lt(y, a, b)` binds `y` to `1` if `a < b`, or to `0` otherwise Add(Var, Var, Var), /// `Sub(y, a, b)` binds `y` to the sum of `a` and `b` Sub(Var, Var, Var), @@ -245,6 +249,12 @@ pub enum Op { Mul(Var, Var, Var), /// `Div(y, a, b)` binds `y` to the sum of `a` and `b` Div(Var, Var, Var), + /// `Lt(y, a, b)` binds `y` to `1` if `a < b`, or to `0` otherwise + Lt(Var, Var, Var), + /// `Trunc(y, a, n)` binds `y` to `a` truncated to `n` bits, up to 64 bits + Trunc(Var, Var, u32), + /// `DivRem64(ys, a, b)` binds `ys` to `(a / b, a % b)` as if they were u64 + DivRem64([Var; 2], Var, Var), /// `Emit(v)` simply prints out the value of `v` when interpreting the code Emit(Var), /// `Hash2(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 2 children `ys` @@ -290,23 +300,26 @@ impl Func { /// Performs the static checks described in LEM's docstring. pub fn check(&self) -> Result<()> { - // Check if variable has already been defined. Panics - // if it is repeated (means `deconflict` is broken) use std::collections::{HashMap, HashSet}; - #[inline(always)] + + /// Check if variable has already been defined. Panics + /// if it is repeated (means `deconflict` is broken) + #[inline] fn is_unique(var: &Var, map: &mut HashMap) { if map.insert(var.clone(), false).is_some() { panic!("Variable {var} already defined. `deconflict` implementation broken."); } } - // Check if variable is bound and sets it as "used" - #[inline(always)] + + /// Check if variable is bound and sets it as "used" + #[inline] fn is_bound(var: &Var, map: &mut HashMap) -> Result<()> { if map.insert(var.clone(), true).is_none() { bail!("Variable {var} is unbound."); } Ok(()) } + fn recurse(block: &Block, return_size: usize, map: &mut HashMap) -> Result<()> { for op in &block.ops { match op { @@ -340,14 +353,29 @@ impl Func { is_bound(src, map)?; is_unique(tgt, map); } - Op::Add(tgt, a, b) + Op::EqTag(tgt, a, b) + | Op::EqVal(tgt, a, b) + | Op::Add(tgt, a, b) | Op::Sub(tgt, a, b) | Op::Mul(tgt, a, b) - | Op::Div(tgt, a, b) => { + | Op::Div(tgt, a, b) + | Op::Lt(tgt, a, b) => { is_bound(a, map)?; is_bound(b, map)?; is_unique(tgt, map); } + Op::Trunc(tgt, a, n) => { + if *n > 64 { + bail!("Cannot yet truncate over 64 bits") + } + is_bound(a, map)?; + is_unique(tgt, map); + } + Op::DivRem64(tgt, a, b) => { + is_bound(a, map)?; + is_bound(b, map)?; + tgt.iter().for_each(|var| is_unique(var, map)) + } Op::Emit(a) => { is_bound(a, map)?; } @@ -551,6 +579,18 @@ impl Block { let tgt = insert_one(map, uniq, &tgt); ops.push(Op::Cast(tgt, tag, src)) } + Op::EqTag(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::EqTag(tgt, a, b)) + } + Op::EqVal(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::EqVal(tgt, a, b)) + } Op::Add(tgt, a, b) => { let a = map.get_cloned(&a)?; let b = map.get_cloned(&b)?; @@ -575,6 +615,23 @@ impl Block { let tgt = insert_one(map, uniq, &tgt); ops.push(Op::Div(tgt, a, b)) } + Op::Lt(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::Lt(tgt, a, b)) + } + Op::Trunc(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::Trunc(tgt, a, b)) + } + Op::DivRem64(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_many(map, uniq, &tgt); + ops.push(Op::DivRem64(tgt.try_into().unwrap(), a, b)) + } Op::Emit(a) => { let a = map.get_cloned(&a)?; ops.push(Op::Emit(a)) @@ -796,7 +853,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, 0))); } #[test] @@ -857,7 +914,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, 0))); } #[test] @@ -891,7 +948,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, 0))); } #[test] @@ -938,6 +995,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, 0))); } } diff --git a/src/lem/pointers.rs b/src/lem/pointers.rs index e46713f410..c1daf77193 100644 --- a/src/lem/pointers.rs +++ b/src/lem/pointers.rs @@ -16,18 +16,18 @@ use super::Tag; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Ptr { Leaf(Tag, F), - Tree2(Tag, usize), - Tree3(Tag, usize), - Tree4(Tag, usize), + Tuple2(Tag, usize), + Tuple3(Tag, usize), + Tuple4(Tag, usize), } impl std::hash::Hash for Ptr { fn hash(&self, state: &mut H) { match self { Ptr::Leaf(tag, f) => (0, tag, f.to_repr().as_ref()).hash(state), - Ptr::Tree2(tag, x) => (1, tag, x).hash(state), - Ptr::Tree3(tag, x) => (2, tag, x).hash(state), - Ptr::Tree4(tag, x) => (3, tag, x).hash(state), + Ptr::Tuple2(tag, x) => (1, tag, x).hash(state), + Ptr::Tuple3(tag, x) => (2, tag, x).hash(state), + Ptr::Tuple4(tag, x) => (3, tag, x).hash(state), } } } @@ -35,7 +35,9 @@ impl std::hash::Hash for Ptr { impl Ptr { pub fn tag(&self) -> &Tag { match self { - Ptr::Leaf(tag, _) | Ptr::Tree2(tag, _) | Ptr::Tree3(tag, _) | Ptr::Tree4(tag, _) => tag, + Ptr::Leaf(tag, _) | Ptr::Tuple2(tag, _) | Ptr::Tuple3(tag, _) | Ptr::Tuple4(tag, _) => { + tag + } } } @@ -63,16 +65,16 @@ impl Ptr { pub fn cast(&self, tag: Tag) -> Self { match self { Ptr::Leaf(_, f) => Ptr::Leaf(tag, *f), - Ptr::Tree2(_, x) => Ptr::Tree2(tag, *x), - Ptr::Tree3(_, x) => Ptr::Tree3(tag, *x), - Ptr::Tree4(_, x) => Ptr::Tree4(tag, *x), + Ptr::Tuple2(_, x) => Ptr::Tuple2(tag, *x), + Ptr::Tuple3(_, x) => Ptr::Tuple3(tag, *x), + Ptr::Tuple4(_, x) => Ptr::Tuple4(tag, *x), } } #[inline] pub fn get_index2(&self) -> Option { match self { - Ptr::Tree2(_, x) => Some(*x), + Ptr::Tuple2(_, x) => Some(*x), _ => None, } } @@ -80,7 +82,7 @@ impl Ptr { #[inline] pub fn get_index3(&self) -> Option { match self { - Ptr::Tree3(_, x) => Some(*x), + Ptr::Tuple3(_, x) => Some(*x), _ => None, } } @@ -88,7 +90,7 @@ impl Ptr { #[inline] pub fn get_index4(&self) -> Option { match self { - Ptr::Tree4(_, x) => Some(*x), + Ptr::Tuple4(_, x) => Some(*x), _ => None, } } @@ -114,9 +116,9 @@ pub struct ZPtr { /// This information is saved during hydration and is needed to content-address /// a store. pub(crate) enum ZChildren { - Tree2(ZPtr, ZPtr), - Tree3(ZPtr, ZPtr, ZPtr), - Tree4(ZPtr, ZPtr, ZPtr, ZPtr), + Tuple2(ZPtr, ZPtr), + Tuple3(ZPtr, ZPtr, ZPtr), + Tuple4(ZPtr, ZPtr, ZPtr, ZPtr), } impl ZPtr { diff --git a/src/lem/slot.rs b/src/lem/slot.rs index 06333b5319..6456e7c2c2 100644 --- a/src/lem/slot.rs +++ b/src/lem/slot.rs @@ -110,16 +110,20 @@ pub struct SlotsCounter { pub hash2: usize, pub hash3: usize, pub hash4: usize, + pub commitment: usize, + pub less_than: 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, usize)) -> Self { Self { hash2: num_slots.0, hash3: num_slots.1, hash4: num_slots.2, + commitment: num_slots.3, + less_than: num_slots.4, } } @@ -141,6 +145,18 @@ impl SlotsCounter { self.hash4 - 1 } + #[inline] + pub fn consume_commitment(&mut self) -> usize { + self.commitment += 1; + self.commitment - 1 + } + + #[inline] + pub fn consume_less_than(&mut self) -> usize { + self.less_than += 1; + self.less_than - 1 + } + #[inline] pub fn max(&self, other: Self) -> Self { use std::cmp::max; @@ -148,6 +164,8 @@ impl SlotsCounter { hash2: max(self.hash2, other.hash2), hash3: max(self.hash3, other.hash3), hash4: max(self.hash4, other.hash4), + commitment: max(self.commitment, other.commitment), + less_than: max(self.less_than, other.less_than), } } @@ -157,6 +175,8 @@ impl SlotsCounter { hash2: self.hash2 + other.hash2, hash3: self.hash3 + other.hash3, hash4: self.hash4 + other.hash4, + commitment: self.commitment + other.commitment, + less_than: self.less_than + other.less_than, } } } @@ -165,9 +185,11 @@ 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, 0)), + Op::Hash3(..) | Op::Unhash3(..) => SlotsCounter::new((0, 1, 0, 0, 0)), + Op::Hash4(..) | Op::Unhash4(..) => SlotsCounter::new((0, 0, 1, 0, 0)), + Op::Hide(..) | Op::Open(..) => SlotsCounter::new((0, 0, 0, 1, 0)), + Op::Lt(..) => SlotsCounter::new((0, 0, 0, 0, 1)), Op::Call(_, func, _) => func.slot, _ => SlotsCounter::default(), }; @@ -205,6 +227,8 @@ pub(crate) enum SlotType { Hash2, Hash3, Hash4, + Commitment, + LessThan, } impl SlotType { @@ -213,6 +237,8 @@ impl SlotType { Self::Hash2 => 4, Self::Hash3 => 6, Self::Hash4 => 8, + Self::Commitment => 3, + Self::LessThan => 2, } } } @@ -223,6 +249,8 @@ impl std::fmt::Display for SlotType { Self::Hash2 => write!(f, "Hash2"), Self::Hash3 => write!(f, "Hash3"), Self::Hash4 => write!(f, "Hash4"), + Self::Commitment => write!(f, "Commitment"), + Self::LessThan => write!(f, "LessThan"), } } } diff --git a/src/lem/store.rs b/src/lem/store.rs index e662c7ff0b..7284b26798 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -21,8 +21,8 @@ use super::pointers::{Ptr, ZChildren, ZPtr}; /// vesatile data structure for many parts of Lurk's data pipeline. /// /// It holds Lurk data structured as trees of `Ptr`s (or `ZPtr`s). When a `Ptr` -/// has children`, we store them in the `IndexSet`s available: `ptrs2`, `ptrs3` -/// or `ptrs4`. These data structures speed up LEM interpretation because lookups +/// has children`, we store them in the `IndexSet`s available: `tuple2`, `tuple3` +/// or `tuple4`. These data structures speed up LEM interpretation because lookups /// by indices are fast. /// /// The `Store` also provides an infra to speed up interning strings and symbols. @@ -38,9 +38,9 @@ use super::pointers::{Ptr, ZChildren, ZPtr}; /// the resulting commitment hash. #[derive(Default)] pub struct Store { - ptrs2: IndexSet<(Ptr, Ptr)>, - ptrs3: IndexSet<(Ptr, Ptr, Ptr)>, - ptrs4: IndexSet<(Ptr, Ptr, Ptr, Ptr)>, + tuple2: IndexSet<(Ptr, Ptr)>, + tuple3: IndexSet<(Ptr, Ptr, Ptr)>, + tuple4: IndexSet<(Ptr, Ptr, Ptr, Ptr)>, str_cache: HashMap>, ptr_str_cache: HashMap, String>, @@ -58,8 +58,8 @@ pub struct Store { impl Store { /// Creates a `Ptr` that's a parent of two children pub fn intern_2_ptrs(&mut self, tag: Tag, a: Ptr, b: Ptr) -> Ptr { - let (idx, inserted) = self.ptrs2.insert_full((a, b)); - let ptr = Ptr::Tree2(tag, idx); + let (idx, inserted) = self.tuple2.insert_full((a, b)); + let ptr = Ptr::Tuple2(tag, idx); if inserted { // this is for `hydrate_z_cache` self.dehydrated.push(ptr); @@ -72,13 +72,13 @@ impl Store { /// `Store` (TODO). #[inline] pub fn intern_2_ptrs_not_dehydrated(&mut self, tag: Tag, a: Ptr, b: Ptr) -> Ptr { - Ptr::Tree2(tag, self.ptrs2.insert_full((a, b)).0) + Ptr::Tuple2(tag, self.tuple2.insert_full((a, b)).0) } /// Creates a `Ptr` that's a parent of three children pub fn intern_3_ptrs(&mut self, tag: Tag, a: Ptr, b: Ptr, c: Ptr) -> Ptr { - let (idx, inserted) = self.ptrs3.insert_full((a, b, c)); - let ptr = Ptr::Tree3(tag, idx); + let (idx, inserted) = self.tuple3.insert_full((a, b, c)); + let ptr = Ptr::Tuple3(tag, idx); if inserted { // this is for `hydrate_z_cache` self.dehydrated.push(ptr); @@ -97,7 +97,7 @@ impl Store { b: Ptr, c: Ptr, ) -> Ptr { - Ptr::Tree3(tag, self.ptrs3.insert_full((a, b, c)).0) + Ptr::Tuple3(tag, self.tuple3.insert_full((a, b, c)).0) } /// Creates a `Ptr` that's a parent of four children @@ -109,8 +109,8 @@ impl Store { c: Ptr, d: Ptr, ) -> Ptr { - let (idx, inserted) = self.ptrs4.insert_full((a, b, c, d)); - let ptr = Ptr::Tree4(tag, idx); + let (idx, inserted) = self.tuple4.insert_full((a, b, c, d)); + let ptr = Ptr::Tuple4(tag, idx); if inserted { // this is for `hydrate_z_cache` self.dehydrated.push(ptr); @@ -130,22 +130,22 @@ impl Store { c: Ptr, d: Ptr, ) -> Ptr { - Ptr::Tree4(tag, self.ptrs4.insert_full((a, b, c, d)).0) + Ptr::Tuple4(tag, self.tuple4.insert_full((a, b, c, d)).0) } #[inline] pub fn fetch_2_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr)> { - self.ptrs2.get_index(idx) + self.tuple2.get_index(idx) } #[inline] pub fn fetch_3_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr, Ptr)> { - self.ptrs3.get_index(idx) + self.tuple3.get_index(idx) } #[inline] pub fn fetch_4_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr, Ptr, Ptr)> { - self.ptrs4.get_index(idx) + self.tuple4.get_index(idx) } /// Interns a string recursively @@ -286,11 +286,11 @@ impl Store { tag: *tag, hash: *x, }), - Ptr::Tree2(tag, idx) => match self.z_cache.get(ptr) { + Ptr::Tuple2(tag, idx) => match self.z_cache.get(ptr) { Some(z_ptr) => Ok(*z_ptr), None => { - let Some((a, b)) = self.ptrs2.get_index(*idx) else { - bail!("Index {idx} not found on ptrs2") + let Some((a, b)) = self.tuple2.get_index(*idx) else { + bail!("Index {idx} not found on tuple2") }; let a = self.hash_ptr(a)?; let b = self.hash_ptr(b)?; @@ -303,16 +303,16 @@ impl Store { b.hash, ]), }; - self.z_dag.insert(z_ptr, ZChildren::Tree2(a, b)); + self.z_dag.insert(z_ptr, ZChildren::Tuple2(a, b)); self.z_cache.insert(*ptr, z_ptr); Ok(z_ptr) } }, - Ptr::Tree3(tag, idx) => match self.z_cache.get(ptr) { + Ptr::Tuple3(tag, idx) => match self.z_cache.get(ptr) { Some(z_ptr) => Ok(*z_ptr), None => { - let Some((a, b, c)) = self.ptrs3.get_index(*idx) else { - bail!("Index {idx} not found on ptrs3") + let Some((a, b, c)) = self.tuple3.get_index(*idx) else { + bail!("Index {idx} not found on tuple3") }; let a = self.hash_ptr(a)?; let b = self.hash_ptr(b)?; @@ -328,16 +328,16 @@ impl Store { c.hash, ]), }; - self.z_dag.insert(z_ptr, ZChildren::Tree3(a, b, c)); + self.z_dag.insert(z_ptr, ZChildren::Tuple3(a, b, c)); self.z_cache.insert(*ptr, z_ptr); Ok(z_ptr) } }, - Ptr::Tree4(tag, idx) => match self.z_cache.get(ptr) { + Ptr::Tuple4(tag, idx) => match self.z_cache.get(ptr) { Some(z_ptr) => Ok(*z_ptr), None => { - let Some((a, b, c, d)) = self.ptrs4.get_index(*idx) else { - bail!("Index {idx} not found on ptrs4") + let Some((a, b, c, d)) = self.tuple4.get_index(*idx) else { + bail!("Index {idx} not found on tuple4") }; let a = self.hash_ptr(a)?; let b = self.hash_ptr(b)?; @@ -356,7 +356,7 @@ impl Store { d.hash, ]), }; - self.z_dag.insert(z_ptr, ZChildren::Tree4(a, b, c, d)); + self.z_dag.insert(z_ptr, ZChildren::Tuple4(a, b, c, d)); self.z_cache.insert(*ptr, z_ptr); Ok(z_ptr) } @@ -375,7 +375,7 @@ impl Store { } impl Ptr { - pub fn to_string(self, store: &Store) -> String { + pub fn dbg_display(self, store: &Store) -> String { if let Some(s) = store.fetch_string(&self) { return format!("\"{}\"", s); } @@ -390,34 +390,34 @@ impl Ptr { format!("{}{:?}", tag, f) } } - Ptr::Tree2(tag, x) => { + Ptr::Tuple2(tag, x) => { let (p1, p2) = store.fetch_2_ptrs(x).unwrap(); format!( "({} {} {})", tag, - (*p1).to_string(store), - (*p2).to_string(store) + (*p1).dbg_display(store), + (*p2).dbg_display(store) ) } - Ptr::Tree3(tag, x) => { + Ptr::Tuple3(tag, x) => { let (p1, p2, p3) = store.fetch_3_ptrs(x).unwrap(); format!( "({} {} {} {})", tag, - (*p1).to_string(store), - (*p2).to_string(store), - (*p3).to_string(store) + (*p1).dbg_display(store), + (*p2).dbg_display(store), + (*p3).dbg_display(store) ) } - Ptr::Tree4(tag, x) => { + Ptr::Tuple4(tag, x) => { let (p1, p2, p3, p4) = store.fetch_4_ptrs(x).unwrap(); format!( "({} {} {} {} {})", tag, - (*p1).to_string(store), - (*p2).to_string(store), - (*p3).to_string(store), - (*p4).to_string(store) + (*p1).dbg_display(store), + (*p2).dbg_display(store), + (*p3).dbg_display(store), + (*p4).dbg_display(store) ) } }