From 9f879c8988d6ba7d4c1e209b95d15e9517083e4b Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:28:27 +0100 Subject: [PATCH 01/17] feat: add binary division in gc . implemented but fails works --- mpc-core/src/protocols/rep3/yao/circuits.rs | 201 +++++++++++++++++++- mpc-core/src/protocols/rep3_ring.rs | 1 + mpc-core/src/protocols/rep3_ring/yao.rs | 150 ++++++++++++++- tests/tests/mpc/rep3_ring.rs | 53 ++++++ 4 files changed, 401 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 9a499608f..69bfb6377 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -183,6 +183,39 @@ impl GarbledCircuits { Ok((result, c)) } + /// Needed for subtraction where xs is a constant 0 bundle (conceptually) with only some values set + #[expect(clippy::type_complexity)] + fn bin_subtraction_with_partial_constant( + g: &mut G, + xs: &[G::Item], + ys: &[G::Item], + ) -> Result<(Vec, G::Item), G::Error> { + // debug_assert_eq!(xs.len(), ys.len()); + 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)?; + // (s, c) = Self::full_adder_const(g, &y, false, &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 +237,101 @@ impl GarbledCircuits { Ok(c) } + // From swanky: + /// Binary division + fn bin_div( + g: &mut G, + x1s: &[G::Item], + x2s: &[G::Item], + y1s: &[G::Item], + y2s: &[G::Item], + c_wire: &[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)?; + 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_with_partial_constant(g, &acc, &divisor)?; + + acc = Self::bin_multiplex(g, &cout, &acc, &res)?; + qs.push(cout); + } + qs.reverse(); // Switch back to little-endian + let mut added = Vec::with_capacity(input_bitlen); + let ys = c_wire; + let (mut s, mut c) = Self::half_adder(g, &qs[0], &ys[0])?; + added.push(s); + + for (x, y) in qs.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(qs.len()) { + let res = Self::full_adder_const(g, y, false, &c)?; + s = res.0; + c = res.1; + added.push(s); + } + Ok(added) + } + + /// Binary division for two vecs of inputs + pub fn bin_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); + 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::bin_div( + g, + chunk_x1, + chunk_x2, + chunk_y1, + chunk_y2, + chunk_c, + input_bitlen, + )?); + } + Ok(BinaryBundle::new(results)) + } + /// 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>>() + } + /// subtracts p from wires (with carry) and returns the result and the overflow bit #[expect(clippy::type_complexity)] fn sub_p( @@ -975,7 +1103,6 @@ impl GarbledCircuits { divisor_bit, )?); } - Ok(BinaryBundle::new(results)) } @@ -1023,6 +1150,7 @@ impl GarbledCircuits { 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; @@ -1032,7 +1160,7 @@ mod test { os::unix::net::UnixStream, }; - const TESTRUNS: usize = 5; + const TESTRUNS: usize = 50; // This puts the X_0 values into garbler_wires and X_c values into evaluator_wires fn encode_field( @@ -1126,4 +1254,73 @@ 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..1 { + gc_test_div_int::(); + } + } } 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..9361db580 100644 --- a/mpc-core/src/protocols/rep3_ring/yao.rs +++ b/mpc-core/src/protocols/rep3_ring/yao.rs @@ -719,14 +719,54 @@ 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_bin_div_many( + input1: &[Rep3RingShare], + input2: &[Rep3RingShare], + io_context: &mut IoContext, +) -> IoResult>> +where + Standard: Distribution, +{ + let num_inputs = input1.len(); + 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::bin_div_many, + (T::K) + ) +} + +/// Divides a ring element by another. +pub fn ring_bin_div( + input1: Rep3RingShare, + input2: Rep3RingShare, + + io_context: &mut IoContext, +) -> IoResult> +where + Standard: Distribution, +{ + let res = ring_bin_div_many(&[input1], &[input2], io_context)?; Ok(res[0]) } @@ -884,3 +924,109 @@ where 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_ring.rs b/tests/tests/mpc/rep3_ring.rs index baf62cabe..3308564c7 100644 --- a/tests/tests/mpc/rep3_ring.rs +++ b/tests/tests/mpc/rep3_ring.rs @@ -60,6 +60,7 @@ mod ring_share { let mut rng = thread_rng(); let x = rng.gen::>(); let y = rng.gen::>(); + println!("x {x} y {x}"); let x_shares = rep3_ring::share_ring_element(x, &mut rng); let y_shares = rep3_ring::share_ring_element(y, &mut rng); let should_result = x + y; @@ -1727,6 +1728,58 @@ mod ring_share { let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); assert_eq!(is_result, should_result); } + 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(|_| rng.gen::>()) + .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_bin_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]); + } #[test] fn rep3_div_power_2_via_yao() { From d18b9c500041e872ee5e639183ffc95287c9885f Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:00:48 +0100 Subject: [PATCH 02/17] feat: add shared/shared to co-brillig --- co-noir/co-brillig/src/mpc/rep3.rs | 28 ++- mpc-core/src/protocols/rep3/yao.rs | 38 +++- mpc-core/src/protocols/rep3/yao/circuits.rs | 207 ++++++++++++++------ mpc-core/src/protocols/rep3_ring/yao.rs | 8 +- tests/tests/mpc/rep3.rs | 50 ++++- tests/tests/mpc/rep3_ring.rs | 5 +- 6 files changed, 258 insertions(+), 78 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 4b3f572c6..1e780556f 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -808,7 +808,28 @@ impl BrilligDriver for Rep3BrilligDriver (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) + } + _ => panic!("type mismatch. Can only mul matching values"), }, }; Ok(result) @@ -850,8 +871,9 @@ impl BrilligDriver for Rep3BrilligDriver } } (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") } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 4cd7dd23e..404f6343f 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -674,7 +674,7 @@ 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 +708,42 @@ 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(); + + // if divisor_bit == 0 { + // return Ok(inputs.to_owned()); + // } + // if divisor_bit >= F::MODULUS_BIT_SIZE as usize { + // return Ok(vec![Rep3PrimeFieldShare::zero_share(); num_inputs]); + // } + 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 a power of 2, 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]) +} + 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 69bfb6377..20c58b50e 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -183,7 +183,7 @@ impl GarbledCircuits { Ok((result, c)) } - /// Needed for subtraction where xs is a constant 0 bundle (conceptually) with only some values set + /// Needed for subtraction in binary division where xs is (conceptually) a constant 0 bundle with only some values set #[expect(clippy::type_complexity)] fn bin_subtraction_with_partial_constant( g: &mut G, @@ -241,15 +241,9 @@ impl GarbledCircuits { /// Binary division fn bin_div( g: &mut G, - x1s: &[G::Item], - x2s: &[G::Item], - y1s: &[G::Item], - y2s: &[G::Item], - c_wire: &[G::Item], - input_bitlen: usize, + dividend: &[G::Item], + divisor: &[G::Item], ) -> Result, G::Error> { - let dividend = Self::bin_addition_no_carry(g, x1s, x2s)?; - let divisor = Self::bin_addition_no_carry(g, y1s, y2s)?; let mut acc: Vec = Vec::with_capacity(dividend.len()); let mut qs: Vec = vec![]; for x in dividend.iter().rev() { @@ -258,67 +252,15 @@ impl GarbledCircuits { } acc.insert(0, x.clone()); - let (res, cout) = Self::bin_subtraction_with_partial_constant(g, &acc, &divisor)?; + let (res, cout) = Self::bin_subtraction_with_partial_constant(g, &acc, divisor)?; acc = Self::bin_multiplex(g, &cout, &acc, &res)?; qs.push(cout); } qs.reverse(); // Switch back to little-endian - let mut added = Vec::with_capacity(input_bitlen); - let ys = c_wire; - let (mut s, mut c) = Self::half_adder(g, &qs[0], &ys[0])?; - added.push(s); - - for (x, y) in qs.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(qs.len()) { - let res = Self::full_adder_const(g, y, false, &c)?; - s = res.0; - c = res.1; - added.push(s); - } - Ok(added) + Ok(qs) } - /// Binary division for two vecs of inputs - pub fn bin_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); - 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::bin_div( - g, - chunk_x1, - chunk_x2, - chunk_y1, - chunk_y2, - chunk_c, - input_bitlen, - )?); - } - Ok(BinaryBundle::new(results)) - } /// Multiplex gadget for binary bundles fn bin_multiplex( g: &mut G, @@ -1067,6 +1009,71 @@ 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 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 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( @@ -1144,6 +1151,80 @@ 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)) + } + + /// 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)) + } } #[cfg(test)] diff --git a/mpc-core/src/protocols/rep3_ring/yao.rs b/mpc-core/src/protocols/rep3_ring/yao.rs index 9361db580..736163179 100644 --- a/mpc-core/src/protocols/rep3_ring/yao.rs +++ b/mpc-core/src/protocols/rep3_ring/yao.rs @@ -731,7 +731,7 @@ where } /// Divides a vector of ring elements by another. -pub fn ring_bin_div_many( +pub fn ring_div_many( input1: &[Rep3RingShare], input2: &[Rep3RingShare], io_context: &mut IoContext, @@ -751,13 +751,13 @@ where io_context, num_inputs, T, - GarbledCircuits::bin_div_many, + GarbledCircuits::ring_div_many, (T::K) ) } /// Divides a ring element by another. -pub fn ring_bin_div( +pub fn ring_div( input1: Rep3RingShare, input2: Rep3RingShare, @@ -766,7 +766,7 @@ pub fn ring_bin_div( where Standard: Distribution, { - let res = ring_bin_div_many(&[input1], &[input2], io_context)?; + let res = ring_div_many(&[input1], &[input2], io_context)?; Ok(res[0]) } diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 5b5a9868e..243c7d4a2 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,11 +1499,55 @@ 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); + } + + 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], @@ -1510,7 +1555,6 @@ mod field_share { ) { 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 +1565,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 3308564c7..adb0248f5 100644 --- a/tests/tests/mpc/rep3_ring.rs +++ b/tests/tests/mpc/rep3_ring.rs @@ -1763,7 +1763,7 @@ mod ring_share { thread::spawn(move || { let mut rep3 = IoContext::init(net).unwrap(); - let div = yao::ring_bin_div_many(&x, &y, &mut rep3).unwrap(); + let div = yao::ring_div_many(&x, &y, &mut rep3).unwrap(); tx.send(div) }); } @@ -1772,13 +1772,12 @@ mod ring_share { 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]); + apply_to_all!(rep3_bin_div_via_yao_t, [Bit]); } #[test] From 4ed4c3e6ed5638d85abbf4dd56a90a358972ea8b Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:16:23 +0100 Subject: [PATCH 03/17] feat: add shared/public int division --- co-noir/co-brillig/src/mpc/rep3.rs | 49 ++++- mpc-core/src/protocols/rep3/yao.rs | 37 ++++ mpc-core/src/protocols/rep3/yao/circuits.rs | 218 +++++++++++++++++++- mpc-core/src/protocols/rep3_ring/yao.rs | 39 ++++ tests/tests/mpc/rep3.rs | 50 +++++ tests/tests/mpc/rep3_ring.rs | 53 ++++- 6 files changed, 434 insertions(+), 12 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 1e780556f..2e6fd142d 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -733,7 +733,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 +751,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 +769,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 +787,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 +805,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,7 +823,7 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u1(divided) } else { - todo!("Implement division for shared/public with divisor not being a power-of-2") + todo!("do we need this?") } } _ => todo!("Implement division for shared/public"), @@ -829,7 +854,10 @@ impl BrilligDriver for Rep3BrilligDriver let divided = rep3_ring::yao::ring_div(lhs, rhs, &mut self.io_context)?; Rep3BrilligType::shared_u8(divided) } - _ => panic!("type mismatch. Can only mul matching values"), + (Shared::Ring1(_), Shared::Ring1(_)) => { + todo!("do we want this?") + } + _ => panic!("type mismatch. Can only div matching values"), }, }; Ok(result) @@ -864,7 +892,12 @@ 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") diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 404f6343f..0b19193e2 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -743,6 +743,43 @@ pub fn field_int_div( 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(); + + // if divisor_bit == 0 { + // return Ok(inputs.to_owned()); + // } + // if divisor_bit >= F::MODULUS_BIT_SIZE as usize { + // return Ok(vec![Rep3PrimeFieldShare::zero_share(); num_inputs]); + // } field_to_bits_as_u16 + 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_as_u16::(*y))); + let divisors_as_bits = divisors_as_bits.iter().map(|&x| x != 0).collect(); // rfield_to_bits_as_u16 returns a 0-1 vec + 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 a power of 2, 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]) +} macro_rules! decompose_circuit_compose_blueprint { ($inputs:expr, $io_context:expr, $output_size:expr, $circuit:expr, ($( $args:expr ),*)) => {{ diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 20c58b50e..e7fc77371 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -2,6 +2,8 @@ //! //! This module contains some garbled circuit implementations. +use std::ops::Not; + use crate::protocols::rep3::yao::GCUtils; use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; @@ -88,6 +90,24 @@ 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 { + let z1 = g.negate(a)?; + let z4 = &z1; + let c = g.xor(z4, a)?; + (a.clone(), c) + } else { + let z1 = a; + let s = g.negate(z1)?; + (s, a.clone()) + }; + Ok((s, c)) + } /// Full adder with carry in set, just outputs carry fn full_adder_carry_cin_set( @@ -185,7 +205,7 @@ impl GarbledCircuits { /// Needed for subtraction in binary division where xs is (conceptually) a constant 0 bundle with only some values set #[expect(clippy::type_complexity)] - fn bin_subtraction_with_partial_constant( + fn bin_subtraction_partial( g: &mut G, xs: &[G::Item], ys: &[G::Item], @@ -211,7 +231,43 @@ impl GarbledCircuits { // FULL ADDER with a=0 (x=0) s = g.xor(&y, &c)?; c = g.and(&y, &c)?; - // (s, c) = Self::full_adder_const(g, &y, false, &c)?; + result.push(s); + } + Ok((result, c)) + } + /// Needed for subtraction in binary division where xs is (conceptually) a constant 0 bundle with only some values set + #[expect(clippy::type_complexity)] + fn bin_subtraction_partial_by_constant( + g: &mut G, + xs: &[G::Item], + ys: &[bool], + ) -> Result<(Vec, G::Item), G::Error> { + // debug_assert_eq!(xs.len(), ys.len()); + 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() { + let y = y.not(); + // FULL ADDER with a=0 (x=0) + (s, c) = if y { + (g.negate(&c)?, c) + } else { + let c_not = g.negate(&c)?; + let c_r = g.and(&c, &c_not)?; //this is stupid + (c, c_r) + }; result.push(s); } Ok((result, c)) @@ -252,7 +308,31 @@ impl GarbledCircuits { } acc.insert(0, x.clone()); - let (res, cout) = Self::bin_subtraction_with_partial_constant(g, &acc, divisor)?; + 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); @@ -1045,6 +1125,40 @@ impl GarbledCircuits { } 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 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, @@ -1074,6 +1188,31 @@ impl GarbledCircuits { 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 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( @@ -1186,6 +1325,41 @@ impl GarbledCircuits { } Ok(BinaryBundle::new(results)) } + /// Binary division for two vecs of inputs + 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)) + } /// 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( @@ -1225,6 +1399,44 @@ impl GarbledCircuits { } 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_by_public_many( + 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)) + } } #[cfg(test)] diff --git a/mpc-core/src/protocols/rep3_ring/yao.rs b/mpc-core/src/protocols/rep3_ring/yao.rs index 736163179..8f1e851f2 100644 --- a/mpc-core/src/protocols/rep3_ring/yao.rs +++ b/mpc-core/src/protocols/rep3_ring/yao.rs @@ -769,6 +769,45 @@ where 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(); + 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_as_u16::(*y))); + let divisors_as_bits = divisors_as_bits.iter().map(|&x| x != 0).collect(); // ring_to_bits_as_u16 returns a 0-1 vec + 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]) +} /// Decomposes a FieldElement into a vector of RingElements of size decompose_bitlen each. In total, there will be num_decomps_per_field decompositions. The output is stored in the ring specified by T. pub fn decompose_field_to_rings_many( diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 243c7d4a2..24e91b366 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -1537,6 +1537,55 @@ mod field_share { 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 y_1 = y.clone(); + let y_2 = y.clone(); + let y_3 = y.clone(); + let ys = [y_1, y_2, y_3]; + 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_c) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter(), + ys.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let decomposed = yao::field_int_div_by_public_many(&x, &y_c, &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); + } + fn reshare_from_2_to_3_parties_test_internal(recipient: PartyID) { const VEC_SIZE: usize = 10; let test_network = Rep3TestNetwork::default(); @@ -1571,6 +1620,7 @@ mod field_share { 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 adb0248f5..4c3df4510 100644 --- a/tests/tests/mpc/rep3_ring.rs +++ b/tests/tests/mpc/rep3_ring.rs @@ -1774,10 +1774,61 @@ mod ring_share { let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); assert_eq!(is_result, should_result); } + 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(|_| rng.gen::>()) + .collect_vec(); + let y_1 = y.clone(); + let y_2 = y.clone(); + let y_3 = y.clone(); + 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.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_c) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter(), + [y_1, y_2, y_3] + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::ring_div_by_public_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_public_via_yao() { + apply_to_all!(rep3_bin_div_by_public_via_yao_t, [u8, u16, u32, u64, u128]); + } #[test] fn rep3_bin_div_via_yao() { - apply_to_all!(rep3_bin_div_via_yao_t, [Bit]); + apply_to_all!(rep3_bin_div_via_yao_t, [u8, u16, u32, u64, u128]); } #[test] From 368b344a29f012f1584c3ff5a7e1689986109bca Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:43:19 +0100 Subject: [PATCH 04/17] feat: add public/shared int division --- co-noir/co-brillig/src/mpc/rep3.rs | 51 +++- mpc-core/src/protocols/rep3/yao.rs | 37 +++ mpc-core/src/protocols/rep3/yao/circuits.rs | 260 +++++++++++++++++++- mpc-core/src/protocols/rep3_ring/yao.rs | 38 +++ tests/tests/mpc/rep3.rs | 53 +++- tests/tests/mpc/rep3_ring.rs | 52 ++++ 6 files changed, 480 insertions(+), 11 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 2e6fd142d..78883cdc6 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(_)) => { + todo!("do we need this?") + } + _ => panic!("type mismatch. Can only div matching values"), } } (Rep3BrilligType::Shared(shared), Rep3BrilligType::Public(public)) => { @@ -873,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") } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 0b19193e2..4774a9abf 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -780,6 +780,43 @@ pub fn field_int_div_by_public( 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(); + + // if divisor_bit == 0 { + // return Ok(inputs.to_owned()); + // } + // if divisor_bit >= F::MODULUS_BIT_SIZE as usize { + // return Ok(vec![Rep3PrimeFieldShare::zero_share(); num_inputs]); + // } field_to_bits_as_u16 + 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_as_u16::(*y))); + let divisors_as_bits = inputs_as_bits.iter().map(|&x| x != 0).collect(); // rfield_to_bits_as_u16 returns a 0-1 vec + decompose_circuit_compose_blueprint!( + &divisors, + io_context, + num_inputs, + GarbledCircuits::field_int_div_by_shared_many::<_, F>, + (divisors_as_bits) + ) +} + +/// Divides a field element by a power of 2, 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 ),*)) => {{ diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index e7fc77371..ececa8cfd 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -203,14 +203,13 @@ impl GarbledCircuits { Ok((result, c)) } - /// Needed for subtraction in binary division where xs is (conceptually) a constant 0 bundle with only some values set + /// 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> { - // debug_assert_eq!(xs.len(), ys.len()); 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(); @@ -235,14 +234,13 @@ impl GarbledCircuits { } Ok((result, c)) } - /// Needed for subtraction in binary division where xs is (conceptually) a constant 0 bundle with only some values set + /// 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> { - // debug_assert_eq!(xs.len(), ys.len()); 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(); @@ -265,13 +263,73 @@ impl GarbledCircuits { (g.negate(&c)?, c) } else { let c_not = g.negate(&c)?; - let c_r = g.and(&c, &c_not)?; //this is stupid + let c_r = g.and(&c, &c_not)?; //this is stupid and can be simplified I guess (c, c_r) }; 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)) + } + /// 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_custom( + g: &mut G, + first: bool, + xs: &[G::Item], + ys: &[G::Item], + ) -> Result<(Vec, G::Item), G::Error> { + let mut result = Vec::with_capacity(ys.len()); + // Twos complement is negation + 1, we implement by having cin in adder = 1, so only negation is required + + let y0 = g.negate(&ys[0])?; + let (mut s, mut c) = Self::full_adder_const_cin_set(g, &y0, first)?; + result.push(s); + + for (x, y) in xs.iter().zip(ys[1..].iter()) { + let y = g.negate(y)?; + let res = Self::full_adder(g, x, &y, &c)?; + s = res.0; + c = res.1; + 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( @@ -341,6 +399,45 @@ impl GarbledCircuits { 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(); + } + let (res, cout) = Self::bin_subtraction_custom(g, *x, &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, &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, @@ -353,6 +450,25 @@ impl GarbledCircuits { .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)] @@ -1159,6 +1275,40 @@ impl GarbledCircuits { } 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, @@ -1213,6 +1363,31 @@ impl GarbledCircuits { 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( @@ -1360,6 +1535,41 @@ impl GarbledCircuits { } Ok(BinaryBundle::new(results)) } + /// Binary division for two vecs of inputs + 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( @@ -1399,7 +1609,7 @@ impl GarbledCircuits { } 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 + /// 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: &mut G, wires_x1: &BinaryBundle, @@ -1437,6 +1647,44 @@ impl GarbledCircuits { } 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: &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)] diff --git a/mpc-core/src/protocols/rep3_ring/yao.rs b/mpc-core/src/protocols/rep3_ring/yao.rs index 8f1e851f2..be43a349b 100644 --- a/mpc-core/src/protocols/rep3_ring/yao.rs +++ b/mpc-core/src/protocols/rep3_ring/yao.rs @@ -808,6 +808,44 @@ where let res = ring_div_by_public_many(&[input], &[divisor], io_context)?; Ok(res[0]) } +/// Divides a vector of ring elements by another public. +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_as_u16::(*y))); + let input_as_bits = input_as_bits.iter().map(|&x| x != 0).collect(); // ring_to_bits_as_u16 returns a 0-1 vec + decompose_circuit_compose_blueprint!( + &divisors, + io_context, + num_inputs, + T, + GarbledCircuits::ring_div_by_shared_many, + (T::K, input_as_bits) + ) +} + +/// Divides a ring element by another public. +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]) +} /// Decomposes a FieldElement into a vector of RingElements of size decompose_bitlen each. In total, there will be num_decomps_per_field decompositions. The output is stored in the ring specified by T. pub fn decompose_field_to_rings_many( diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 24e91b366..4d9b663e0 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -1574,7 +1574,8 @@ mod field_share { ) { thread::spawn(move || { let mut rep3 = IoContext::init(net).unwrap(); - let decomposed = yao::field_int_div_by_public_many(&x, &y_c, &mut rep3).unwrap(); + let decomposed = + yao::field_int_div_by_public_many(&x, y_c.as_ref(), &mut rep3).unwrap(); tx.send(decomposed) }); } @@ -1586,6 +1587,55 @@ mod field_share { 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 x_1 = x.clone(); + let x_2 = x.clone(); + let x_3 = x.clone(); + 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, y_c, x_c) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + y_shares.into_iter(), + [x_1, x_2, x_3] + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::field_int_div_by_shared_many(x_c.as_ref(), &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(); @@ -1620,7 +1670,6 @@ mod field_share { 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 4c3df4510..4aff6cfe5 100644 --- a/tests/tests/mpc/rep3_ring.rs +++ b/tests/tests/mpc/rep3_ring.rs @@ -1822,6 +1822,58 @@ mod ring_share { let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); assert_eq!(is_result, should_result); } + 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(|_| rng.gen::>()) + .collect_vec(); + let x_1 = x.clone(); + let x_2 = x.clone(); + let x_3 = x.clone(); + 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, y_c, x_c) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + y_shares.into_iter(), + [x_1, x_2, x_3] + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + + let div = yao::ring_div_by_shared_many(&x_c, &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]); + } #[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]); From 371b9325bbc2ff479a2e0a4e71e4fe7251519e92 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:40:32 +0100 Subject: [PATCH 05/17] feat: to_radix for public radix --- co-noir/co-brillig/src/mpc/rep3.rs | 20 +++++++++++++++++++- mpc-core/src/protocols/rep3_ring/yao.rs | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 78883cdc6..a890ffeee 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -1418,6 +1418,7 @@ impl BrilligDriver for Rep3BrilligDriver } 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(); @@ -1433,7 +1434,24 @@ 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, but is this necessary? + 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/mpc-core/src/protocols/rep3_ring/yao.rs b/mpc-core/src/protocols/rep3_ring/yao.rs index be43a349b..5f0009912 100644 --- a/mpc-core/src/protocols/rep3_ring/yao.rs +++ b/mpc-core/src/protocols/rep3_ring/yao.rs @@ -986,7 +986,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, @@ -995,7 +995,7 @@ where Standard: Distribution, { decompose_field_to_rings_many( - &[inputs], + &[input], io_context, num_decomps_per_field, decompose_bitlen, From 79294095ec95cec60c25e2f19b4b93ecc063f830 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:09:05 +0100 Subject: [PATCH 06/17] feat: to_radix for public val/shared radix --- co-noir/co-brillig/src/mpc/rep3.rs | 65 +++++++++++++++++---- mpc-core/src/protocols/rep3/yao/circuits.rs | 2 +- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index a890ffeee..ed65f5ffe 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -1434,7 +1434,8 @@ impl BrilligDriver for Rep3BrilligDriver .map(|val| Rep3BrilligType::Shared(Shared::Ring8(val))) .collect() } else { - let mut limbs:Vec> = vec![Rep3BrilligType::default(); output_size]; + 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, @@ -1442,13 +1443,13 @@ impl BrilligDriver for Rep3BrilligDriver &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, but is this necessary? - limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); - + + 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, but is this necessary? + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + input = div; } limbs @@ -1458,8 +1459,52 @@ impl BrilligDriver for Rep3BrilligDriver } } (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) { + if bits { + todo!("Implement to_radix for public value and shared radix for bits=true") + } + // //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 my_id = self.io_context.network.get_id(); + 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)?, + my_id, + ); // this feels very stupid? + + 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)?, + ); // this feels very stupid? + + 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[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/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index ececa8cfd..d0b2c8bf0 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -145,7 +145,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], From e1b56d5ae1ef2b751eb9ef238df889ebf6bdb759 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:13:07 +0100 Subject: [PATCH 07/17] feat: to_radix for shared val/shared radix --- co-noir/co-brillig/src/mpc/rep3.rs | 34 ++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index ed65f5ffe..f9462b9ee 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -1468,7 +1468,6 @@ impl BrilligDriver for Rep3BrilligDriver vec![Rep3BrilligType::default(); output_size]; let radix_as_field = rep3_ring::yao::ring_to_field_many(&[radix], &mut self.io_context)?; - let my_id = self.io_context.network.get_id(); let div = rep3::yao::field_int_div_by_shared( val, radix_as_field[0], @@ -1477,7 +1476,7 @@ impl BrilligDriver for Rep3BrilligDriver let limb = rep3::arithmetic::sub_public_by_shared( val, rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, - my_id, + self.io_context.network.get_id(), ); // this feels very stupid? let limb = rep3_ring::yao::field_to_ring_many::<_, u8, _>( @@ -1510,8 +1509,35 @@ impl BrilligDriver for Rep3BrilligDriver } } (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) { + if bits { + todo!("Implement to_radix for shared value and shared radix for bits=true") + } + // //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 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)?, + ); // this feels very stupid? + + 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[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + input = div; + } + limbs } else { eyre::bail!("can only ToRadix on field and radix must be Int32") } From 26a111294c6288640120c23e470fd47314d98fa1 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:45:13 +0100 Subject: [PATCH 08/17] feat: bits case for shared/public --- co-noir/co-brillig/src/mpc/rep3.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index f9462b9ee..42ccab2aa 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -1,6 +1,7 @@ use super::{BrilligDriver, PlainBrilligDriver}; use ark_ff::{One as _, PrimeField}; use brillig::{BitSize, IntegerBitSize}; +use mpc_core::protocols::rep3_ring::conversion::a2b; use core::panic; use mpc_core::protocols::rep3::network::{IoContext, Rep3Network}; use mpc_core::protocols::rep3::{self, Rep3PrimeFieldShare}; @@ -1413,9 +1414,7 @@ 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; @@ -1443,13 +1442,19 @@ impl BrilligDriver for Rep3BrilligDriver &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, but is this necessary? - limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + if bits { + let limb_2b = a2b(limb[0], &mut self.io_context)?; + let limb_bit = rep3_ring::conversion::bit_inject(&limb_2b, &mut self.io_context)?; + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb_bit)); + } + else { + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + }; input = div; } limbs @@ -1463,7 +1468,7 @@ impl BrilligDriver for Rep3BrilligDriver if bits { todo!("Implement to_radix for public value and shared radix for bits=true") } - // //todo: do we want to do checks for radix <= 256? + //todo: do we want to do checks for radix <= 256? let mut limbs: Vec> = vec![Rep3BrilligType::default(); output_size]; let radix_as_field = @@ -1513,7 +1518,7 @@ impl BrilligDriver for Rep3BrilligDriver if bits { todo!("Implement to_radix for shared value and shared radix for bits=true") } - // //todo: do we want to do checks for radix <= 256? + //todo: do we want to do checks for radix <= 256? let mut limbs: Vec> = vec![Rep3BrilligType::default(); output_size]; let radix_as_field = From f75c5fa886a8ab25d6563cbe1abae3af912cad3d Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:22:31 +0100 Subject: [PATCH 09/17] feat: works for unique num_bits --- co-noir/co-acvm/src/mpc.rs | 8 +++ co-noir/co-acvm/src/mpc/plain.rs | 15 ++++++ co-noir/co-acvm/src/mpc/rep3.rs | 21 ++++++++ co-noir/co-acvm/src/mpc/shamir.rs | 10 ++++ co-noir/co-builder/src/builder.rs | 87 +++++++++++++++++++++++-------- 5 files changed, 120 insertions(+), 21 deletions(-) diff --git a/co-noir/co-acvm/src/mpc.rs b/co-noir/co-acvm/src/mpc.rs index 77eeb1c60..63f359f4e 100644 --- a/co-noir/co-acvm/src/mpc.rs +++ b/co-noir/co-acvm/src/mpc.rs @@ -146,6 +146,14 @@ pub trait NoirWitnessExtensionProtocol { 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], + // io_context: &mut IoContext, + 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( diff --git a/co-noir/co-acvm/src/mpc/plain.rs b/co-noir/co-acvm/src/mpc/plain.rs index 4e5766006..75c5d8c40 100644 --- a/co-noir/co-acvm/src/mpc/plain.rs +++ b/co-noir/co-acvm/src/mpc/plain.rs @@ -203,4 +203,19 @@ impl NoirWitnessExtensionProtocol for PlainAcvmSolver { result.sort(); Ok(result) } + + fn decompose_arithmetic_many( + &mut self, + input: &[Self::ArithmeticShare], + // io_context: &mut IoContext, + 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() + } } diff --git a/co-noir/co-acvm/src/mpc/rep3.rs b/co-noir/co-acvm/src/mpc/rep3.rs index d6ddb804e..d92a80c99 100644 --- a/co-noir/co-acvm/src/mpc/rep3.rs +++ b/co-noir/co-acvm/src/mpc/rep3.rs @@ -498,4 +498,25 @@ impl NoirWitnessExtensionProtocol for Rep3Acvm ) -> std::io::Result> { radix_sort_fields(inputs, &mut self.io_context, bitsize) } + + fn decompose_arithmetic_many( + &mut self, + input: &[Self::ArithmeticShare], + // io_context: &mut IoContext, + total_bit_size_per_field: usize, + decompose_bit_size: usize, + ) -> std::io::Result>> { + let num_decomps_per_field = total_bit_size_per_field.div_ceil(decompose_bit_size); + let result = yao::decompose_arithmetic_many( + input, + &mut self.io_context, + total_bit_size_per_field, + decompose_bit_size, + )?; + let results = result + .chunks(num_decomps_per_field) + .map(|chunk| chunk.to_vec()) + .collect(); + Ok(results) + } } diff --git a/co-noir/co-acvm/src/mpc/shamir.rs b/co-noir/co-acvm/src/mpc/shamir.rs index 64e8cb3dc..c4531e02b 100644 --- a/co-noir/co-acvm/src/mpc/shamir.rs +++ b/co-noir/co-acvm/src/mpc/shamir.rs @@ -415,4 +415,14 @@ impl NoirWitnessExtensionProtocol for Shamir ) -> std::io::Result> { panic!("functionality sort not feasible for Shamir") } + + fn decompose_arithmetic_many( + &mut self, + _input: &[Self::ArithmeticShare], + // io_context: &mut IoContext, + _total_bit_size_per_field: usize, + _decompose_bit_size: usize, + ) -> std::io::Result>> { + panic!("functionality decompose_arithmetic_many not feasible for Shamir") + } } diff --git a/co-noir/co-builder/src/builder.rs b/co-noir/co-builder/src/builder.rs index 23a6b6489..37d10331b 100644 --- a/co-noir/co-builder/src/builder.rs +++ b/co-noir/co-builder/src/builder.rs @@ -632,6 +632,14 @@ impl> GenericUltraCi } } + fn prepare_for_range_decompose( + &mut self, + driver: &mut T, + mut constraint_system: AcirFormat, + ) -> std::io::Result<(Vec<(bool, usize)>, Vec>)> { + todo!() + } + fn build_constraints( &mut self, driver: &mut T, @@ -719,8 +727,46 @@ impl> GenericUltraCi // todo!("Logic gates"); // } + let mut to_decompose: Vec = vec![]; + let mut decompose_indices: Vec<(bool, usize)> = vec![]; + let mut num_bits = 0; + for constraint in constraint_system.range_constraints.iter() { + let val = &self.get_variable(constraint.witness as usize); + if !(constraint.num_bits == 1 + && constraint.num_bits <= Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32) + && T::is_shared(val) + { + if num_bits == 0 { + num_bits = constraint.num_bits; + } + if num_bits != constraint.num_bits { + todo!("constraint with different num_bits") + } + to_decompose.push(T::get_shared(val).expect("Already checked it is shared")); + decompose_indices.push((true, to_decompose.len() - 1)); + } else { + decompose_indices.push((false, 0)); + } + } + + let decomposed = T::decompose_arithmetic_many( + driver, + &to_decompose, + num_bits as usize, + Self::DEFAULT_PLOOKUP_RANGE_BITNUM, + )?; for (i, constraint) in constraint_system.range_constraints.iter().enumerate() { - self.create_range_constraint(driver, constraint.witness, constraint.num_bits)?; + if decompose_indices[i].0 { + self.decompose_into_default_range( + driver, + constraint.witness, + constraint.num_bits as u64, + Some(&decomposed[decompose_indices[i].1]), + Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u64, + )?; + } else { + self.create_range_constraint(driver, constraint.witness, constraint.num_bits)?; + } gate_counter.track_diff( self, &mut constraint_system.gates_per_opcode, @@ -2349,6 +2395,7 @@ impl> GenericUltraCi driver, variable_index, num_bits as u64, + None, Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u64, )?; } @@ -2420,6 +2467,7 @@ 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)); @@ -2451,28 +2499,25 @@ 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 { + Some(decomposed) => decomposed + .iter() + .map(|item| T::AcvmType::from(item.clone())) + .collect(), + None => { + 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()); From 835d72362edcc210ddc3b2ed074b0e4fbb113870 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:41:35 +0100 Subject: [PATCH 10/17] feat: handle different num_bits --- co-noir/co-builder/src/builder.rs | 114 +++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 34 deletions(-) diff --git a/co-noir/co-builder/src/builder.rs b/co-noir/co-builder/src/builder.rs index 37d10331b..d37668a7c 100644 --- a/co-noir/co-builder/src/builder.rs +++ b/co-noir/co-builder/src/builder.rs @@ -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,13 +632,36 @@ impl> GenericUltraCi } } - fn prepare_for_range_decompose( - &mut self, - driver: &mut T, - mut constraint_system: AcirFormat, - ) -> std::io::Result<(Vec<(bool, usize)>, Vec>)> { - todo!() - } + // fn prepare_for_range_decompose( + // &mut self, + // driver: &mut T, + // mut constraint_system: AcirFormat, + // ) -> std::io::Result>> { + // // let map: BTreeMap> = BTreeMap::new(); + // // let mut decompose_indices: Vec> = vec![]; + // // let mut to_decompose: Vec = vec![]; + // // let mut num_bits = 0; + // // for constraint in constraint_system.range_constraints.iter() { + // // let val = &self.get_variable(constraint.witness as usize); + // // if !(constraint.num_bits == 1 + // // && constraint.num_bits <= Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32) + // // && T::is_shared(val) + // // { + // // if num_bits == 0 { + // // num_bits = constraint.num_bits; + // // } + // // if num_bits != constraint.num_bits { + // // todo!("constraint with different num_bits") + // // } + // // to_decompose.push(T::get_shared(val).expect("Already checked it is shared")); + // // decompose_indices.push((true, to_decompose.len() - 1)); + // // } else { + // // decompose_indices.push((false, 0)); + // // } + // // } + + // todo!() + // } fn build_constraints( &mut self, @@ -727,46 +750,68 @@ impl> GenericUltraCi // todo!("Logic gates"); // } - let mut to_decompose: Vec = vec![]; + let mut to_decompose: Vec> = vec![]; let mut decompose_indices: Vec<(bool, usize)> = vec![]; - let mut num_bits = 0; + let mut bitsloc: HashMap = HashMap::new(); + for constraint in constraint_system.range_constraints.iter() { let val = &self.get_variable(constraint.witness as usize); - if !(constraint.num_bits == 1 - && constraint.num_bits <= Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32) - && T::is_shared(val) + + if constraint.num_bits > Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32 && T::is_shared(val) { - if num_bits == 0 { - num_bits = constraint.num_bits; - } - if num_bits != constraint.num_bits { - todo!("constraint with different num_bits") + let num_bits = constraint.num_bits; + + if let Some(&idx) = bitsloc.get(&num_bits) { + to_decompose[idx] + .push(T::get_shared(val).expect("Already checked it is shared")); + decompose_indices.push((true, to_decompose[idx].len() - 1)); + } else { + let new_idx = to_decompose.len(); + to_decompose.push(vec![ + T::get_shared(val).expect("Already checked it is shared") + ]); + decompose_indices.push((true, 0)); + bitsloc.insert(num_bits, new_idx); } - to_decompose.push(T::get_shared(val).expect("Already checked it is shared")); - decompose_indices.push((true, to_decompose.len() - 1)); } else { decompose_indices.push((false, 0)); } } - let decomposed = T::decompose_arithmetic_many( - driver, - &to_decompose, - num_bits as usize, - Self::DEFAULT_PLOOKUP_RANGE_BITNUM, - )?; + let mut decomposed: Vec>> = Vec::with_capacity(to_decompose.len()); + + for (i, inp) in to_decompose.iter().enumerate() { + let num_bits = bitsloc + .iter() + .find(|&(_, &idx)| idx == i) + .map(|(&num_bits, _)| num_bits) + .expect("Index not found in bitsloc"); + + decomposed.push(T::decompose_arithmetic_many( + driver, + inp, + num_bits as usize, + Self::DEFAULT_PLOOKUP_RANGE_BITNUM, + )?); + } + for (i, constraint) in constraint_system.range_constraints.iter().enumerate() { - if decompose_indices[i].0 { - self.decompose_into_default_range( - driver, - constraint.witness, - constraint.num_bits as u64, - Some(&decomposed[decompose_indices[i].1]), - Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u64, - )?; + if let Some(&idx) = bitsloc.get(&constraint.num_bits) { + if decompose_indices[i].0 { + 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 { + self.create_range_constraint(driver, constraint.witness, constraint.num_bits)?; + } } else { self.create_range_constraint(driver, constraint.witness, constraint.num_bits)?; } + gate_counter.track_diff( self, &mut constraint_system.gates_per_opcode, @@ -2474,6 +2519,7 @@ impl> GenericUltraCi 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() { From 3dd4bb0e38d04e2869d8f89d4ccbe51177a55dd6 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:44:40 +0100 Subject: [PATCH 11/17] feat: test case with diff. uints --- co-noir/co-noir/examples/run_full_ranges.sh | 16 ++++++++++++++++ .../examples/test_vectors/ranges/Nargo.toml | 7 +++++++ .../examples/test_vectors/ranges/Prover.toml | 7 +++++++ .../examples/test_vectors/ranges/ranges.gz | Bin 0 -> 101 bytes .../examples/test_vectors/ranges/ranges.json | 1 + .../examples/test_vectors/ranges/src/main.nr | 3 +++ 6 files changed, 34 insertions(+) create mode 100755 co-noir/co-noir/examples/run_full_ranges.sh create mode 100644 co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml create mode 100644 co-noir/co-noir/examples/test_vectors/ranges/Prover.toml create mode 100644 co-noir/co-noir/examples/test_vectors/ranges/ranges.gz create mode 100644 co-noir/co-noir/examples/test_vectors/ranges/ranges.json create mode 100644 co-noir/co-noir/examples/test_vectors/ranges/src/main.nr 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..684d70449 --- /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 = ">=0.38.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 0000000000000000000000000000000000000000..fe60dbfb4218dda3bc49f48489626731d5788b3a GIT binary patch literal 101 zcmV-r0Gj_FiwFP!00002|E 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..a1eeb26f8 --- /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) +} From 87a4cfaa7e28c3083872c1649e1153bbdbe781a6 Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:20:16 +0100 Subject: [PATCH 12/17] feat: move batch decompose into a separate fct --- co-noir/co-builder/src/builder.rs | 137 +++++++++++++----------------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/co-noir/co-builder/src/builder.rs b/co-noir/co-builder/src/builder.rs index d37668a7c..6e5d9aded 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, @@ -631,37 +631,62 @@ 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); + + if constraint.num_bits > Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32 && T::is_shared(val) + { + let num_bits = constraint.num_bits; + + if let Some(&idx) = bits_locations.get(&num_bits) { + to_decompose[idx] + .push(T::get_shared(val).expect("Already checked it is shared")); + decompose_indices.push((true, to_decompose[idx].len() - 1)); + } else { + let new_idx = to_decompose.len(); + to_decompose.push(vec![ + T::get_shared(val).expect("Already checked it is shared") + ]); + decompose_indices.push((true, 0)); + bits_locations.insert(num_bits, new_idx); + } + } else { + decompose_indices.push((false, 0)); + } + } + + let mut decomposed: Vec>> = Vec::with_capacity(to_decompose.len()); - // fn prepare_for_range_decompose( - // &mut self, - // driver: &mut T, - // mut constraint_system: AcirFormat, - // ) -> std::io::Result>> { - // // let map: BTreeMap> = BTreeMap::new(); - // // let mut decompose_indices: Vec> = vec![]; - // // let mut to_decompose: Vec = vec![]; - // // let mut num_bits = 0; - // // for constraint in constraint_system.range_constraints.iter() { - // // let val = &self.get_variable(constraint.witness as usize); - // // if !(constraint.num_bits == 1 - // // && constraint.num_bits <= Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32) - // // && T::is_shared(val) - // // { - // // if num_bits == 0 { - // // num_bits = constraint.num_bits; - // // } - // // if num_bits != constraint.num_bits { - // // todo!("constraint with different num_bits") - // // } - // // to_decompose.push(T::get_shared(val).expect("Already checked it is shared")); - // // decompose_indices.push((true, to_decompose.len() - 1)); - // // } else { - // // decompose_indices.push((false, 0)); - // // } - // // } - - // todo!() - // } + for (i, inp) in to_decompose.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, @@ -750,53 +775,11 @@ impl> GenericUltraCi // todo!("Logic gates"); // } - let mut to_decompose: Vec> = vec![]; - let mut decompose_indices: Vec<(bool, usize)> = vec![]; - let mut bitsloc: HashMap = HashMap::new(); - - for constraint in constraint_system.range_constraints.iter() { - let val = &self.get_variable(constraint.witness as usize); - - if constraint.num_bits > Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32 && T::is_shared(val) - { - let num_bits = constraint.num_bits; - - if let Some(&idx) = bitsloc.get(&num_bits) { - to_decompose[idx] - .push(T::get_shared(val).expect("Already checked it is shared")); - decompose_indices.push((true, to_decompose[idx].len() - 1)); - } else { - let new_idx = to_decompose.len(); - to_decompose.push(vec![ - T::get_shared(val).expect("Already checked it is shared") - ]); - decompose_indices.push((true, 0)); - bitsloc.insert(num_bits, new_idx); - } - } else { - decompose_indices.push((false, 0)); - } - } - - let mut decomposed: Vec>> = Vec::with_capacity(to_decompose.len()); - - for (i, inp) in to_decompose.iter().enumerate() { - let num_bits = bitsloc - .iter() - .find(|&(_, &idx)| idx == i) - .map(|(&num_bits, _)| num_bits) - .expect("Index not found in bitsloc"); - - decomposed.push(T::decompose_arithmetic_many( - driver, - inp, - num_bits as usize, - Self::DEFAULT_PLOOKUP_RANGE_BITNUM, - )?); - } + 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() { - if let Some(&idx) = bitsloc.get(&constraint.num_bits) { + if let Some(&idx) = bits_locations.get(&constraint.num_bits) { if decompose_indices[i].0 { self.decompose_into_default_range( driver, From f139d69775ef8fd1004e1cb5aa9c9e40fbdf7d63 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:08:57 +0100 Subject: [PATCH 13/17] chore: Define upper bound for decompose arithmetic_many, clean up some code and prevent division by 0 in testcases --- co-noir/co-acvm/src/mpc.rs | 7 +- co-noir/co-acvm/src/mpc/plain.rs | 29 ++++---- co-noir/co-acvm/src/mpc/rep3.rs | 43 ++++++----- co-noir/co-acvm/src/mpc/shamir.rs | 18 ++--- co-noir/co-brillig/src/mpc/rep3.rs | 21 +++--- co-noir/co-builder/src/builder.rs | 49 +++++++------ .../examples/test_vectors/ranges/Nargo.toml | 2 +- .../examples/test_vectors/ranges/src/main.nr | 2 +- mpc-core/src/protocols/rep3/yao.rs | 49 ++++++------- mpc-core/src/protocols/rep3/yao/circuits.rs | 2 +- mpc-core/src/protocols/rep3_ring/yao.rs | 26 +++++-- tests/tests/mpc/rep3.rs | 25 +++---- tests/tests/mpc/rep3_ring.rs | 73 +++++++++++-------- 13 files changed, 178 insertions(+), 168 deletions(-) diff --git a/co-noir/co-acvm/src/mpc.rs b/co-noir/co-acvm/src/mpc.rs index 63f359f4e..6f880a43c 100644 --- a/co-noir/co-acvm/src/mpc.rs +++ b/co-noir/co-acvm/src/mpc.rs @@ -142,15 +142,14 @@ 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( + + /// 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], - // io_context: &mut IoContext, total_bit_size_per_field: usize, decompose_bit_size: usize, ) -> std::io::Result>>; diff --git a/co-noir/co-acvm/src/mpc/plain.rs b/co-noir/co-acvm/src/mpc/plain.rs index 75c5d8c40..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], @@ -203,19 +217,4 @@ impl NoirWitnessExtensionProtocol for PlainAcvmSolver { result.sort(); Ok(result) } - - fn decompose_arithmetic_many( - &mut self, - input: &[Self::ArithmeticShare], - // io_context: &mut IoContext, - 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() - } } diff --git a/co-noir/co-acvm/src/mpc/rep3.rs b/co-noir/co-acvm/src/mpc/rep3.rs index d92a80c99..2249556af 100644 --- a/co-noir/co-acvm/src/mpc/rep3.rs +++ b/co-noir/co-acvm/src/mpc/rep3.rs @@ -491,32 +491,37 @@ impl NoirWitnessExtensionProtocol for Rep3Acvm ) } - fn sort( - &mut self, - inputs: &[Self::ArithmeticShare], - bitsize: usize, - ) -> std::io::Result> { - radix_sort_fields(inputs, &mut self.io_context, bitsize) - } - fn decompose_arithmetic_many( &mut self, input: &[Self::ArithmeticShare], - // io_context: &mut IoContext, 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 result = yao::decompose_arithmetic_many( - input, - &mut self.io_context, - total_bit_size_per_field, - decompose_bit_size, - )?; - let results = result - .chunks(num_decomps_per_field) - .map(|chunk| chunk.to_vec()) - .collect(); + 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], + bitsize: usize, + ) -> std::io::Result> { + radix_sort_fields(inputs, &mut self.io_context, bitsize) + } } diff --git a/co-noir/co-acvm/src/mpc/shamir.rs b/co-noir/co-acvm/src/mpc/shamir.rs index c4531e02b..aee4768b7 100644 --- a/co-noir/co-acvm/src/mpc/shamir.rs +++ b/co-noir/co-acvm/src/mpc/shamir.rs @@ -407,22 +407,20 @@ impl NoirWitnessExtensionProtocol for Shamir ) -> std::io::Result> { panic!("functionality decompose_arithmetic not feasible for Shamir") } - - fn sort( - &mut self, - _inputs: &[Self::ArithmeticShare], - _bitsize: usize, - ) -> std::io::Result> { - panic!("functionality sort not feasible for Shamir") - } - fn decompose_arithmetic_many( &mut self, _input: &[Self::ArithmeticShare], - // io_context: &mut IoContext, _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, + _inputs: &[Self::ArithmeticShare], + _bitsize: usize, + ) -> std::io::Result> { + panic!("functionality sort not feasible for Shamir") + } } diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 42ccab2aa..4e285f23c 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -1,10 +1,10 @@ use super::{BrilligDriver, PlainBrilligDriver}; use ark_ff::{One as _, PrimeField}; use brillig::{BitSize, IntegerBitSize}; -use mpc_core::protocols::rep3_ring::conversion::a2b; use core::panic; use mpc_core::protocols::rep3::network::{IoContext, Rep3Network}; use mpc_core::protocols::rep3::{self, Rep3PrimeFieldShare}; +use mpc_core::protocols::rep3_ring::conversion::a2b; use mpc_core::protocols::rep3_ring::ring::bit::Bit; use mpc_core::protocols::rep3_ring::ring::int_ring::IntRing2k; use mpc_core::protocols::rep3_ring::ring::ring_impl::RingElement; @@ -1414,8 +1414,6 @@ impl BrilligDriver for Rep3BrilligDriver (Rep3BrilligType::Shared(val), Rep3BrilligType::Public(radix)) => { if let (Shared::Field(val), Public::Int(radix, IntegerBitSize::U32)) = (val, radix) { - - let radix = u32::try_from(radix).expect("must be u32"); let mut input = val; assert!(radix <= 256, "radix is at most 256"); @@ -1447,14 +1445,15 @@ impl BrilligDriver for Rep3BrilligDriver &mut self.io_context, )?; //radix is at most 256, so should fit into u8, but is this necessary? if bits { - let limb_2b = a2b(limb[0], &mut self.io_context)?; - let limb_bit = rep3_ring::conversion::bit_inject(&limb_2b, &mut self.io_context)?; - limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb_bit)); - } - else { - - limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); - }; + let limb_2b = a2b(limb[0], &mut self.io_context)?; + let limb_bit = rep3_ring::conversion::bit_inject( + &limb_2b, + &mut self.io_context, + )?; + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb_bit)); + } else { + limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + }; input = div; } limbs diff --git a/co-noir/co-builder/src/builder.rs b/co-noir/co-builder/src/builder.rs index 6e5d9aded..f0309a509 100644 --- a/co-noir/co-builder/src/builder.rs +++ b/co-noir/co-builder/src/builder.rs @@ -631,6 +631,7 @@ 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( @@ -649,19 +650,15 @@ impl> GenericUltraCi for constraint in range_constraints.iter() { let val = &self.get_variable(constraint.witness as usize); - if constraint.num_bits > Self::DEFAULT_PLOOKUP_RANGE_BITNUM as u32 && T::is_shared(val) - { - let num_bits = constraint.num_bits; - + 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(T::get_shared(val).expect("Already checked it is shared")); + 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![ - T::get_shared(val).expect("Already checked it is shared") - ]); + to_decompose.push(vec![share_val]); decompose_indices.push((true, 0)); bits_locations.insert(num_bits, new_idx); } @@ -670,9 +667,9 @@ impl> GenericUltraCi } } - let mut decomposed: Vec>> = Vec::with_capacity(to_decompose.len()); + let mut decomposed = Vec::with_capacity(to_decompose.len()); - for (i, inp) in to_decompose.iter().enumerate() { + 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 }) @@ -680,7 +677,7 @@ impl> GenericUltraCi decomposed.push(T::decompose_arithmetic_many( driver, - inp, + &inp, num_bits as usize, Self::DEFAULT_PLOOKUP_RANGE_BITNUM, )?); @@ -775,23 +772,24 @@ 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() { - if let Some(&idx) = bits_locations.get(&constraint.num_bits) { - if decompose_indices[i].0 { - 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 { - 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)?; } @@ -2419,6 +2417,7 @@ 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, @@ -2529,11 +2528,13 @@ impl> GenericUltraCi let mut sublimb_indices: Vec = Vec::with_capacity(num_limbs as usize); 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(); diff --git a/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml b/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml index 684d70449..0eca26e12 100644 --- a/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml +++ b/co-noir/co-noir/examples/test_vectors/ranges/Nargo.toml @@ -2,6 +2,6 @@ name = "ranges" type = "bin" authors = [""] -compiler_version = ">=0.38.0" +compiler_version = ">=1.0.0" [dependencies] 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 index a1eeb26f8..7bbc3fc85 100644 --- a/co-noir/co-noir/examples/test_vectors/ranges/src/main.nr +++ b/co-noir/co-noir/examples/test_vectors/ranges/src/main.nr @@ -1,3 +1,3 @@ -fn main(x: u64,w: u16,u: u32, y: pub u64, z: pub u64, s: u32, t: u16) -> pub (u64, u32, u16) { +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 4774a9abf..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,6 +681,7 @@ pub fn decompose_arithmetic( decompose_bit_size, ) } + /// Divides a vector of field elements by a power of 2, rounding down. pub fn field_int_div_power_2_many( inputs: &[Rep3PrimeFieldShare], @@ -715,13 +723,8 @@ pub fn field_int_div_many( io_context: &mut IoContext, ) -> IoResult>> { let num_inputs = input1.len(); + debug_assert_eq!(input1.len(), input2.len()); - // if divisor_bit == 0 { - // return Ok(inputs.to_owned()); - // } - // if divisor_bit >= F::MODULUS_BIT_SIZE as usize { - // return Ok(vec![Rep3PrimeFieldShare::zero_share(); num_inputs]); - // } let mut combined_inputs = Vec::with_capacity(input1.len() + input2.len()); combined_inputs.extend_from_slice(input1); combined_inputs.extend_from_slice(input2); @@ -734,7 +737,7 @@ pub fn field_int_div_many( ) } -/// Divides a field element by a power of 2, rounding down. +/// Divides a field element by another, rounding down. pub fn field_int_div( input1: Rep3PrimeFieldShare, input2: Rep3PrimeFieldShare, @@ -743,6 +746,7 @@ pub fn field_int_div( 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], @@ -750,18 +754,13 @@ pub fn field_int_div_by_public_many( io_context: &mut IoContext, ) -> IoResult>> { let num_inputs = input.len(); + debug_assert_eq!(input.len(), divisors.len()); - // if divisor_bit == 0 { - // return Ok(inputs.to_owned()); - // } - // if divisor_bit >= F::MODULUS_BIT_SIZE as usize { - // return Ok(vec![Rep3PrimeFieldShare::zero_share(); num_inputs]); - // } field_to_bits_as_u16 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_as_u16::(*y))); - let divisors_as_bits = divisors_as_bits.iter().map(|&x| x != 0).collect(); // rfield_to_bits_as_u16 returns a 0-1 vec + .for_each(|y| divisors_as_bits.extend(GCUtils::field_to_bits::(*y))); + decompose_circuit_compose_blueprint!( &input, io_context, @@ -771,7 +770,7 @@ pub fn field_int_div_by_public_many( ) } -/// Divides a field element by a power of 2, rounding down. +/// Divides a field element by another, rounding down. pub fn field_int_div_by_public( input: Rep3PrimeFieldShare, divisor: F, @@ -780,6 +779,7 @@ pub fn field_int_div_by_public( 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], @@ -787,28 +787,23 @@ pub fn field_int_div_by_shared_many( io_context: &mut IoContext, ) -> IoResult>> { let num_inputs = input.len(); + debug_assert_eq!(input.len(), divisors.len()); - // if divisor_bit == 0 { - // return Ok(inputs.to_owned()); - // } - // if divisor_bit >= F::MODULUS_BIT_SIZE as usize { - // return Ok(vec![Rep3PrimeFieldShare::zero_share(); num_inputs]); - // } field_to_bits_as_u16 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_as_u16::(*y))); - let divisors_as_bits = inputs_as_bits.iter().map(|&x| x != 0).collect(); // rfield_to_bits_as_u16 returns a 0-1 vec + .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>, - (divisors_as_bits) + (inputs_as_bits) ) } -/// Divides a field element by a power of 2, rounding down. +/// Divides a field element by another, rounding down. pub fn field_int_div_by_shared( input: F, divisor: Rep3PrimeFieldShare, diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index d0b2c8bf0..eac2ee03a 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -483,7 +483,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); diff --git a/mpc-core/src/protocols/rep3_ring/yao.rs b/mpc-core/src/protocols/rep3_ring/yao.rs index 5f0009912..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, @@ -740,7 +750,7 @@ where Standard: Distribution, { let num_inputs = input1.len(); - assert_eq!(input1.len(), input2.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); @@ -769,6 +779,7 @@ where 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], @@ -779,12 +790,11 @@ where Standard: Distribution, { let num_inputs = input.len(); - assert_eq!(input.len(), divisors.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_as_u16::(*y))); - let divisors_as_bits = divisors_as_bits.iter().map(|&x| x != 0).collect(); // ring_to_bits_as_u16 returns a 0-1 vec + .for_each(|y| divisors_as_bits.extend(GCUtils::ring_to_bits::(*y))); decompose_circuit_compose_blueprint!( &input, io_context, @@ -808,7 +818,8 @@ where let res = ring_div_by_public_many(&[input], &[divisor], io_context)?; Ok(res[0]) } -/// Divides a vector of ring elements by another public. + +/// Divides a public vector of ring elements by another. pub fn ring_div_by_shared_many( input: &[RingElement], divisors: &[Rep3RingShare], @@ -822,8 +833,7 @@ where 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_as_u16::(*y))); - let input_as_bits = input_as_bits.iter().map(|&x| x != 0).collect(); // ring_to_bits_as_u16 returns a 0-1 vec + .for_each(|y| input_as_bits.extend(GCUtils::ring_to_bits::(*y))); decompose_circuit_compose_blueprint!( &divisors, io_context, @@ -834,7 +844,7 @@ where ) } -/// Divides a ring element by another public. +/// Divides a public ring element by another. pub fn ring_div_by_shared( input: RingElement, divisor: Rep3RingShare, diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 4d9b663e0..61a7e1929 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -1550,12 +1550,8 @@ mod field_share { let y = (0..VEC_SIZE) .map(|_| ark_bn254::Fr::rand(&mut rng)) .collect_vec(); - let y_1 = y.clone(); - let y_2 = y.clone(); - let y_3 = y.clone(); - let ys = [y_1, y_2, y_3]; let mut should_result = Vec::with_capacity(VEC_SIZE); - for (x, y) in x.into_iter().zip(y.into_iter()) { + for (x, y) in x.into_iter().zip(y.iter().cloned()) { let x: BigUint = x.into(); let y: BigUint = y.into(); @@ -1566,16 +1562,15 @@ mod field_share { let (tx2, rx2) = mpsc::channel(); let (tx3, rx3) = mpsc::channel(); - for (net, tx, x, y_c) in izip!( + for (net, tx, x) in izip!( test_network.get_party_networks().into_iter(), [tx1, tx2, tx3], x_shares.into_iter(), - ys.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_c.as_ref(), &mut rep3).unwrap(); + let decomposed = yao::field_int_div_by_public_many(&x, &y_, &mut rep3).unwrap(); tx.send(decomposed) }); } @@ -1600,11 +1595,9 @@ mod field_share { .map(|_| ark_bn254::Fr::rand(&mut rng)) .collect_vec(); let y_shares = rep3::share_field_elements(&y, &mut rng); - let x_1 = x.clone(); - let x_2 = x.clone(); - let x_3 = x.clone(); + let mut should_result = Vec::with_capacity(VEC_SIZE); - for (x, y) in x.into_iter().zip(y.into_iter()) { + for (x, y) in x.iter().cloned().zip(y.into_iter()) { let x: BigUint = x.into(); let y: BigUint = y.into(); @@ -1615,16 +1608,16 @@ mod field_share { let (tx2, rx2) = mpsc::channel(); let (tx3, rx3) = mpsc::channel(); - for (net, tx, y_c, x_c) in izip!( + for (net, tx, y_c) in izip!( test_network.get_party_networks().into_iter(), [tx1, tx2, tx3], y_shares.into_iter(), - [x_1, x_2, x_3] ) { + 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_c.as_ref(), &y_c, &mut rep3).unwrap(); + let div = yao::field_int_div_by_shared_many(&x_, &y_c, &mut rep3).unwrap(); tx.send(div) }); } diff --git a/tests/tests/mpc/rep3_ring.rs b/tests/tests/mpc/rep3_ring.rs index 4aff6cfe5..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() @@ -60,7 +72,6 @@ mod ring_share { let mut rng = thread_rng(); let x = rng.gen::>(); let y = rng.gen::>(); - println!("x {x} y {x}"); let x_shares = rep3_ring::share_ring_element(x, &mut rng); let y_shares = rep3_ring::share_ring_element(y, &mut rng); let should_result = x + y; @@ -1728,6 +1739,12 @@ mod ring_share { let is_result = rep3_ring::combine_ring_elements(&result1, &result2, &result3); assert_eq!(is_result, should_result); } + + #[test] + 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, @@ -1740,7 +1757,7 @@ mod ring_share { .map(|_| rng.gen::>()) .collect_vec(); let y = (0..VEC_SIZE) - .map(|_| rng.gen::>()) + .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); @@ -1774,6 +1791,12 @@ mod ring_share { 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, @@ -1786,14 +1809,11 @@ mod ring_share { .map(|_| rng.gen::>()) .collect_vec(); let y = (0..VEC_SIZE) - .map(|_| rng.gen::>()) + .map(|_| gen_non_zero::(&mut rng)) .collect_vec(); - let y_1 = y.clone(); - let y_2 = y.clone(); - let y_3 = y.clone(); 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.into_iter()) { + 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()), ))); @@ -1802,16 +1822,16 @@ mod ring_share { let (tx2, rx2) = mpsc::channel(); let (tx3, rx3) = mpsc::channel(); - for (net, tx, x, y_c) in izip!( + for (net, tx, x) in izip!( test_network.get_party_networks().into_iter(), [tx1, tx2, tx3], x_shares.into_iter(), - [y_1, y_2, y_3] ) { + 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_c, &mut rep3).unwrap(); + let div = yao::ring_div_by_public_many(&x, &y_, &mut rep3).unwrap(); tx.send(div) }); } @@ -1822,6 +1842,12 @@ mod ring_share { 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, @@ -1834,14 +1860,11 @@ mod ring_share { .map(|_| rng.gen::>()) .collect_vec(); let y = (0..VEC_SIZE) - .map(|_| rng.gen::>()) + .map(|_| gen_non_zero::(&mut rng)) .collect_vec(); - let x_1 = x.clone(); - let x_2 = x.clone(); - let x_3 = x.clone(); 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()) { + 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()), ))); @@ -1850,16 +1873,16 @@ mod ring_share { let (tx2, rx2) = mpsc::channel(); let (tx3, rx3) = mpsc::channel(); - for (net, tx, y_c, x_c) in izip!( + for (net, tx, y_c) in izip!( test_network.get_party_networks().into_iter(), [tx1, tx2, tx3], y_shares.into_iter(), - [x_1, x_2, x_3] ) { + let x_ = x.to_owned(); thread::spawn(move || { let mut rep3 = IoContext::init(net).unwrap(); - let div = yao::ring_div_by_shared_many(&x_c, &y_c, &mut rep3).unwrap(); + let div = yao::ring_div_by_shared_many(&x_, &y_c, &mut rep3).unwrap(); tx.send(div) }); } @@ -1870,21 +1893,9 @@ mod ring_share { 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]); } - #[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]); - } - #[test] - fn rep3_bin_div_via_yao() { - apply_to_all!(rep3_bin_div_via_yao_t, [u8, u16, u32, u64, u128]); - } - - #[test] - fn rep3_div_power_2_via_yao() { - apply_to_all!(rep3_div_power_2_via_yao_t, [Bit, u8, u16, u32, u64, u128]); - } } From 38cadcf8b58663612987f8e5e5c2b8e86ca12800 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:36:36 +0100 Subject: [PATCH 14/17] fix: to_radix for weird constelations --- co-noir/co-brillig/src/mpc/rep3.rs | 51 +++++++++++++++--------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/co-noir/co-brillig/src/mpc/rep3.rs b/co-noir/co-brillig/src/mpc/rep3.rs index 4e285f23c..d83b7d7f6 100644 --- a/co-noir/co-brillig/src/mpc/rep3.rs +++ b/co-noir/co-brillig/src/mpc/rep3.rs @@ -4,7 +4,6 @@ use brillig::{BitSize, IntegerBitSize}; use core::panic; use mpc_core::protocols::rep3::network::{IoContext, Rep3Network}; use mpc_core::protocols::rep3::{self, Rep3PrimeFieldShare}; -use mpc_core::protocols::rep3_ring::conversion::a2b; use mpc_core::protocols::rep3_ring::ring::bit::Bit; use mpc_core::protocols::rep3_ring::ring::int_ring::IntRing2k; use mpc_core::protocols::rep3_ring::ring::ring_impl::RingElement; @@ -757,7 +756,7 @@ impl BrilligDriver for Rep3BrilligDriver Rep3BrilligType::shared_u8(divided) } (Public::Int(_, IntegerBitSize::U1), Shared::Ring1(_)) => { - todo!("do we need this?") + panic!("We do not implement division for a bit") } _ => panic!("type mismatch. Can only div matching values"), } @@ -867,10 +866,10 @@ impl BrilligDriver for Rep3BrilligDriver )?; Rep3BrilligType::shared_u1(divided) } else { - todo!("do we need this?") + 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) { @@ -899,7 +898,7 @@ impl BrilligDriver for Rep3BrilligDriver Rep3BrilligType::shared_u8(divided) } (Shared::Ring1(_), Shared::Ring1(_)) => { - todo!("do we want this?") + panic!("We do not implement division for a bit") } _ => panic!("type mismatch. Can only div matching values"), }, @@ -1443,14 +1442,11 @@ impl BrilligDriver for Rep3BrilligDriver 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, but is this necessary? + )?; //radix is at most 256, so should fit into u8 if bits { - let limb_2b = a2b(limb[0], &mut self.io_context)?; - let limb_bit = rep3_ring::conversion::bit_inject( - &limb_2b, - &mut self.io_context, - )?; - limbs[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb_bit)); + 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])); }; @@ -1464,9 +1460,6 @@ impl BrilligDriver for Rep3BrilligDriver } (Rep3BrilligType::Public(val), Rep3BrilligType::Shared(radix)) => { if let (Public::Field(val), Shared::Ring32(radix)) = (val, radix) { - if bits { - todo!("Implement to_radix for public value and shared radix for bits=true") - } //todo: do we want to do checks for radix <= 256? let mut limbs: Vec> = vec![Rep3BrilligType::default(); output_size]; @@ -1481,8 +1474,7 @@ impl BrilligDriver for Rep3BrilligDriver val, rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, self.io_context.network.get_id(), - ); // this feels very stupid? - + ); let limb = rep3_ring::yao::field_to_ring_many::<_, u8, _>( &[limb], &mut self.io_context, @@ -1498,13 +1490,19 @@ impl BrilligDriver for Rep3BrilligDriver let limb = rep3::arithmetic::sub( input, rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, - ); // this feels very stupid? + ); 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[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + 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 @@ -1514,10 +1512,6 @@ impl BrilligDriver for Rep3BrilligDriver } (Rep3BrilligType::Shared(val), Rep3BrilligType::Shared(radix)) => { if let (Shared::Field(val), Shared::Ring32(radix)) = (val, radix) { - if bits { - todo!("Implement to_radix for shared value and shared radix for bits=true") - } - //todo: do we want to do checks for radix <= 256? let mut limbs: Vec> = vec![Rep3BrilligType::default(); output_size]; let radix_as_field = @@ -1532,13 +1526,18 @@ impl BrilligDriver for Rep3BrilligDriver let limb = rep3::arithmetic::sub( input, rep3::arithmetic::mul(div, radix_as_field[0], &mut self.io_context)?, - ); // this feels very stupid? - + ); 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[i] = Rep3BrilligType::Shared(Shared::::Ring8(limb[0])); + 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 From 0e91a386439c62d94c7ba67b933539701a217814 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:31:39 +0100 Subject: [PATCH 15/17] fix: Fix a bug preventing constants from being used in garbled circuits. TODO: Adapt the division circuits to use constants whenever possible --- mpc-core/src/protocols/rep3/yao/circuits.rs | 11 +++-------- mpc-core/src/protocols/rep3/yao/garbler.rs | 2 +- mpc-core/src/protocols/rep3/yao/streaming_garbler.rs | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index eac2ee03a..b3c61a0f9 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -2,13 +2,12 @@ //! //! This module contains some garbled circuit implementations. -use std::ops::Not; - use crate::protocols::rep3::yao::GCUtils; use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; use itertools::izip; use num_bigint::BigUint; +use std::ops::Not; /// This struct contains some predefined garbled circuits. pub struct GarbledCircuits {} @@ -97,13 +96,9 @@ impl GarbledCircuits { b: bool, ) -> Result<(G::Item, G::Item), G::Error> { let (s, c) = if b { - let z1 = g.negate(a)?; - let z4 = &z1; - let c = g.xor(z4, a)?; - (a.clone(), c) + (a.clone(), g.constant(1, 2)?) } else { - let z1 = a; - let s = g.negate(z1)?; + let s = g.negate(a)?; (s, a.clone()) }; Ok((s, c)) diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index ce4602b09..f1d6c8725 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -237,7 +237,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) } diff --git a/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs b/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs index c9d9b10c0..dd152a5bd 100644 --- a/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs @@ -215,7 +215,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) } From a1275f8506012064f95b5d1e2407303ea6bc87b1 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:12:51 +0100 Subject: [PATCH 16/17] feat: Add possibility to lazily initialize constants in garbled circuits to only send them once --- mpc-core/src/protocols/rep3/yao/circuits.rs | 45 ++++++++++++------- mpc-core/src/protocols/rep3/yao/evaluator.rs | 32 ++++++++++++- mpc-core/src/protocols/rep3/yao/garbler.rs | 33 +++++++++++++- .../protocols/rep3/yao/streaming_evaluator.rs | 32 ++++++++++++- .../protocols/rep3/yao/streaming_garbler.rs | 33 +++++++++++++- 5 files changed, 156 insertions(+), 19 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index b3c61a0f9..703540aea 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -9,6 +9,15 @@ 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 {} @@ -90,13 +99,13 @@ impl GarbledCircuits { Ok((s, c)) } /// Full adder with carry in set - fn full_adder_const_cin_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.constant(1, 2)?) + (a.clone(), g.const_one()?) } else { let s = g.negate(a)?; (s, a.clone()) @@ -231,7 +240,7 @@ impl GarbledCircuits { } /// 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( + fn bin_subtraction_partial_by_constant( g: &mut G, xs: &[G::Item], ys: &[bool], @@ -268,7 +277,7 @@ impl GarbledCircuits { /// 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( + fn bin_subtraction_partial_from_constant( g: &mut G, xs: &[bool], ys: &[G::Item], @@ -301,7 +310,7 @@ impl GarbledCircuits { } /// 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_custom( + fn bin_subtraction_custom( g: &mut G, first: bool, xs: &[G::Item], @@ -372,7 +381,7 @@ impl GarbledCircuits { // From swanky: /// Binary division by a public value - fn bin_div_by_public( + fn bin_div_by_public( g: &mut G, dividend: &[G::Item], divisor: &[bool], @@ -396,7 +405,7 @@ impl GarbledCircuits { // From swanky: /// Binary division of a public by a shared value - fn bin_div_by_shared( + fn bin_div_by_shared( g: &mut G, dividend: &[bool], divisor: &[G::Item], @@ -1237,7 +1246,7 @@ impl GarbledCircuits { 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( + fn ring_div_by_public( g: &mut G, x1s: &[G::Item], x2s: &[G::Item], @@ -1271,7 +1280,7 @@ impl GarbledCircuits { 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( + fn ring_div_by_shared( g: &mut G, x1s: &[G::Item], x2s: &[G::Item], @@ -1334,7 +1343,7 @@ impl GarbledCircuits { 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( + fn field_int_div_by_public( g: &mut G, x1s: &[G::Item], x2s: &[G::Item], @@ -1359,7 +1368,7 @@ impl GarbledCircuits { 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( + fn field_int_div_by_shared( g: &mut G, x1s: &[G::Item], x2s: &[G::Item], @@ -1496,7 +1505,7 @@ impl GarbledCircuits { Ok(BinaryBundle::new(results)) } /// Binary division for two vecs of inputs - pub fn ring_div_by_public_many( + pub fn ring_div_by_public_many( g: &mut G, wires_x1: &BinaryBundle, wires_x2: &BinaryBundle, @@ -1531,7 +1540,7 @@ impl GarbledCircuits { Ok(BinaryBundle::new(results)) } /// Binary division for two vecs of inputs - pub fn ring_div_by_shared_many( + pub fn ring_div_by_shared_many( g: &mut G, wires_x1: &BinaryBundle, wires_x2: &BinaryBundle, @@ -1605,7 +1614,10 @@ impl GarbledCircuits { 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( + pub(crate) fn field_int_div_by_public_many< + G: FancyBinary + FancyBinaryConstant, + F: PrimeField, + >( g: &mut G, wires_x1: &BinaryBundle, wires_x2: &BinaryBundle, @@ -1643,7 +1655,10 @@ impl GarbledCircuits { 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( + pub(crate) fn field_int_div_by_shared_many< + G: FancyBinary + FancyBinaryConstant, + F: PrimeField, + >( g: &mut G, wires_x1: &BinaryBundle, wires_x2: &BinaryBundle, 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 f1d6c8725..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, } } @@ -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 dd152a5bd..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, } } @@ -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) + } +} From ddba6efa37d19abb61ccf9db38244a0008f9fa3a Mon Sep 17 00:00:00 2001 From: Florin F <156660445+florin5f@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:24:35 +0100 Subject: [PATCH 17/17] chore: remove silly constructions and use constant instead --- mpc-core/src/protocols/rep3/yao/circuits.rs | 77 +++++++++------------ 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 703540aea..90440bdc0 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -107,8 +107,7 @@ impl GarbledCircuits { let (s, c) = if b { (a.clone(), g.const_one()?) } else { - let s = g.negate(a)?; - (s, a.clone()) + (g.negate(a)?, a.clone()) }; Ok((s, c)) } @@ -182,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], @@ -238,6 +237,7 @@ impl GarbledCircuits { } 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( @@ -261,14 +261,11 @@ impl GarbledCircuits { } } for y in ys[length..].iter() { - let y = y.not(); // FULL ADDER with a=0 (x=0) - (s, c) = if y { - (g.negate(&c)?, c) + (s, c) = if *y { + (c, g.const_zero()?) } else { - let c_not = g.negate(&c)?; - let c_r = g.and(&c, &c_not)?; //this is stupid and can be simplified I guess - (c, c_r) + (g.negate(&c)?, c) }; result.push(s); } @@ -308,31 +305,6 @@ impl GarbledCircuits { } 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_custom( - g: &mut G, - first: bool, - xs: &[G::Item], - ys: &[G::Item], - ) -> Result<(Vec, G::Item), G::Error> { - let mut result = Vec::with_capacity(ys.len()); - // Twos complement is negation + 1, we implement by having cin in adder = 1, so only negation is required - - let y0 = g.negate(&ys[0])?; - let (mut s, mut c) = Self::full_adder_const_cin_set(g, &y0, first)?; - result.push(s); - - for (x, y) in xs.iter().zip(ys[1..].iter()) { - let y = g.negate(y)?; - let res = Self::full_adder(g, x, &y, &c)?; - s = res.0; - c = res.1; - result.push(s); - } - - Ok((result, c)) - } /// Binary subtraction. Returns whether it underflowed. /// I.e., calculates the msb of 2^k + x1 - x2 @@ -357,7 +329,7 @@ impl GarbledCircuits { // From swanky: /// Binary division - fn bin_div( + fn bin_div( g: &mut G, dividend: &[G::Item], divisor: &[G::Item], @@ -423,7 +395,8 @@ impl GarbledCircuits { if acc_g.len() == divisor.len() { acc_g.pop(); } - let (res, cout) = Self::bin_subtraction_custom(g, *x, &acc_g, divisor)?; + 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, { @@ -434,7 +407,7 @@ impl GarbledCircuits { g.and(&cout, &res[0])? } }); - acc_g_tmp.extend(Self::bin_multiplex(g, &cout, &acc_g, &res[1..])?); + acc_g_tmp.extend(Self::bin_multiplex(g, &cout, &acc_g[1..], &res[1..])?); acc_g = acc_g_tmp; qs.push(cout); } @@ -454,6 +427,7 @@ impl GarbledCircuits { .map(|(xwire, ywire)| g.mux(b, xwire, ywire)) .collect::, G::Error>>() } + /// Multiplex gadget for public/shared fn bin_multiplex_const( g: &mut G, @@ -1209,8 +1183,9 @@ 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( + fn ring_div( g: &mut G, x1s: &[G::Item], x2s: &[G::Item], @@ -1245,6 +1220,7 @@ impl GarbledCircuits { } 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, @@ -1279,6 +1255,7 @@ impl GarbledCircuits { } 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, @@ -1313,8 +1290,9 @@ impl GarbledCircuits { } 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( + fn field_int_div( g: &mut G, x1s: &[G::Item], x2s: &[G::Item], @@ -1342,6 +1320,7 @@ impl GarbledCircuits { 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, @@ -1367,6 +1346,7 @@ impl GarbledCircuits { 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, @@ -1469,8 +1449,9 @@ impl GarbledCircuits { Ok(BinaryBundle::new(results)) } + /// Binary division for two vecs of inputs - pub fn ring_div_many( + pub fn ring_div_many( g: &mut G, wires_x1: &BinaryBundle, wires_x2: &BinaryBundle, @@ -1504,7 +1485,8 @@ impl GarbledCircuits { } Ok(BinaryBundle::new(results)) } - /// Binary division for two vecs of inputs + + /// 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, @@ -1539,7 +1521,8 @@ impl GarbledCircuits { } Ok(BinaryBundle::new(results)) } - /// Binary division for two vecs of inputs + + /// 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, @@ -1576,7 +1559,7 @@ impl GarbledCircuits { } /// 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( + pub(crate) fn field_int_div_many( g: &mut G, wires_x1: &BinaryBundle, wires_x2: &BinaryBundle, @@ -1613,6 +1596,7 @@ impl GarbledCircuits { } 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, @@ -1654,6 +1638,7 @@ impl GarbledCircuits { } 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, @@ -1711,7 +1696,7 @@ mod test { os::unix::net::UnixStream, }; - const TESTRUNS: usize = 50; + const TESTRUNS: usize = 5; // This puts the X_0 values into garbler_wires and X_c values into evaluator_wires fn encode_field( @@ -1805,6 +1790,7 @@ mod test { gc_test::(); } } + fn gc_test_div_int() where num_bigint::BigUint: std::convert::From, @@ -1868,9 +1854,10 @@ mod test { let result = GCUtils::u16_bits_to_field::(result).unwrap(); assert_eq!(result, is_result); } + #[test] fn gc_test_bn254_div_int() { - for _ in 0..1 { + for _ in 0..TESTRUNS { gc_test_div_int::(); } }