diff --git a/co-noir/co-acvm/src/mpc.rs b/co-noir/co-acvm/src/mpc.rs index 77eeb1c60..6f880a43c 100644 --- a/co-noir/co-acvm/src/mpc.rs +++ b/co-noir/co-acvm/src/mpc.rs @@ -142,11 +142,18 @@ pub trait NoirWitnessExtensionProtocol { fn decompose_arithmetic( &mut self, input: Self::ArithmeticShare, - // io_context: &mut IoContext, total_bit_size_per_field: usize, decompose_bit_size: usize, ) -> std::io::Result>; + /// Decompose a shared value into a vector of shared values: \[a\] = a_1 + a_2 + ... + a_n. Each value a_i has at most decompose_bit_size bits, whereas the total bit size of the shares is total_bit_size_per_field. Thus, a_n, might have a smaller bitsize than the other chunks + fn decompose_arithmetic_many( + &mut self, + input: &[Self::ArithmeticShare], + total_bit_size_per_field: usize, + decompose_bit_size: usize, + ) -> std::io::Result>>; + /// Sorts a vector of shared values in ascending order, only considering the first bitsize bits. fn sort( &mut self, diff --git a/co-noir/co-acvm/src/mpc/plain.rs b/co-noir/co-acvm/src/mpc/plain.rs index 4e5766006..05276e806 100644 --- a/co-noir/co-acvm/src/mpc/plain.rs +++ b/co-noir/co-acvm/src/mpc/plain.rs @@ -188,6 +188,20 @@ impl NoirWitnessExtensionProtocol for PlainAcvmSolver { Ok(result) } + fn decompose_arithmetic_many( + &mut self, + input: &[Self::ArithmeticShare], + total_bit_size_per_field: usize, + decompose_bit_size: usize, + ) -> std::io::Result>> { + input + .iter() + .map(|&inp| { + Self::decompose_arithmetic(self, inp, total_bit_size_per_field, decompose_bit_size) + }) + .collect() + } + fn sort( &mut self, inputs: &[Self::ArithmeticShare], diff --git a/co-noir/co-acvm/src/mpc/rep3.rs b/co-noir/co-acvm/src/mpc/rep3.rs index d6ddb804e..2249556af 100644 --- a/co-noir/co-acvm/src/mpc/rep3.rs +++ b/co-noir/co-acvm/src/mpc/rep3.rs @@ -491,6 +491,32 @@ impl NoirWitnessExtensionProtocol for Rep3Acvm ) } + fn decompose_arithmetic_many( + &mut self, + input: &[Self::ArithmeticShare], + total_bit_size_per_field: usize, + decompose_bit_size: usize, + ) -> std::io::Result>> { + // Defines an upper bound on the size of the input vector to keep the GC at a reasonable size (for RAM) + const BATCH_SIZE: usize = 512; // TODO adapt this if it requires too much RAM + + let num_decomps_per_field = total_bit_size_per_field.div_ceil(decompose_bit_size); + let mut results = Vec::with_capacity(input.len()); + + for inp_chunk in input.chunks(BATCH_SIZE) { + let result = yao::decompose_arithmetic_many( + inp_chunk, + &mut self.io_context, + total_bit_size_per_field, + decompose_bit_size, + )?; + for chunk in result.chunks(num_decomps_per_field) { + results.push(chunk.to_vec()); + } + } + Ok(results) + } + fn sort( &mut self, inputs: &[Self::ArithmeticShare], diff --git a/co-noir/co-acvm/src/mpc/shamir.rs b/co-noir/co-acvm/src/mpc/shamir.rs index 64e8cb3dc..aee4768b7 100644 --- a/co-noir/co-acvm/src/mpc/shamir.rs +++ b/co-noir/co-acvm/src/mpc/shamir.rs @@ -407,6 +407,14 @@ impl NoirWitnessExtensionProtocol for Shamir ) -> std::io::Result> { panic!("functionality decompose_arithmetic not feasible for Shamir") } + fn decompose_arithmetic_many( + &mut self, + _input: &[Self::ArithmeticShare], + _total_bit_size_per_field: usize, + _decompose_bit_size: usize, + ) -> std::io::Result>> { + panic!("functionality decompose_arithmetic_many not feasible for Shamir") + } fn sort( &mut self, diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 4b3f572c6..d83b7d7f6 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -715,7 +715,50 @@ impl BrilligDriver for Rep3BrilligDriver (Public::Field(lhs), Shared::Field(rhs)) => Rep3BrilligType::shared_field( rep3::arithmetic::div_public_by_shared(lhs, rhs, &mut self.io_context)?, ), - _ => todo!("Implement division for public/shared"), + (Public::Int(public, IntegerBitSize::U128), Shared::Ring128(shared)) => { + let divided = rep3_ring::yao::ring_div_by_shared( + public.into(), + shared, + &mut self.io_context, + )?; + Rep3BrilligType::shared_u128(divided) + } + (Public::Int(public, IntegerBitSize::U64), Shared::Ring64(shared)) => { + let divided = rep3_ring::yao::ring_div_by_shared( + u64::try_from(public).expect("must be u64").into(), + shared, + &mut self.io_context, + )?; + Rep3BrilligType::shared_u64(divided) + } + (Public::Int(public, IntegerBitSize::U32), Shared::Ring32(shared)) => { + let divided = rep3_ring::yao::ring_div_by_shared( + u32::try_from(public).expect("must be u32").into(), + shared, + &mut self.io_context, + )?; + Rep3BrilligType::shared_u32(divided) + } + (Public::Int(public, IntegerBitSize::U16), Shared::Ring16(shared)) => { + let divided = rep3_ring::yao::ring_div_by_shared( + u16::try_from(public).expect("must be u16").into(), + shared, + &mut self.io_context, + )?; + Rep3BrilligType::shared_u16(divided) + } + (Public::Int(public, IntegerBitSize::U8), Shared::Ring8(shared)) => { + let divided = rep3_ring::yao::ring_div_by_shared( + u8::try_from(public).expect("must be u8").into(), + shared, + &mut self.io_context, + )?; + Rep3BrilligType::shared_u8(divided) + } + (Public::Int(_, IntegerBitSize::U1), Shared::Ring1(_)) => { + panic!("We do not implement division for a bit") + } + _ => panic!("type mismatch. Can only div matching values"), } } (Rep3BrilligType::Shared(shared), Rep3BrilligType::Public(public)) => { @@ -733,7 +776,12 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u128(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + let divided = rep3_ring::yao::ring_div_by_public( + shared, + public.into(), + &mut self.io_context, + )?; + Rep3BrilligType::shared_u128(divided) } } (Public::Int(public, IntegerBitSize::U64), Shared::Ring64(shared)) => { @@ -746,7 +794,12 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u64(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + let divided = rep3_ring::yao::ring_div_by_public( + shared, + u64::try_from(public).expect("must be u64").into(), + &mut self.io_context, + )?; + Rep3BrilligType::shared_u64(divided) } } (Public::Int(public, IntegerBitSize::U32), Shared::Ring32(shared)) => { @@ -759,7 +812,12 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u32(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + let divided = rep3_ring::yao::ring_div_by_public( + shared, + u32::try_from(public).expect("must be u32").into(), + &mut self.io_context, + )?; + Rep3BrilligType::shared_u32(divided) } } (Public::Int(public, IntegerBitSize::U16), Shared::Ring16(shared)) => { @@ -772,7 +830,12 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u16(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + let divided = rep3_ring::yao::ring_div_by_public( + shared, + u16::try_from(public).expect("must be u16").into(), + &mut self.io_context, + )?; + Rep3BrilligType::shared_u16(divided) } } (Public::Int(public, IntegerBitSize::U8), Shared::Ring8(shared)) => { @@ -785,7 +848,12 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u8(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + let divided = rep3_ring::yao::ring_div_by_public( + shared, + u8::try_from(public).expect("must be u8").into(), + &mut self.io_context, + )?; + Rep3BrilligType::shared_u8(divided) } } (Public::Int(public, IntegerBitSize::U1), Shared::Ring1(shared)) => { @@ -798,17 +866,41 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u1(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + unreachable!("Bit is 0") } } - _ => todo!("Implement division for shared/public"), + _ => panic!("type mismatch. Can only div matching values"), } } (Rep3BrilligType::Shared(s1), Rep3BrilligType::Shared(s2)) => match (s1, s2) { (Shared::Field(lhs), Shared::Field(rhs)) => Rep3BrilligType::shared_field( rep3::arithmetic::div(lhs, rhs, &mut self.io_context)?, ), - _ => todo!("Implement division for shared/shared"), + + (Shared::Ring128(lhs), Shared::Ring128(rhs)) => { + let divided = rep3_ring::yao::ring_div(lhs, rhs, &mut self.io_context)?; + Rep3BrilligType::shared_u128(divided) + } + (Shared::Ring64(lhs), Shared::Ring64(rhs)) => { + let divided = rep3_ring::yao::ring_div(lhs, rhs, &mut self.io_context)?; + Rep3BrilligType::shared_u64(divided) + } + (Shared::Ring32(lhs), Shared::Ring32(rhs)) => { + let divided = rep3_ring::yao::ring_div(lhs, rhs, &mut self.io_context)?; + Rep3BrilligType::shared_u32(divided) + } + (Shared::Ring16(lhs), Shared::Ring16(rhs)) => { + let divided = rep3_ring::yao::ring_div(lhs, rhs, &mut self.io_context)?; + Rep3BrilligType::shared_u16(divided) + } + (Shared::Ring8(lhs), Shared::Ring8(rhs)) => { + let divided = rep3_ring::yao::ring_div(lhs, rhs, &mut self.io_context)?; + Rep3BrilligType::shared_u8(divided) + } + (Shared::Ring1(_), Shared::Ring1(_)) => { + panic!("We do not implement division for a bit") + } + _ => panic!("type mismatch. Can only div matching values"), }, }; Ok(result) @@ -824,8 +916,10 @@ impl BrilligDriver for Rep3BrilligDriver Rep3BrilligType::Public(self.plain_driver.int_div(lhs, rhs)?) } (Rep3BrilligType::Public(public), Rep3BrilligType::Shared(shared)) => { - if let (Public::Field(_), Shared::Field(_)) = (public, shared) { - todo!("Implement IntDiv for public/shared") + if let (Public::Field(public), Shared::Field(shared)) = (public, shared) { + let divided = + rep3::yao::field_int_div_by_shared(public, shared, &mut self.io_context)?; + Rep3BrilligType::shared_field(divided) } else { eyre::bail!("IntDiv only supported on fields") } @@ -843,15 +937,21 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_field(divided) } else { - todo!("Implement IntDiv for shared/public with divisor not being a power-of-2") + let divided = rep3::yao::field_int_div_by_public( + shared, + public, + &mut self.io_context, + )?; + Rep3BrilligType::shared_field(divided) } } else { eyre::bail!("IntDiv only supported on fields") } } (Rep3BrilligType::Shared(s1), Rep3BrilligType::Shared(s2)) => { - if let (Shared::Field(_), Shared::Field(_)) = (s1, s2) { - todo!("Implement IntDiv for shared/shared") + if let (Shared::Field(s1), Shared::Field(s2)) = (s1, s2) { + let divided = rep3::yao::field_int_div(s1, s2, &mut self.io_context)?; + Rep3BrilligType::shared_field(divided) } else { eyre::bail!("IntDiv only supported on fields") } @@ -1313,11 +1413,8 @@ impl BrilligDriver for Rep3BrilligDriver (Rep3BrilligType::Shared(val), Rep3BrilligType::Public(radix)) => { if let (Shared::Field(val), Public::Int(radix, IntegerBitSize::U32)) = (val, radix) { - if bits { - todo!("Implement to_radix for shared value and public radix for bits=true") - } - let radix = u32::try_from(radix).expect("must be u32"); + let mut input = val; assert!(radix <= 256, "radix is at most 256"); if radix.is_power_of_two() { let bits = radix.ilog2(); @@ -1333,22 +1430,117 @@ impl BrilligDriver for Rep3BrilligDriver .map(|val| Rep3BrilligType::Shared(Shared::Ring8(val))) .collect() } else { - todo!("Implement to_radix for shared value and public radix for non-power-of-two radix") + let mut limbs: Vec> = + vec![Rep3BrilligType::default(); output_size]; + for i in (0..output_size).rev() { + let div = rep3::yao::field_int_div_by_public( + input, + radix.into(), + &mut self.io_context, + )?; + let limb = input - rep3::arithmetic::mul_public(div, radix.into()); + let limb = rep3_ring::yao::field_to_ring_many::<_, u8, _>( + &[limb], + &mut self.io_context, + )?; //radix is at most 256, so should fit into u8 + if bits { + let limb_bit = + rep3_ring::binary::is_zero(&limb[0], &mut self.io_context)?; + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring1(limb_bit)); + } else { + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + }; + input = div; + } + limbs } } else { eyre::bail!("can only ToRadix on field and radix must be Int32") } } (Rep3BrilligType::Public(val), Rep3BrilligType::Shared(radix)) => { - if let (Public::Field(_val), Shared::Ring32(_radix)) = (val, radix) { - todo!("Implement to_radix for public value and shared radix") + if let (Public::Field(val), Shared::Ring32(radix)) = (val, radix) { + //todo: do we want to do checks for radix <= 256? + let mut limbs: Vec> = + vec![Rep3BrilligType::default(); output_size]; + let radix_as_field = + rep3_ring::yao::ring_to_field_many(&[radix], &mut self.io_context)?; + let div = rep3::yao::field_int_div_by_shared( + val, + radix_as_field[0], + &mut self.io_context, + )?; + let limb = rep3::arithmetic::sub_public_by_shared( + val, + rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, + self.io_context.network.get_id(), + ); + let limb = rep3_ring::yao::field_to_ring_many::<_, u8, _>( + &[limb], + &mut self.io_context, + )?; //radix is at most 256, so should fit into u8 + limbs[output_size - 1] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + let mut input = div; + for i in (0..output_size).rev().skip(1) { + let div = rep3::yao::field_int_div( + input, + radix_as_field[0], + &mut self.io_context, + )?; + let limb = rep3::arithmetic::sub( + input, + rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, + ); + + let limb = rep3_ring::yao::field_to_ring_many::<_, u8, _>( + &[limb], + &mut self.io_context, + )?; //radix is at most 256, so should fit into u8 + if bits { + let limb_bit = + rep3_ring::binary::is_zero(&limb[0], &mut self.io_context)?; + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring1(limb_bit)); + } else { + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + } + input = div; + } + limbs } else { eyre::bail!("can only ToRadix on field and radix must be Int32") } } (Rep3BrilligType::Shared(val), Rep3BrilligType::Shared(radix)) => { - if let (Shared::Field(_val), Shared::Ring32(_radix)) = (val, radix) { - todo!("Implement to_radix for shared value and shared radix") + if let (Shared::Field(val), Shared::Ring32(radix)) = (val, radix) { + let mut limbs: Vec> = + vec![Rep3BrilligType::default(); output_size]; + let radix_as_field = + rep3_ring::yao::ring_to_field_many(&[radix], &mut self.io_context)?; + let mut input = val; + for i in (0..output_size).rev() { + let div = rep3::yao::field_int_div( + input, + radix_as_field[0], + &mut self.io_context, + )?; + let limb = rep3::arithmetic::sub( + input, + rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, + ); + let limb = rep3_ring::yao::field_to_ring_many::<_, u8, _>( + &[limb], + &mut self.io_context, + )?; //radix is at most 256, so should fit into u8 + if bits { + let limb_bit = + rep3_ring::binary::is_zero(&limb[0], &mut self.io_context)?; + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring1(limb_bit)); + } else { + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + } + input = div; + } + limbs } else { eyre::bail!("can only ToRadix on field and radix must be Int32") } diff --git a/co-noir/co-builder/src/builder.rs b/co-noir/co-builder/src/builder.rs index 23a6b6489..f0309a509 100644 --- a/co-noir/co-builder/src/builder.rs +++ b/co-noir/co-builder/src/builder.rs @@ -12,8 +12,8 @@ use crate::{ AddQuad, AddTriple, AggregationObjectIndices, AggregationObjectPubInputIndices, AuxSelectors, BlockConstraint, BlockType, CachedPartialNonNativeFieldMultiplication, ColumnIdx, FieldCT, GateCounter, MulQuad, PlookupBasicTable, PolyTriple, RamTranscript, - RangeList, ReadData, RomRecord, RomTable, RomTranscript, UltraTraceBlock, - UltraTraceBlocks, NUM_WIRES, + RangeConstraint, RangeList, ReadData, RomRecord, RomTable, RomTranscript, + UltraTraceBlock, UltraTraceBlocks, NUM_WIRES, }, }, utils::Utils, @@ -23,7 +23,7 @@ use ark_ec::pairing::Pairing; use ark_ff::{One, Zero}; use co_acvm::{mpc::NoirWitnessExtensionProtocol, PlainAcvmSolver}; use num_bigint::BigUint; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; type GateBlocks = UltraTraceBlocks>; @@ -632,6 +632,59 @@ impl> GenericUltraCi } } + // decomposes the shared values in batches, separated into the corresponding number of bits the values have + #[expect(clippy::type_complexity)] + fn prepare_for_range_decompose( + &mut self, + driver: &mut T, + range_constraints: &[RangeConstraint], + ) -> std::io::Result<( + HashMap, + Vec>>, + Vec<(bool, usize)>, + )> { + let mut to_decompose: Vec> = vec![]; + let mut decompose_indices: Vec<(bool, usize)> = vec![]; + let mut bits_locations: HashMap = HashMap::new(); + + for constraint in range_constraints.iter() { + let val = &self.get_variable(constraint.witness as usize); + + let num_bits = constraint.num_bits; + if num_bits > Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32 && T::is_shared(val) { + let share_val = T::get_shared(val).expect("Already checked it is shared"); + if let Some(&idx) = bits_locations.get(&num_bits) { + to_decompose[idx].push(share_val); + decompose_indices.push((true, to_decompose[idx].len() - 1)); + } else { + let new_idx = to_decompose.len(); + to_decompose.push(vec![share_val]); + decompose_indices.push((true, 0)); + bits_locations.insert(num_bits, new_idx); + } + } else { + decompose_indices.push((false, 0)); + } + } + + let mut decomposed = Vec::with_capacity(to_decompose.len()); + + for (i, inp) in to_decompose.into_iter().enumerate() { + let num_bits = bits_locations + .iter() + .find_map(|(&key, &value)| if value == i { Some(key) } else { None }) + .expect("Index not found in bitsloc"); + + decomposed.push(T::decompose_arithmetic_many( + driver, + &inp, + num_bits as usize, + Self::DEFAULT_PLOOKUP_RANGE_BITNUM, + )?); + } + Ok((bits_locations, decomposed, decompose_indices)) + } + fn build_constraints( &mut self, driver: &mut T, @@ -719,8 +772,27 @@ impl> GenericUltraCi // todo!("Logic gates"); // } + // We want to decompose all shared elements in parallel + let (bits_locations, decomposed, decompose_indices) = + self.prepare_for_range_decompose(driver, &constraint_system.range_constraints)?; + for (i, constraint) in constraint_system.range_constraints.iter().enumerate() { - self.create_range_constraint(driver, constraint.witness, constraint.num_bits)?; + let idx_option = bits_locations.get(&constraint.num_bits); + if idx_option.is_some() && decompose_indices[i].0 { + // Already decomposed + let idx = idx_option.unwrap().to_owned(); + self.decompose_into_default_range( + driver, + constraint.witness, + constraint.num_bits as u64, + Some(&decomposed[idx][decompose_indices[i].1]), + Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u64, + )?; + } else { + // Either we do not have to decompose or the value is public + self.create_range_constraint(driver, constraint.witness, constraint.num_bits)?; + } + gate_counter.track_diff( self, &mut constraint_system.gates_per_opcode, @@ -2345,10 +2417,12 @@ impl> GenericUltraCi self.create_new_range_constraint(variable_index, (1u64 << num_bits) - 1); } else { + // The value must be public, otherwise it would have been batch decomposed already self.decompose_into_default_range( driver, variable_index, num_bits as u64, + None, Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u64, )?; } @@ -2420,12 +2494,14 @@ impl> GenericUltraCi driver: &mut T, variable_index: u32, num_bits: u64, + decompose: Option<&[T::ArithmeticShare]>, target_range_bitnum: u64, ) -> std::io::Result> { assert!(self.is_valid_variable(variable_index as usize)); assert!(num_bits > 0); let val = self.get_variable(variable_index as usize); + // We cannot check that easily in MPC: // If the value is out of range, set the composer error to the given msg. // if val.msb() >= num_bits && !self.failed() { @@ -2451,28 +2527,27 @@ impl> GenericUltraCi let last_limb_range = (1u64 << last_limb_size) - 1; let mut sublimb_indices: Vec = Vec::with_capacity(num_limbs as usize); - let sublimbs = if T::is_shared(&val) { - let decomp = T::decompose_arithmetic( - driver, - T::get_shared(&val).expect("Already checked it is shared"), - num_bits as usize, - target_range_bitnum as usize, - )?; - decomp.into_iter().map(T::AcvmType::from).collect() - } else { - let mut sublimbs = Vec::with_capacity(num_limbs as usize); - let mut accumulator: BigUint = T::get_public(&val) - .expect("Already checked it is public") - .into(); - for _ in 0..num_limbs { - let sublimb_value = P::ScalarField::from(&accumulator & &sublimb_mask.into()); - sublimbs.push(T::AcvmType::from(sublimb_value)); - accumulator >>= target_range_bitnum; + let sublimbs: Vec = match decompose { + // Already decomposed, i.e., we just take the values + Some(decomposed) => decomposed + .iter() + .map(|item| T::AcvmType::from(item.clone())) + .collect(), + None => { + // Not yet decomposed, i.e., it was a public value + let mut accumulator: BigUint = T::get_public(&val) + .expect("Already checked it is public") + .into(); + let sublimb_mask: BigUint = sublimb_mask.into(); + (0..num_limbs) + .map(|_| { + let sublimb_value = P::ScalarField::from(&accumulator & &sublimb_mask); + accumulator >>= target_range_bitnum; + T::AcvmType::from(sublimb_value) + }) + .collect() } - - sublimbs }; - for (i, sublimb) in sublimbs.iter().enumerate() { let limb_idx = self.add_variable(sublimb.clone()); diff --git a/co-noir/co-noir/examples/run_full_ranges.sh b/co-noir/co-noir/examples/run_full_ranges.sh new file mode 100755 index 000000000..39e1e6cd8 --- /dev/null +++ b/co-noir/co-noir/examples/run_full_ranges.sh @@ -0,0 +1,16 @@ +# split input into shares +cargo run --release --bin co-noir -- split-input --circuit test_vectors/ranges/ranges.json --input test_vectors/ranges/Prover.toml --protocol REP3 --out-dir test_vectors/ranges +# run witness extension in MPC +cargo run --release --bin co-noir -- generate-witness --input test_vectors/ranges/Prover.toml.0.shared --circuit test_vectors/ranges/ranges.json --protocol REP3 --config configs/party1.toml --out test_vectors/ranges/ranges.gz.0.shared & +cargo run --release --bin co-noir -- generate-witness --input test_vectors/ranges/Prover.toml.1.shared --circuit test_vectors/ranges/ranges.json --protocol REP3 --config configs/party2.toml --out test_vectors/ranges/ranges.gz.1.shared & +cargo run --release --bin co-noir -- generate-witness --input test_vectors/ranges/Prover.toml.2.shared --circuit test_vectors/ranges/ranges.json --protocol REP3 --config configs/party3.toml --out test_vectors/ranges/ranges.gz.2.shared +wait $(jobs -p) +# run proving in MPC +cargo run --release --bin co-noir -- build-and-generate-proof --witness test_vectors/ranges/ranges.gz.0.shared --circuit test_vectors/ranges/ranges.json --crs test_vectors/bn254_g1.dat --protocol REP3 --hasher KECCAK --config configs/party1.toml --out proof.0.proof --public-input public_input.json & +cargo run --release --bin co-noir -- build-and-generate-proof --witness test_vectors/ranges/ranges.gz.1.shared --circuit test_vectors/ranges/ranges.json --crs test_vectors/bn254_g1.dat --protocol REP3 --hasher KECCAK --config configs/party2.toml --out proof.1.proof & +cargo run --release --bin co-noir -- build-and-generate-proof --witness test_vectors/ranges/ranges.gz.2.shared --circuit test_vectors/ranges/ranges.json --crs test_vectors/bn254_g1.dat --protocol REP3 --hasher KECCAK --config configs/party3.toml --out proof.2.proof +wait $(jobs -p) +# Create verification key +cargo run --release --bin co-noir -- create-vk --circuit test_vectors/ranges/ranges.json --crs test_vectors/bn254_g1.dat --hasher KECCAK --vk test_vectors/ranges/verification_key +# verify proof +cargo run --release --bin co-noir -- verify --proof proof.0.proof --vk test_vectors/ranges/verification_key --hasher KECCAK --crs test_vectors/bn254_g2.dat diff --git a/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml b/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml new file mode 100644 index 000000000..0eca26e12 --- /dev/null +++ b/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ranges" +type = "bin" +authors = [""] +compiler_version = ">=1.0.0" + +[dependencies] diff --git a/co-noir/co-noir/examples/test_vectors/ranges/Prover.toml b/co-noir/co-noir/examples/test_vectors/ranges/Prover.toml new file mode 100644 index 000000000..803ac1eb9 --- /dev/null +++ b/co-noir/co-noir/examples/test_vectors/ranges/Prover.toml @@ -0,0 +1,7 @@ +x = "1" +w = "30" +u = "32" +y = "2" +z = "3" +s = "12" +t = "121" diff --git a/co-noir/co-noir/examples/test_vectors/ranges/ranges.gz b/co-noir/co-noir/examples/test_vectors/ranges/ranges.gz new file mode 100644 index 000000000..fe60dbfb4 Binary files /dev/null and b/co-noir/co-noir/examples/test_vectors/ranges/ranges.gz differ diff --git a/co-noir/co-noir/examples/test_vectors/ranges/ranges.json b/co-noir/co-noir/examples/test_vectors/ranges/ranges.json new file mode 100644 index 000000000..a6bceb18a --- /dev/null +++ b/co-noir/co-noir/examples/test_vectors/ranges/ranges.json @@ -0,0 +1 @@ +{"noir_version":"1.0.0-beta.0+7311d8ca566c3b3e0744389fc5e4163741927767","hash":13046917000996441616,"abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"w","type":{"kind":"integer","sign":"unsigned","width":16},"visibility":"private"},{"name":"u","type":{"kind":"integer","sign":"unsigned","width":32},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},{"name":"z","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},{"name":"s","type":{"kind":"integer","sign":"unsigned","width":32},"visibility":"private"},{"name":"t","type":{"kind":"integer","sign":"unsigned","width":16},"visibility":"private"}],"return_type":{"abi_type":{"kind":"tuple","fields":[{"kind":"integer","sign":"unsigned","width":64},{"kind":"integer","sign":"unsigned","width":32},{"kind":"integer","sign":"unsigned","width":16}]},"visibility":"public"},"error_types":{"5019202896831570965":{"error_kind":"string","string":"attempt to add with overflow"},"7233212735005103307":{"error_kind":"string","string":"attempt to multiply with overflow"}}},"bytecode":"H4sIAAAAAAAA/81WTc7CIBC1pfp9/saYuHDnEaCAhZ0uPIiNde8tPIB30JO40IWX8CB2dJpMSHUjJH3JhOZNeYVXBohabwzLmOBzVAbDFrB0OIixw8VlzB2O1fRNarh2Td8OfoOCkfEA+G8QPrUY0ZJ8oVSRpYWQYsNTmxvNlc4XRhihjd6mRsrCKJPZ3GbcCiULsdNW7lCs529cnHpKdUN5mnjU6nn0tB/IU6obefYyxtpwdX/1YhDIC9CdB/Ki2hN8ezEM5AXouvtX7NmTv4bWR8g5/ze0DkLOudvg9Z6gVpvMv7oPANchfjB8vzpzYP3C/+wSnRZygOljNdtfT2uSep1hn3IDbG/H++V82GxpbvQlB3gCOAMWOQgJAAA=","debug_symbols":"ldFNCoMwEAXgu8zahYlttV6lFIkaJRCSkJ9CCd69iTRFglCymzePbzbjYaajWwcmFmmgf3jgciKWSRGSh3ZfGUVETMYSbaFHNa6AijlO7VbBwjiF/tZtzwq6UnD/AxDKAKqLBToVqPmJLhe4WDSnAtdJ4GsuLmUihFEzztk6HF8U1i+iGRk5/cbFienQ2rdKTfJKy4nOTtN4ae/C+Q8=","file_map":{"68":{"source":"fn main(x: u64,w: u16,u: u32, y: pub u64, z: pub u64, s: u32, t: u16) -> pub (u64, u32, u16) {\n (x + y + z, u * s, w * t)\n}\n","path":"/home/fabsits/co-snarks/co-noir/co-noir/examples/test_vectors/ranges/src/main.nr"}},"names":["main"],"brillig_names":[]} \ No newline at end of file diff --git a/co-noir/co-noir/examples/test_vectors/ranges/src/main.nr b/co-noir/co-noir/examples/test_vectors/ranges/src/main.nr new file mode 100644 index 000000000..7bbc3fc85 --- /dev/null +++ b/co-noir/co-noir/examples/test_vectors/ranges/src/main.nr @@ -0,0 +1,3 @@ +fn main(x: u64, w: u16, u: u32, y: pub u64, z: pub u64, s: u32, t: u16) -> pub (u64, u32, u16) { + (x + y + z, u * s, w * t) +} diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 4cd7dd23e..a0e4563bb 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -235,7 +235,7 @@ impl GCUtils { Ok(F::from(res)) } - fn biguint_to_bits(input: BigUint, n_bits: usize) -> Vec { + fn biguint_to_bits(input: &BigUint, n_bits: usize) -> Vec { let mut res = Vec::with_capacity(n_bits); let mut bits = 0; for mut el in input.to_u64_digits() { @@ -252,6 +252,13 @@ impl GCUtils { res } + fn field_to_bits(field: F) -> Vec { + let n_bits = F::MODULUS_BIT_SIZE as usize; + let bigint: BigUint = field.into(); + + Self::biguint_to_bits(&bigint, n_bits) + } + fn field_to_bits_as_u16(field: F) -> Vec { let n_bits = F::MODULUS_BIT_SIZE as usize; let bigint: BigUint = field.into(); @@ -674,7 +681,8 @@ pub fn decompose_arithmetic( decompose_bit_size, ) } -/// Divides a vector of field elements by a power of 2, roudning down. + +/// Divides a vector of field elements by a power of 2, rounding down. pub fn field_int_div_power_2_many( inputs: &[Rep3PrimeFieldShare], io_context: &mut IoContext, @@ -708,6 +716,103 @@ pub fn field_int_div_power_2( Ok(res[0]) } +/// Divides a vector of field elements by another, rounding down. +pub fn field_int_div_many( + input1: &[Rep3PrimeFieldShare], + input2: &[Rep3PrimeFieldShare], + io_context: &mut IoContext, +) -> IoResult>> { + let num_inputs = input1.len(); + debug_assert_eq!(input1.len(), input2.len()); + + let mut combined_inputs = Vec::with_capacity(input1.len() + input2.len()); + combined_inputs.extend_from_slice(input1); + combined_inputs.extend_from_slice(input2); + decompose_circuit_compose_blueprint!( + &combined_inputs, + io_context, + num_inputs, + GarbledCircuits::field_int_div_many::<_, F>, + () + ) +} + +/// Divides a field element by another, rounding down. +pub fn field_int_div( + input1: Rep3PrimeFieldShare, + input2: Rep3PrimeFieldShare, + io_context: &mut IoContext, +) -> IoResult> { + let res = field_int_div_many(&[input1], &[input2], io_context)?; + Ok(res[0]) +} + +/// Divides a vector of field elements by another, rounding down. +pub fn field_int_div_by_public_many( + input: &[Rep3PrimeFieldShare], + divisors: &[F], + io_context: &mut IoContext, +) -> IoResult>> { + let num_inputs = input.len(); + debug_assert_eq!(input.len(), divisors.len()); + + let mut divisors_as_bits = Vec::with_capacity(F::MODULUS_BIT_SIZE as usize * num_inputs); + divisors + .iter() + .for_each(|y| divisors_as_bits.extend(GCUtils::field_to_bits::(*y))); + + decompose_circuit_compose_blueprint!( + &input, + io_context, + num_inputs, + GarbledCircuits::field_int_div_by_public_many::<_, F>, + (divisors_as_bits) + ) +} + +/// Divides a field element by another, rounding down. +pub fn field_int_div_by_public( + input: Rep3PrimeFieldShare, + divisor: F, + io_context: &mut IoContext, +) -> IoResult> { + let res = field_int_div_by_public_many(&[input], &[divisor], io_context)?; + Ok(res[0]) +} + +/// Divides a vector of field elements by another, rounding down. +pub fn field_int_div_by_shared_many( + input: &[F], + divisors: &[Rep3PrimeFieldShare], + io_context: &mut IoContext, +) -> IoResult>> { + let num_inputs = input.len(); + debug_assert_eq!(input.len(), divisors.len()); + + let mut inputs_as_bits = Vec::with_capacity(F::MODULUS_BIT_SIZE as usize * num_inputs); + input + .iter() + .for_each(|y| inputs_as_bits.extend(GCUtils::field_to_bits::(*y))); + + decompose_circuit_compose_blueprint!( + &divisors, + io_context, + num_inputs, + GarbledCircuits::field_int_div_by_shared_many::<_, F>, + (inputs_as_bits) + ) +} + +/// Divides a field element by another, rounding down. +pub fn field_int_div_by_shared( + input: F, + divisor: Rep3PrimeFieldShare, + io_context: &mut IoContext, +) -> IoResult> { + let res = field_int_div_by_shared_many(&[input], &[divisor], io_context)?; + Ok(res[0]) +} + macro_rules! decompose_circuit_compose_blueprint { ($inputs:expr, $io_context:expr, $output_size:expr, $circuit:expr, ($( $args:expr ),*)) => {{ use $crate::protocols::rep3::id::PartyID; diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 9a499608f..90440bdc0 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -7,6 +7,16 @@ use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; use itertools::izip; use num_bigint::BigUint; +use std::ops::Not; + +/// This trait allows to lazily initialize the constants 0 and 1 for the garbled circuit, such that these constants are only send at most once each. +pub trait FancyBinaryConstant: FancyBinary { + /// Takes an already initialized constant 0 or adds it to the garbled circuit if not yet present + fn const_zero(&mut self) -> Result; + + /// Takes an already initialized constant 1 or adds it to the garbled circuit if not yet present + fn const_one(&mut self) -> Result; +} /// This struct contains some predefined garbled circuits. pub struct GarbledCircuits {} @@ -88,6 +98,19 @@ impl GarbledCircuits { let c = g.xor(&z4, a)?; Ok((s, c)) } + /// Full adder with carry in set + fn full_adder_const_cin_set( + g: &mut G, + a: &G::Item, + b: bool, + ) -> Result<(G::Item, G::Item), G::Error> { + let (s, c) = if b { + (a.clone(), g.const_one()?) + } else { + (g.negate(a)?, a.clone()) + }; + Ok((s, c)) + } /// Full adder with carry in set, just outputs carry fn full_adder_carry_cin_set( @@ -125,7 +148,7 @@ impl GarbledCircuits { Ok((result, c)) } - /// Binary addition. Returns the result and the carry. + /// Binary addition. Returns just the result. fn bin_addition_no_carry( g: &mut G, xs: &[G::Item], @@ -158,7 +181,7 @@ impl GarbledCircuits { /// Binary subtraction. Returns the result and whether it underflowed. /// I.e., calculates 2^k + x1 - x2 - #[expect(dead_code, clippy::type_complexity)] + #[expect(clippy::type_complexity)] fn bin_subtraction( g: &mut G, xs: &[G::Item], @@ -183,6 +206,106 @@ impl GarbledCircuits { Ok((result, c)) } + /// Needed for subtraction in binary division (for shared/shared) where xs is (conceptually) a constant 0 bundle with only some values set + #[expect(clippy::type_complexity)] + fn bin_subtraction_partial( + g: &mut G, + xs: &[G::Item], + ys: &[G::Item], + ) -> Result<(Vec, G::Item), G::Error> { + let mut result = Vec::with_capacity(xs.len()); + // Twos complement is negation + 1, we implement by having cin in adder = 1, so only negation is required + let length = xs.len(); + let y0 = g.negate(&ys[0])?; + let (mut s, mut c) = Self::full_adder_cin_set(g, &xs[0], &y0)?; + result.push(s); + if xs.len() > 1 { + for (x, y) in xs.iter().zip(ys.iter().take(xs.len())).skip(1) { + let y = g.negate(y)?; + let res = Self::full_adder(g, x, &y, &c)?; + s = res.0; + c = res.1; + result.push(s); + } + } + for y in ys[length..].iter() { + let y = g.negate(y)?; + // FULL ADDER with a=0 (x=0) + s = g.xor(&y, &c)?; + c = g.and(&y, &c)?; + result.push(s); + } + Ok((result, c)) + } + + /// Needed for subtraction in binary division (for shared/public) where xs is (conceptually) a constant 0 bundle with only some values set and the subtrahend is a constant/public + #[expect(clippy::type_complexity)] + fn bin_subtraction_partial_by_constant( + g: &mut G, + xs: &[G::Item], + ys: &[bool], + ) -> Result<(Vec, G::Item), G::Error> { + let mut result = Vec::with_capacity(xs.len()); + // Twos complement is negation + 1, we implement by having cin in adder = 1, so only negation is required + let length = xs.len(); + let y0 = &ys[0].not(); + let (mut s, mut c) = Self::full_adder_const_cin_set(g, &xs[0], *y0)?; + result.push(s); + if xs.len() > 1 { + for (x, y) in xs.iter().zip(ys.iter().take(xs.len())).skip(1) { + let y = y.not(); + let res = Self::full_adder_const(g, x, y, &c)?; + s = res.0; + c = res.1; + result.push(s); + } + } + for y in ys[length..].iter() { + // FULL ADDER with a=0 (x=0) + (s, c) = if *y { + (c, g.const_zero()?) + } else { + (g.negate(&c)?, c) + }; + result.push(s); + } + Ok((result, c)) + } + + /// Needed for subtraction in binary division (public/shared) where xs is (conceptually) a constant 0 bundle with only some constant/public values set + #[expect(clippy::type_complexity)] + fn bin_subtraction_partial_from_constant( + g: &mut G, + xs: &[bool], + ys: &[G::Item], + ) -> Result<(Vec, G::Item), G::Error> { + let mut result = Vec::with_capacity(xs.len()); + // Twos complement is negation + 1, we implement by having cin in adder = 1, so only negation is required + let length = xs.len(); + let y0 = g.negate(&ys[0])?; + let (mut s, mut c) = Self::full_adder_const_cin_set(g, &y0, xs[0])?; + result.push(s); + if xs.len() > 1 { + for (x, y) in xs.iter().zip(ys.iter().take(xs.len())).skip(1) { + let y = g.negate(y)?; + let res = Self::full_adder_const(g, &y, *x, &c)?; + s = res.0; + c = res.1; + result.push(s); + } + } + if length < ys.len() { + for y in ys[length..].iter() { + let y = g.negate(y)?; + // FULL ADDER with a=0 (x=0) + s = g.xor(&y, &c)?; + c = g.and(&y, &c)?; + result.push(s); + } + } + Ok((result, c)) + } + /// Binary subtraction. Returns whether it underflowed. /// I.e., calculates the msb of 2^k + x1 - x2 fn bin_subtraction_get_carry_only( @@ -204,6 +327,127 @@ impl GarbledCircuits { Ok(c) } + // From swanky: + /// Binary division + fn bin_div( + g: &mut G, + dividend: &[G::Item], + divisor: &[G::Item], + ) -> Result, G::Error> { + let mut acc: Vec = Vec::with_capacity(dividend.len()); + let mut qs: Vec = vec![]; + for x in dividend.iter().rev() { + if acc.len() == dividend.len() { + acc.pop(); + } + acc.insert(0, x.clone()); + + let (res, cout) = Self::bin_subtraction_partial(g, &acc, divisor)?; + + acc = Self::bin_multiplex(g, &cout, &acc, &res)?; + qs.push(cout); + } + qs.reverse(); // Switch back to little-endian + Ok(qs) + } + + // From swanky: + /// Binary division by a public value + fn bin_div_by_public( + g: &mut G, + dividend: &[G::Item], + divisor: &[bool], + ) -> Result, G::Error> { + let mut acc: Vec = Vec::with_capacity(dividend.len()); + let mut qs: Vec = vec![]; + for x in dividend.iter().rev() { + if acc.len() == dividend.len() { + acc.pop(); + } + acc.insert(0, x.clone()); + + let (res, cout) = Self::bin_subtraction_partial_by_constant(g, &acc, divisor)?; + + acc = Self::bin_multiplex(g, &cout, &acc, &res)?; + qs.push(cout); + } + qs.reverse(); // Switch back to little-endian + Ok(qs) + } + + // From swanky: + /// Binary division of a public by a shared value + fn bin_div_by_shared( + g: &mut G, + dividend: &[bool], + divisor: &[G::Item], + ) -> Result, G::Error> { + let mut acc: Vec = vec![false; divisor.len() - 1]; + let mut acc_g; + let mut qs: Vec = vec![]; + acc.insert(0, *dividend.last().unwrap()); + + let (res, cout) = Self::bin_subtraction_partial_from_constant(g, &acc, divisor)?; + + acc_g = Self::bin_multiplex_const(g, &cout, &acc, &res)?; + qs.push(cout); + for x in dividend.iter().rev().skip(1) { + if acc_g.len() == divisor.len() { + acc_g.pop(); + } + acc_g.insert(0, if *x { g.const_one()? } else { g.const_zero()? }); + let (res, cout) = Self::bin_subtraction(g, &acc_g, divisor)?; + let mut acc_g_tmp = Vec::with_capacity(res.len()); + // this is the first part of the multiplex as the "first" entry is a public bool + acc_g_tmp.insert(0, { + if *x { + let cout_not = g.negate(&cout)?; + g.or(&cout_not, &res[0])? + } else { + g.and(&cout, &res[0])? + } + }); + acc_g_tmp.extend(Self::bin_multiplex(g, &cout, &acc_g[1..], &res[1..])?); + acc_g = acc_g_tmp; + qs.push(cout); + } + qs.reverse(); // Switch back to little-endian + Ok(qs) + } + + /// Multiplex gadget for binary bundles + fn bin_multiplex( + g: &mut G, + b: &G::Item, + x: &[G::Item], + y: &[G::Item], + ) -> Result, G::Error> { + x.iter() + .zip(y.iter()) + .map(|(xwire, ywire)| g.mux(b, xwire, ywire)) + .collect::, G::Error>>() + } + + /// Multiplex gadget for public/shared + fn bin_multiplex_const( + g: &mut G, + b: &G::Item, + x: &[bool], + y: &[G::Item], + ) -> Result, G::Error> { + x.iter() + .zip(y.iter()) + .map(|(xwire, ywire)| { + if *xwire { + let b_not = g.negate(b)?; + g.or(&b_not, ywire) + } else { + g.and(b, ywire) + } + }) + .collect::, G::Error>>() + } + /// subtracts p from wires (with carry) and returns the result and the overflow bit #[expect(clippy::type_complexity)] fn sub_p( @@ -217,7 +461,7 @@ impl GarbledCircuits { // Prepare p for subtraction let new_bitlen = bitlen + 1; let p_ = (BigUint::from(1u64) << new_bitlen) - F::MODULUS.into(); - let p_bits = GCUtils::biguint_to_bits(p_, new_bitlen); + let p_bits = GCUtils::biguint_to_bits(&p_, new_bitlen); // manual_rca: let mut subtracted = Vec::with_capacity(bitlen); @@ -940,6 +1184,195 @@ impl GarbledCircuits { Ok(result) } + /// Divides a ring element by another. The ring elements are represented as bitdecompositions x1s, x2s, y1s and y2s which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as the input wires + fn ring_div( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + y1s: &[G::Item], + y2s: &[G::Item], + wires_c: &[G::Item], + input_bitlen: usize, + ) -> Result, G::Error> { + let dividend = Self::bin_addition_no_carry(g, x1s, x2s)?; + let divisor = Self::bin_addition_no_carry(g, y1s, y2s)?; + + debug_assert_eq!(dividend.len(), input_bitlen); + debug_assert_eq!(dividend.len(), divisor.len()); + let quotient = Self::bin_div(g, ÷nd, &divisor)?; + + let mut added = Vec::with_capacity(input_bitlen); + let ys = wires_c; + let (mut s, mut c) = Self::half_adder(g, "ient[0], &ys[0])?; + added.push(s); + + for (x, y) in quotient.iter().zip(ys.iter()).skip(1) { + let res = Self::full_adder(g, x, y, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + for y in ys.iter().take(ys.len() - 1).skip(quotient.len()) { + let res = Self::full_adder_const(g, y, false, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + Ok(added) + } + + /// Divides a ring element by another public ring element. The ring element is represented as bitdecompositions x1s and x2s which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as the input wires + fn ring_div_by_public( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + divisor: &[bool], + wires_c: &[G::Item], + input_bitlen: usize, + ) -> Result, G::Error> { + let dividend = Self::bin_addition_no_carry(g, x1s, x2s)?; + + debug_assert_eq!(dividend.len(), input_bitlen); + debug_assert_eq!(dividend.len(), divisor.len()); + let quotient = Self::bin_div_by_public(g, ÷nd, divisor)?; + + let mut added = Vec::with_capacity(input_bitlen); + let ys = wires_c; + let (mut s, mut c) = Self::half_adder(g, "ient[0], &ys[0])?; + added.push(s); + + for (x, y) in quotient.iter().zip(ys.iter()).skip(1) { + let res = Self::full_adder(g, x, y, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + for y in ys.iter().take(ys.len() - 1).skip(quotient.len()) { + let res = Self::full_adder_const(g, y, false, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + Ok(added) + } + + /// Divides a public ring element by another shared ring element. The ring element is represented as bitdecompositions x1s and x2s which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as the input wires + fn ring_div_by_shared( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + dividend: &[bool], + wires_c: &[G::Item], + input_bitlen: usize, + ) -> Result, G::Error> { + let divisor = Self::bin_addition_no_carry(g, x1s, x2s)?; + + debug_assert_eq!(dividend.len(), input_bitlen); + debug_assert_eq!(dividend.len(), dividend.len()); + let quotient = Self::bin_div_by_shared(g, dividend, &divisor)?; + + let mut added = Vec::with_capacity(input_bitlen); + let ys = wires_c; + let (mut s, mut c) = Self::half_adder(g, "ient[0], &ys[0])?; + added.push(s); + + for (x, y) in quotient.iter().zip(ys.iter()).skip(1) { + let res = Self::full_adder(g, x, y, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + for y in ys.iter().take(ys.len() - 1).skip(quotient.len()) { + let res = Self::full_adder_const(g, y, false, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + Ok(added) + } + + /// Divides a field element by another. The field elements are represented as bitdecompositions x1s, x2s, y1s and y2s which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as the input wires + fn field_int_div( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + y1s: &[G::Item], + y2s: &[G::Item], + wires_c: &[G::Item], + input_bitlen: usize, + ) -> Result, G::Error> { + let n_bits = F::MODULUS_BIT_SIZE as usize; + assert_eq!(input_bitlen, n_bits); + debug_assert_eq!(input_bitlen, x1s.len()); + debug_assert_eq!(input_bitlen, wires_c.len()); + + // Add x1s and x2s to get the first input bits as Yao wires + let added1 = Self::adder_mod_p_with_output_size::<_, F>(g, x1s, x2s, x1s.len())?; + + // Add y1s and y2s to get the second input bits as Yao wires + let added2 = Self::adder_mod_p_with_output_size::<_, F>(g, y1s, y2s, x1s.len())?; + + // compute the division + let quotient = Self::bin_div(g, &added1, &added2)?; + + // compose chunk_bits again + let result = Self::compose_field_element::(g, "ient, wires_c)?; + + Ok(result) + } + + /// Divides a field element by another public field element. The field elements is represented as bitdecompositions x1s and x2s which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as the input wires + fn field_int_div_by_public( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + divisor: &[bool], + wires_c: &[G::Item], + input_bitlen: usize, + ) -> Result, G::Error> { + let n_bits = F::MODULUS_BIT_SIZE as usize; + assert_eq!(input_bitlen, n_bits); + debug_assert_eq!(input_bitlen, x1s.len()); + debug_assert_eq!(input_bitlen, wires_c.len()); + + // Add x1s and x2s to get the first input bits as Yao wires + let added1 = Self::adder_mod_p_with_output_size::<_, F>(g, x1s, x2s, x1s.len())?; + + // compute the division + let quotient = Self::bin_div_by_public(g, &added1, divisor)?; + + // compose chunk_bits again + let result = Self::compose_field_element::(g, "ient, wires_c)?; + + Ok(result) + } + + /// Divides a public field element by another shared field element. The field elements is represented as bitdecompositions x1s and x2s which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as the input wires + fn field_int_div_by_shared( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + dividend: &[bool], + wires_c: &[G::Item], + input_bitlen: usize, + ) -> Result, G::Error> { + let n_bits = F::MODULUS_BIT_SIZE as usize; + assert_eq!(input_bitlen, n_bits); + debug_assert_eq!(input_bitlen, x1s.len()); + debug_assert_eq!(input_bitlen, wires_c.len()); + + // Add x1s and x2s to get the first input bits as Yao wires + let added1 = Self::adder_mod_p_with_output_size::<_, F>(g, x1s, x2s, x1s.len())?; + + // compute the division + let quotient = Self::bin_div_by_shared(g, dividend, &added1)?; + + // compose chunk_bits again + let result = Self::compose_field_element::(g, "ient, wires_c)?; + + Ok(result) + } + /// Divides a ring element by a power of 2. The ring element is represented as two bitdecompositions wires_a, wires_b which need to be added first. The output is composed using wires_c, whereas wires_c are the same size as wires_a and wires_b pub(crate) fn ring_div_power_2_many( g: &mut G, @@ -975,7 +1408,6 @@ impl GarbledCircuits { divisor_bit, )?); } - Ok(BinaryBundle::new(results)) } @@ -1017,12 +1449,244 @@ impl GarbledCircuits { Ok(BinaryBundle::new(results)) } + + /// Binary division for two vecs of inputs + pub fn ring_div_many( + g: &mut G, + wires_x1: &BinaryBundle, + wires_x2: &BinaryBundle, + wires_c: &BinaryBundle, + input_bitlen: usize, + ) -> Result, G::Error> { + debug_assert_eq!(wires_x1.size(), wires_x2.size()); + let length = wires_x1.size(); + debug_assert_eq!(length % 2, 0); + + debug_assert_eq!(length / 2 % input_bitlen, 0); + debug_assert_eq!(wires_c.size(), length / 2); + let mut results = Vec::with_capacity(wires_c.size()); + + for (chunk_x1, chunk_x2, chunk_y1, chunk_y2, chunk_c) in izip!( + wires_x1.wires()[0..length / 2].chunks(input_bitlen), + wires_x2.wires()[0..length / 2].chunks(input_bitlen), + wires_x1.wires()[length / 2..].chunks(input_bitlen), + wires_x2.wires()[length / 2..].chunks(input_bitlen), + wires_c.wires().chunks(input_bitlen), + ) { + results.extend(Self::ring_div( + g, + chunk_x1, + chunk_x2, + chunk_y1, + chunk_y2, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } + + /// Binary division for two vecs of inputs. The ring elements are represented as two bitdecompositions wires_a, wires_b which need to be split first to get the two inputs. The output is composed using wires_c, whereas wires_c is half the size as wires_a and wires_b + pub fn ring_div_by_public_many( + g: &mut G, + wires_x1: &BinaryBundle, + wires_x2: &BinaryBundle, + wires_c: &BinaryBundle, + input_bitlen: usize, + divisor: Vec, + ) -> Result, G::Error> { + debug_assert_eq!(wires_x1.size(), wires_x2.size()); + let length = wires_x1.size(); + debug_assert_eq!(length % 2, 0); + + debug_assert_eq!(length % input_bitlen, 0); + debug_assert_eq!(wires_c.size(), length); + debug_assert_eq!(divisor.len(), length); + let mut results = Vec::with_capacity(wires_c.size()); + + for (chunk_x1, chunk_x2, div, chunk_c) in izip!( + wires_x1.wires().chunks(input_bitlen), + wires_x2.wires().chunks(input_bitlen), + divisor.chunks(input_bitlen), + wires_c.wires().chunks(input_bitlen), + ) { + results.extend(Self::ring_div_by_public( + g, + chunk_x1, + chunk_x2, + div, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } + + /// Binary division for two vecs of inputs. The ring elements are represented as two bitdecompositions wires_a, wires_b which need to be split first to get the two inputs. The output is composed using wires_c, whereas wires_c is half the size as wires_a and wires_b + pub fn ring_div_by_shared_many( + g: &mut G, + wires_x1: &BinaryBundle, + wires_x2: &BinaryBundle, + wires_c: &BinaryBundle, + input_bitlen: usize, + dividend: Vec, + ) -> Result, G::Error> { + debug_assert_eq!(wires_x1.size(), wires_x2.size()); + let length = wires_x1.size(); + debug_assert_eq!(length % 2, 0); + + debug_assert_eq!(length % input_bitlen, 0); + debug_assert_eq!(wires_c.size(), length); + debug_assert_eq!(dividend.len(), length); + let mut results = Vec::with_capacity(wires_c.size()); + + for (chunk_x1, chunk_x2, div, chunk_c) in izip!( + wires_x1.wires().chunks(input_bitlen), + wires_x2.wires().chunks(input_bitlen), + dividend.chunks(input_bitlen), + wires_c.wires().chunks(input_bitlen), + ) { + results.extend(Self::ring_div_by_shared( + g, + chunk_x1, + chunk_x2, + div, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } + + /// Divides a field element by another. The field elements are represented as two bitdecompositions wires_a, wires_b which need to be split first to get the two inputs. The output is composed using wires_c, whereas wires_c is half the size as wires_a and wires_b + pub(crate) fn field_int_div_many( + g: &mut G, + wires_x1: &BinaryBundle, + wires_x2: &BinaryBundle, + wires_c: &BinaryBundle, + ) -> Result, G::Error> + where + G::Item: Default, + { + let input_bitlen = F::MODULUS_BIT_SIZE as usize; + debug_assert_eq!(wires_x1.size(), wires_x2.size()); + let length = wires_x1.size(); + debug_assert_eq!(length % 2, 0); + + debug_assert_eq!(length / 2 % input_bitlen, 0); + debug_assert_eq!(wires_c.size(), length / 2); + let mut results = Vec::with_capacity(wires_c.size()); + + for (chunk_x1, chunk_x2, chunk_y1, chunk_y2, chunk_c) in izip!( + wires_x1.wires()[0..length / 2].chunks(input_bitlen), + wires_x2.wires()[0..length / 2].chunks(input_bitlen), + wires_x1.wires()[length / 2..].chunks(input_bitlen), + wires_x2.wires()[length / 2..].chunks(input_bitlen), + wires_c.wires().chunks(input_bitlen), + ) { + results.extend(Self::field_int_div::( + g, + chunk_x1, + chunk_x2, + chunk_y1, + chunk_y2, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } + + /// Divides a field element by another public. The field elements are represented as two bitdecompositions wires_a, wires_b which need to be split first to get the two inputs. The output is composed using wires_c, whereas wires_c is half the size as wires_a and wires_b + pub(crate) fn field_int_div_by_public_many< + G: FancyBinary + FancyBinaryConstant, + F: PrimeField, + >( + g: &mut G, + wires_x1: &BinaryBundle, + wires_x2: &BinaryBundle, + wires_c: &BinaryBundle, + divisor: Vec, + ) -> Result, G::Error> + where + G::Item: Default, + { + let input_bitlen = F::MODULUS_BIT_SIZE as usize; + debug_assert_eq!(wires_x1.size(), wires_x2.size()); + let length = wires_x1.size(); + debug_assert_eq!(length % 2, 0); + + debug_assert_eq!(length % input_bitlen, 0); + debug_assert_eq!(wires_c.size(), length); + debug_assert_eq!(divisor.len(), length); + let mut results = Vec::with_capacity(wires_c.size()); + + for (chunk_x1, chunk_x2, div, chunk_c) in izip!( + wires_x1.wires().chunks(input_bitlen), + wires_x2.wires().chunks(input_bitlen), + divisor.chunks(input_bitlen), + wires_c.wires().chunks(input_bitlen), + ) { + results.extend(Self::field_int_div_by_public::( + g, + chunk_x1, + chunk_x2, + div, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } + + /// Divides a public field element by another shared. The field elements are represented as two bitdecompositions wires_a, wires_b which need to be split first to get the two inputs. The output is composed using wires_c, whereas wires_c is half the size as wires_a and wires_b + pub(crate) fn field_int_div_by_shared_many< + G: FancyBinary + FancyBinaryConstant, + F: PrimeField, + >( + g: &mut G, + wires_x1: &BinaryBundle, + wires_x2: &BinaryBundle, + wires_c: &BinaryBundle, + divisor: Vec, + ) -> Result, G::Error> + where + G::Item: Default, + { + let input_bitlen = F::MODULUS_BIT_SIZE as usize; + debug_assert_eq!(wires_x1.size(), wires_x2.size()); + let length = wires_x1.size(); + debug_assert_eq!(length % 2, 0); + + debug_assert_eq!(length % input_bitlen, 0); + debug_assert_eq!(wires_c.size(), length); + debug_assert_eq!(divisor.len(), length); + let mut results = Vec::with_capacity(wires_c.size()); + + for (chunk_x1, chunk_x2, div, chunk_c) in izip!( + wires_x1.wires().chunks(input_bitlen), + wires_x2.wires().chunks(input_bitlen), + divisor.chunks(input_bitlen), + wires_c.wires().chunks(input_bitlen), + ) { + results.extend(Self::field_int_div_by_shared::( + g, + chunk_x1, + chunk_x2, + div, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } } #[cfg(test)] mod test { use super::*; use crate::protocols::rep3::yao::GCInputs; + use fancy_garbling::BinaryGadgets; use fancy_garbling::{Evaluator, Fancy, Garbler, WireMod2}; use rand::{thread_rng, CryptoRng, Rng, SeedableRng}; use rand_chacha::ChaCha12Rng; @@ -1126,4 +1790,75 @@ mod test { gc_test::(); } } + + fn gc_test_div_int() + where + num_bigint::BigUint: std::convert::From, + { + let mut rng = thread_rng(); + + let a = F::rand(&mut rng); + let b = F::rand(&mut rng); + let is_result = F::from(BigUint::from(a) / BigUint::from(b)); + let (sender, receiver) = UnixStream::pair().unwrap(); + + std::thread::spawn(move || { + let rng = ChaCha12Rng::from_entropy(); + let reader = BufReader::new(sender.try_clone().unwrap()); + let writer = BufWriter::new(sender); + let channel_sender = Channel::new(reader, writer); + + let mut garbler = Garbler::<_, _, WireMod2>::new(channel_sender, rng); + + // This is without OT, just a simulation + let a = encode_field(a, &mut garbler); + let b = encode_field(b, &mut garbler); + for a in a.evaluator_wires.wires().iter() { + garbler.send_wire(a).unwrap(); + } + for b in b.evaluator_wires.wires().iter() { + garbler.send_wire(b).unwrap(); + } + + let garble_result = + BinaryGadgets::bin_div(&mut garbler, &a.garbler_wires, &b.garbler_wires).unwrap(); + + // Output + garbler.outputs(garble_result.wires()).unwrap(); + }); + + let reader = BufReader::new(receiver.try_clone().unwrap()); + let writer = BufWriter::new(receiver); + let channel_rcv = Channel::new(reader, writer); + + let mut evaluator = Evaluator::<_, WireMod2>::new(channel_rcv); + + // This is without OT, just a simulation + let n_bits = F::MODULUS_BIT_SIZE as usize; + let mut a = Vec::with_capacity(n_bits); + let mut b = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let a_ = evaluator.read_wire(2).unwrap(); + a.push(a_); + } + for _ in 0..n_bits { + let b_ = evaluator.read_wire(2).unwrap(); + b.push(b_); + } + let a = BinaryBundle::new(a); + let b = BinaryBundle::new(b); + + let eval_result = BinaryGadgets::bin_div(&mut evaluator, &a, &b).unwrap(); + + let result = evaluator.outputs(eval_result.wires()).unwrap().unwrap(); + let result = GCUtils::u16_bits_to_field::(result).unwrap(); + assert_eq!(result, is_result); + } + + #[test] + fn gc_test_bn254_div_int() { + for _ in 0..TESTRUNS { + gc_test_div_int::(); + } + } } diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 9a97f110d..be569dc74 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -4,7 +4,7 @@ //! //! This file is heavily inspired by [fancy-garbling](https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs) -use super::GCUtils; +use super::{circuits::FancyBinaryConstant, GCUtils}; use crate::protocols::rep3::{ id::PartyID, network::{IoContext, Rep3Network}, @@ -24,6 +24,8 @@ pub struct Rep3Evaluator<'a, N: Rep3Network> { current_gate: usize, circuit: Vec<[u8; 16]>, current_circuit_element: usize, + const_zero: Option, + const_one: Option, } impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { @@ -40,6 +42,8 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { current_gate: 0, circuit: Vec::new(), current_circuit_element: 0, + const_zero: None, + const_one: None, } } @@ -263,3 +267,29 @@ impl FancyBinary for Rep3Evaluator<'_, N> { Ok(self.evaluate_and_gate(a, b, &gate0, &gate1)) } } + +impl FancyBinaryConstant for Rep3Evaluator<'_, N> { + fn const_zero(&mut self) -> Result { + let zero = match self.const_zero { + Some(zero) => zero, + None => { + let zero = self.constant(0, 2)?; + self.const_zero = Some(zero); + zero + } + }; + Ok(zero) + } + + fn const_one(&mut self) -> Result { + let one = match self.const_one { + Some(one) => one, + None => { + let one = self.constant(1, 2)?; + self.const_one = Some(one); + one + } + }; + Ok(one) + } +} diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index ce4602b09..2441e36ff 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -4,7 +4,7 @@ //! //! This implementation is heavily inspired by [fancy-garbling](https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs) -use super::{GCInputs, GCUtils}; +use super::{circuits::FancyBinaryConstant, GCInputs, GCUtils}; use crate::{ protocols::rep3::{ id::PartyID, @@ -31,6 +31,8 @@ pub struct Rep3Garbler<'a, N: Rep3Network> { pub(crate) rng: RngType, hash: Sha3_256, // For the ID2 to match everything sent with one hash circuit: Vec<[u8; 16]>, + const_zero: Option, + const_one: Option, } impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { @@ -55,6 +57,8 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { rng, hash: Sha3_256::default(), circuit: Vec::new(), + const_zero: None, + const_one: None, } } @@ -237,7 +241,7 @@ impl Fancy for Rep3Garbler<'_, N> { fn constant(&mut self, x: u16, q: u16) -> Result { let zero = WireMod2::rand(&mut self.rng, q); - let wire = zero.plus(self.delta.cmul_eq(x)); + let wire = zero.plus(&self.delta.cmul(x)); self.add_wire_to_circuit(&wire); Ok(zero) } @@ -274,3 +278,30 @@ impl FancyBinary for Rep3Garbler<'_, N> { self.xor(&delta, x) } } + +impl FancyBinaryConstant for Rep3Garbler<'_, N> { + fn const_zero(&mut self) -> Result { + let zero = match self.const_zero { + Some(zero) => zero, + None => { + let zero = self.constant(0, 2)?; + self.const_zero = Some(zero); + zero + } + }; + Ok(zero) + } + + fn const_one(&mut self) -> Result { + // We cannot use the const_zero wire since it would leak the delta + let zero = match self.const_one { + Some(zero) => zero, + None => { + let zero = self.constant(1, 2)?; + self.const_one = Some(zero); // The garbler stores the 0 wire + zero + } + }; + Ok(zero) + } +} diff --git a/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs b/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs index da3eeeff4..ccc25a4fe 100644 --- a/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs @@ -4,7 +4,7 @@ //! //! This file is heavily inspired by [fancy-garbling](https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs) -use super::GCUtils; +use super::{circuits::FancyBinaryConstant, GCUtils}; use crate::protocols::rep3::{ id::PartyID, network::{IoContext, Rep3Network}, @@ -23,6 +23,8 @@ pub struct StreamingRep3Evaluator<'a, N: Rep3Network> { current_output: usize, current_gate: usize, hash: Sha3_256, // For the ID2 to match everything sent with one hash + const_zero: Option, + const_one: Option, } impl<'a, N: Rep3Network> StreamingRep3Evaluator<'a, N> { @@ -38,6 +40,8 @@ impl<'a, N: Rep3Network> StreamingRep3Evaluator<'a, N> { current_output: 0, current_gate: 0, hash: Sha3_256::default(), + const_zero: None, + const_one: None, } } @@ -253,3 +257,29 @@ impl FancyBinary for StreamingRep3Evaluator<'_, N> { Ok(self.evaluate_and_gate(a, b, &gate0, &gate1)) } } + +impl FancyBinaryConstant for StreamingRep3Evaluator<'_, N> { + fn const_zero(&mut self) -> Result { + let zero = match self.const_zero { + Some(zero) => zero, + None => { + let zero = self.constant(0, 2)?; + self.const_zero = Some(zero); + zero + } + }; + Ok(zero) + } + + fn const_one(&mut self) -> Result { + let one = match self.const_one { + Some(one) => one, + None => { + let one = self.constant(1, 2)?; + self.const_one = Some(one); + one + } + }; + Ok(one) + } +} diff --git a/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs b/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs index c9d9b10c0..5ec4ba280 100644 --- a/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs @@ -4,7 +4,7 @@ //! //! This implementation is heavily inspired by [fancy-garbling](https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs) -use super::{GCInputs, GCUtils}; +use super::{circuits::FancyBinaryConstant, GCInputs, GCUtils}; use crate::{ protocols::rep3::{ id::PartyID, @@ -30,6 +30,8 @@ pub struct StreamingRep3Garbler<'a, N: Rep3Network> { current_gate: usize, pub(crate) rng: RngType, hash: Sha3_256, // For the ID2 to match everything sent with one hash + const_zero: Option, + const_one: Option, } impl<'a, N: Rep3Network> StreamingRep3Garbler<'a, N> { @@ -53,6 +55,8 @@ impl<'a, N: Rep3Network> StreamingRep3Garbler<'a, N> { current_gate: 0, rng, hash: Sha3_256::default(), + const_zero: None, + const_one: None, } } @@ -215,7 +219,7 @@ impl Fancy for StreamingRep3Garbler<'_, N> { fn constant(&mut self, x: u16, q: u16) -> Result { let zero = WireMod2::rand(&mut self.rng, q); - let wire = zero.plus(self.delta.cmul_eq(x)); + let wire = zero.plus(&self.delta.cmul(x)); self.send_wire(&wire)?; Ok(zero) } @@ -252,3 +256,30 @@ impl FancyBinary for StreamingRep3Garbler<'_, N> { self.xor(&delta, x) } } + +impl FancyBinaryConstant for StreamingRep3Garbler<'_, N> { + fn const_zero(&mut self) -> Result { + let zero = match self.const_zero { + Some(zero) => zero, + None => { + let zero = self.constant(0, 2)?; + self.const_zero = Some(zero); + zero + } + }; + Ok(zero) + } + + fn const_one(&mut self) -> Result { + // We cannot use the const_zero wire since it would leak the delta + let zero = match self.const_one { + Some(zero) => zero, + None => { + let zero = self.constant(1, 2)?; + self.const_one = Some(zero); // The garbler stores the 0 wire + zero + } + }; + Ok(zero) + } +} diff --git a/mpc-core/src/protocols/rep3_ring.rs b/mpc-core/src/protocols/rep3_ring.rs index b3b7d112b..da3147640 100644 --- a/mpc-core/src/protocols/rep3_ring.rs +++ b/mpc-core/src/protocols/rep3_ring.rs @@ -28,6 +28,7 @@ where { let a = rng.gen::>(); let b = rng.gen::>(); + let c = val - a - b; let share1 = Rep3RingShare::new_ring(a, c); let share2 = Rep3RingShare::new_ring(b, a); diff --git a/mpc-core/src/protocols/rep3_ring/yao.rs b/mpc-core/src/protocols/rep3_ring/yao.rs index c6e33c247..3c4aa03a4 100644 --- a/mpc-core/src/protocols/rep3_ring/yao.rs +++ b/mpc-core/src/protocols/rep3_ring/yao.rs @@ -63,6 +63,16 @@ impl GCUtils { res } + fn ring_to_bits(input: RingElement) -> Vec { + let mut res = Vec::with_capacity(T::K); + let mut el = input; + for _ in 0..T::K { + res.push((el & RingElement::one()) == RingElement::one()); + el >>= 1; + } + res + } + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires pub fn encode_ring( ring: RingElement, @@ -719,14 +729,131 @@ where /// Divides a ring element by a power of 2. pub fn ring_div_power_2( - inputs: Rep3RingShare, + input: Rep3RingShare, io_context: &mut IoContext, divisor_bit: usize, ) -> IoResult> where Standard: Distribution, { - let res = ring_div_power_2_many(&[inputs], io_context, divisor_bit)?; + let res = ring_div_power_2_many(&[input], io_context, divisor_bit)?; + Ok(res[0]) +} + +/// Divides a vector of ring elements by another. +pub fn ring_div_many( + input1: &[Rep3RingShare], + input2: &[Rep3RingShare], + io_context: &mut IoContext, +) -> IoResult>> +where + Standard: Distribution, +{ + let num_inputs = input1.len(); + debug_assert_eq!(input1.len(), input2.len()); + + let mut combined_inputs = Vec::with_capacity(input1.len() + input2.len()); + combined_inputs.extend_from_slice(input1); + combined_inputs.extend_from_slice(input2); + + decompose_circuit_compose_blueprint!( + &combined_inputs, + io_context, + num_inputs, + T, + GarbledCircuits::ring_div_many, + (T::K) + ) +} + +/// Divides a ring element by another. +pub fn ring_div( + input1: Rep3RingShare, + input2: Rep3RingShare, + + io_context: &mut IoContext, +) -> IoResult> +where + Standard: Distribution, +{ + let res = ring_div_many(&[input1], &[input2], io_context)?; + Ok(res[0]) +} + +/// Divides a vector of ring elements by another public. +pub fn ring_div_by_public_many( + input: &[Rep3RingShare], + divisors: &[RingElement], + io_context: &mut IoContext, +) -> IoResult>> +where + Standard: Distribution, +{ + let num_inputs = input.len(); + debug_assert_eq!(input.len(), divisors.len()); + let mut divisors_as_bits = Vec::with_capacity(T::K * num_inputs); + divisors + .iter() + .for_each(|y| divisors_as_bits.extend(GCUtils::ring_to_bits::(*y))); + decompose_circuit_compose_blueprint!( + &input, + io_context, + num_inputs, + T, + GarbledCircuits::ring_div_by_public_many, + (T::K, divisors_as_bits) + ) +} + +/// Divides a ring element by another public. +pub fn ring_div_by_public( + input: Rep3RingShare, + divisor: RingElement, + + io_context: &mut IoContext, +) -> IoResult> +where + Standard: Distribution, +{ + let res = ring_div_by_public_many(&[input], &[divisor], io_context)?; + Ok(res[0]) +} + +/// Divides a public vector of ring elements by another. +pub fn ring_div_by_shared_many( + input: &[RingElement], + divisors: &[Rep3RingShare], + io_context: &mut IoContext, +) -> IoResult>> +where + Standard: Distribution, +{ + let num_inputs = input.len(); + assert_eq!(input.len(), divisors.len()); + let mut input_as_bits = Vec::with_capacity(T::K * num_inputs); + input + .iter() + .for_each(|y| input_as_bits.extend(GCUtils::ring_to_bits::(*y))); + decompose_circuit_compose_blueprint!( + &divisors, + io_context, + num_inputs, + T, + GarbledCircuits::ring_div_by_shared_many, + (T::K, input_as_bits) + ) +} + +/// Divides a public ring element by another. +pub fn ring_div_by_shared( + input: RingElement, + divisor: Rep3RingShare, + io_context: &mut IoContext, +) -> IoResult> +where + Standard: Distribution, +{ + let res = ring_div_by_shared_many(&[input], &[divisor], io_context)?; Ok(res[0]) } @@ -869,7 +996,7 @@ where /// Decomposes a FieldElement into a vector of RingElements of size decompose_bitlen each. In total, ther will be num_decomps_per_field decompositions. The output is stored in the ring specified by T. pub fn decompose_field_to_rings( - inputs: Rep3PrimeFieldShare, + input: Rep3PrimeFieldShare, io_context: &mut IoContext, num_decomps_per_field: usize, decompose_bitlen: usize, @@ -878,9 +1005,115 @@ where Standard: Distribution, { decompose_field_to_rings_many( - &[inputs], + &[input], io_context, num_decomps_per_field, decompose_bitlen, ) } + +// Don't know if we need this at some point? +#[expect(unused_macros)] +macro_rules! decompose_circuit_compose_blueprint_2 { + ($input1:expr,$input2:expr, $io_context:expr, $output_size:expr, $t:ty, $circuit:expr, ($( $args:expr ),*)) => {{ + use $crate::protocols::rep3::id::PartyID; + use itertools::izip; + use $crate::protocols::rep3_ring::yao; + use $crate::protocols::rep3_ring::Rep3RingShare; + + let delta = $io_context + .rngs + .generate_random_garbler_delta($io_context.id); + + let [x01, x2] = yao::joint_input_arithmetic_added_many($input1, delta, $io_context)?; + let [y01, y2] = yao::joint_input_arithmetic_added_many($input2, delta, $io_context)?; + + let mut res = vec![Rep3RingShare::zero_share(); $output_size]; + + match $io_context.id { + PartyID::ID0 => { + for res in res.iter_mut() { + let k3 = $io_context.rngs.bitcomp2.random_elements_3keys::>(); + res.b = (k3.0 + k3.1 + k3.2).neg(); + } + // TODO this can be parallelized with joint_input_arithmetic_added_many + let x23 = yao::input_ring_id2_many::<$t, _>(None, None, $output_size, $io_context)?; + + let mut evaluator = rep3::yao::evaluator::Rep3Evaluator::new($io_context); + evaluator.receive_circuit()?; + + let x1 = $circuit(&mut evaluator, &x01, &x2,&y01, &y2, &x23, $($args),*); + let x1 = yao::GCUtils::garbled_circuits_error(x1)?; + let x1 = evaluator.output_to_id0_and_id1(x1.wires())?; //here + // Compose the bits + for (res, x1) in izip!(res.iter_mut(), x1.chunks(<$t>::K)) { + res.a = yao::GCUtils::bits_to_ring(x1)?; + } + } + PartyID::ID1 => { + for res in res.iter_mut() { + let k2 = $io_context.rngs.bitcomp1.random_elements_3keys::>(); + res.a = (k2.0 + k2.1 + k2.2).neg(); + } + + // TODO this can be parallelized with joint_input_arithmetic_added_many + let x23 = yao::input_ring_id2_many::<$t, _>(None, None, $output_size, $io_context)?; + + let mut garbler = + rep3::yao::garbler::Rep3Garbler::new_with_delta($io_context, delta.expect("Delta not provided")); + + let x1 = $circuit(&mut garbler, &x01, &x2,&y01, &y2, &x23, $($args),*); + + let x1 = yao::GCUtils::garbled_circuits_error(x1)?; + + let x1 = garbler.output_to_id0_and_id1(x1.wires())?; + + let x1 = match x1 { + Some(x1) => x1, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "No output received", + ))?, + }; + + // Compose the bits + for (res, x1) in izip!(res.iter_mut(), x1.chunks(<$t>::K)) { + res.b = yao::GCUtils::bits_to_ring(x1)?; + } + } + PartyID::ID2 => { + let mut x23 = Vec::with_capacity($output_size); + for res in res.iter_mut() { + let k2 = $io_context.rngs.bitcomp1.random_elements_3keys::>(); + let k3 = $io_context.rngs.bitcomp2.random_elements_3keys::>(); + let k2_comp = k2.0 + k2.1 + k2.2; + let k3_comp = k3.0 + k3.1 + k3.2; + x23.push(k2_comp + k3_comp); + res.a = k3_comp.neg(); + res.b = k2_comp.neg(); + } + + // TODO this can be parallelized with joint_input_arithmetic_added_many + let x23 = yao::input_ring_id2_many(Some(x23), delta, $output_size, $io_context)?; + + let mut garbler = + rep3::yao::garbler::Rep3Garbler::new_with_delta($io_context, delta.expect("Delta not provided")); + + let x1 = $circuit(&mut garbler, &x01, &x2,&y01, &y2, &x23, $($args),*); + + let x1 = yao::GCUtils::garbled_circuits_error(x1)?; + let x1 = garbler.output_to_id0_and_id1(x1.wires())?; + if x1.is_some() { + Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Unexpected output received", + ))?; + } + } + } + + Ok(res) + }}; +} +#[expect(unused_imports)] +pub(crate) use decompose_circuit_compose_blueprint_2; diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 5b5a9868e..61a7e1929 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -1489,7 +1489,8 @@ mod field_share { assert_eq!(is_result, should_result); } - fn reshare_from_2_to_3_parties_test_internal(recipient: PartyID) { + #[test] + fn rep3_int_div_via_yao() { const VEC_SIZE: usize = 10; let test_network = Rep3TestNetwork::default(); @@ -1498,6 +1499,64 @@ mod field_share { .map(|_| ark_bn254::Fr::rand(&mut rng)) .collect_vec(); let x_shares = rep3::share_field_elements(&x, &mut rng); + let y = (0..VEC_SIZE) + .map(|_| ark_bn254::Fr::rand(&mut rng)) + .collect_vec(); + let y_shares = rep3::share_field_elements(&y, &mut rng); + + let mut should_result = Vec::with_capacity(VEC_SIZE); + for (x, y) in x.into_iter().zip(y.into_iter()) { + let x: BigUint = x.into(); + let y: BigUint = y.into(); + + should_result.push(ark_bn254::Fr::from(x / y)); + } + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x, y) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter(), + y_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let decomposed = yao::field_int_div_many(&x, &y, &mut rep3).unwrap(); + tx.send(decomposed) + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_elements(&result1, &result2, &result3); + assert_eq!(is_result, should_result); + } + + #[test] + fn rep3_int_div_by_public_via_yao() { + const VEC_SIZE: usize = 10; + + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = (0..VEC_SIZE) + .map(|_| ark_bn254::Fr::rand(&mut rng)) + .collect_vec(); + let x_shares = rep3::share_field_elements(&x, &mut rng); + let y = (0..VEC_SIZE) + .map(|_| ark_bn254::Fr::rand(&mut rng)) + .collect_vec(); + let mut should_result = Vec::with_capacity(VEC_SIZE); + for (x, y) in x.into_iter().zip(y.iter().cloned()) { + let x: BigUint = x.into(); + let y: BigUint = y.into(); + + should_result.push(ark_bn254::Fr::from(x / y)); + } let (tx1, rx1) = mpsc::channel(); let (tx2, rx2) = mpsc::channel(); @@ -1506,11 +1565,88 @@ mod field_share { for (net, tx, x) in izip!( test_network.get_party_networks().into_iter(), [tx1, tx2, tx3], - x_shares.into_iter() + x_shares.into_iter(), ) { + let y_ = y.to_owned(); thread::spawn(move || { let mut rep3 = IoContext::init(net).unwrap(); + let decomposed = yao::field_int_div_by_public_many(&x, &y_, &mut rep3).unwrap(); + tx.send(decomposed) + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_elements(&result1, &result2, &result3); + assert_eq!(is_result, should_result); + } + + #[test] + fn rep3_int_div_by_shared_via_yao() { + const VEC_SIZE: usize = 10; + + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = (0..VEC_SIZE) + .map(|_| ark_bn254::Fr::rand(&mut rng)) + .collect_vec(); + let y = (0..VEC_SIZE) + .map(|_| ark_bn254::Fr::rand(&mut rng)) + .collect_vec(); + let y_shares = rep3::share_field_elements(&y, &mut rng); + + let mut should_result = Vec::with_capacity(VEC_SIZE); + for (x, y) in x.iter().cloned().zip(y.into_iter()) { + let x: BigUint = x.into(); + let y: BigUint = y.into(); + + should_result.push(ark_bn254::Fr::from(x / y)); + } + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, y_c) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + y_shares.into_iter(), + ) { + let x_ = x.to_owned(); + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::field_int_div_by_shared_many(&x_, &y_c, &mut rep3).unwrap(); + tx.send(div) + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_elements(&result1, &result2, &result3); + assert_eq!(is_result, should_result); + } + + fn reshare_from_2_to_3_parties_test_internal(recipient: PartyID) { + const VEC_SIZE: usize = 10; + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = (0..VEC_SIZE) + .map(|_| ark_bn254::Fr::rand(&mut rng)) + .collect_vec(); + let x_shares = rep3::share_field_elements(&x, &mut rng); + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); let decomposed = arithmetic::reshare_from_2_to_3_parties( Some(x), VEC_SIZE, @@ -1521,14 +1657,12 @@ mod field_share { tx.send(decomposed) }); } - let result1 = rx1.recv().unwrap(); let result2 = rx2.recv().unwrap(); let result3 = rx3.recv().unwrap(); let is_result = rep3::combine_field_elements(&result1, &result2, &result3); assert_eq!(is_result, x); } - #[test] fn reshare_from_2_to_3_parties_test() { reshare_from_2_to_3_parties_test_internal(PartyID::ID0); diff --git a/tests/tests/mpc/rep3_ring.rs b/tests/tests/mpc/rep3_ring.rs index baf62cabe..34156942c 100644 --- a/tests/tests/mpc/rep3_ring.rs +++ b/tests/tests/mpc/rep3_ring.rs @@ -51,6 +51,18 @@ mod ring_share { }; } + fn gen_non_zero(rng: &mut R) -> RingElement + where + Standard: Distribution, + { + loop { + let el = rng.gen::>(); + if !el.is_zero() { + return el; + } + } + } + // TODO we dont need channels, we can just join fn rep3_add_t() @@ -1732,4 +1744,158 @@ mod ring_share { fn rep3_div_power_2_via_yao() { apply_to_all!(rep3_div_power_2_via_yao_t, [Bit, u8, u16, u32, u64, u128]); } + + fn rep3_bin_div_via_yao_t() + where + Standard: Distribution, + { + const VEC_SIZE: usize = 10; + + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = (0..VEC_SIZE) + .map(|_| rng.gen::>()) + .collect_vec(); + let y = (0..VEC_SIZE) + .map(|_| gen_non_zero::(&mut rng)) + .collect_vec(); + let x_shares = rep3_ring::share_ring_elements(&x, &mut rng); + let y_shares = rep3_ring::share_ring_elements(&y, &mut rng); + let mut should_result: Vec> = Vec::with_capacity(VEC_SIZE); + for (x, y) in x.into_iter().zip(y.into_iter()) { + should_result.push(RingElement(T::cast_from_biguint( + &(x.0.cast_to_biguint() / y.0.cast_to_biguint()), + ))); + } + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x, y) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter(), + y_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::ring_div_many(&x, &y, &mut rep3).unwrap(); + tx.send(div) + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); + assert_eq!(is_result, should_result); + } + + #[test] + fn rep3_bin_div_via_yao() { + apply_to_all!(rep3_bin_div_via_yao_t, [u8, u16, u32, u64, u128]); + } + + fn rep3_bin_div_by_public_via_yao_t() + where + Standard: Distribution, + { + const VEC_SIZE: usize = 10; + + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = (0..VEC_SIZE) + .map(|_| rng.gen::>()) + .collect_vec(); + let y = (0..VEC_SIZE) + .map(|_| gen_non_zero::(&mut rng)) + .collect_vec(); + let x_shares = rep3_ring::share_ring_elements(&x, &mut rng); + let mut should_result: Vec> = Vec::with_capacity(VEC_SIZE); + for (x, y) in x.into_iter().zip(y.iter()) { + should_result.push(RingElement(T::cast_from_biguint( + &(x.0.cast_to_biguint() / y.0.cast_to_biguint()), + ))); + } + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter(), + ) { + let y_ = y.to_owned(); + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::ring_div_by_public_many(&x, &y_, &mut rep3).unwrap(); + tx.send(div) + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); + assert_eq!(is_result, should_result); + } + + #[test] + fn rep3_bin_div_by_public_via_yao() { + apply_to_all!(rep3_bin_div_by_public_via_yao_t, [u8, u16, u32, u64, u128]); + } + + fn rep3_bin_div_by_shared_via_yao_t() + where + Standard: Distribution, + { + const VEC_SIZE: usize = 10; + + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = (0..VEC_SIZE) + .map(|_| rng.gen::>()) + .collect_vec(); + let y = (0..VEC_SIZE) + .map(|_| gen_non_zero::(&mut rng)) + .collect_vec(); + let y_shares = rep3_ring::share_ring_elements(&y, &mut rng); + let mut should_result: Vec> = Vec::with_capacity(VEC_SIZE); + for (x, y) in x.iter().zip(y.into_iter()) { + should_result.push(RingElement(T::cast_from_biguint( + &(x.0.cast_to_biguint() / y.0.cast_to_biguint()), + ))); + } + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, y_c) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + y_shares.into_iter(), + ) { + let x_ = x.to_owned(); + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::ring_div_by_shared_many(&x_, &y_c, &mut rep3).unwrap(); + tx.send(div) + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); + assert_eq!(is_result, should_result); + } + + #[test] + fn rep3_bin_div_by_shared_via_yao() { + apply_to_all!(rep3_bin_div_by_shared_via_yao_t, [u8, u16, u32, u64, u128]); + } }