From 878437d45ea4756a906a8f11a84117e9ab549b47 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:33:59 +0200 Subject: [PATCH 01/56] start to add yao stuff --- mpc-core/Cargo.toml | 2 + mpc-core/src/protocols/rep3.rs | 1 + mpc-core/src/protocols/rep3/detail.rs | 11 +- mpc-core/src/protocols/rep3/yao.rs | 253 ++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 mpc-core/src/protocols/rep3/yao.rs diff --git a/mpc-core/Cargo.toml b/mpc-core/Cargo.toml index a6f9427de..96182a97b 100644 --- a/mpc-core/Cargo.toml +++ b/mpc-core/Cargo.toml @@ -21,6 +21,7 @@ ark-ff = { workspace = true } ark-serialize = { workspace = true } bytes = { workspace = true } eyre = { workspace = true } +fancy-garbling = { version = "0.6", git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } itertools = { workspace = true } mpc-net = { version = "0.1.2", path = "../mpc-net" } num-bigint = { workspace = true } @@ -28,6 +29,7 @@ num-traits = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } rayon = { workspace = true } +scuttlebutt = { version = "0.6", git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } serde = { workspace = true } tokio = { workspace = true } tracing.workspace = true diff --git a/mpc-core/src/protocols/rep3.rs b/mpc-core/src/protocols/rep3.rs index 8b9189e49..c60bc2168 100644 --- a/mpc-core/src/protocols/rep3.rs +++ b/mpc-core/src/protocols/rep3.rs @@ -12,6 +12,7 @@ pub mod network; pub mod pointshare; pub mod poly; pub mod rngs; +mod yao; use std::marker::PhantomData; diff --git a/mpc-core/src/protocols/rep3/detail.rs b/mpc-core/src/protocols/rep3/detail.rs index df2601dbd..64abe7a8e 100644 --- a/mpc-core/src/protocols/rep3/detail.rs +++ b/mpc-core/src/protocols/rep3/detail.rs @@ -74,19 +74,14 @@ fn low_depth_sub_p_cmux( ) -> IoResult> { let original_bitlen = bitlen - 1; // before the potential overflow after an addition let mask = (BigUint::from(1u64) << original_bitlen) - BigUint::one(); - let x_msb = x >> original_bitlen; + let mut y = low_depth_binary_sub_p::(x, io_context, bitlen)?; let x = x & &mask; - let mut y = low_depth_binary_sub_p::(&x, io_context, bitlen)?; let y_msb = &y >> (bitlen); y &= &mask; // Spread the ov share to the whole biguint - let ov_a = (x_msb.a.iter_u64_digits().next().unwrap_or_default() - ^ y_msb.a.iter_u64_digits().next().unwrap_or_default()) - & 1; - let ov_b = (x_msb.b.iter_u64_digits().next().unwrap_or_default() - ^ y_msb.b.iter_u64_digits().next().unwrap_or_default()) - & 1; + let ov_a = y_msb.a.iter_u64_digits().next().unwrap_or_default() & 1; + let ov_b = y_msb.b.iter_u64_digits().next().unwrap_or_default() & 1; let ov_a = if ov_a == 1 { mask.to_owned() diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs new file mode 100644 index 000000000..e09f9f46d --- /dev/null +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -0,0 +1,253 @@ +use ark_ff::PrimeField; +use fancy_garbling::{ + BinaryBundle, BinaryGadgets, Bundle, Evaluator, FancyBinary, Garbler, WireMod2, +}; +use num_bigint::BigUint; +use rand::{CryptoRng, Rng, SeedableRng}; +use rand_chacha::ChaCha12Rng; +use scuttlebutt::AbstractChannel; + +/// A structure that contains both the garbler and the evaluators +/// wires. This structure simplifies the API of the garbled circuit. +struct GCInputs { + pub garbler_wires: BinaryBundle, + pub evaluator_wires: BinaryBundle, +} + +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() { + for _ in 0..64 { + res.push(el & 1 == 1); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, false); + res +} + +fn biguint_to_bits_as_u16(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() { + for _ in 0..64 { + res.push((el & 1) as u16); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, 0); + res +} + +fn field_to_bits_as_u16(field: F) -> Vec { + let n_bits = F::MODULUS_BIT_SIZE as usize; + let bigint: BigUint = field.into(); + + biguint_to_bits_as_u16(bigint, n_bits) +} + +// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires +fn encode_field( + field: F, + garbler: &mut Garbler, +) -> GCInputs { + let bits = field_to_bits_as_u16(field); + let mut garbler_wires = Vec::with_capacity(bits.len()); + let mut evaluator_wires = Vec::with_capacity(bits.len()); + for bit in bits { + let (mine, theirs) = garbler.encode_wire(bit, 2); + garbler_wires.push(mine); + evaluator_wires.push(theirs); + } + GCInputs { + garbler_wires: BinaryBundle::from(Bundle::new(garbler_wires)), + evaluator_wires: BinaryBundle::from(Bundle::new(evaluator_wires)), + } +} + +fn full_adder_gc_const( + g: &mut G, + a: &G::Item, + b: bool, + c: &G::Item, +) -> Result<(G::Item, G::Item), G::Error> +where + G: FancyBinary, +{ + let (s, c) = if b { + let z1 = g.negate(a)?; + let s = g.xor(&z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(&z1, &z3)?; + let c = g.xor(&z4, a)?; + (s, c) + } else { + let z1 = a; + let s = g.xor(z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(z1, &z3)?; + let c = g.xor(&z4, a)?; + (s, c) + }; + + Ok((s, c)) +} + +fn adder_mod_p_gc( + g: &mut G, + wires_a: BinaryBundle, + wires_b: BinaryBundle, +) -> Result, G::Error> +where + G: BinaryGadgets, +{ + let bitlen = wires_a.size(); + debug_assert_eq!(bitlen, wires_b.size()); + + // First addition + let (added, carry_add) = g.bin_addition(&wires_a, &wires_b)?; + let added_wires = added.wires(); + + // Prepare p for subtraction + let new_bitlen = bitlen + 1; + let p_ = (BigUint::from(1u64) << new_bitlen) - F::MODULUS.into(); + let p_bits = biguint_to_bits(p_, new_bitlen); + + // manual_rca: + let mut subtracted = Vec::with_capacity(bitlen); + // half_adder: + debug_assert!(p_bits[0]); + let s = g.negate(&added_wires[0])?; + subtracted.push(s); + let mut c = added_wires[0].to_owned(); + // full_adders: + for (a, b) in added_wires.iter().zip(p_bits.iter()).skip(1) { + let (s, c_) = full_adder_gc_const(g, a, *b, &c)?; + c = c_; + subtracted.push(s); + } + // final_full_adder to get ov bit + let z = if p_bits[bitlen] { + g.negate(&carry_add)? + } else { + carry_add + }; + let ov = g.xor(&z, &c)?; + + // multiplex for result + let mut result = Vec::with_capacity(bitlen); + for (s, a) in subtracted.iter().zip(added.iter()) { + // CMUX + // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though + let xor = g.xor(s, a)?; + let and = g.and(&ov, &xor)?; + let r = g.xor(&and, s)?; + result.push(r); + } + + Ok(BinaryBundle::from(Bundle::new(result))) +} + +#[cfg(test)] +mod test { + use super::*; + use ark_ff::Zero; + use fancy_garbling::Fancy; + use rand::thread_rng; + use scuttlebutt::Channel; + use std::{ + io::{BufReader, BufWriter}, + os::unix::net::UnixStream, + }; + + const TESTRUNS: usize = 5; + + fn bits_to_field(bits: Vec) -> F { + let mut res = BigUint::zero(); + for bit in bits.iter().rev() { + assert!(*bit < 2); + res <<= 1; + res += *bit as u64; + } + assert!(res < F::MODULUS.into()); + F::from(res) + } + + fn gc_test() { + let mut rng = thread_rng(); + + let a = F::rand(&mut rng); + let b = F::rand(&mut rng); + let is_result = a + 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 = + adder_mod_p_gc::<_, F>(&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 wihout 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::from(Bundle::new(a)); + let b = BinaryBundle::from(Bundle::new(b)); + + let eval_result = adder_mod_p_gc::<_, F>(&mut evaluator, a, b).unwrap(); + + let result = evaluator.outputs(eval_result.wires()).unwrap().unwrap(); + let result = bits_to_field::(result); + assert_eq!(result, is_result); + } + + #[test] + fn gc_test_bn254() { + for _ in 0..TESTRUNS { + gc_test::(); + } + } +} From 9610ebb1d7db6c143363f487ea19630f457d209a Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:45:30 +0200 Subject: [PATCH 02/56] . --- mpc-core/src/protocols/rep3/yao.rs | 123 ++--------------- mpc-core/src/protocols/rep3/yao/circuits.rs | 145 ++++++++++++++++++++ 2 files changed, 157 insertions(+), 111 deletions(-) create mode 100644 mpc-core/src/protocols/rep3/yao/circuits.rs diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index e09f9f46d..6834237d4 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -1,12 +1,11 @@ use ark_ff::PrimeField; -use fancy_garbling::{ - BinaryBundle, BinaryGadgets, Bundle, Evaluator, FancyBinary, Garbler, WireMod2, -}; +use fancy_garbling::{BinaryBundle, Garbler, WireMod2}; use num_bigint::BigUint; -use rand::{CryptoRng, Rng, SeedableRng}; -use rand_chacha::ChaCha12Rng; +use rand::{CryptoRng, Rng}; use scuttlebutt::AbstractChannel; +mod circuits; + /// A structure that contains both the garbler and the evaluators /// wires. This structure simplifies the API of the garbled circuit. struct GCInputs { @@ -14,23 +13,6 @@ struct GCInputs { pub evaluator_wires: BinaryBundle, } -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() { - for _ in 0..64 { - res.push(el & 1 == 1); - el >>= 1; - bits += 1; - if bits == n_bits { - break; - } - } - } - res.resize(n_bits, false); - res -} - fn biguint_to_bits_as_u16(input: BigUint, n_bits: usize) -> Vec { let mut res = Vec::with_capacity(n_bits); let mut bits = 0; @@ -69,100 +51,19 @@ fn encode_field( evaluator_wires.push(theirs); } GCInputs { - garbler_wires: BinaryBundle::from(Bundle::new(garbler_wires)), - evaluator_wires: BinaryBundle::from(Bundle::new(evaluator_wires)), - } -} - -fn full_adder_gc_const( - g: &mut G, - a: &G::Item, - b: bool, - c: &G::Item, -) -> Result<(G::Item, G::Item), G::Error> -where - G: FancyBinary, -{ - let (s, c) = if b { - let z1 = g.negate(a)?; - let s = g.xor(&z1, c)?; - let z3 = g.xor(a, c)?; - let z4 = g.and(&z1, &z3)?; - let c = g.xor(&z4, a)?; - (s, c) - } else { - let z1 = a; - let s = g.xor(z1, c)?; - let z3 = g.xor(a, c)?; - let z4 = g.and(z1, &z3)?; - let c = g.xor(&z4, a)?; - (s, c) - }; - - Ok((s, c)) -} - -fn adder_mod_p_gc( - g: &mut G, - wires_a: BinaryBundle, - wires_b: BinaryBundle, -) -> Result, G::Error> -where - G: BinaryGadgets, -{ - let bitlen = wires_a.size(); - debug_assert_eq!(bitlen, wires_b.size()); - - // First addition - let (added, carry_add) = g.bin_addition(&wires_a, &wires_b)?; - let added_wires = added.wires(); - - // Prepare p for subtraction - let new_bitlen = bitlen + 1; - let p_ = (BigUint::from(1u64) << new_bitlen) - F::MODULUS.into(); - let p_bits = biguint_to_bits(p_, new_bitlen); - - // manual_rca: - let mut subtracted = Vec::with_capacity(bitlen); - // half_adder: - debug_assert!(p_bits[0]); - let s = g.negate(&added_wires[0])?; - subtracted.push(s); - let mut c = added_wires[0].to_owned(); - // full_adders: - for (a, b) in added_wires.iter().zip(p_bits.iter()).skip(1) { - let (s, c_) = full_adder_gc_const(g, a, *b, &c)?; - c = c_; - subtracted.push(s); + garbler_wires: BinaryBundle::new(garbler_wires), + evaluator_wires: BinaryBundle::new(evaluator_wires), } - // final_full_adder to get ov bit - let z = if p_bits[bitlen] { - g.negate(&carry_add)? - } else { - carry_add - }; - let ov = g.xor(&z, &c)?; - - // multiplex for result - let mut result = Vec::with_capacity(bitlen); - for (s, a) in subtracted.iter().zip(added.iter()) { - // CMUX - // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though - let xor = g.xor(s, a)?; - let and = g.and(&ov, &xor)?; - let r = g.xor(&and, s)?; - result.push(r); - } - - Ok(BinaryBundle::from(Bundle::new(result))) } #[cfg(test)] mod test { use super::*; use ark_ff::Zero; - use fancy_garbling::Fancy; - use rand::thread_rng; + use circuits::adder_mod_p_gc; + use fancy_garbling::{Evaluator, Fancy}; + use rand::{thread_rng, SeedableRng}; + use rand_chacha::ChaCha12Rng; use scuttlebutt::Channel; use std::{ io::{BufReader, BufWriter}, @@ -234,8 +135,8 @@ mod test { let b_ = evaluator.read_wire(2).unwrap(); b.push(b_); } - let a = BinaryBundle::from(Bundle::new(a)); - let b = BinaryBundle::from(Bundle::new(b)); + let a = BinaryBundle::new(a); + let b = BinaryBundle::new(b); let eval_result = adder_mod_p_gc::<_, F>(&mut evaluator, a, b).unwrap(); diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs new file mode 100644 index 000000000..a8bb1cab0 --- /dev/null +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -0,0 +1,145 @@ +use ark_ff::PrimeField; +use fancy_garbling::{BinaryBundle, FancyBinary}; +use num_bigint::BigUint; + +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() { + for _ in 0..64 { + res.push(el & 1 == 1); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, false); + res +} + +fn full_adder_gc_const( + g: &mut G, + a: &G::Item, + b: bool, + c: &G::Item, +) -> Result<(G::Item, G::Item), G::Error> { + let (s, c) = if b { + let z1 = g.negate(a)?; + let s = g.xor(&z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(&z1, &z3)?; + let c = g.xor(&z4, a)?; + (s, c) + } else { + let z1 = a; + let s = g.xor(z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(z1, &z3)?; + let c = g.xor(&z4, a)?; + (s, c) + }; + + Ok((s, c)) +} + +fn half_adder_gc( + g: &mut G, + a: &G::Item, + b: &G::Item, +) -> Result<(G::Item, G::Item), G::Error> { + let s = g.xor(a, b)?; + let c = g.and(a, b)?; + Ok((s, c)) +} + +fn full_adder_gc( + g: &mut G, + a: &G::Item, + b: &G::Item, + c: &G::Item, +) -> Result<(G::Item, G::Item), G::Error> { + let z1 = g.xor(a, b)?; + let s = g.xor(&z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(&z1, &z3)?; + let c = g.xor(&z4, a)?; + Ok((s, c)) +} + +/// Binary addition. Returns the result and the carry. +fn bin_addition_gc( + g: &mut G, + xs: &BinaryBundle, + ys: &BinaryBundle, +) -> Result<(BinaryBundle, G::Item), G::Error> { + let xwires = xs.wires(); + let ywires = ys.wires(); + debug_assert_eq!(xwires.len(), ywires.len()); + let mut result = Vec::with_capacity(xwires.len()); + + let (mut s, mut c) = half_adder_gc(g, &xwires[0], &ywires[0])?; + result.push(s); + + for (x, y) in xwires.iter().zip(ywires.iter()).skip(1) { + let res = full_adder_gc(g, x, y, &c)?; + s = res.0; + c = res.1; + result.push(s); + } + + Ok((BinaryBundle::new(result), c)) +} + +pub(crate) fn adder_mod_p_gc( + g: &mut G, + wires_a: BinaryBundle, + wires_b: BinaryBundle, +) -> Result, G::Error> { + let bitlen = wires_a.size(); + debug_assert_eq!(bitlen, wires_b.size()); + + // First addition + let (added, carry_add) = bin_addition_gc(g, &wires_a, &wires_b)?; + let added_wires = added.wires(); + + // Prepare p for subtraction + let new_bitlen = bitlen + 1; + let p_ = (BigUint::from(1u64) << new_bitlen) - F::MODULUS.into(); + let p_bits = biguint_to_bits(p_, new_bitlen); + + // manual_rca: + let mut subtracted = Vec::with_capacity(bitlen); + // half_adder: + debug_assert!(p_bits[0]); + let s = g.negate(&added_wires[0])?; + subtracted.push(s); + let mut c = added_wires[0].to_owned(); + // full_adders: + for (a, b) in added_wires.iter().zip(p_bits.iter()).skip(1) { + let (s, c_) = full_adder_gc_const(g, a, *b, &c)?; + c = c_; + subtracted.push(s); + } + // final_full_adder to get ov bit + let z = if p_bits[bitlen] { + g.negate(&carry_add)? + } else { + carry_add + }; + let ov = g.xor(&z, &c)?; + + // multiplex for result + let mut result = Vec::with_capacity(bitlen); + for (s, a) in subtracted.iter().zip(added.iter()) { + // CMUX + // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though + let xor = g.xor(s, a)?; + let and = g.and(&ov, &xor)?; + let r = g.xor(&and, s)?; + result.push(r); + } + + Ok(BinaryBundle::new(result)) +} From cd4ae8d66515f23e1570ed15f0ed06c0555592ab Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:50:04 +0200 Subject: [PATCH 03/56] . --- mpc-core/src/protocols/rep3/yao.rs | 100 ++++++++++++++--------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 6834237d4..2c85c2aae 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -6,56 +6,6 @@ use scuttlebutt::AbstractChannel; mod circuits; -/// A structure that contains both the garbler and the evaluators -/// wires. This structure simplifies the API of the garbled circuit. -struct GCInputs { - pub garbler_wires: BinaryBundle, - pub evaluator_wires: BinaryBundle, -} - -fn biguint_to_bits_as_u16(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() { - for _ in 0..64 { - res.push((el & 1) as u16); - el >>= 1; - bits += 1; - if bits == n_bits { - break; - } - } - } - res.resize(n_bits, 0); - res -} - -fn field_to_bits_as_u16(field: F) -> Vec { - let n_bits = F::MODULUS_BIT_SIZE as usize; - let bigint: BigUint = field.into(); - - biguint_to_bits_as_u16(bigint, n_bits) -} - -// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires -fn encode_field( - field: F, - garbler: &mut Garbler, -) -> GCInputs { - let bits = field_to_bits_as_u16(field); - let mut garbler_wires = Vec::with_capacity(bits.len()); - let mut evaluator_wires = Vec::with_capacity(bits.len()); - for bit in bits { - let (mine, theirs) = garbler.encode_wire(bit, 2); - garbler_wires.push(mine); - evaluator_wires.push(theirs); - } - GCInputs { - garbler_wires: BinaryBundle::new(garbler_wires), - evaluator_wires: BinaryBundle::new(evaluator_wires), - } -} - #[cfg(test)] mod test { use super::*; @@ -72,6 +22,56 @@ mod test { const TESTRUNS: usize = 5; + /// A structure that contains both the garbler and the evaluators + /// wires. This structure simplifies the API of the garbled circuit. + struct GCInputs { + pub garbler_wires: BinaryBundle, + pub evaluator_wires: BinaryBundle, + } + + fn biguint_to_bits_as_u16(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() { + for _ in 0..64 { + res.push((el & 1) as u16); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, 0); + res + } + + fn field_to_bits_as_u16(field: F) -> Vec { + let n_bits = F::MODULUS_BIT_SIZE as usize; + let bigint: BigUint = field.into(); + + biguint_to_bits_as_u16(bigint, n_bits) + } + + // This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + fn encode_field( + field: F, + garbler: &mut Garbler, + ) -> GCInputs { + let bits = field_to_bits_as_u16(field); + let mut garbler_wires = Vec::with_capacity(bits.len()); + let mut evaluator_wires = Vec::with_capacity(bits.len()); + for bit in bits { + let (mine, theirs) = garbler.encode_wire(bit, 2); + garbler_wires.push(mine); + evaluator_wires.push(theirs); + } + GCInputs { + garbler_wires: BinaryBundle::new(garbler_wires), + evaluator_wires: BinaryBundle::new(evaluator_wires), + } + } + fn bits_to_field(bits: Vec) -> F { let mut res = BigUint::zero(); for bit in bits.iter().rev() { From e21cc9bd559515d385b9008294e8eabd911d755c Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:38:36 +0200 Subject: [PATCH 04/56] garbler --- mpc-core/Cargo.toml | 1 + mpc-core/src/protocols/rep3/rngs.rs | 10 ++ mpc-core/src/protocols/rep3/yao.rs | 154 +---------------- mpc-core/src/protocols/rep3/yao/circuits.rs | 146 ++++++++++++++++ mpc-core/src/protocols/rep3/yao/garbler.rs | 178 ++++++++++++++++++++ 5 files changed, 336 insertions(+), 153 deletions(-) create mode 100644 mpc-core/src/protocols/rep3/yao/garbler.rs diff --git a/mpc-core/Cargo.toml b/mpc-core/Cargo.toml index 96182a97b..927fc919a 100644 --- a/mpc-core/Cargo.toml +++ b/mpc-core/Cargo.toml @@ -30,6 +30,7 @@ rand = { workspace = true } rand_chacha = { workspace = true } rayon = { workspace = true } scuttlebutt = { version = "0.6", git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } +subtle = "2.6" serde = { workspace = true } tokio = { workspace = true } tracing.workspace = true diff --git a/mpc-core/src/protocols/rep3/rngs.rs b/mpc-core/src/protocols/rep3/rngs.rs index f6bc91da7..c711820bb 100644 --- a/mpc-core/src/protocols/rep3/rngs.rs +++ b/mpc-core/src/protocols/rep3/rngs.rs @@ -124,6 +124,16 @@ impl Rep3Rand { let seed2 = self.rng2.gen(); (seed1, seed2) } + + /// Generate a seed from rng1 + pub fn random_seed1(&mut self) -> [u8; crate::SEED_SIZE] { + self.rng1.gen() + } + + /// Generate a seed from rng2 + pub fn random_seed2(&mut self) -> [u8; crate::SEED_SIZE] { + self.rng2.gen() + } } /// This struct is responsible for creating random shares for the Binary to Arithmetic conversion. The approach is the following: for a final sharing x = x1 + x2 + x3, we want to have random values x2, x3 and subtract these from the original value x using a binary circuit to get the share x1. Hence, we need to sample random x2 and x3 and share them amongst the parties. One RandBitComp struct is responsible for either sampling x2 or x3. For sampling x2, parties 1 and 2 will get x2 in plain (since this is the final share of x), so they need to have a PRF key from all parties. party 3, however, will not get x2 in plain and must thus only be able to sample its shares of x2, requiring two PRF keys. diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 2c85c2aae..29ae7a84c 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -1,154 +1,2 @@ -use ark_ff::PrimeField; -use fancy_garbling::{BinaryBundle, Garbler, WireMod2}; -use num_bigint::BigUint; -use rand::{CryptoRng, Rng}; -use scuttlebutt::AbstractChannel; - mod circuits; - -#[cfg(test)] -mod test { - use super::*; - use ark_ff::Zero; - use circuits::adder_mod_p_gc; - use fancy_garbling::{Evaluator, Fancy}; - use rand::{thread_rng, SeedableRng}; - use rand_chacha::ChaCha12Rng; - use scuttlebutt::Channel; - use std::{ - io::{BufReader, BufWriter}, - os::unix::net::UnixStream, - }; - - const TESTRUNS: usize = 5; - - /// A structure that contains both the garbler and the evaluators - /// wires. This structure simplifies the API of the garbled circuit. - struct GCInputs { - pub garbler_wires: BinaryBundle, - pub evaluator_wires: BinaryBundle, - } - - fn biguint_to_bits_as_u16(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() { - for _ in 0..64 { - res.push((el & 1) as u16); - el >>= 1; - bits += 1; - if bits == n_bits { - break; - } - } - } - res.resize(n_bits, 0); - res - } - - fn field_to_bits_as_u16(field: F) -> Vec { - let n_bits = F::MODULUS_BIT_SIZE as usize; - let bigint: BigUint = field.into(); - - biguint_to_bits_as_u16(bigint, n_bits) - } - - // This puts the X_0 values into garbler_wires and X_c values into evaluator_wires - fn encode_field( - field: F, - garbler: &mut Garbler, - ) -> GCInputs { - let bits = field_to_bits_as_u16(field); - let mut garbler_wires = Vec::with_capacity(bits.len()); - let mut evaluator_wires = Vec::with_capacity(bits.len()); - for bit in bits { - let (mine, theirs) = garbler.encode_wire(bit, 2); - garbler_wires.push(mine); - evaluator_wires.push(theirs); - } - GCInputs { - garbler_wires: BinaryBundle::new(garbler_wires), - evaluator_wires: BinaryBundle::new(evaluator_wires), - } - } - - fn bits_to_field(bits: Vec) -> F { - let mut res = BigUint::zero(); - for bit in bits.iter().rev() { - assert!(*bit < 2); - res <<= 1; - res += *bit as u64; - } - assert!(res < F::MODULUS.into()); - F::from(res) - } - - fn gc_test() { - let mut rng = thread_rng(); - - let a = F::rand(&mut rng); - let b = F::rand(&mut rng); - let is_result = a + 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 = - adder_mod_p_gc::<_, F>(&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 wihout 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 = adder_mod_p_gc::<_, F>(&mut evaluator, a, b).unwrap(); - - let result = evaluator.outputs(eval_result.wires()).unwrap().unwrap(); - let result = bits_to_field::(result); - assert_eq!(result, is_result); - } - - #[test] - fn gc_test_bn254() { - for _ in 0..TESTRUNS { - gc_test::(); - } - } -} +mod garbler; diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index a8bb1cab0..c27b4216f 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -143,3 +143,149 @@ pub(crate) fn adder_mod_p_gc( Ok(BinaryBundle::new(result)) } + +#[cfg(test)] +mod test { + use super::*; + use ark_ff::Zero; + use fancy_garbling::{Evaluator, Fancy, Garbler, WireMod2}; + use rand::{thread_rng, CryptoRng, Rng, SeedableRng}; + use rand_chacha::ChaCha12Rng; + use scuttlebutt::{AbstractChannel, Channel}; + use std::{ + io::{BufReader, BufWriter}, + os::unix::net::UnixStream, + }; + + const TESTRUNS: usize = 5; + + /// A structure that contains both the garbler and the evaluators + /// wires. This structure simplifies the API of the garbled circuit. + struct GCInputs { + pub garbler_wires: BinaryBundle, + pub evaluator_wires: BinaryBundle, + } + + fn biguint_to_bits_as_u16(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() { + for _ in 0..64 { + res.push((el & 1) as u16); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, 0); + res + } + + fn field_to_bits_as_u16(field: F) -> Vec { + let n_bits = F::MODULUS_BIT_SIZE as usize; + let bigint: BigUint = field.into(); + + biguint_to_bits_as_u16(bigint, n_bits) + } + + // This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + fn encode_field( + field: F, + garbler: &mut Garbler, + ) -> GCInputs { + let bits = field_to_bits_as_u16(field); + let mut garbler_wires = Vec::with_capacity(bits.len()); + let mut evaluator_wires = Vec::with_capacity(bits.len()); + for bit in bits { + let (mine, theirs) = garbler.encode_wire(bit, 2); + garbler_wires.push(mine); + evaluator_wires.push(theirs); + } + GCInputs { + garbler_wires: BinaryBundle::new(garbler_wires), + evaluator_wires: BinaryBundle::new(evaluator_wires), + } + } + + fn bits_to_field(bits: Vec) -> F { + let mut res = BigUint::zero(); + for bit in bits.iter().rev() { + assert!(*bit < 2); + res <<= 1; + res += *bit as u64; + } + assert!(res < F::MODULUS.into()); + F::from(res) + } + + fn gc_test() { + let mut rng = thread_rng(); + + let a = F::rand(&mut rng); + let b = F::rand(&mut rng); + let is_result = a + 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 = + adder_mod_p_gc::<_, F>(&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 wihout 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 = adder_mod_p_gc::<_, F>(&mut evaluator, a, b).unwrap(); + + let result = evaluator.outputs(eval_result.wires()).unwrap().unwrap(); + let result = bits_to_field::(result); + assert_eq!(result, is_result); + } + + #[test] + fn gc_test_bn254() { + for _ in 0..TESTRUNS { + gc_test::(); + } + } +} diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs new file mode 100644 index 000000000..501a5a553 --- /dev/null +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -0,0 +1,178 @@ +use crate::{ + protocols::rep3::{ + id::PartyID, + network::{IoContext, Rep3Network}, + }, + RngType, +}; +use fancy_garbling::{ + errors::GarblerError, + hash_wires, + util::{output_tweak, tweak2}, + Fancy, FancyBinary, WireLabel, WireMod2, +}; +use rand::SeedableRng; +use scuttlebutt::Block; +use subtle::ConditionallySelectable; + +// This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs + +pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { + io_context: &'a mut IoContext, + delta: WireMod2, + current_output: usize, + current_gate: usize, + rng: RngType, +} + +impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { + /// Create a new garbler. + pub(crate) fn new(io_context: &'a mut IoContext) -> Self { + let id = io_context.id; + let seed = match id { + PartyID::ID0 => { + panic!("Garbler should not be PartyID::ID0") + } + PartyID::ID1 => io_context.rngs.rand.random_seed1(), + PartyID::ID2 => io_context.rngs.rand.random_seed2(), + }; + let mut rng = RngType::from_seed(seed); + + Self { + io_context, + delta: WireMod2::rand_delta(&mut rng, 2), + current_output: 0, + current_gate: 0, + rng, + } + } + + /// The current non-free gate index of the garbling computation + fn current_gate(&mut self) -> usize { + let current = self.current_gate; + self.current_gate += 1; + current + } + + /// The current output index of the garbling computation. + fn current_output(&mut self) -> usize { + let current = self.current_output; + self.current_output += 1; + current + } + + /// Send a block over the network to the evaluator. + fn send_block(&mut self, block: &Block) -> Result<(), GarblerError> { + self.io_context.network.send(PartyID::ID0, block.as_ref())?; + Ok(()) + } + + /// Send a wire over the established channel. + fn send_wire(&mut self, wire: &WireMod2) -> Result<(), GarblerError> { + self.send_block(&wire.as_block())?; + Ok(()) + } + + /// Encode a wire, producing the zero wire as well as the encoded value. + pub fn encode_wire(&mut self, val: u16) -> (WireMod2, WireMod2) { + let zero = WireMod2::rand(&mut self.rng, 2); + let enc = zero.plus(&self.delta.cmul(val)); + (zero, enc) + } + + /// Garbles an 'and' gate given two input wires and the delta. + /// + /// Outputs a tuple consisting of the two gates (that should be transfered to the evaluator) + /// and the next wire label for the garbler. + /// + /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. + fn garble_and_gate( + &mut self, + a: &WireMod2, + b: &WireMod2, + delta: &WireMod2, + ) -> (Block, Block, WireMod2) { + let q = 2; + let d = delta; + let gate_num = self.current_gate(); + + let r = b.color(); // secret value known only to the garbler (ev knows r+b) + + let g = tweak2(gate_num as u64, 0); + + // X = H(A+aD) + arD such that a + A.color == 0 + let alpha = a.color(); // alpha = -A.color + let x1 = a.plus(&d.cmul(alpha)); + + // Y = H(B + bD) + (b + r)A such that b + B.color == 0 + let beta = (q - b.color()) % q; + let y1 = b.plus(&d.cmul(beta)); + + let ad = a.plus(d); + let bd = b.plus(d); + + // idx is always boolean for binary gates, so it can be represented as a `u8` + let a_selector = (a.color() as u8).into(); + let b_selector = (b.color() as u8).into(); + + let b = WireMod2::conditional_select(&bd, b, b_selector); + let new_a = WireMod2::conditional_select(&ad, a, a_selector); + let idx = u8::conditional_select(&(r as u8), &0u8, a_selector); + + let [hash_a, hash_b, hash_x, hash_y] = hash_wires([&new_a, &b, &x1, &y1], g); + + let x = WireMod2::hash_to_mod(hash_x, q).plus_mov(&d.cmul(alpha * r % q)); + let y = WireMod2::hash_to_mod(hash_y, q); + + let gate0 = + hash_a ^ Block::conditional_select(&x.as_block(), &x.plus(d).as_block(), idx.into()); + let gate1 = hash_b ^ y.plus(a).as_block(); + + (gate0, gate1, x.plus_mov(&y)) + } +} + +impl<'a, N: Rep3Network> Fancy for Rep3Garbler<'a, N> { + type Item = WireMod2; + type Error = GarblerError; + + 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)); + self.send_wire(&wire)?; + Ok(zero) + } + + fn output(&mut self, x: &WireMod2) -> Result, GarblerError> { + let i = self.current_output(); + let d = self.delta; + for k in 0..2 { + let block = x.plus(&d.cmul(k)).hash(output_tweak(i, k)); + self.send_block(&block)?; + } + Ok(None) + } +} + +impl<'a, N: Rep3Network> FancyBinary for Rep3Garbler<'a, N> { + fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { + let delta = self.delta; + let (gate0, gate1, c) = self.garble_and_gate(a, b, &delta); + self.send_block(&gate0)?; + self.send_block(&gate1)?; + Ok(c) + } + + fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Result { + Ok(x.plus(y)) + } + + /// We can negate by having garbler xor wire with Delta + /// + /// Since we treat all garbler wires as zero, + /// xoring with delta conceptually negates the value of the wire + fn negate(&mut self, x: &Self::Item) -> Result { + let delta = self.delta; + self.xor(&delta, x) + } +} From a9562c37dc967a5f39f595ac1a1bb052aedabe3c Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:57:30 +0200 Subject: [PATCH 05/56] evaluator --- mpc-core/src/protocols/rep3/yao.rs | 1 + mpc-core/src/protocols/rep3/yao/evaluator.rs | 154 +++++++++++++++++++ mpc-core/src/protocols/rep3/yao/garbler.rs | 4 +- 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 mpc-core/src/protocols/rep3/yao/evaluator.rs diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 29ae7a84c..bd2e2d4c2 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -1,2 +1,3 @@ mod circuits; +mod evaluator; mod garbler; diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs new file mode 100644 index 000000000..6666cd45c --- /dev/null +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -0,0 +1,154 @@ +// This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs + +use crate::protocols::rep3::{ + id::PartyID, + network::{IoContext, Rep3Network}, +}; +use fancy_garbling::{ + errors::EvaluatorError, + hash_wires, + util::{output_tweak, tweak2}, + Fancy, WireLabel, WireMod2, +}; +use scuttlebutt::Block; +use subtle::ConditionallySelectable; + +pub(crate) struct Rep3Evaluator<'a, N: Rep3Network> { + io_context: &'a mut IoContext, + current_output: usize, + current_gate: usize, +} + +impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { + /// Create a new garbler. + pub(crate) fn new(io_context: &'a mut IoContext) -> Self { + let id = io_context.id; + if id != PartyID::ID0 { + panic!("Garbler should be PartyID::ID0") + } + + Self { + io_context, + current_output: 0, + current_gate: 0, + } + } + + /// The current non-free gate index of the garbling computation. + fn current_gate(&mut self) -> usize { + let current = self.current_gate; + self.current_gate += 1; + current + } + + /// The current output index of the garbling computation. + fn current_output(&mut self) -> usize { + let current = self.current_output; + self.current_output += 1; + current + } + + fn receive_block_from(&mut self, id: PartyID) -> Result { + let data: Vec = self.io_context.network.recv(id)?; + if data.len() != 16 { + return Err(EvaluatorError::DecodingFailed); + } + let mut v = Block::default(); + v.as_mut().copy_from_slice(&data); + + Ok(v) + } + + /// Send a block over the network to the evaluator. + fn receive_block(&mut self) -> Result { + let block1 = self.receive_block_from(PartyID::ID1)?; + let block2 = self.receive_block_from(PartyID::ID2)?; + + // TODO maybe do this at separate points + + if block1 != block2 { + return Err(EvaluatorError::CommunicationError( + "Blocks of two garblers do not match!".to_string(), + )); + } + + Ok(block1) + } + + /// Read `n` `Block`s from the channel. + #[inline(always)] + fn read_blocks(&mut self, n: usize) -> Result, EvaluatorError> { + (0..n).map(|_| self.receive_block()).collect() + } + + /// Read a Wire from the reader. + pub fn read_wire(&mut self) -> Result { + let block = self.receive_block()?; + Ok(WireMod2::from_block(block, 2)) + } + + /// Evaluates an 'and' gate given two inputs wires and two half-gates from the garbler. + /// + /// Outputs C = A & B + /// + /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. + fn evaluate_and_gate( + &mut self, + a: &WireMod2, + b: &WireMod2, + gate0: &Block, + gate1: &Block, + ) -> WireMod2 { + let gate_num = self.current_gate(); + let g = tweak2(gate_num as u64, 0); + + let [hash_a, hash_b] = hash_wires([a, b], g); + + // garbler's half gate + let l = WireMod2::from_block( + Block::conditional_select(&hash_a, &(hash_a ^ *gate0), (a.color() as u8).into()), + 2, + ); + + // evaluator's half gate + let r = WireMod2::from_block( + Block::conditional_select(&hash_b, &(hash_b ^ *gate1), (b.color() as u8).into()), + 2, + ); + + l.plus_mov(&r.plus_mov(&a.cmul(b.color()))) + } +} + +impl<'a, N: Rep3Network> Fancy for Rep3Evaluator<'a, N> { + type Item = WireMod2; + type Error = EvaluatorError; + + fn constant(&mut self, _: u16, _q: u16) -> Result { + self.read_wire() + } + + fn output(&mut self, x: &WireMod2) -> Result, EvaluatorError> { + let q = 2; + let i = self.current_output(); + + // Receive the output ciphertext from the garbler + let ct = self.read_blocks(q as usize)?; + + // Attempt to brute force x using the output ciphertext + let mut decoded = None; + for k in 0..q { + let hashed_wire = x.hash(output_tweak(i, k)); + if hashed_wire == ct[k as usize] { + decoded = Some(k); + break; + } + } + + if let Some(output) = decoded { + Ok(Some(output)) + } else { + Err(EvaluatorError::DecodingFailed) + } + } +} diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 501a5a553..9bfff594f 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -1,3 +1,5 @@ +// This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs + use crate::{ protocols::rep3::{ id::PartyID, @@ -15,8 +17,6 @@ use rand::SeedableRng; use scuttlebutt::Block; use subtle::ConditionallySelectable; -// This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs - pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { io_context: &'a mut IoContext, delta: WireMod2, From 8e1bdecffc0ab69fdc1d873785dfcf5d0f3839b5 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:46:40 +0200 Subject: [PATCH 06/56] . --- mpc-core/Cargo.toml | 1 + mpc-core/src/protocols/rep3/yao/evaluator.rs | 30 +++++++++++++------- mpc-core/src/protocols/rep3/yao/garbler.rs | 22 +++++++++++++- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/mpc-core/Cargo.toml b/mpc-core/Cargo.toml index 927fc919a..5bbb3565e 100644 --- a/mpc-core/Cargo.toml +++ b/mpc-core/Cargo.toml @@ -32,6 +32,7 @@ rayon = { workspace = true } scuttlebutt = { version = "0.6", git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } subtle = "2.6" serde = { workspace = true } +sha3 = { workspace = true } tokio = { workspace = true } tracing.workspace = true diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 6666cd45c..33f168ecc 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -11,12 +11,14 @@ use fancy_garbling::{ Fancy, WireLabel, WireMod2, }; use scuttlebutt::Block; +use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; pub(crate) struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, current_gate: usize, + hash: Sha3_256, // For the ID2 to match everything sent with one hash } impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { @@ -31,6 +33,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { io_context, current_output: 0, current_gate: 0, + hash: Sha3_256::default(), } } @@ -59,20 +62,27 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(v) } - /// Send a block over the network to the evaluator. - fn receive_block(&mut self) -> Result { - let block1 = self.receive_block_from(PartyID::ID1)?; - let block2 = self.receive_block_from(PartyID::ID2)?; - - // TODO maybe do this at separate points - - if block1 != block2 { + // Receive a hash of ID2 (the second garbler) to verify the garbled circuit. + fn receive_hash(&mut self) -> Result<(), EvaluatorError> { + let data: Vec = self.io_context.network.recv(PartyID::ID2)?; + let mut hash = Sha3_256::default(); + std::mem::swap(&mut hash, &mut self.hash); + let digest = hash.finalize(); + if &data != digest.as_slice() { return Err(EvaluatorError::CommunicationError( - "Blocks of two garblers do not match!".to_string(), + "Inconsistent Garbled Circuits: Hashes do not match!".to_string(), )); } - Ok(block1) + Ok(()) + } + + /// Send a block over the network to the evaluator. + fn receive_block(&mut self) -> Result { + let block = self.receive_block_from(PartyID::ID1)?; + self.hash.update(block.as_ref()); + + Ok(block) } /// Read `n` `Block`s from the channel. diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 9bfff594f..dcdef33da 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -15,6 +15,7 @@ use fancy_garbling::{ }; use rand::SeedableRng; use scuttlebutt::Block; +use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { @@ -23,6 +24,7 @@ pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { current_output: usize, current_gate: usize, rng: RngType, + hash: Sha3_256, // For the ID2 to match everything sent with one hash } impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { @@ -44,6 +46,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { current_output: 0, current_gate: 0, rng, + hash: Sha3_256::default(), } } @@ -63,7 +66,24 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Send a block over the network to the evaluator. fn send_block(&mut self, block: &Block) -> Result<(), GarblerError> { - self.io_context.network.send(PartyID::ID0, block.as_ref())?; + if self.io_context.id == PartyID::ID1 { + self.io_context.network.send(PartyID::ID0, block.as_ref())?; + } else { + self.hash.update(block.as_ref()); + } + Ok(()) + } + + /// As ID2, send a hash of the sended data to the evaluator. + fn send_hash(&mut self) -> Result<(), GarblerError> { + if self.io_context.id == PartyID::ID2 { + let mut hash = Sha3_256::default(); + std::mem::swap(&mut hash, &mut self.hash); + let digest = hash.finalize(); + self.io_context + .network + .send(PartyID::ID0, digest.as_slice())?; + } Ok(()) } From 25fb41e4a797e14a1d1737a246e3c005c36fb2e5 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:50:19 +0200 Subject: [PATCH 07/56] . --- mpc-core/src/protocols/rep3/yao/garbler.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index dcdef33da..5d5c7510c 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -30,6 +30,13 @@ pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Create a new garbler. pub(crate) fn new(io_context: &'a mut IoContext) -> Self { + let mut res = Self::new_with_delta(io_context, WireMod2::default()); + res.delta = WireMod2::rand_delta(&mut res.rng, 2); + res + } + + /// Create a new garbler with existing delta. + pub(crate) fn new_with_delta(io_context: &'a mut IoContext, delta: WireMod2) -> Self { let id = io_context.id; let seed = match id { PartyID::ID0 => { @@ -38,11 +45,11 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { PartyID::ID1 => io_context.rngs.rand.random_seed1(), PartyID::ID2 => io_context.rngs.rand.random_seed2(), }; - let mut rng = RngType::from_seed(seed); + let rng = RngType::from_seed(seed); Self { io_context, - delta: WireMod2::rand_delta(&mut rng, 2), + delta, current_output: 0, current_gate: 0, rng, @@ -50,6 +57,11 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } } + // Consumes the Garbler and returns the delta. + fn into_delta(self) -> WireMod2 { + self.delta + } + /// The current non-free gate index of the garbling computation fn current_gate(&mut self) -> usize { let current = self.current_gate; From c1b862f8600c4200513850b4852bf662c1585dab Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:58:48 +0200 Subject: [PATCH 08/56] . --- mpc-core/src/protocols/rep3/yao/evaluator.rs | 4 ++-- mpc-core/src/protocols/rep3/yao/garbler.rs | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 33f168ecc..75511eb01 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -68,7 +68,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { let mut hash = Sha3_256::default(); std::mem::swap(&mut hash, &mut self.hash); let digest = hash.finalize(); - if &data != digest.as_slice() { + if data != digest.as_slice() { return Err(EvaluatorError::CommunicationError( "Inconsistent Garbled Circuits: Hashes do not match!".to_string(), )); @@ -80,7 +80,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { /// Send a block over the network to the evaluator. fn receive_block(&mut self) -> Result { let block = self.receive_block_from(PartyID::ID1)?; - self.hash.update(block.as_ref()); + self.hash.update(block.as_ref()); // "Receive" from ID2 Ok(block) } diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 5d5c7510c..b4b756366 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -1,5 +1,7 @@ // This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs +use core::panic; + use crate::{ protocols::rep3::{ id::PartyID, @@ -78,10 +80,16 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Send a block over the network to the evaluator. fn send_block(&mut self, block: &Block) -> Result<(), GarblerError> { - if self.io_context.id == PartyID::ID1 { - self.io_context.network.send(PartyID::ID0, block.as_ref())?; - } else { - self.hash.update(block.as_ref()); + match self.io_context.id { + PartyID::ID0 => { + panic!("Garbler should not be PartyID::ID0"); + } + PartyID::ID1 => { + self.io_context.network.send(PartyID::ID0, block.as_ref())?; + } + PartyID::ID2 => { + self.hash.update(block.as_ref()); + } } Ok(()) } From afcb7f064ec4379c58f38cfec797aa2ec7d67aa3 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:08:10 +0200 Subject: [PATCH 09/56] . --- mpc-core/src/protocols/rep3/yao/evaluator.rs | 27 ++++++++++++++++++++ mpc-core/src/protocols/rep3/yao/garbler.rs | 17 +++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 75511eb01..68ac76fb6 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -62,6 +62,33 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(v) } + /// Outputs the values to the evaluator. + fn output_evaluator(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { + let result = self.outputs(x)?; + match result { + Some(outputs) => { + let mut res = Vec::with_capacity(outputs.len()); + for val in outputs { + if val >= 2 { + return Err(EvaluatorError::DecodingFailed); + } + res.push(val == 1); + } + Ok(res) + } + None => Err(EvaluatorError::DecodingFailed), + } + } + + /// Outputs the value to all parties + fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { + // Evaluator to garbler + todo!(); + + // Garbler's to evaluator + self.output_evaluator(x) + } + // Receive a hash of ID2 (the second garbler) to verify the garbled circuit. fn receive_hash(&mut self) -> Result<(), EvaluatorError> { let data: Vec = self.io_context.network.recv(PartyID::ID2)?; diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index b4b756366..b7309a29c 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -59,7 +59,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } } - // Consumes the Garbler and returns the delta. + /// Consumes the Garbler and returns the delta. fn into_delta(self) -> WireMod2 { self.delta } @@ -94,6 +94,21 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { Ok(()) } + /// Outputs the values to the evaluator. + fn output_evaluator(&mut self, x: &[WireMod2]) -> Result<(), GarblerError> { + self.outputs(x)?; + Ok(()) + } + + /// Outputs the value to all parties + fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, GarblerError> { + // Garbler's to evaluator + self.output_evaluator(x)?; + + // Evaluator to garbler + todo!() + } + /// As ID2, send a hash of the sended data to the evaluator. fn send_hash(&mut self) -> Result<(), GarblerError> { if self.io_context.id == PartyID::ID2 { From 8d88f5087b4727cd990a75a67c0b9114222113bc Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:24:57 +0200 Subject: [PATCH 10/56] output function --- mpc-core/src/protocols/rep3/yao/evaluator.rs | 52 +++++++++---- mpc-core/src/protocols/rep3/yao/garbler.rs | 77 +++++++++++++++----- 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 68ac76fb6..dd5451f33 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -51,17 +51,6 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { current } - fn receive_block_from(&mut self, id: PartyID) -> Result { - let data: Vec = self.io_context.network.recv(id)?; - if data.len() != 16 { - return Err(EvaluatorError::DecodingFailed); - } - let mut v = Block::default(); - v.as_mut().copy_from_slice(&data); - - Ok(v) - } - /// Outputs the values to the evaluator. fn output_evaluator(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { let result = self.outputs(x)?; @@ -80,13 +69,27 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } } + /// Outputs the values to the garbler. + fn output_garbler(&mut self, x: &[WireMod2]) -> Result<(), EvaluatorError> { + for val in x { + let block = val.as_block(); + self.send_block(&block)?; + } + Ok(()) + } + /// Outputs the value to all parties fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { + // Garbler's to evaluator + let res = self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before releasing the result + self.receive_hash()?; + // Evaluator to garbler - todo!(); + self.output_garbler(x)?; - // Garbler's to evaluator - self.output_evaluator(x) + Ok(res) } // Receive a hash of ID2 (the second garbler) to verify the garbled circuit. @@ -104,6 +107,27 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(()) } + /// Send a block over the network to the garblers. + fn send_block(&mut self, block: &Block) -> Result<(), EvaluatorError> { + self.io_context.network.send(PartyID::ID1, block.as_ref())?; + self.io_context.network.send(PartyID::ID2, block.as_ref())?; + Ok(()) + } + + /// Receive a block from a specific party. + fn receive_block_from(&mut self, id: PartyID) -> Result { + let data: Vec = self.io_context.network.recv(id)?; + if data.len() != 16 { + return Err(EvaluatorError::CommunicationError( + "Invalid data length received".to_string(), + )); + } + let mut v = Block::default(); + v.as_mut().copy_from_slice(&data); + + Ok(v) + } + /// Send a block over the network to the evaluator. fn receive_block(&mut self) -> Result { let block = self.receive_block_from(PartyID::ID1)?; diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index b7309a29c..feda98fa3 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -78,35 +78,41 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { current } - /// Send a block over the network to the evaluator. - fn send_block(&mut self, block: &Block) -> Result<(), GarblerError> { - match self.io_context.id { - PartyID::ID0 => { - panic!("Garbler should not be PartyID::ID0"); - } - PartyID::ID1 => { - self.io_context.network.send(PartyID::ID0, block.as_ref())?; - } - PartyID::ID2 => { - self.hash.update(block.as_ref()); - } - } - Ok(()) - } - /// Outputs the values to the evaluator. fn output_evaluator(&mut self, x: &[WireMod2]) -> Result<(), GarblerError> { self.outputs(x)?; Ok(()) } + /// Outputs the values to the garbler. + fn output_garbler(&mut self, x: &[WireMod2]) -> Result, GarblerError> { + let blocks = self.read_blocks(x.len())?; + + let mut result = Vec::with_capacity(x.len()); + for (block, wire) in blocks.into_iter().zip(x.iter()) { + if block == wire.as_block() { + result.push(true); + } else if block == wire.plus(&self.delta).as_block() { + result.push(false); + } else { + return Err(GarblerError::CommunicationError( + "Invalid block received".to_string(), + )); + } + } + Ok(result) + } + /// Outputs the value to all parties fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, GarblerError> { // Garbler's to evaluator self.output_evaluator(x)?; + // Check consistency with the second garbled circuit before receiving the result + self.send_hash()?; + // Evaluator to garbler - todo!() + self.output_garbler(x) } /// As ID2, send a hash of the sended data to the evaluator. @@ -122,6 +128,43 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { Ok(()) } + /// Send a block over the network to the evaluator. + fn send_block(&mut self, block: &Block) -> Result<(), GarblerError> { + match self.io_context.id { + PartyID::ID0 => { + panic!("Garbler should not be PartyID::ID0"); + } + PartyID::ID1 => { + self.io_context.network.send(PartyID::ID0, block.as_ref())?; + } + PartyID::ID2 => { + self.hash.update(block.as_ref()); + } + } + Ok(()) + } + + fn receive_block_from(&mut self, id: PartyID) -> Result { + let data: Vec = self.io_context.network.recv(id)?; + if data.len() != 16 { + return Err(GarblerError::CommunicationError( + "Invalid data length received".to_string(), + )); + } + let mut v = Block::default(); + v.as_mut().copy_from_slice(&data); + + Ok(v) + } + + /// Read `n` `Block`s from the channel. + #[inline(always)] + fn read_blocks(&mut self, n: usize) -> Result, GarblerError> { + (0..n) + .map(|_| self.receive_block_from(PartyID::ID0)) + .collect() + } + /// Send a wire over the established channel. fn send_wire(&mut self, wire: &WireMod2) -> Result<(), GarblerError> { self.send_block(&wire.as_block())?; From fb45acb4f1446d2edb7343ed96db8571ed948bc3 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:18:51 +0200 Subject: [PATCH 11/56] first garble test, does not work --- mpc-core/src/protocols/rep3.rs | 2 +- mpc-core/src/protocols/rep3/yao.rs | 88 +++++- mpc-core/src/protocols/rep3/yao/circuits.rs | 301 ++++++++----------- mpc-core/src/protocols/rep3/yao/evaluator.rs | 36 ++- mpc-core/src/protocols/rep3/yao/garbler.rs | 37 ++- tests/tests/mpc/rep3.rs | 73 +++++ 6 files changed, 349 insertions(+), 188 deletions(-) diff --git a/mpc-core/src/protocols/rep3.rs b/mpc-core/src/protocols/rep3.rs index c60bc2168..70433e8fb 100644 --- a/mpc-core/src/protocols/rep3.rs +++ b/mpc-core/src/protocols/rep3.rs @@ -12,7 +12,7 @@ pub mod network; pub mod pointshare; pub mod poly; pub mod rngs; -mod yao; +pub mod yao; use std::marker::PhantomData; diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index bd2e2d4c2..6542a3319 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -1,3 +1,85 @@ -mod circuits; -mod evaluator; -mod garbler; +pub mod circuits; +pub mod evaluator; +pub mod garbler; + +use ark_ff::{PrimeField, Zero}; +use fancy_garbling::BinaryBundle; +use num_bigint::BigUint; + +/// A structure that contains both the garbler and the evaluators +/// wires. This structure simplifies the API of the garbled circuit. +pub struct GCInputs { + pub garbler_wires: BinaryBundle, + pub evaluator_wires: BinaryBundle, +} + +pub struct GCUtils {} + +impl GCUtils { + fn u16_bits_to_field(bits: Vec) -> eyre::Result { + let mut res = BigUint::zero(); + for bit in bits.iter().rev() { + assert!(*bit < 2); + res <<= 1; + res += *bit as u64; + } + + if res >= F::MODULUS.into() { + return Err(eyre::eyre!("Invalid field element")); + } + Ok(F::from(res)) + } + + pub fn bits_to_field(bits: Vec) -> eyre::Result { + let mut res = BigUint::zero(); + for bit in bits.iter().rev() { + res <<= 1; + res += *bit as u64; + } + if res >= F::MODULUS.into() { + return Err(eyre::eyre!("Invalid field element")); + } + Ok(F::from(res)) + } + + 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() { + for _ in 0..64 { + res.push(el & 1 == 1); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, false); + res + } + + fn field_to_bits_as_u16(field: F) -> Vec { + let n_bits = F::MODULUS_BIT_SIZE as usize; + let bigint: BigUint = field.into(); + + Self::biguint_to_bits_as_u16(bigint, n_bits) + } + + fn biguint_to_bits_as_u16(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() { + for _ in 0..64 { + res.push((el & 1) as u16); + el >>= 1; + bits += 1; + if bits == n_bits { + break; + } + } + } + res.resize(n_bits, 0); + res + } +} diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index c27b4216f..1966054dc 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -2,152 +2,141 @@ use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; use num_bigint::BigUint; -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() { - for _ in 0..64 { - res.push(el & 1 == 1); - el >>= 1; - bits += 1; - if bits == n_bits { - break; - } - } +use crate::protocols::rep3::yao::GCUtils; + +pub struct GarbledCircuits {} + +impl GarbledCircuits { + fn full_adder_const( + g: &mut G, + a: &G::Item, + b: bool, + c: &G::Item, + ) -> Result<(G::Item, G::Item), G::Error> { + let (s, c) = if b { + let z1 = g.negate(a)?; + let s = g.xor(&z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(&z1, &z3)?; + let c = g.xor(&z4, a)?; + (s, c) + } else { + let z1 = a; + let s = g.xor(z1, c)?; + let z3 = g.xor(a, c)?; + let z4 = g.and(z1, &z3)?; + let c = g.xor(&z4, a)?; + (s, c) + }; + + Ok((s, c)) + } + + fn half_adder( + g: &mut G, + a: &G::Item, + b: &G::Item, + ) -> Result<(G::Item, G::Item), G::Error> { + let s = g.xor(a, b)?; + let c = g.and(a, b)?; + Ok((s, c)) } - res.resize(n_bits, false); - res -} -fn full_adder_gc_const( - g: &mut G, - a: &G::Item, - b: bool, - c: &G::Item, -) -> Result<(G::Item, G::Item), G::Error> { - let (s, c) = if b { - let z1 = g.negate(a)?; + fn full_adder( + g: &mut G, + a: &G::Item, + b: &G::Item, + c: &G::Item, + ) -> Result<(G::Item, G::Item), G::Error> { + let z1 = g.xor(a, b)?; let s = g.xor(&z1, c)?; let z3 = g.xor(a, c)?; let z4 = g.and(&z1, &z3)?; let c = g.xor(&z4, a)?; - (s, c) - } else { - let z1 = a; - let s = g.xor(z1, c)?; - let z3 = g.xor(a, c)?; - let z4 = g.and(z1, &z3)?; - let c = g.xor(&z4, a)?; - (s, c) - }; - - Ok((s, c)) -} + Ok((s, c)) + } -fn half_adder_gc( - g: &mut G, - a: &G::Item, - b: &G::Item, -) -> Result<(G::Item, G::Item), G::Error> { - let s = g.xor(a, b)?; - let c = g.and(a, b)?; - Ok((s, c)) -} + /// Binary addition. Returns the result and the carry. + fn bin_addition( + g: &mut G, + xs: &BinaryBundle, + ys: &BinaryBundle, + ) -> Result<(BinaryBundle, G::Item), G::Error> { + let xwires = xs.wires(); + let ywires = ys.wires(); + debug_assert_eq!(xwires.len(), ywires.len()); + let mut result = Vec::with_capacity(xwires.len()); + + let (mut s, mut c) = Self::half_adder(g, &xwires[0], &ywires[0])?; + result.push(s); -fn full_adder_gc( - g: &mut G, - a: &G::Item, - b: &G::Item, - c: &G::Item, -) -> Result<(G::Item, G::Item), G::Error> { - let z1 = g.xor(a, b)?; - let s = g.xor(&z1, c)?; - let z3 = g.xor(a, c)?; - let z4 = g.and(&z1, &z3)?; - let c = g.xor(&z4, a)?; - Ok((s, c)) -} + for (x, y) in xwires.iter().zip(ywires.iter()).skip(1) { + let res = Self::full_adder(g, x, y, &c)?; + s = res.0; + c = res.1; + result.push(s); + } -/// Binary addition. Returns the result and the carry. -fn bin_addition_gc( - g: &mut G, - xs: &BinaryBundle, - ys: &BinaryBundle, -) -> Result<(BinaryBundle, G::Item), G::Error> { - let xwires = xs.wires(); - let ywires = ys.wires(); - debug_assert_eq!(xwires.len(), ywires.len()); - let mut result = Vec::with_capacity(xwires.len()); - - let (mut s, mut c) = half_adder_gc(g, &xwires[0], &ywires[0])?; - result.push(s); - - for (x, y) in xwires.iter().zip(ywires.iter()).skip(1) { - let res = full_adder_gc(g, x, y, &c)?; - s = res.0; - c = res.1; - result.push(s); + Ok((BinaryBundle::new(result), c)) } - Ok((BinaryBundle::new(result), c)) -} - -pub(crate) fn adder_mod_p_gc( - g: &mut G, - wires_a: BinaryBundle, - wires_b: BinaryBundle, -) -> Result, G::Error> { - let bitlen = wires_a.size(); - debug_assert_eq!(bitlen, wires_b.size()); - - // First addition - let (added, carry_add) = bin_addition_gc(g, &wires_a, &wires_b)?; - let added_wires = added.wires(); - - // Prepare p for subtraction - let new_bitlen = bitlen + 1; - let p_ = (BigUint::from(1u64) << new_bitlen) - F::MODULUS.into(); - let p_bits = biguint_to_bits(p_, new_bitlen); - - // manual_rca: - let mut subtracted = Vec::with_capacity(bitlen); - // half_adder: - debug_assert!(p_bits[0]); - let s = g.negate(&added_wires[0])?; - subtracted.push(s); - let mut c = added_wires[0].to_owned(); - // full_adders: - for (a, b) in added_wires.iter().zip(p_bits.iter()).skip(1) { - let (s, c_) = full_adder_gc_const(g, a, *b, &c)?; - c = c_; + pub fn adder_mod_p( + g: &mut G, + wires_a: &BinaryBundle, + wires_b: &BinaryBundle, + ) -> Result, G::Error> { + let bitlen = wires_a.size(); + debug_assert_eq!(bitlen, wires_b.size()); + + // First addition + let (added, carry_add) = Self::bin_addition(g, &wires_a, &wires_b)?; + let added_wires = added.wires(); + + // 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); + + // manual_rca: + let mut subtracted = Vec::with_capacity(bitlen); + // half_adder: + debug_assert!(p_bits[0]); + let s = g.negate(&added_wires[0])?; subtracted.push(s); - } - // final_full_adder to get ov bit - let z = if p_bits[bitlen] { - g.negate(&carry_add)? - } else { - carry_add - }; - let ov = g.xor(&z, &c)?; - - // multiplex for result - let mut result = Vec::with_capacity(bitlen); - for (s, a) in subtracted.iter().zip(added.iter()) { - // CMUX - // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though - let xor = g.xor(s, a)?; - let and = g.and(&ov, &xor)?; - let r = g.xor(&and, s)?; - result.push(r); - } + let mut c = added_wires[0].to_owned(); + // full_adders: + for (a, b) in added_wires.iter().zip(p_bits.iter()).skip(1) { + let (s, c_) = Self::full_adder_const(g, a, *b, &c)?; + c = c_; + subtracted.push(s); + } + // final_full_adder to get ov bit + let z = if p_bits[bitlen] { + g.negate(&carry_add)? + } else { + carry_add + }; + let ov = g.xor(&z, &c)?; + + // multiplex for result + let mut result = Vec::with_capacity(bitlen); + for (s, a) in subtracted.iter().zip(added.iter()) { + // CMUX + // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though + let xor = g.xor(s, a)?; + let and = g.and(&ov, &xor)?; + let r = g.xor(&and, s)?; + result.push(r); + } - Ok(BinaryBundle::new(result)) + Ok(BinaryBundle::new(result)) + } } #[cfg(test)] mod test { use super::*; - use ark_ff::Zero; + use crate::protocols::rep3::yao::GCInputs; use fancy_garbling::{Evaluator, Fancy, Garbler, WireMod2}; use rand::{thread_rng, CryptoRng, Rng, SeedableRng}; use rand_chacha::ChaCha12Rng; @@ -159,43 +148,12 @@ mod test { const TESTRUNS: usize = 5; - /// A structure that contains both the garbler and the evaluators - /// wires. This structure simplifies the API of the garbled circuit. - struct GCInputs { - pub garbler_wires: BinaryBundle, - pub evaluator_wires: BinaryBundle, - } - - fn biguint_to_bits_as_u16(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() { - for _ in 0..64 { - res.push((el & 1) as u16); - el >>= 1; - bits += 1; - if bits == n_bits { - break; - } - } - } - res.resize(n_bits, 0); - res - } - - fn field_to_bits_as_u16(field: F) -> Vec { - let n_bits = F::MODULUS_BIT_SIZE as usize; - let bigint: BigUint = field.into(); - - biguint_to_bits_as_u16(bigint, n_bits) - } - // This puts the X_0 values into garbler_wires and X_c values into evaluator_wires fn encode_field( field: F, garbler: &mut Garbler, ) -> GCInputs { - let bits = field_to_bits_as_u16(field); + let bits = GCUtils::field_to_bits_as_u16(field); let mut garbler_wires = Vec::with_capacity(bits.len()); let mut evaluator_wires = Vec::with_capacity(bits.len()); for bit in bits { @@ -209,17 +167,6 @@ mod test { } } - fn bits_to_field(bits: Vec) -> F { - let mut res = BigUint::zero(); - for bit in bits.iter().rev() { - assert!(*bit < 2); - res <<= 1; - res += *bit as u64; - } - assert!(res < F::MODULUS.into()); - F::from(res) - } - fn gc_test() { let mut rng = thread_rng(); @@ -247,8 +194,12 @@ mod test { garbler.send_wire(b).unwrap(); } - let garble_result = - adder_mod_p_gc::<_, F>(&mut garbler, a.garbler_wires, b.garbler_wires).unwrap(); + let garble_result = GarbledCircuits::adder_mod_p::<_, F>( + &mut garbler, + &a.garbler_wires, + &b.garbler_wires, + ) + .unwrap(); // Output garbler.outputs(garble_result.wires()).unwrap(); @@ -275,10 +226,10 @@ mod test { let a = BinaryBundle::new(a); let b = BinaryBundle::new(b); - let eval_result = adder_mod_p_gc::<_, F>(&mut evaluator, a, b).unwrap(); + let eval_result = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &a, &b).unwrap(); let result = evaluator.outputs(eval_result.wires()).unwrap().unwrap(); - let result = bits_to_field::(result); + let result = GCUtils::u16_bits_to_field::(result).unwrap(); assert_eq!(result, is_result); } diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index dd5451f33..51f20a512 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -8,13 +8,13 @@ use fancy_garbling::{ errors::EvaluatorError, hash_wires, util::{output_tweak, tweak2}, - Fancy, WireLabel, WireMod2, + BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; -pub(crate) struct Rep3Evaluator<'a, N: Rep3Network> { +pub struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, current_gate: usize, @@ -23,7 +23,7 @@ pub(crate) struct Rep3Evaluator<'a, N: Rep3Network> { impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { /// Create a new garbler. - pub(crate) fn new(io_context: &'a mut IoContext) -> Self { + pub fn new(io_context: &'a mut IoContext) -> Self { let id = io_context.id; if id != PartyID::ID0 { panic!("Garbler should be PartyID::ID0") @@ -79,7 +79,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } /// Outputs the value to all parties - fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { + pub fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { // Garbler's to evaluator let res = self.output_evaluator(x)?; @@ -148,6 +148,17 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(WireMod2::from_block(block, 2)) } + /// Receive a bundle of wires over the established channel. + pub fn receive_bundle(&mut self, n: usize) -> Result, EvaluatorError> { + let mut wires = Vec::with_capacity(n); + for _ in 0..n { + let wire = WireMod2::from_block(self.receive_block()?, 2); + wires.push(wire); + } + + Ok(BinaryBundle::new(wires)) + } + /// Evaluates an 'and' gate given two inputs wires and two half-gates from the garbler. /// /// Outputs C = A & B @@ -213,3 +224,20 @@ impl<'a, N: Rep3Network> Fancy for Rep3Evaluator<'a, N> { } } } + +impl<'a, N: Rep3Network> FancyBinary for Rep3Evaluator<'a, N> { + /// Negate is a noop for the evaluator + fn negate(&mut self, x: &Self::Item) -> Result { + Ok(*x) + } + + fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Result { + Ok(x.plus(y)) + } + + fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { + let gate0 = self.receive_block()?; + let gate1 = self.receive_block()?; + Ok(self.evaluate_and_gate(a, b, &gate0, &gate1)) + } +} diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index feda98fa3..6782d1870 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -9,18 +9,21 @@ use crate::{ }, RngType, }; +use ark_ff::PrimeField; use fancy_garbling::{ errors::GarblerError, hash_wires, util::{output_tweak, tweak2}, - Fancy, FancyBinary, WireLabel, WireMod2, + BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; use rand::SeedableRng; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; -pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { +use super::{GCInputs, GCUtils}; + +pub struct Rep3Garbler<'a, N: Rep3Network> { io_context: &'a mut IoContext, delta: WireMod2, current_output: usize, @@ -31,7 +34,7 @@ pub(crate) struct Rep3Garbler<'a, N: Rep3Network> { impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Create a new garbler. - pub(crate) fn new(io_context: &'a mut IoContext) -> Self { + pub fn new(io_context: &'a mut IoContext) -> Self { let mut res = Self::new_with_delta(io_context, WireMod2::default()); res.delta = WireMod2::rand_delta(&mut res.rng, 2); res @@ -59,8 +62,24 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } } + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + pub fn encode_field(&mut self, field: F) -> GCInputs { + let bits = GCUtils::field_to_bits_as_u16(field); + let mut garbler_wires = Vec::with_capacity(bits.len()); + let mut evaluator_wires = Vec::with_capacity(bits.len()); + for bit in bits { + let (mine, theirs) = self.encode_wire(bit); + garbler_wires.push(mine); + evaluator_wires.push(theirs); + } + GCInputs { + garbler_wires: BinaryBundle::new(garbler_wires), + evaluator_wires: BinaryBundle::new(evaluator_wires), + } + } + /// Consumes the Garbler and returns the delta. - fn into_delta(self) -> WireMod2 { + pub fn into_delta(self) -> WireMod2 { self.delta } @@ -104,7 +123,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } /// Outputs the value to all parties - fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, GarblerError> { + pub fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, GarblerError> { // Garbler's to evaluator self.output_evaluator(x)?; @@ -171,6 +190,14 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { Ok(()) } + /// Send a bundle of wires over the established channel. + pub fn send_bundle(&mut self, wires: &BinaryBundle) -> Result<(), GarblerError> { + for wire in wires.wires() { + self.send_wire(wire)?; + } + Ok(()) + } + /// Encode a wire, producing the zero wire as well as the encoded value. pub fn encode_wire(&mut self, val: u16) -> (WireMod2, WireMod2) { let zero = WireMod2::rand(&mut self.rng, 2); diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index bfb496929..1ceafeb61 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -1,9 +1,14 @@ mod field_share { use ark_ff::Field; + use ark_ff::PrimeField; use ark_std::{UniformRand, Zero}; use itertools::izip; use mpc_core::protocols::rep3::conversion; use mpc_core::protocols::rep3::id::PartyID; + use mpc_core::protocols::rep3::yao::circuits::GarbledCircuits; + use mpc_core::protocols::rep3::yao::evaluator::Rep3Evaluator; + use mpc_core::protocols::rep3::yao::garbler::Rep3Garbler; + use mpc_core::protocols::rep3::yao::GCUtils; use mpc_core::protocols::rep3::{self, arithmetic, network::IoContext}; use rand::thread_rng; use std::sync::mpsc; @@ -599,6 +604,74 @@ mod field_share { let is_result = rep3::combine_field_element(result1, result2, result3); assert_eq!(is_result, x); } + + #[test] + fn rep3_gc() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let y = ark_bn254::Fr::rand(&mut rng); + let should_result = x + y; + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + let [net1, net2, net3] = test_network.get_party_networks(); + + // Both Garblers + for (net, tx) in izip!([net2, net3], [tx2, tx3]) { + thread::spawn(move || { + let mut ctx = IoContext::init(net).unwrap(); + + let mut garbler = Rep3Garbler::new(&mut ctx); + let x_ = garbler.encode_field(x); + let y_ = garbler.encode_field(y); + + // This is without OT, just a simulation + garbler.send_bundle(&x_.evaluator_wires).unwrap(); + garbler.send_bundle(&y_.evaluator_wires).unwrap(); + + let circuit_output = GarbledCircuits::adder_mod_p::<_, ark_bn254::Fr>( + &mut garbler, + &x_.garbler_wires, + &y_.garbler_wires, + ) + .unwrap(); + + let output = garbler.output_all_parties(circuit_output.wires()).unwrap(); + let add = GCUtils::bits_to_field::(output).unwrap(); + tx.send(add) + }); + } + + // The evaluator (ID0) + thread::spawn(move || { + let mut ctx = IoContext::init(net1).unwrap(); + + let mut evaluator = Rep3Evaluator::new(&mut ctx); + let n_bits = ark_bn254::Fr::MODULUS_BIT_SIZE as usize; + + // This is without OT, just a simulation + let x_ = evaluator.receive_bundle(n_bits).unwrap(); + let y_ = evaluator.receive_bundle(n_bits).unwrap(); + + let circuit_output = + GarbledCircuits::adder_mod_p::<_, ark_bn254::Fr>(&mut evaluator, &x_, &y_).unwrap(); + + let output = evaluator + .output_all_parties(circuit_output.wires()) + .unwrap(); + let add = GCUtils::bits_to_field::(output).unwrap(); + tx1.send(add) + }); + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + assert_eq!(result1, should_result); + assert_eq!(result2, should_result); + assert_eq!(result3, should_result); + } } mod curve_share { From b4dc938fb2ab8f9ea57266687bb1ba1d3dcd6bbc Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:26:36 +0200 Subject: [PATCH 12/56] fix test --- mpc-core/src/protocols/rep3/yao/garbler.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 6782d1870..a7ac64dce 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -108,11 +108,11 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { let blocks = self.read_blocks(x.len())?; let mut result = Vec::with_capacity(x.len()); - for (block, wire) in blocks.into_iter().zip(x.iter()) { - if block == wire.as_block() { - result.push(true); - } else if block == wire.plus(&self.delta).as_block() { + for (block, zero) in blocks.into_iter().zip(x.iter()) { + if block == zero.as_block() { result.push(false); + } else if block == zero.plus(&self.delta).as_block() { + result.push(true); } else { return Err(GarblerError::CommunicationError( "Invalid block received".to_string(), From 43dc0880b55b696386902c76a01515c88d569ae3 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:56:13 +0200 Subject: [PATCH 13/56] add function that allows to generate the same value on all parties in rep3 --- mpc-core/src/protocols/rep3/rngs.rs | 15 +++++++++++++- mpc-core/src/protocols/rep3/yao.rs | 23 +++++++++++++++++++-- mpc-core/src/protocols/rep3/yao/circuits.rs | 1 + mpc-core/src/protocols/rep3/yao/garbler.rs | 1 + 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/mpc-core/src/protocols/rep3/rngs.rs b/mpc-core/src/protocols/rep3/rngs.rs index c711820bb..e4c6b5fc1 100644 --- a/mpc-core/src/protocols/rep3/rngs.rs +++ b/mpc-core/src/protocols/rep3/rngs.rs @@ -2,11 +2,12 @@ //! //! This module contains implementations of rep3 rngs +use super::id::PartyID; use crate::RngType; use ark_ec::CurveGroup; use ark_ff::{One, PrimeField}; use num_bigint::BigUint; -use rand::{Rng, RngCore, SeedableRng}; +use rand::{distributions::Standard, prelude::Distribution, Rng, RngCore, SeedableRng}; use rayon::prelude::*; #[derive(Debug)] @@ -38,6 +39,18 @@ impl Rep3CorrelatedRng { bitcomp2, } } + + /// Generate a value that is equal on all three parties + pub fn generate_shared(&mut self, id: PartyID) -> T + where + Standard: Distribution, + { + match id { + PartyID::ID0 => self.bitcomp1.rng2.gen(), + PartyID::ID1 => self.bitcomp1.rng2.gen(), + PartyID::ID2 => self.bitcomp1.rng1.gen(), + } + } } #[derive(Debug)] diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 6542a3319..231bdacd2 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -2,15 +2,19 @@ pub mod circuits; pub mod evaluator; pub mod garbler; +use super::{ + network::{IoContext, Rep3Network}, + IoResult, Rep3PrimeFieldShare, +}; use ark_ff::{PrimeField, Zero}; -use fancy_garbling::BinaryBundle; +use fancy_garbling::{BinaryBundle, WireMod2}; use num_bigint::BigUint; /// A structure that contains both the garbler and the evaluators -/// wires. This structure simplifies the API of the garbled circuit. pub struct GCInputs { pub garbler_wires: BinaryBundle, pub evaluator_wires: BinaryBundle, + pub delta: F, } pub struct GCUtils {} @@ -83,3 +87,18 @@ impl GCUtils { res } } + +/// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into three yao shares [x_1]^Y, [x_2]^Y, [x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. +pub fn joint_input_arithmetic( + x: Rep3PrimeFieldShare, + delta: Option, + io_context: &mut IoContext, +) -> IoResult<[BinaryBundle; 3]> { + match io_context.id { + crate::protocols::rep3::id::PartyID::ID0 => todo!(), + crate::protocols::rep3::id::PartyID::ID1 => todo!(), + crate::protocols::rep3::id::PartyID::ID2 => todo!(), + } + + todo!() +} diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 1966054dc..53074b81d 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -164,6 +164,7 @@ mod test { GCInputs { garbler_wires: BinaryBundle::new(garbler_wires), evaluator_wires: BinaryBundle::new(evaluator_wires), + delta: garbler.delta(2), } } diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index a7ac64dce..91af944ee 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -75,6 +75,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { GCInputs { garbler_wires: BinaryBundle::new(garbler_wires), evaluator_wires: BinaryBundle::new(evaluator_wires), + delta: self.delta, } } From 1f17737c9a5ddc4ccaf7cb7c3de083c3a3dcf096 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:20:59 +0200 Subject: [PATCH 14/56] progress --- mpc-core/src/protocols/rep3/yao.rs | 89 ++++++++++++++++++++-- mpc-core/src/protocols/rep3/yao/garbler.rs | 18 +---- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 231bdacd2..a8f025b86 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -6,9 +6,13 @@ use super::{ network::{IoContext, Rep3Network}, IoResult, Rep3PrimeFieldShare, }; +use crate::protocols::rep3::id::PartyID; use ark_ff::{PrimeField, Zero}; -use fancy_garbling::{BinaryBundle, WireMod2}; +use fancy_garbling::{BinaryBundle, WireLabel, WireMod2}; +use itertools::Itertools; use num_bigint::BigUint; +use rand::{CryptoRng, Rng}; +use scuttlebutt::Block; /// A structure that contains both the garbler and the evaluators pub struct GCInputs { @@ -86,6 +90,38 @@ impl GCUtils { res.resize(n_bits, 0); res } + + /// Encode a wire, producing the zero wire as well as the encoded value. + pub fn encode_wire( + rng: &mut R, + delta: &WireMod2, + val: u16, + ) -> (WireMod2, WireMod2) { + let zero = WireMod2::rand(rng, 2); + let enc = zero.plus(&delta.cmul(val)); + (zero, enc) + } + + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + pub fn encode_field( + field: F, + rng: &mut R, + delta: WireMod2, + ) -> GCInputs { + let bits = GCUtils::field_to_bits_as_u16(field); + let mut garbler_wires = Vec::with_capacity(bits.len()); + let mut evaluator_wires = Vec::with_capacity(bits.len()); + for bit in bits { + let (mine, theirs) = Self::encode_wire(rng, &delta, bit); + garbler_wires.push(mine); + evaluator_wires.push(theirs); + } + GCInputs { + garbler_wires: BinaryBundle::new(garbler_wires), + evaluator_wires: BinaryBundle::new(evaluator_wires), + delta, + } + } } /// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into three yao shares [x_1]^Y, [x_2]^Y, [x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. @@ -94,11 +130,54 @@ pub fn joint_input_arithmetic( delta: Option, io_context: &mut IoContext, ) -> IoResult<[BinaryBundle; 3]> { - match io_context.id { - crate::protocols::rep3::id::PartyID::ID0 => todo!(), - crate::protocols::rep3::id::PartyID::ID1 => todo!(), - crate::protocols::rep3::id::PartyID::ID2 => todo!(), + let id = io_context.id; + let n_bits = F::MODULUS_BIT_SIZE as usize; + + // x1 is known by both garblers, we can do a shortcut + let mut x1_x = (0..n_bits) + .map(|_| WireMod2::from_block(io_context.rngs.generate_shared::(id), 2)) + .collect_vec(); + + match id { + PartyID::ID0 => {} + PartyID::ID1 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + // Modify x1 + let x1_bits = GCUtils::field_to_bits_as_u16(x.a); + x1_x.iter_mut().zip(x1_bits).for_each(|(x, bit)| { + x.plus_eq(&delta.cmul(bit)); + }); + + // Input x0 + } + PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + // Modify x1 + let x1_bits = GCUtils::field_to_bits_as_u16(x.a); + x1_x.iter_mut().zip(x1_bits).for_each(|(x, bit)| { + x.plus_eq(&delta.cmul(bit)); + }); + + // Input x2 + todo!() + } } + let x1 = BinaryBundle::new(x1_x); + todo!() } diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 91af944ee..1beefac79 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -64,19 +64,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires pub fn encode_field(&mut self, field: F) -> GCInputs { - let bits = GCUtils::field_to_bits_as_u16(field); - let mut garbler_wires = Vec::with_capacity(bits.len()); - let mut evaluator_wires = Vec::with_capacity(bits.len()); - for bit in bits { - let (mine, theirs) = self.encode_wire(bit); - garbler_wires.push(mine); - evaluator_wires.push(theirs); - } - GCInputs { - garbler_wires: BinaryBundle::new(garbler_wires), - evaluator_wires: BinaryBundle::new(evaluator_wires), - delta: self.delta, - } + GCUtils::encode_field(field, &mut self.rng, self.delta) } /// Consumes the Garbler and returns the delta. @@ -201,9 +189,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Encode a wire, producing the zero wire as well as the encoded value. pub fn encode_wire(&mut self, val: u16) -> (WireMod2, WireMod2) { - let zero = WireMod2::rand(&mut self.rng, 2); - let enc = zero.plus(&self.delta.cmul(val)); - (zero, enc) + GCUtils::encode_wire(&mut self.rng, &self.delta, val) } /// Garbles an 'and' gate given two input wires and the delta. From e23249f1f8ce0e2dce9184d915c3ff63f6c13d3b Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:38:35 +0200 Subject: [PATCH 15/56] . --- mpc-core/src/protocols/rep3/yao.rs | 105 ++++++++++++++++--- mpc-core/src/protocols/rep3/yao/evaluator.rs | 16 ++- mpc-core/src/protocols/rep3/yao/garbler.rs | 14 +-- 3 files changed, 102 insertions(+), 33 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index a8f025b86..88b97d3b3 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -6,12 +6,12 @@ use super::{ network::{IoContext, Rep3Network}, IoResult, Rep3PrimeFieldShare, }; -use crate::protocols::rep3::id::PartyID; +use crate::{protocols::rep3::id::PartyID, RngType}; use ark_ff::{PrimeField, Zero}; use fancy_garbling::{BinaryBundle, WireLabel, WireMod2}; use itertools::Itertools; use num_bigint::BigUint; -use rand::{CryptoRng, Rng}; +use rand::{CryptoRng, Rng, SeedableRng}; use scuttlebutt::Block; /// A structure that contains both the garbler and the evaluators @@ -24,6 +24,20 @@ pub struct GCInputs { pub struct GCUtils {} impl GCUtils { + fn receive_block_from(network: &mut N, id: PartyID) -> IoResult { + let data: Vec = network.recv(id)?; + if data.len() != 16 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "To little elements received", + )); + } + let mut v = Block::default(); + v.as_mut().copy_from_slice(&data); + + Ok(v) + } + fn u16_bits_to_field(bits: Vec) -> eyre::Result { let mut res = BigUint::zero(); for bit in bits.iter().rev() { @@ -134,12 +148,29 @@ pub fn joint_input_arithmetic( let n_bits = F::MODULUS_BIT_SIZE as usize; // x1 is known by both garblers, we can do a shortcut - let mut x1_x = (0..n_bits) + let mut x1 = (0..n_bits) .map(|_| WireMod2::from_block(io_context.rngs.generate_shared::(id), 2)) .collect_vec(); - match id { - PartyID::ID0 => {} + let (x0, x2) = match id { + PartyID::ID0 => { + // Receive x0 + let mut x0 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; + x0.push(WireMod2::from_block(block, 2)); + } + let x0 = BinaryBundle::new(x0); + + // Receive x2 + let mut x2 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x2.push(WireMod2::from_block(block, 2)); + } + let x2 = BinaryBundle::new(x2); + (x0, x2) + } PartyID::ID1 => { let delta = match delta { Some(delta) => delta, @@ -151,11 +182,36 @@ pub fn joint_input_arithmetic( // Modify x1 let x1_bits = GCUtils::field_to_bits_as_u16(x.a); - x1_x.iter_mut().zip(x1_bits).for_each(|(x, bit)| { + x1.iter_mut().zip(x1_bits).for_each(|(x, bit)| { x.plus_eq(&delta.cmul(bit)); }); // Input x0 + let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed1()); + let x0 = GCUtils::encode_field(x.b, &mut rng, delta); + + // Send x0 to the other parties + for val in x0.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID2, val.as_block().as_ref())?; + } + for val in x0.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + let x0 = x0.garbler_wires; + + // Receive x2 + let mut x2 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x2.push(WireMod2::from_block(block, 2)); + } + let x2 = BinaryBundle::new(x2); + (x0, x2) } PartyID::ID2 => { let delta = match delta { @@ -167,17 +223,40 @@ pub fn joint_input_arithmetic( }; // Modify x1 - let x1_bits = GCUtils::field_to_bits_as_u16(x.a); - x1_x.iter_mut().zip(x1_bits).for_each(|(x, bit)| { + let x1_bits = GCUtils::field_to_bits_as_u16(x.b); + x1.iter_mut().zip(x1_bits).for_each(|(x, bit)| { x.plus_eq(&delta.cmul(bit)); }); // Input x2 - todo!() - } - } + let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed2()); + let x2 = GCUtils::encode_field(x.a, &mut rng, delta); - let x1 = BinaryBundle::new(x1_x); + // Send x2 to the other parties + for val in x2.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID1, val.as_block().as_ref())?; + } + for val in x2.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + let x2 = x2.garbler_wires; + + // Receive x0 + let mut x0 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; + x0.push(WireMod2::from_block(block, 2)); + } + let x0 = BinaryBundle::new(x0); + (x0, x2) + } + }; + let x1 = BinaryBundle::new(x1); - todo!() + Ok([x0, x1, x2]) } diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 51f20a512..282edf86b 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -14,6 +14,8 @@ use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; +use super::GCUtils; + pub struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, @@ -116,16 +118,10 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { /// Receive a block from a specific party. fn receive_block_from(&mut self, id: PartyID) -> Result { - let data: Vec = self.io_context.network.recv(id)?; - if data.len() != 16 { - return Err(EvaluatorError::CommunicationError( - "Invalid data length received".to_string(), - )); - } - let mut v = Block::default(); - v.as_mut().copy_from_slice(&data); - - Ok(v) + Ok(GCUtils::receive_block_from( + &mut self.io_context.network, + id, + )?) } /// Send a block over the network to the evaluator. diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 1beefac79..f85aa4288 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -153,16 +153,10 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } fn receive_block_from(&mut self, id: PartyID) -> Result { - let data: Vec = self.io_context.network.recv(id)?; - if data.len() != 16 { - return Err(GarblerError::CommunicationError( - "Invalid data length received".to_string(), - )); - } - let mut v = Block::default(); - v.as_mut().copy_from_slice(&data); - - Ok(v) + Ok(GCUtils::receive_block_from( + &mut self.io_context.network, + id, + )?) } /// Read `n` `Block`s from the channel. From 4c23a669017c37357db7167c71a5a5e0c6d3d79f Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:51:20 +0200 Subject: [PATCH 16/56] progress --- mpc-core/src/protocols/rep3/conversion.rs | 42 +++++++-- mpc-core/src/protocols/rep3/yao.rs | 106 ++++++++++++++++++++++ 2 files changed, 142 insertions(+), 6 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 3a5bca4c8..ff2a4b7b7 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -2,14 +2,16 @@ //! //! This module contains conversions between share types -use ark_ff::PrimeField; -use num_bigint::BigUint; - -use crate::protocols::rep3::{id::PartyID, network::Rep3Network}; - use super::{ - arithmetic, detail, network::IoContext, IoResult, Rep3BigUintShare, Rep3PrimeFieldShare, + arithmetic, detail, + id::PartyID, + network::{IoContext, Rep3Network}, + yao::{self, circuits::GarbledCircuits, evaluator::Rep3Evaluator, garbler::Rep3Garbler}, + IoResult, Rep3BigUintShare, Rep3PrimeFieldShare, }; +use ark_ff::PrimeField; +use fancy_garbling::{BinaryBundle, WireMod2}; +use num_bigint::BigUint; /// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. pub fn a2b( @@ -159,3 +161,31 @@ pub fn bit_inject( let e = arithmetic::arithmetic_xor(d, b2, io_context)?; Ok(e) } + +pub fn a2y( + x: Rep3PrimeFieldShare, + delta: Option, + io_context: &mut IoContext, +) -> IoResult> { + let [x01, x2] = yao::joint_input_arithmetic_added(x, delta, io_context)?; + + let converted = match io_context.id { + PartyID::ID0 => { + let mut evaluator = Rep3Evaluator::new(io_context); + GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2)? + } + PartyID::ID1 | PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); + GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2)? + } + }; + + Ok(converted) +} diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 88b97d3b3..5b9cfd5b5 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -260,3 +260,109 @@ pub fn joint_input_arithmetic( Ok([x0, x1, x2]) } + +/// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into two yao shares [x_1]^Y, [x_2 + x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. +pub fn joint_input_arithmetic_added( + x: Rep3PrimeFieldShare, + delta: Option, + io_context: &mut IoContext, +) -> IoResult<[BinaryBundle; 2]> { + let id = io_context.id; + let n_bits = F::MODULUS_BIT_SIZE as usize; + + let (x01, x2) = match id { + PartyID::ID0 => { + // Receive x0 + let mut x01 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; + x01.push(WireMod2::from_block(block, 2)); + } + let x01 = BinaryBundle::new(x01); + + // Receive x2 + let mut x2 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x2.push(WireMod2::from_block(block, 2)); + } + let x2 = BinaryBundle::new(x2); + (x01, x2) + } + PartyID::ID1 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + // Input x01 + let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed1()); + let sum = x.a + x.b; + let x01 = GCUtils::encode_field(sum, &mut rng, delta); + + // Send x01 to the other parties + for val in x01.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID2, val.as_block().as_ref())?; + } + for val in x01.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + let x01 = x01.garbler_wires; + + // Receive x2 + let mut x2 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x2.push(WireMod2::from_block(block, 2)); + } + let x2 = BinaryBundle::new(x2); + (x01, x2) + } + PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + // Input x2 + let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed2()); + let x2 = GCUtils::encode_field(x.a, &mut rng, delta); + + // Send x2 to the other parties + for val in x2.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID1, val.as_block().as_ref())?; + } + for val in x2.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + let x2 = x2.garbler_wires; + + // Receive x01 + let mut x01 = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; + x01.push(WireMod2::from_block(block, 2)); + } + let x01 = BinaryBundle::new(x01); + (x01, x2) + } + }; + + Ok([x01, x2]) +} From b09578aeed533ad42f6e9484d784c5baf4cf0e0b Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:52:59 +0200 Subject: [PATCH 17/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 8 ++++++-- mpc-core/src/protocols/rep3/yao/evaluator.rs | 2 +- mpc-core/src/protocols/rep3/yao/garbler.rs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index ff2a4b7b7..953e966b5 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -172,7 +172,9 @@ pub fn a2y( let converted = match io_context.id { PartyID::ID0 => { let mut evaluator = Rep3Evaluator::new(io_context); - GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2)? + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2)?; + evaluator.receive_hash()?; + res } PartyID::ID1 | PartyID::ID2 => { let delta = match delta { @@ -183,7 +185,9 @@ pub fn a2y( ))?, }; let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); - GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2)? + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2)?; + garbler.send_hash()?; + res } }; diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 282edf86b..9188d76f8 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -95,7 +95,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } // Receive a hash of ID2 (the second garbler) to verify the garbled circuit. - fn receive_hash(&mut self) -> Result<(), EvaluatorError> { + pub fn receive_hash(&mut self) -> Result<(), EvaluatorError> { let data: Vec = self.io_context.network.recv(PartyID::ID2)?; let mut hash = Sha3_256::default(); std::mem::swap(&mut hash, &mut self.hash); diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index f85aa4288..f40a18f69 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -124,7 +124,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } /// As ID2, send a hash of the sended data to the evaluator. - fn send_hash(&mut self) -> Result<(), GarblerError> { + pub fn send_hash(&mut self) -> Result<(), GarblerError> { if self.io_context.id == PartyID::ID2 { let mut hash = Sha3_256::default(); std::mem::swap(&mut hash, &mut self.hash); From ceb16caeabd3018a52eb30c6277894b95b3dac48 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:09:00 +0200 Subject: [PATCH 18/56] a2y untested --- mpc-core/src/protocols/rep3/conversion.rs | 10 +++-- mpc-core/src/protocols/rep3/yao.rs | 9 +++- mpc-core/src/protocols/rep3/yao/circuits.rs | 3 +- mpc-core/src/protocols/rep3/yao/evaluator.rs | 46 ++++++++++++-------- mpc-core/src/protocols/rep3/yao/garbler.rs | 35 ++++++++------- 5 files changed, 63 insertions(+), 40 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 953e966b5..9af0d4e5e 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -6,7 +6,9 @@ use super::{ arithmetic, detail, id::PartyID, network::{IoContext, Rep3Network}, - yao::{self, circuits::GarbledCircuits, evaluator::Rep3Evaluator, garbler::Rep3Garbler}, + yao::{ + self, circuits::GarbledCircuits, evaluator::Rep3Evaluator, garbler::Rep3Garbler, GCUtils, + }, IoResult, Rep3BigUintShare, Rep3PrimeFieldShare, }; use ark_ff::PrimeField; @@ -172,7 +174,8 @@ pub fn a2y( let converted = match io_context.id { PartyID::ID0 => { let mut evaluator = Rep3Evaluator::new(io_context); - let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2)?; + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2); + let res = GCUtils::garbled_circuits_error(res)?; evaluator.receive_hash()?; res } @@ -185,7 +188,8 @@ pub fn a2y( ))?, }; let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); - let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2)?; + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2); + let res = GCUtils::garbled_circuits_error(res)?; garbler.send_hash()?; res } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 5b9cfd5b5..0844aaf27 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -8,7 +8,7 @@ use super::{ }; use crate::{protocols::rep3::id::PartyID, RngType}; use ark_ff::{PrimeField, Zero}; -use fancy_garbling::{BinaryBundle, WireLabel, WireMod2}; +use fancy_garbling::{BinaryBundle, FancyBinary, WireLabel, WireMod2}; use itertools::Itertools; use num_bigint::BigUint; use rand::{CryptoRng, Rng, SeedableRng}; @@ -24,6 +24,13 @@ pub struct GCInputs { pub struct GCUtils {} impl GCUtils { + pub(crate) fn garbled_circuits_error(input: Result) -> IoResult { + input.or(Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Garbled Circuit failed", + ))) + } + fn receive_block_from(network: &mut N, id: PartyID) -> IoResult { let data: Vec = network.recv(id)?; if data.len() != 16 { diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 53074b81d..91cb0a981 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -1,9 +1,8 @@ +use crate::protocols::rep3::yao::GCUtils; use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; use num_bigint::BigUint; -use crate::protocols::rep3::yao::GCUtils; - pub struct GarbledCircuits {} impl GarbledCircuits { diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 9188d76f8..b21f4ee0a 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -1,8 +1,10 @@ // This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs +use super::GCUtils; use crate::protocols::rep3::{ id::PartyID, network::{IoContext, Rep3Network}, + IoResult, }; use fancy_garbling::{ errors::EvaluatorError, @@ -14,8 +16,6 @@ use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; -use super::GCUtils; - pub struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, @@ -54,25 +54,34 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } /// Outputs the values to the evaluator. - fn output_evaluator(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { - let result = self.outputs(x)?; + fn output_evaluator(&mut self, x: &[WireMod2]) -> IoResult> { + let result = self.outputs(x).or(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Output failed", + )))?; match result { Some(outputs) => { let mut res = Vec::with_capacity(outputs.len()); for val in outputs { if val >= 2 { - return Err(EvaluatorError::DecodingFailed); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Value is not a bool", + )); } res.push(val == 1); } Ok(res) } - None => Err(EvaluatorError::DecodingFailed), + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "No output received", + )), } } /// Outputs the values to the garbler. - fn output_garbler(&mut self, x: &[WireMod2]) -> Result<(), EvaluatorError> { + fn output_garbler(&mut self, x: &[WireMod2]) -> IoResult<()> { for val in x { let block = val.as_block(); self.send_block(&block)?; @@ -81,7 +90,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } /// Outputs the value to all parties - pub fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, EvaluatorError> { + pub fn output_all_parties(&mut self, x: &[WireMod2]) -> IoResult> { // Garbler's to evaluator let res = self.output_evaluator(x)?; @@ -95,14 +104,15 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } // Receive a hash of ID2 (the second garbler) to verify the garbled circuit. - pub fn receive_hash(&mut self) -> Result<(), EvaluatorError> { + pub fn receive_hash(&mut self) -> IoResult<()> { let data: Vec = self.io_context.network.recv(PartyID::ID2)?; let mut hash = Sha3_256::default(); std::mem::swap(&mut hash, &mut self.hash); let digest = hash.finalize(); if data != digest.as_slice() { - return Err(EvaluatorError::CommunicationError( - "Inconsistent Garbled Circuits: Hashes do not match!".to_string(), + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Inconsistent Garbled Circuits: Hashes do not match!", )); } @@ -110,14 +120,14 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } /// Send a block over the network to the garblers. - fn send_block(&mut self, block: &Block) -> Result<(), EvaluatorError> { + fn send_block(&mut self, block: &Block) -> IoResult<()> { self.io_context.network.send(PartyID::ID1, block.as_ref())?; self.io_context.network.send(PartyID::ID2, block.as_ref())?; Ok(()) } /// Receive a block from a specific party. - fn receive_block_from(&mut self, id: PartyID) -> Result { + fn receive_block_from(&mut self, id: PartyID) -> IoResult { Ok(GCUtils::receive_block_from( &mut self.io_context.network, id, @@ -125,7 +135,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { } /// Send a block over the network to the evaluator. - fn receive_block(&mut self) -> Result { + fn receive_block(&mut self) -> IoResult { let block = self.receive_block_from(PartyID::ID1)?; self.hash.update(block.as_ref()); // "Receive" from ID2 @@ -134,18 +144,18 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { /// Read `n` `Block`s from the channel. #[inline(always)] - fn read_blocks(&mut self, n: usize) -> Result, EvaluatorError> { + fn read_blocks(&mut self, n: usize) -> IoResult> { (0..n).map(|_| self.receive_block()).collect() } /// Read a Wire from the reader. - pub fn read_wire(&mut self) -> Result { + pub fn read_wire(&mut self) -> IoResult { let block = self.receive_block()?; Ok(WireMod2::from_block(block, 2)) } /// Receive a bundle of wires over the established channel. - pub fn receive_bundle(&mut self, n: usize) -> Result, EvaluatorError> { + pub fn receive_bundle(&mut self, n: usize) -> IoResult> { let mut wires = Vec::with_capacity(n); for _ in 0..n { let wire = WireMod2::from_block(self.receive_block()?, 2); @@ -193,7 +203,7 @@ impl<'a, N: Rep3Network> Fancy for Rep3Evaluator<'a, N> { type Error = EvaluatorError; fn constant(&mut self, _: u16, _q: u16) -> Result { - self.read_wire() + Ok(self.read_wire()?) } fn output(&mut self, x: &WireMod2) -> Result, EvaluatorError> { diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index f40a18f69..952d636bd 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -1,15 +1,16 @@ // This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs -use core::panic; - +use super::{GCInputs, GCUtils}; use crate::{ protocols::rep3::{ id::PartyID, network::{IoContext, Rep3Network}, + IoResult, }, RngType, }; use ark_ff::PrimeField; +use core::panic; use fancy_garbling::{ errors::GarblerError, hash_wires, @@ -21,8 +22,6 @@ use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; -use super::{GCInputs, GCUtils}; - pub struct Rep3Garbler<'a, N: Rep3Network> { io_context: &'a mut IoContext, delta: WireMod2, @@ -87,13 +86,16 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } /// Outputs the values to the evaluator. - fn output_evaluator(&mut self, x: &[WireMod2]) -> Result<(), GarblerError> { - self.outputs(x)?; + fn output_evaluator(&mut self, x: &[WireMod2]) -> IoResult<()> { + self.outputs(x).or(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Output failed", + )))?; Ok(()) } /// Outputs the values to the garbler. - fn output_garbler(&mut self, x: &[WireMod2]) -> Result, GarblerError> { + fn output_garbler(&mut self, x: &[WireMod2]) -> IoResult> { let blocks = self.read_blocks(x.len())?; let mut result = Vec::with_capacity(x.len()); @@ -103,8 +105,9 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } else if block == zero.plus(&self.delta).as_block() { result.push(true); } else { - return Err(GarblerError::CommunicationError( - "Invalid block received".to_string(), + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid block received", )); } } @@ -112,7 +115,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } /// Outputs the value to all parties - pub fn output_all_parties(&mut self, x: &[WireMod2]) -> Result, GarblerError> { + pub fn output_all_parties(&mut self, x: &[WireMod2]) -> IoResult> { // Garbler's to evaluator self.output_evaluator(x)?; @@ -124,7 +127,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } /// As ID2, send a hash of the sended data to the evaluator. - pub fn send_hash(&mut self) -> Result<(), GarblerError> { + pub fn send_hash(&mut self) -> IoResult<()> { if self.io_context.id == PartyID::ID2 { let mut hash = Sha3_256::default(); std::mem::swap(&mut hash, &mut self.hash); @@ -137,7 +140,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } /// Send a block over the network to the evaluator. - fn send_block(&mut self, block: &Block) -> Result<(), GarblerError> { + fn send_block(&mut self, block: &Block) -> IoResult<()> { match self.io_context.id { PartyID::ID0 => { panic!("Garbler should not be PartyID::ID0"); @@ -152,7 +155,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { Ok(()) } - fn receive_block_from(&mut self, id: PartyID) -> Result { + fn receive_block_from(&mut self, id: PartyID) -> IoResult { Ok(GCUtils::receive_block_from( &mut self.io_context.network, id, @@ -161,20 +164,20 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Read `n` `Block`s from the channel. #[inline(always)] - fn read_blocks(&mut self, n: usize) -> Result, GarblerError> { + fn read_blocks(&mut self, n: usize) -> IoResult> { (0..n) .map(|_| self.receive_block_from(PartyID::ID0)) .collect() } /// Send a wire over the established channel. - fn send_wire(&mut self, wire: &WireMod2) -> Result<(), GarblerError> { + fn send_wire(&mut self, wire: &WireMod2) -> IoResult<()> { self.send_block(&wire.as_block())?; Ok(()) } /// Send a bundle of wires over the established channel. - pub fn send_bundle(&mut self, wires: &BinaryBundle) -> Result<(), GarblerError> { + pub fn send_bundle(&mut self, wires: &BinaryBundle) -> IoResult<()> { for wire in wires.wires() { self.send_wire(wire)?; } From e0fc0091b57912b15d4cc82f9377d692b830c10b Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:12:50 +0200 Subject: [PATCH 19/56] . --- mpc-core/src/protocols/rep3/yao/garbler.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 952d636bd..a6f61e399 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -17,7 +17,7 @@ use fancy_garbling::{ util::{output_tweak, tweak2}, BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; -use rand::SeedableRng; +use rand::{CryptoRng, Rng, SeedableRng}; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; @@ -35,10 +35,14 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Create a new garbler. pub fn new(io_context: &'a mut IoContext) -> Self { let mut res = Self::new_with_delta(io_context, WireMod2::default()); - res.delta = WireMod2::rand_delta(&mut res.rng, 2); + res.delta = Self::random_delta(&mut res.rng); res } + pub fn random_delta(rng: &mut R) -> WireMod2 { + WireMod2::rand_delta(rng, 2) + } + /// Create a new garbler with existing delta. pub(crate) fn new_with_delta(io_context: &'a mut IoContext, delta: WireMod2) -> Self { let id = io_context.id; From 0ed7aeeb9d49199fea6b834d94c706f3b3a9fcb6 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:40:06 +0200 Subject: [PATCH 20/56] a2y works --- mpc-core/src/protocols/rep3/rngs.rs | 22 ++++++++++ mpc-core/src/protocols/rep3/yao/garbler.rs | 16 ++----- tests/tests/mpc/rep3.rs | 49 ++++++++++++++++++++++ 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/mpc-core/src/protocols/rep3/rngs.rs b/mpc-core/src/protocols/rep3/rngs.rs index e4c6b5fc1..b7ee7c83a 100644 --- a/mpc-core/src/protocols/rep3/rngs.rs +++ b/mpc-core/src/protocols/rep3/rngs.rs @@ -6,6 +6,7 @@ use super::id::PartyID; use crate::RngType; use ark_ec::CurveGroup; use ark_ff::{One, PrimeField}; +use fancy_garbling::{WireLabel, WireMod2}; use num_bigint::BigUint; use rand::{distributions::Standard, prelude::Distribution, Rng, RngCore, SeedableRng}; use rayon::prelude::*; @@ -51,6 +52,27 @@ impl Rep3CorrelatedRng { PartyID::ID2 => self.bitcomp1.rng1.gen(), } } + + /// Generate a value that is equal on all two garbler parties + pub fn generate_garbler_randomness(&mut self, id: PartyID) -> T + where + Standard: Distribution, + { + match id { + PartyID::ID0 => panic!("Garbler should not be PartyID::ID0"), + PartyID::ID1 => self.rand.rng1.gen(), + PartyID::ID2 => self.rand.rng2.gen(), + } + } + + /// Generate a random delta that is equal for the two garblers + pub fn generate_random_garbler_delta(&mut self, id: PartyID) -> Option { + match id { + PartyID::ID0 => None, + PartyID::ID1 => Some(WireMod2::rand_delta(&mut self.rand.rng1, 2)), + PartyID::ID2 => Some(WireMod2::rand_delta(&mut self.rand.rng2, 2)), + } + } } #[derive(Debug)] diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index a6f61e399..105affa52 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -35,24 +35,14 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Create a new garbler. pub fn new(io_context: &'a mut IoContext) -> Self { let mut res = Self::new_with_delta(io_context, WireMod2::default()); - res.delta = Self::random_delta(&mut res.rng); + res.delta = WireMod2::rand_delta(&mut res.rng, 2); res } - pub fn random_delta(rng: &mut R) -> WireMod2 { - WireMod2::rand_delta(rng, 2) - } - /// Create a new garbler with existing delta. - pub(crate) fn new_with_delta(io_context: &'a mut IoContext, delta: WireMod2) -> Self { + pub fn new_with_delta(io_context: &'a mut IoContext, delta: WireMod2) -> Self { let id = io_context.id; - let seed = match id { - PartyID::ID0 => { - panic!("Garbler should not be PartyID::ID0") - } - PartyID::ID1 => io_context.rngs.rand.random_seed1(), - PartyID::ID2 => io_context.rngs.rand.random_seed2(), - }; + let seed = io_context.rngs.generate_garbler_randomness(id); let rng = RngType::from_seed(seed); Self { diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 1ceafeb61..16834c305 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -545,6 +545,7 @@ mod field_share { let is_result_f: ark_bn254::Fr = is_result.into(); assert_eq!(is_result_f, x); } + #[test] fn rep3_a2b() { let test_network = Rep3TestNetwork::default(); @@ -672,6 +673,54 @@ mod field_share { assert_eq!(result2, should_result); assert_eq!(result3, should_result); } + + #[test] + fn rep3_a2y() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_field_element(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + // Both Garblers + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let id = rep3.network.id; + let delta = rep3.rngs.generate_random_garbler_delta(id); + + let converted = conversion::a2y(x, delta, &mut rep3).unwrap(); + + let output = match id { + PartyID::ID0 => { + let mut evaluator = Rep3Evaluator::new(&mut rep3); + evaluator.output_all_parties(converted.wires()).unwrap() + } + PartyID::ID1 | PartyID::ID2 => { + let mut garbler = Rep3Garbler::new_with_delta(&mut rep3, delta.unwrap()); + garbler.output_all_parties(converted.wires()).unwrap() + } + }; + + tx.send(GCUtils::bits_to_field::(output).unwrap()) + .unwrap(); + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + assert_eq!(result1, x); + assert_eq!(result2, x); + assert_eq!(result3, x); + } } mod curve_share { From 7db3efe8f2f4105fb342bc04c654372a0035f6ed Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:41:59 +0200 Subject: [PATCH 21/56] . --- mpc-core/src/protocols/rep3/yao.rs | 1 + mpc-core/src/protocols/rep3/yao/evaluator.rs | 7 ++----- mpc-core/src/protocols/rep3/yao/garbler.rs | 7 ++----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 0844aaf27..a99ab883b 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -45,6 +45,7 @@ impl GCUtils { Ok(v) } + #[cfg(test)] fn u16_bits_to_field(bits: Vec) -> eyre::Result { let mut res = BigUint::zero(); for bit in bits.iter().rev() { diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index b21f4ee0a..6cbc0ddf7 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -103,7 +103,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(res) } - // Receive a hash of ID2 (the second garbler) to verify the garbled circuit. + /// Receive a hash of ID2 (the second garbler) to verify the garbled circuit. pub fn receive_hash(&mut self) -> IoResult<()> { let data: Vec = self.io_context.network.recv(PartyID::ID2)?; let mut hash = Sha3_256::default(); @@ -128,10 +128,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { /// Receive a block from a specific party. fn receive_block_from(&mut self, id: PartyID) -> IoResult { - Ok(GCUtils::receive_block_from( - &mut self.io_context.network, - id, - )?) + GCUtils::receive_block_from(&mut self.io_context.network, id) } /// Send a block over the network to the evaluator. diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 105affa52..cba72a4b8 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -17,7 +17,7 @@ use fancy_garbling::{ util::{output_tweak, tweak2}, BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; -use rand::{CryptoRng, Rng, SeedableRng}; +use rand::SeedableRng; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; @@ -150,10 +150,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } fn receive_block_from(&mut self, id: PartyID) -> IoResult { - Ok(GCUtils::receive_block_from( - &mut self.io_context.network, - id, - )?) + GCUtils::receive_block_from(&mut self.io_context.network, id) } /// Read `n` `Block`s from the channel. From b09d3507acd44fbcf709896841cb57525dc52624 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:16:12 +0200 Subject: [PATCH 22/56] progress --- mpc-core/src/protocols/rep3/conversion.rs | 70 ++++++++++++++++++++- mpc-core/src/protocols/rep3/rngs.rs | 10 --- mpc-core/src/protocols/rep3/yao.rs | 74 ++++++++++++++++++++--- tests/tests/mpc/rep3.rs | 3 +- 4 files changed, 134 insertions(+), 23 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 9af0d4e5e..5783aaf7a 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -2,6 +2,8 @@ //! //! This module contains conversions between share types +use crate::protocols::rep3::yao::input_field_party2; + use super::{ arithmetic, detail, id::PartyID, @@ -14,6 +16,7 @@ use super::{ use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, WireMod2}; use num_bigint::BigUint; +use rand::{CryptoRng, Rng}; /// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. pub fn a2b( @@ -164,12 +167,13 @@ pub fn bit_inject( Ok(e) } -pub fn a2y( +pub fn a2y( x: Rep3PrimeFieldShare, delta: Option, io_context: &mut IoContext, + rng: &mut R, ) -> IoResult> { - let [x01, x2] = yao::joint_input_arithmetic_added(x, delta, io_context)?; + let [x01, x2] = yao::joint_input_arithmetic_added(x, delta, io_context, rng)?; let converted = match io_context.id { PartyID::ID0 => { @@ -197,3 +201,65 @@ pub fn a2y( Ok(converted) } + +pub fn y2a( + x: BinaryBundle, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let mut res = Rep3PrimeFieldShare::zero_share(); + + match io_context.id { + PartyID::ID0 => { + let k3 = io_context.rngs.bitcomp2.random_fes_3keys::(); + res.b = (k3.0 + k3.1 + k3.2).neg(); + let x23 = input_field_party2::(None, None, io_context, rng)?; + + let mut evaluator = Rep3Evaluator::new(io_context); + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x, &x23); + let res = GCUtils::garbled_circuits_error(res)?; + } + PartyID::ID1 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + let k2 = io_context.rngs.bitcomp1.random_fes_3keys::(); + res.a = (k2.0 + k2.1 + k2.2).neg(); + let x23 = input_field_party2::(None, None, io_context, rng)?; + + let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); + let res = GCUtils::garbled_circuits_error(res)?; + } + PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + let k2 = io_context.rngs.bitcomp1.random_fes_3keys::(); + let k3 = io_context.rngs.bitcomp2.random_fes_3keys::(); + let k2_comp = k2.0 + k2.1 + k2.2; + let k3_comp = k3.0 + k3.1 + k3.2; + let x23 = Some(k2_comp + k3_comp); + res.a = k3_comp.neg(); + res.b = k2_comp.neg(); + let x23 = input_field_party2(x23, Some(delta), io_context, rng)?; + + let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); + let res = GCUtils::garbled_circuits_error(res)?; + } + }; + + todo!() +} diff --git a/mpc-core/src/protocols/rep3/rngs.rs b/mpc-core/src/protocols/rep3/rngs.rs index b7ee7c83a..ee64576a5 100644 --- a/mpc-core/src/protocols/rep3/rngs.rs +++ b/mpc-core/src/protocols/rep3/rngs.rs @@ -159,16 +159,6 @@ impl Rep3Rand { let seed2 = self.rng2.gen(); (seed1, seed2) } - - /// Generate a seed from rng1 - pub fn random_seed1(&mut self) -> [u8; crate::SEED_SIZE] { - self.rng1.gen() - } - - /// Generate a seed from rng2 - pub fn random_seed2(&mut self) -> [u8; crate::SEED_SIZE] { - self.rng2.gen() - } } /// This struct is responsible for creating random shares for the Binary to Arithmetic conversion. The approach is the following: for a final sharing x = x1 + x2 + x3, we want to have random values x2, x3 and subtract these from the original value x using a binary circuit to get the share x1. Hence, we need to sample random x2 and x3 and share them amongst the parties. One RandBitComp struct is responsible for either sampling x2 or x3. For sampling x2, parties 1 and 2 will get x2 in plain (since this is the final share of x), so they need to have a PRF key from all parties. party 3, however, will not get x2 in plain and must thus only be able to sample its shares of x2, requiring two PRF keys. diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index a99ab883b..58df6afbb 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -147,10 +147,11 @@ impl GCUtils { } /// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into three yao shares [x_1]^Y, [x_2]^Y, [x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. -pub fn joint_input_arithmetic( +pub fn joint_input_arithmetic( x: Rep3PrimeFieldShare, delta: Option, io_context: &mut IoContext, + rng: &mut R, ) -> IoResult<[BinaryBundle; 3]> { let id = io_context.id; let n_bits = F::MODULUS_BIT_SIZE as usize; @@ -195,8 +196,7 @@ pub fn joint_input_arithmetic( }); // Input x0 - let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed1()); - let x0 = GCUtils::encode_field(x.b, &mut rng, delta); + let x0 = GCUtils::encode_field(x.b, rng, delta); // Send x0 to the other parties for val in x0.garbler_wires.iter() { @@ -237,8 +237,7 @@ pub fn joint_input_arithmetic( }); // Input x2 - let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed2()); - let x2 = GCUtils::encode_field(x.a, &mut rng, delta); + let x2 = GCUtils::encode_field(x.a, rng, delta); // Send x2 to the other parties for val in x2.garbler_wires.iter() { @@ -270,10 +269,11 @@ pub fn joint_input_arithmetic( } /// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into two yao shares [x_1]^Y, [x_2 + x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. -pub fn joint_input_arithmetic_added( +pub fn joint_input_arithmetic_added( x: Rep3PrimeFieldShare, delta: Option, io_context: &mut IoContext, + rng: &mut R, ) -> IoResult<[BinaryBundle; 2]> { let id = io_context.id; let n_bits = F::MODULUS_BIT_SIZE as usize; @@ -307,9 +307,8 @@ pub fn joint_input_arithmetic_added( }; // Input x01 - let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed1()); let sum = x.a + x.b; - let x01 = GCUtils::encode_field(sum, &mut rng, delta); + let x01 = GCUtils::encode_field(sum, rng, delta); // Send x01 to the other parties for val in x01.garbler_wires.iter() { @@ -344,8 +343,7 @@ pub fn joint_input_arithmetic_added( }; // Input x2 - let mut rng = RngType::from_seed(io_context.rngs.rand.random_seed2()); - let x2 = GCUtils::encode_field(x.a, &mut rng, delta); + let x2 = GCUtils::encode_field(x.a, rng, delta); // Send x2 to the other parties for val in x2.garbler_wires.iter() { @@ -374,3 +372,59 @@ pub fn joint_input_arithmetic_added( Ok([x01, x2]) } + +pub fn input_field_party2( + x: Option, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let id = io_context.id; + let n_bits = F::MODULUS_BIT_SIZE as usize; + + let x = match id { + PartyID::ID0 | PartyID::ID1 => { + // Receive x + let mut x = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x.push(WireMod2::from_block(block, 2)); + } + BinaryBundle::new(x) + } + PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + let x = match x { + Some(x) => x, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No input provided", + ))?, + }; + + let x = GCUtils::encode_field(x, rng, delta); + + // Send x to the other parties + for val in x.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID2, val.as_block().as_ref())?; + } + for val in x.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + x.garbler_wires + } + }; + Ok(x) +} diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 16834c305..bf9289d58 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -696,7 +696,8 @@ mod field_share { let id = rep3.network.id; let delta = rep3.rngs.generate_random_garbler_delta(id); - let converted = conversion::a2y(x, delta, &mut rep3).unwrap(); + let mut rng = thread_rng(); + let converted = conversion::a2y(x, delta, &mut rep3, &mut rng).unwrap(); let output = match id { PartyID::ID0 => { From d50ef568049f8b5e4222bd89c383f1b520ea73c4 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:27:37 +0200 Subject: [PATCH 23/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 40 ++++++++++++++------ mpc-core/src/protocols/rep3/yao.rs | 22 +++++++---- mpc-core/src/protocols/rep3/yao/evaluator.rs | 23 +++++++++++ mpc-core/src/protocols/rep3/yao/garbler.rs | 16 ++++++++ 4 files changed, 82 insertions(+), 19 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 5783aaf7a..bec0ca005 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -2,7 +2,7 @@ //! //! This module contains conversions between share types -use crate::protocols::rep3::yao::input_field_party2; +use crate::protocols::rep3::yao::input_field_id2; use super::{ arithmetic, detail, @@ -214,11 +214,13 @@ pub fn y2a( PartyID::ID0 => { let k3 = io_context.rngs.bitcomp2.random_fes_3keys::(); res.b = (k3.0 + k3.1 + k3.2).neg(); - let x23 = input_field_party2::(None, None, io_context, rng)?; + let x23 = input_field_id2::(None, None, io_context, rng)?; let mut evaluator = Rep3Evaluator::new(io_context); - let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x, &x23); - let res = GCUtils::garbled_circuits_error(res)?; + let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x, &x23); + let x1 = GCUtils::garbled_circuits_error(x1)?; + let x1 = evaluator.output_to_id0_and_id1(x1.wires())?; + res.a = GCUtils::bits_to_field(x1)?; } PartyID::ID1 => { let delta = match delta { @@ -231,11 +233,20 @@ pub fn y2a( let k2 = io_context.rngs.bitcomp1.random_fes_3keys::(); res.a = (k2.0 + k2.1 + k2.2).neg(); - let x23 = input_field_party2::(None, None, io_context, rng)?; + let x23 = input_field_id2::(None, None, io_context, rng)?; let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); - let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); - let res = GCUtils::garbled_circuits_error(res)?; + let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); + let x1 = 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", + ))?, + }; + res.b = GCUtils::bits_to_field(x1)?; } PartyID::ID2 => { let delta = match delta { @@ -253,13 +264,20 @@ pub fn y2a( let x23 = Some(k2_comp + k3_comp); res.a = k3_comp.neg(); res.b = k2_comp.neg(); - let x23 = input_field_party2(x23, Some(delta), io_context, rng)?; + let x23 = input_field_id2(x23, Some(delta), io_context, rng)?; let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); - let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); - let res = GCUtils::garbled_circuits_error(res)?; + let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); + let x1 = 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", + ))?; + } } }; - todo!() + Ok(res) } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 58df6afbb..7d8d023b3 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -6,12 +6,12 @@ use super::{ network::{IoContext, Rep3Network}, IoResult, Rep3PrimeFieldShare, }; -use crate::{protocols::rep3::id::PartyID, RngType}; +use crate::protocols::rep3::id::PartyID; use ark_ff::{PrimeField, Zero}; -use fancy_garbling::{BinaryBundle, FancyBinary, WireLabel, WireMod2}; +use fancy_garbling::{BinaryBundle, WireLabel, WireMod2}; use itertools::Itertools; use num_bigint::BigUint; -use rand::{CryptoRng, Rng, SeedableRng}; +use rand::{CryptoRng, Rng}; use scuttlebutt::Block; /// A structure that contains both the garbler and the evaluators @@ -46,7 +46,7 @@ impl GCUtils { } #[cfg(test)] - fn u16_bits_to_field(bits: Vec) -> eyre::Result { + fn u16_bits_to_field(bits: Vec) -> IoResult { let mut res = BigUint::zero(); for bit in bits.iter().rev() { assert!(*bit < 2); @@ -55,19 +55,25 @@ impl GCUtils { } if res >= F::MODULUS.into() { - return Err(eyre::eyre!("Invalid field element")); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid field element", + )); } Ok(F::from(res)) } - pub fn bits_to_field(bits: Vec) -> eyre::Result { + pub fn bits_to_field(bits: Vec) -> IoResult { let mut res = BigUint::zero(); for bit in bits.iter().rev() { res <<= 1; res += *bit as u64; } if res >= F::MODULUS.into() { - return Err(eyre::eyre!("Invalid field element")); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid field element", + )); } Ok(F::from(res)) } @@ -373,7 +379,7 @@ pub fn joint_input_arithmetic_added( +pub fn input_field_id2( x: Option, delta: Option, io_context: &mut IoContext, diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 6cbc0ddf7..c33372c31 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -89,6 +89,15 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(()) } + /// Outputs the values to the garbler with id1. + fn output_garbler_id1(&mut self, x: &[WireMod2]) -> IoResult<()> { + for val in x { + let block = val.as_block(); + self.io_context.network.send(PartyID::ID1, block.as_ref())?; + } + Ok(()) + } + /// Outputs the value to all parties pub fn output_all_parties(&mut self, x: &[WireMod2]) -> IoResult> { // Garbler's to evaluator @@ -103,6 +112,20 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { Ok(res) } + /// Outputs the value to parties ID0 and ID1 + pub fn output_to_id0_and_id1(&mut self, x: &[WireMod2]) -> IoResult> { + // Garbler's to evaluator + let res = self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before releasing the result + self.receive_hash()?; + + // Evaluator to garbler + self.output_garbler_id1(x)?; + + Ok(res) + } + /// Receive a hash of ID2 (the second garbler) to verify the garbled circuit. pub fn receive_hash(&mut self) -> IoResult<()> { let data: Vec = self.io_context.network.recv(PartyID::ID2)?; diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index cba72a4b8..41bff8631 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -120,6 +120,22 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { self.output_garbler(x) } + /// Outputs the value to parties ID0 and ID1 + pub fn output_to_id0_and_id1(&mut self, x: &[WireMod2]) -> IoResult>> { + // Garbler's to evaluator + self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before receiving the result + self.send_hash()?; + + // Evaluator to garbler + if self.io_context.id == PartyID::ID1 { + Ok(Some(self.output_garbler(x)?)) + } else { + Ok(None) + } + } + /// As ID2, send a hash of the sended data to the evaluator. pub fn send_hash(&mut self) -> IoResult<()> { if self.io_context.id == PartyID::ID2 { From 36a0cdf212129be6c6898f5783e4a6e8ece9c604 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:37:50 +0200 Subject: [PATCH 24/56] y2a test not working --- mpc-core/src/protocols/rep3/rngs.rs | 6 ++-- mpc-core/src/protocols/rep3/yao.rs | 5 +++ mpc-core/src/protocols/rep3/yao/garbler.rs | 4 +-- tests/tests/mpc/rep3.rs | 40 +++++++++++++++++++++- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/mpc-core/src/protocols/rep3/rngs.rs b/mpc-core/src/protocols/rep3/rngs.rs index ee64576a5..46ff31ccc 100644 --- a/mpc-core/src/protocols/rep3/rngs.rs +++ b/mpc-core/src/protocols/rep3/rngs.rs @@ -2,7 +2,7 @@ //! //! This module contains implementations of rep3 rngs -use super::id::PartyID; +use super::{id::PartyID, yao::GCUtils}; use crate::RngType; use ark_ec::CurveGroup; use ark_ff::{One, PrimeField}; @@ -69,8 +69,8 @@ impl Rep3CorrelatedRng { pub fn generate_random_garbler_delta(&mut self, id: PartyID) -> Option { match id { PartyID::ID0 => None, - PartyID::ID1 => Some(WireMod2::rand_delta(&mut self.rand.rng1, 2)), - PartyID::ID2 => Some(WireMod2::rand_delta(&mut self.rand.rng2, 2)), + PartyID::ID1 => Some(GCUtils::random_delta(&mut self.rand.rng1)), + PartyID::ID2 => Some(GCUtils::random_delta(&mut self.rand.rng2)), } } } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 7d8d023b3..7f0eb004a 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -45,6 +45,11 @@ impl GCUtils { Ok(v) } + /// Samples a random delta + pub fn random_delta(rng: &mut R) -> WireMod2 { + WireMod2::rand_delta(rng, 2) + } + #[cfg(test)] fn u16_bits_to_field(bits: Vec) -> IoResult { let mut res = BigUint::zero(); diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 41bff8631..b8a38bea3 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -17,7 +17,7 @@ use fancy_garbling::{ util::{output_tweak, tweak2}, BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; -use rand::SeedableRng; +use rand::{CryptoRng, Rng, SeedableRng}; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; @@ -35,7 +35,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Create a new garbler. pub fn new(io_context: &'a mut IoContext) -> Self { let mut res = Self::new_with_delta(io_context, WireMod2::default()); - res.delta = WireMod2::rand_delta(&mut res.rng, 2); + res.delta = GCUtils::random_delta(&mut res.rng); res } diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index bf9289d58..aa517bf66 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -685,7 +685,6 @@ mod field_share { let (tx2, rx2) = mpsc::channel(); let (tx3, rx3) = mpsc::channel(); - // Both Garblers for (net, tx, x) in izip!( test_network.get_party_networks().into_iter(), [tx1, tx2, tx3], @@ -722,6 +721,45 @@ mod field_share { assert_eq!(result2, x); assert_eq!(result3, x); } + + #[test] + fn rep3_y2a() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let delta = GCUtils::random_delta(&mut rng); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = GCUtils::encode_field(x, &mut rng, delta); + let x_shares = [ + x_shares.evaluator_wires, + x_shares.garbler_wires.to_owned(), + x_shares.garbler_wires, + ]; + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let mut rng = thread_rng(); + let converted = + conversion::y2a::(x, Some(delta), &mut rep3, &mut rng) + .unwrap(); + tx.send(converted).unwrap(); + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_element(result1, result2, result3); + assert_eq!(is_result, x); + } } mod curve_share { From e14f857d2ee264947219138e3904bd926a265493 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:41:49 +0200 Subject: [PATCH 25/56] fix y2a --- mpc-core/src/protocols/rep3/yao.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 7f0eb004a..5ef797679 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -426,7 +426,7 @@ pub fn input_field_id2( for val in x.garbler_wires.iter() { io_context .network - .send(PartyID::ID2, val.as_block().as_ref())?; + .send(PartyID::ID1, val.as_block().as_ref())?; } for val in x.evaluator_wires.iter() { io_context From e43828bc0fa1252d19906810a21e5a681b1ac9a7 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:49:59 +0200 Subject: [PATCH 26/56] . --- mpc-core/src/protocols/rep3/yao.rs | 133 ++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 5ef797679..1d834ea59 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -4,7 +4,7 @@ pub mod garbler; use super::{ network::{IoContext, Rep3Network}, - IoResult, Rep3PrimeFieldShare, + IoResult, Rep3BigUintShare, Rep3PrimeFieldShare, }; use crate::protocols::rep3::id::PartyID; use ark_ff::{PrimeField, Zero}; @@ -136,12 +136,11 @@ impl GCUtils { } /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires - pub fn encode_field( - field: F, + fn encode_bits( + bits: Vec, rng: &mut R, delta: WireMod2, ) -> GCInputs { - let bits = GCUtils::field_to_bits_as_u16(field); let mut garbler_wires = Vec::with_capacity(bits.len()); let mut evaluator_wires = Vec::with_capacity(bits.len()); for bit in bits { @@ -155,6 +154,27 @@ impl GCUtils { delta, } } + + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + pub fn encode_bigint( + bigint: BigUint, + n_bits: usize, + rng: &mut R, + delta: WireMod2, + ) -> GCInputs { + let bits = Self::biguint_to_bits_as_u16(bigint, n_bits); + Self::encode_bits(bits, rng, delta) + } + + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + pub fn encode_field( + field: F, + rng: &mut R, + delta: WireMod2, + ) -> GCInputs { + let bits = Self::field_to_bits_as_u16(field); + Self::encode_bits(bits, rng, delta) + } } /// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into three yao shares [x_1]^Y, [x_2]^Y, [x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. @@ -384,6 +404,111 @@ pub fn joint_input_arithmetic_added( + x: Rep3BigUintShare, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, + bitlen: usize, +) -> IoResult<[BinaryBundle; 2]> { + let id = io_context.id; + + let (x01, x2) = match id { + PartyID::ID0 => { + // Receive x0 + let mut x01 = Vec::with_capacity(bitlen); + for _ in 0..bitlen { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; + x01.push(WireMod2::from_block(block, 2)); + } + let x01 = BinaryBundle::new(x01); + + // Receive x2 + let mut x2 = Vec::with_capacity(bitlen); + for _ in 0..bitlen { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x2.push(WireMod2::from_block(block, 2)); + } + let x2 = BinaryBundle::new(x2); + (x01, x2) + } + PartyID::ID1 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + // Input x01 + let xor = x.a ^ x.b; + let x01 = GCUtils::encode_bigint(xor, bitlen, rng, delta); + + // Send x01 to the other parties + for val in x01.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID2, val.as_block().as_ref())?; + } + for val in x01.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + let x01 = x01.garbler_wires; + + // Receive x2 + let mut x2 = Vec::with_capacity(bitlen); + for _ in 0..bitlen { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; + x2.push(WireMod2::from_block(block, 2)); + } + let x2 = BinaryBundle::new(x2); + (x01, x2) + } + PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + // Input x2 + let x2 = GCUtils::encode_bigint(x.a, bitlen, rng, delta); + + // Send x2 to the other parties + for val in x2.garbler_wires.iter() { + io_context + .network + .send(PartyID::ID1, val.as_block().as_ref())?; + } + for val in x2.evaluator_wires.iter() { + io_context + .network + .send(PartyID::ID0, val.as_block().as_ref())?; + } + + let x2 = x2.garbler_wires; + + // Receive x01 + let mut x01 = Vec::with_capacity(bitlen); + for _ in 0..bitlen { + let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; + x01.push(WireMod2::from_block(block, 2)); + } + let x01 = BinaryBundle::new(x01); + (x01, x2) + } + }; + + Ok([x01, x2]) +} + pub fn input_field_id2( x: Option, delta: Option, From e22526cb9f861431bcf0089aeeddb69b4eecbb8b Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:01:16 +0200 Subject: [PATCH 27/56] . --- mpc-core/src/protocols/rep3/yao.rs | 205 +++++++---------------------- 1 file changed, 49 insertions(+), 156 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 1d834ea59..b7fbd71aa 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -45,6 +45,34 @@ impl GCUtils { Ok(v) } + fn receive_bundle_from( + n_bits: usize, + network: &mut N, + id: PartyID, + ) -> IoResult> { + let mut x = Vec::with_capacity(n_bits); + for _ in 0..n_bits { + let block = GCUtils::receive_block_from(network, id)?; + x.push(WireMod2::from_block(block, 2)); + } + Ok(BinaryBundle::new(x)) + } + + fn send_inputs( + input: &GCInputs, + network: &mut N, + garbler_id: PartyID, + ) -> IoResult<()> { + for val in input.garbler_wires.iter() { + network.send(garbler_id, val.as_block().as_ref())?; + } + for val in input.evaluator_wires.iter() { + network.send(PartyID::ID0, val.as_block().as_ref())?; + } + + Ok(()) + } + /// Samples a random delta pub fn random_delta(rng: &mut R) -> WireMod2 { WireMod2::rand_delta(rng, 2) @@ -195,20 +223,10 @@ pub fn joint_input_arithmetic let (x0, x2) = match id { PartyID::ID0 => { // Receive x0 - let mut x0 = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; - x0.push(WireMod2::from_block(block, 2)); - } - let x0 = BinaryBundle::new(x0); + let x0 = GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID1)?; // Receive x2 - let mut x2 = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; - x2.push(WireMod2::from_block(block, 2)); - } - let x2 = BinaryBundle::new(x2); + let x2 = GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID2)?; (x0, x2) } PartyID::ID1 => { @@ -230,26 +248,11 @@ pub fn joint_input_arithmetic let x0 = GCUtils::encode_field(x.b, rng, delta); // Send x0 to the other parties - for val in x0.garbler_wires.iter() { - io_context - .network - .send(PartyID::ID2, val.as_block().as_ref())?; - } - for val in x0.evaluator_wires.iter() { - io_context - .network - .send(PartyID::ID0, val.as_block().as_ref())?; - } - + GCUtils::send_inputs(&x0, &mut io_context.network, PartyID::ID2)?; let x0 = x0.garbler_wires; // Receive x2 - let mut x2 = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; - x2.push(WireMod2::from_block(block, 2)); - } - let x2 = BinaryBundle::new(x2); + let x2 = GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID2)?; (x0, x2) } PartyID::ID2 => { @@ -271,26 +274,11 @@ pub fn joint_input_arithmetic let x2 = GCUtils::encode_field(x.a, rng, delta); // Send x2 to the other parties - for val in x2.garbler_wires.iter() { - io_context - .network - .send(PartyID::ID1, val.as_block().as_ref())?; - } - for val in x2.evaluator_wires.iter() { - io_context - .network - .send(PartyID::ID0, val.as_block().as_ref())?; - } - + GCUtils::send_inputs(&x2, &mut io_context.network, PartyID::ID1)?; let x2 = x2.garbler_wires; // Receive x0 - let mut x0 = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; - x0.push(WireMod2::from_block(block, 2)); - } - let x0 = BinaryBundle::new(x0); + let x0 = GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID1)?; (x0, x2) } }; @@ -312,20 +300,10 @@ pub fn joint_input_arithmetic_added { // Receive x0 - let mut x01 = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; - x01.push(WireMod2::from_block(block, 2)); - } - let x01 = BinaryBundle::new(x01); + let x01 = GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID1)?; // Receive x2 - let mut x2 = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; - x2.push(WireMod2::from_block(block, 2)); - } - let x2 = BinaryBundle::new(x2); + let x2 = GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID2)?; (x01, x2) } PartyID::ID1 => { @@ -342,26 +320,11 @@ pub fn joint_input_arithmetic_added { @@ -377,26 +340,11 @@ pub fn joint_input_arithmetic_added { - // Receive x0 - let mut x01 = Vec::with_capacity(bitlen); - for _ in 0..bitlen { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID1)?; - x01.push(WireMod2::from_block(block, 2)); - } - let x01 = BinaryBundle::new(x01); + // Receive x01 + let x01 = GCUtils::receive_bundle_from(bitlen, &mut io_context.network, PartyID::ID1)?; // Receive x2 - let mut x2 = Vec::with_capacity(bitlen); - for _ in 0..bitlen { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; - x2.push(WireMod2::from_block(block, 2)); - } - let x2 = BinaryBundle::new(x2); + let x2 = GCUtils::receive_bundle_from(bitlen, &mut io_context.network, PartyID::ID2)?; (x01, x2) } PartyID::ID1 => { @@ -447,26 +385,11 @@ pub fn joint_input_binary_xored { @@ -482,26 +405,11 @@ pub fn joint_input_binary_xored( let x = match id { PartyID::ID0 | PartyID::ID1 => { // Receive x - let mut x = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(&mut io_context.network, PartyID::ID2)?; - x.push(WireMod2::from_block(block, 2)); - } - BinaryBundle::new(x) + GCUtils::receive_bundle_from(n_bits, &mut io_context.network, PartyID::ID2)? } PartyID::ID2 => { let delta = match delta { @@ -548,17 +451,7 @@ pub fn input_field_id2( let x = GCUtils::encode_field(x, rng, delta); // Send x to the other parties - for val in x.garbler_wires.iter() { - io_context - .network - .send(PartyID::ID1, val.as_block().as_ref())?; - } - for val in x.evaluator_wires.iter() { - io_context - .network - .send(PartyID::ID0, val.as_block().as_ref())?; - } - + GCUtils::send_inputs(&x, &mut io_context.network, PartyID::ID1)?; x.garbler_wires } }; From 2630bce86583c6377a06ff301690c627123f045d Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:07:35 +0200 Subject: [PATCH 28/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 34 +++++++++++++++++++++ mpc-core/src/protocols/rep3/yao/circuits.rs | 16 ++++++++++ 2 files changed, 50 insertions(+) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index bec0ca005..92abbfb2c 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -281,3 +281,37 @@ pub fn y2a( Ok(res) } + +pub fn b2y( + x: Rep3BigUintShare, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let [x01, x2] = + yao::joint_input_binary_xored(x, delta, io_context, rng, F::MODULUS_BIT_SIZE as usize)?; + + let converted = match io_context.id { + PartyID::ID0 => { + let mut evaluator = Rep3Evaluator::new(io_context); + let res = GarbledCircuits::xor_many(&mut evaluator, &x01, &x2); + GCUtils::garbled_circuits_error(res)? + // evaluator.receive_hash()?; // No network used here + } + PartyID::ID1 | PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); + let res = GarbledCircuits::xor_many(&mut garbler, &x01, &x2); + GCUtils::garbled_circuits_error(res)? + // garbler.send_hash()?; // No network used here + } + }; + + Ok(converted) +} diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 91cb0a981..ae44bfa84 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -130,6 +130,22 @@ impl GarbledCircuits { Ok(BinaryBundle::new(result)) } + + pub fn xor_many( + g: &mut G, + wires_a: &BinaryBundle, + wires_b: &BinaryBundle, + ) -> Result, G::Error> { + let bitlen = wires_a.size(); + debug_assert_eq!(bitlen, wires_b.size()); + + let mut result = Vec::with_capacity(wires_a.size()); + for (a, b) in wires_a.wires().iter().zip(wires_b.wires().iter()) { + let r = g.xor(a, b)?; + result.push(r); + } + Ok(BinaryBundle::new(result)) + } } #[cfg(test)] From 7f9a7960c90dcef718961ec487e69f74b9edceab Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:48:55 +0200 Subject: [PATCH 29/56] . --- mpc-core/Cargo.toml | 5 ++- mpc-core/src/protocols/rep3/conversion.rs | 34 ++++++++++++++++ mpc-core/src/protocols/rep3/rngs.rs | 18 ++++++++- mpc-core/src/protocols/rep3/yao.rs | 12 ++++++ tests/tests/mpc/rep3.rs | 47 +++++++++++++++++++++++ 5 files changed, 113 insertions(+), 3 deletions(-) diff --git a/mpc-core/Cargo.toml b/mpc-core/Cargo.toml index 5bbb3565e..c1bd8eee7 100644 --- a/mpc-core/Cargo.toml +++ b/mpc-core/Cargo.toml @@ -21,7 +21,7 @@ ark-ff = { workspace = true } ark-serialize = { workspace = true } bytes = { workspace = true } eyre = { workspace = true } -fancy-garbling = { version = "0.6", git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } +fancy-garbling = { git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } itertools = { workspace = true } mpc-net = { version = "0.1.2", path = "../mpc-net" } num-bigint = { workspace = true } @@ -29,12 +29,13 @@ num-traits = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } rayon = { workspace = true } -scuttlebutt = { version = "0.6", git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } +scuttlebutt = { git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } subtle = "2.6" serde = { workspace = true } sha3 = { workspace = true } tokio = { workspace = true } tracing.workspace = true +vectoreyes = { git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } [dev-dependencies] ark-bn254 = { workspace = true } diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 92abbfb2c..ca266986d 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -315,3 +315,37 @@ pub fn b2y( Ok(converted) } + +pub fn y2b( + x: BinaryBundle, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let bitlen = x.size(); + let collapsed = GCUtils::collabse_bundle_to_lsb_bits_as_biguint(x); + + let converted = match io_context.id { + PartyID::ID0 => { + let x_xor_px = collapsed; + let r = io_context.rngs.rand.random_biguint_rng1(bitlen); + let r_xor_x_xor_px = x_xor_px ^ &r; + io_context + .network + .send(PartyID::ID2, r_xor_x_xor_px.to_owned())?; + Rep3BigUintShare::new(r_xor_x_xor_px, r) + } + PartyID::ID1 => { + let px = collapsed; + let r = io_context.rngs.rand.random_biguint_rng2(bitlen); + Rep3BigUintShare::new(r, px) + } + PartyID::ID2 => { + let px = collapsed; + let r_xor_x_xor_px = io_context.network.recv(PartyID::ID0)?; + Rep3BigUintShare::new(px, r_xor_x_xor_px) + } + }; + + Ok(converted) +} diff --git a/mpc-core/src/protocols/rep3/rngs.rs b/mpc-core/src/protocols/rep3/rngs.rs index 46ff31ccc..2ab8fbd37 100644 --- a/mpc-core/src/protocols/rep3/rngs.rs +++ b/mpc-core/src/protocols/rep3/rngs.rs @@ -6,7 +6,7 @@ use super::{id::PartyID, yao::GCUtils}; use crate::RngType; use ark_ec::CurveGroup; use ark_ff::{One, PrimeField}; -use fancy_garbling::{WireLabel, WireMod2}; +use fancy_garbling::WireMod2; use num_bigint::BigUint; use rand::{distributions::Standard, prelude::Distribution, Rng, RngCore, SeedableRng}; use rayon::prelude::*; @@ -153,6 +153,22 @@ impl Rep3Rand { (a & &mask, b & mask) } + /// Generate a random [`BigUint`] with given `bitlen` from rng1 + pub fn random_biguint_rng1(&mut self, bitlen: usize) -> BigUint { + let limbsize = bitlen.div_ceil(8); + let val = BigUint::new((0..limbsize).map(|_| self.rng1.gen()).collect()); + let mask = (BigUint::from(1u32) << bitlen) - BigUint::one(); + val & &mask + } + + /// Generate a random [`BigUint`] with given `bitlen` from rng2 + pub fn random_biguint_rng2(&mut self, bitlen: usize) -> BigUint { + let limbsize = bitlen.div_ceil(8); + let val = BigUint::new((0..limbsize).map(|_| self.rng2.gen()).collect()); + let mask = (BigUint::from(1u32) << bitlen) - BigUint::one(); + val & &mask + } + /// Generate a seed from each rng pub fn random_seeds(&mut self) -> ([u8; crate::SEED_SIZE], [u8; crate::SEED_SIZE]) { let seed1 = self.rng1.gen(); diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index b7fbd71aa..4adacf353 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -13,6 +13,7 @@ use itertools::Itertools; use num_bigint::BigUint; use rand::{CryptoRng, Rng}; use scuttlebutt::Block; +use vectoreyes::SimdBase; /// A structure that contains both the garbler and the evaluators pub struct GCInputs { @@ -45,6 +46,17 @@ impl GCUtils { Ok(v) } + pub(crate) fn collabse_bundle_to_lsb_bits_as_biguint(input: BinaryBundle) -> BigUint { + let mut res = BigUint::zero(); + let one = Block::set_lo(1); + for wire in input.wires().iter().rev() { + res <<= 1; + let lsb = (wire.as_block() & one) == one; + res += lsb as u64; + } + res + } + fn receive_bundle_from( n_bits: usize, network: &mut N, diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index aa517bf66..77163e001 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -760,6 +760,53 @@ mod field_share { let is_result = rep3::combine_field_element(result1, result2, result3); assert_eq!(is_result, x); } + + #[test] + fn rep3_b2y() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_biguint(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for ((net, tx), x) in test_network + .get_party_networks() + .into_iter() + .zip([tx1, tx2, tx3]) + .zip(x_shares.into_iter()) + { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let id = rep3.network.id; + let delta = rep3.rngs.generate_random_garbler_delta(id); + + let mut rng = thread_rng(); + let converted = conversion::b2y(x, delta, &mut rep3, &mut rng).unwrap(); + + let output = match id { + PartyID::ID0 => { + let mut evaluator = Rep3Evaluator::new(&mut rep3); + evaluator.output_all_parties(converted.wires()).unwrap() + } + PartyID::ID1 | PartyID::ID2 => { + let mut garbler = Rep3Garbler::new_with_delta(&mut rep3, delta.unwrap()); + garbler.output_all_parties(converted.wires()).unwrap() + } + }; + + tx.send(GCUtils::bits_to_field::(output).unwrap()) + .unwrap(); + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + assert_eq!(result1, x); + assert_eq!(result2, x); + assert_eq!(result3, x); + } } mod curve_share { From 4e339f88f3a556a8cbf5c541b8ac44b060771a18 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:51:07 +0200 Subject: [PATCH 30/56] y2b test --- tests/tests/mpc/rep3.rs | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 77163e001..afc63f116 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -807,6 +807,49 @@ mod field_share { assert_eq!(result2, x); assert_eq!(result3, x); } + + #[test] + fn rep3_y2b() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let delta = GCUtils::random_delta(&mut rng); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = GCUtils::encode_field(x, &mut rng, delta); + let x_shares = [ + x_shares.evaluator_wires, + x_shares.garbler_wires.to_owned(), + x_shares.garbler_wires, + ]; + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let mut rng = thread_rng(); + let converted = + conversion::y2b::(x, Some(delta), &mut rep3, &mut rng) + .unwrap(); + tx.send(converted).unwrap(); + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_binary_element(result1, result2, result3); + + let should_result = x.into(); + assert_eq!(is_result, should_result); + let is_result_f: ark_bn254::Fr = is_result.into(); + assert_eq!(is_result_f, x); + } } mod curve_share { From 9506fdbd0c620ef1b824fccaad26c0b8b8e3632e Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:55:18 +0200 Subject: [PATCH 31/56] . --- mpc-core/src/protocols/rep3/yao.rs | 2 +- mpc-core/src/protocols/rep3/yao/circuits.rs | 8 ++++++-- mpc-core/src/protocols/rep3/yao/evaluator.rs | 1 + mpc-core/src/protocols/rep3/yao/garbler.rs | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 4adacf353..8aeedd7f0 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -15,7 +15,7 @@ use rand::{CryptoRng, Rng}; use scuttlebutt::Block; use vectoreyes::SimdBase; -/// A structure that contains both the garbler and the evaluators +/// A structure that contains both the garbler and the evaluators wires pub struct GCInputs { pub garbler_wires: BinaryBundle, pub evaluator_wires: BinaryBundle, diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index ae44bfa84..9e914fc7c 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -3,6 +3,7 @@ use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; use num_bigint::BigUint; +/// This struct contains some predefined garbled circuits. pub struct GarbledCircuits {} impl GarbledCircuits { @@ -56,6 +57,7 @@ impl GarbledCircuits { } /// Binary addition. Returns the result and the carry. + #[allow(clippy::type_complexity)] fn bin_addition( g: &mut G, xs: &BinaryBundle, @@ -79,6 +81,7 @@ impl GarbledCircuits { Ok((BinaryBundle::new(result), c)) } + /// Adds two field shared field elements mod p. The field elements are encoded as Yao shared wires pub fn adder_mod_p( g: &mut G, wires_a: &BinaryBundle, @@ -88,7 +91,7 @@ impl GarbledCircuits { debug_assert_eq!(bitlen, wires_b.size()); // First addition - let (added, carry_add) = Self::bin_addition(g, &wires_a, &wires_b)?; + let (added, carry_add) = Self::bin_addition(g, wires_a, wires_b)?; let added_wires = added.wires(); // Prepare p for subtraction @@ -131,7 +134,8 @@ impl GarbledCircuits { Ok(BinaryBundle::new(result)) } - pub fn xor_many( + /// XORs two bundles of wires. Does not require any network interaction. + pub(crate) fn xor_many( g: &mut G, wires_a: &BinaryBundle, wires_b: &BinaryBundle, diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index c33372c31..0217c6cdd 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -16,6 +16,7 @@ use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; +/// This struct implements the evaluator for replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. pub struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index b8a38bea3..46814088f 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -17,11 +17,12 @@ use fancy_garbling::{ util::{output_tweak, tweak2}, BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; -use rand::{CryptoRng, Rng, SeedableRng}; +use rand::SeedableRng; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; +/// This struct implements the garbler for replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. pub struct Rep3Garbler<'a, N: Rep3Network> { io_context: &'a mut IoContext, delta: WireMod2, From 5e763aeab8114b60f84e9e354c6e8391da4e8602 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:02:30 +0200 Subject: [PATCH 32/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 15 ++++++++++++--- tests/tests/mpc/rep3.rs | 5 +---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index ca266986d..a9fac326d 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -167,6 +167,7 @@ pub fn bit_inject( Ok(e) } +/// Transforms the replicated shared value x from an arithmetic sharing to a yao sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x. pub fn a2y( x: Rep3PrimeFieldShare, delta: Option, @@ -202,6 +203,11 @@ pub fn a2y( Ok(converted) } +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x gets transformed into x = x_1 + x_2 + x_3. + +// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. + pub fn y2a( x: BinaryBundle, delta: Option, @@ -282,6 +288,10 @@ pub fn y2a( Ok(res) } +/// Transforms the replicated shared value x from a binary sharing to a yao sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x. + +// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. pub fn b2y( x: Rep3BigUintShare, delta: Option, @@ -316,11 +326,10 @@ pub fn b2y( Ok(converted) } -pub fn y2b( +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x gets transformed into x = x_1 xor x_2 xor x_3. +pub fn y2b( x: BinaryBundle, - delta: Option, io_context: &mut IoContext, - rng: &mut R, ) -> IoResult> { let bitlen = x.size(); let collapsed = GCUtils::collabse_bundle_to_lsb_bits_as_biguint(x); diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index afc63f116..1b4a0ecf4 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -832,10 +832,7 @@ mod field_share { ) { thread::spawn(move || { let mut rep3 = IoContext::init(net).unwrap(); - let mut rng = thread_rng(); - let converted = - conversion::y2b::(x, Some(delta), &mut rep3, &mut rng) - .unwrap(); + let converted = conversion::y2b::(x, &mut rep3).unwrap(); tx.send(converted).unwrap(); }); } From 538d03cb01ba92740e01cb377f4d03245fd470a8 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:04:22 +0200 Subject: [PATCH 33/56] . --- mpc-core/src/protocols/rep3/yao.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 8aeedd7f0..02839c80f 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -1,3 +1,7 @@ +//! Yao +//! +//! This module contains operations with Yao's garbled circuits + pub mod circuits; pub mod evaluator; pub mod garbler; @@ -364,7 +368,7 @@ pub fn joint_input_arithmetic_added( x: Rep3BigUintShare, delta: Option, @@ -429,6 +433,7 @@ pub fn joint_input_binary_xored( x: Option, delta: Option, From e19514fc7ae38653f4115ecaf1b6e5d849f14bcf Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:07:03 +0200 Subject: [PATCH 34/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 8 ++++---- mpc-core/src/protocols/rep3/yao.rs | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index a9fac326d..da722ba37 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -167,7 +167,7 @@ pub fn bit_inject( Ok(e) } -/// Transforms the replicated shared value x from an arithmetic sharing to a yao sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x. +/// Transforms the replicated shared value x from an arithmetic sharing to a yao sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x. pub fn a2y( x: Rep3PrimeFieldShare, delta: Option, @@ -203,7 +203,7 @@ pub fn a2y( Ok(converted) } -/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x gets transformed into x = x_1 + x_2 + x_3. +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x gets transformed into x = x_1 + x_2 + x_3. // Keep in mind: Only works if the input is actually a binary sharing of a valid field element // If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. @@ -288,7 +288,7 @@ pub fn y2a( Ok(res) } -/// Transforms the replicated shared value x from a binary sharing to a yao sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x. +/// Transforms the replicated shared value x from a binary sharing to a yao sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x. // Keep in mind: Only works if the input is actually a binary sharing of a valid field element // If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. @@ -326,7 +326,7 @@ pub fn b2y( Ok(converted) } -/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta\cdot x gets transformed into x = x_1 xor x_2 xor x_3. +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x gets transformed into x = x_1 xor x_2 xor x_3. pub fn y2b( x: BinaryBundle, io_context: &mut IoContext, diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 02839c80f..ea3fc2a5b 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -21,11 +21,15 @@ use vectoreyes::SimdBase; /// A structure that contains both the garbler and the evaluators wires pub struct GCInputs { + /// The wires of the garbler. These represent random keys x_0 pub garbler_wires: BinaryBundle, + /// The wires of the evaluator. These represent the keys x_c = x_0 xor delta * val pub evaluator_wires: BinaryBundle, + /// The delta used for encoding known to the garbler pub delta: F, } +/// This struct contains some useful utility functions for garbled circuits. pub struct GCUtils {} impl GCUtils { @@ -112,6 +116,7 @@ impl GCUtils { Ok(F::from(res)) } + /// Converts bits into a field element pub fn bits_to_field(bits: Vec) -> IoResult { let mut res = BigUint::zero(); for bit in bits.iter().rev() { From ab986cd90395fe784e82c8402d37364b8ee1895c Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:09:33 +0200 Subject: [PATCH 35/56] . --- mpc-core/src/protocols/rep3/yao/circuits.rs | 4 ++++ mpc-core/src/protocols/rep3/yao/evaluator.rs | 6 +++++- mpc-core/src/protocols/rep3/yao/garbler.rs | 6 +++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 9e914fc7c..5196dc5a7 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -1,3 +1,7 @@ +//! Circuits +//! +//! This module contains some garbled circuit implementations. + use crate::protocols::rep3::yao::GCUtils; use ark_ff::PrimeField; use fancy_garbling::{BinaryBundle, FancyBinary}; diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 0217c6cdd..07d9eea3b 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -1,4 +1,8 @@ -// This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs +//! Evaluator +//! +//! This module contains the implementation of the evaluator for the replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. +//! +//! This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs use super::GCUtils; use crate::protocols::rep3::{ diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 46814088f..783993af1 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -1,4 +1,8 @@ -// This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs +//! Garbler +//! +//! This module contains the implementation of the garbler for the replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. +//! +//! This implementation is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs use super::{GCInputs, GCUtils}; use crate::{ From c89256c163604372d54531889ee766990568e1d8 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:12:53 +0200 Subject: [PATCH 36/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 19 +++++++ tests/tests/mpc/rep3.rs | 60 +++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index da722ba37..aa1771983 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -358,3 +358,22 @@ pub fn y2b( Ok(converted) } + +/// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. . +pub fn a2y2b( + x: Rep3PrimeFieldShare, + io_context: &mut IoContext, +) -> IoResult> { + todo!() +} + +/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol abd currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. + +// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. +pub fn b2y2a( + x: &Rep3BigUintShare, + io_context: &mut IoContext, +) -> IoResult> { + todo!() +} diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 1b4a0ecf4..daacdebfc 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -578,6 +578,38 @@ mod field_share { assert_eq!(is_result_f, x); } + #[test] + fn rep3_a2y2b() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_field_element(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for ((net, tx), x) in test_network + .get_party_networks() + .into_iter() + .zip([tx1, tx2, tx3]) + .zip(x_shares.into_iter()) + { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + tx.send(conversion::a2y2b(x, &mut rep3).unwrap()) + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_binary_element(result1, result2, result3); + + let should_result = x.into(); + assert_eq!(is_result, should_result); + let is_result_f: ark_bn254::Fr = is_result.into(); + assert_eq!(is_result_f, x); + } + #[test] fn rep3_b2a() { let test_network = Rep3TestNetwork::default(); @@ -606,6 +638,34 @@ mod field_share { assert_eq!(is_result, x); } + #[test] + fn rep3_b2y2a() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_biguint(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for ((net, tx), x) in test_network + .get_party_networks() + .into_iter() + .zip([tx1, tx2, tx3]) + .zip(x_shares.into_iter()) + { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + tx.send(conversion::b2y2a(&x, &mut rep3).unwrap()) + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_element(result1, result2, result3); + assert_eq!(is_result, x); + } + #[test] fn rep3_gc() { let test_network = Rep3TestNetwork::default(); From 7d144e2c8a110178e5936f59b3e4b3c8d3a3f52f Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:20:09 +0200 Subject: [PATCH 37/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 27 ++++++++++++++++++----- mpc-core/src/protocols/rep3/yao.rs | 14 ++++++------ tests/tests/mpc/rep3.rs | 8 ++++--- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index aa1771983..dabd4458f 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -293,7 +293,7 @@ pub fn y2a( // Keep in mind: Only works if the input is actually a binary sharing of a valid field element // If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. pub fn b2y( - x: Rep3BigUintShare, + x: &Rep3BigUintShare, delta: Option, io_context: &mut IoContext, rng: &mut R, @@ -360,20 +360,35 @@ pub fn y2b( } /// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. . -pub fn a2y2b( +pub fn a2y2b( x: Rep3PrimeFieldShare, io_context: &mut IoContext, + rng: &mut R, ) -> IoResult> { - todo!() + let delta = io_context.rngs.generate_random_garbler_delta(io_context.id); + let y = a2y(x, delta, io_context, rng)?; + y2b(y, io_context) } -/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol abd currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. +/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. // Keep in mind: Only works if the input is actually a binary sharing of a valid field element // If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. -pub fn b2y2a( +pub fn b2y2a( x: &Rep3BigUintShare, io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let delta = io_context.rngs.generate_random_garbler_delta(io_context.id); + let y = b2y(x, delta, io_context, rng)?; + y2a(y, delta, io_context, rng) +} + +/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. his implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. +pub fn b2y2a_consume( + x: Rep3BigUintShare, + io_context: &mut IoContext, + rng: &mut R, ) -> IoResult> { - todo!() + b2y2a(&x, io_context, rng) } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index ea3fc2a5b..d8e3d3e04 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -153,10 +153,10 @@ impl GCUtils { let n_bits = F::MODULUS_BIT_SIZE as usize; let bigint: BigUint = field.into(); - Self::biguint_to_bits_as_u16(bigint, n_bits) + Self::biguint_to_bits_as_u16(&bigint, n_bits) } - fn biguint_to_bits_as_u16(input: BigUint, n_bits: usize) -> Vec { + fn biguint_to_bits_as_u16(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() { @@ -206,7 +206,7 @@ impl GCUtils { /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires pub fn encode_bigint( - bigint: BigUint, + bigint: &BigUint, n_bits: usize, rng: &mut R, delta: WireMod2, @@ -375,7 +375,7 @@ pub fn joint_input_arithmetic_added( - x: Rep3BigUintShare, + x: &Rep3BigUintShare, delta: Option, io_context: &mut IoContext, rng: &mut R, @@ -402,8 +402,8 @@ pub fn joint_input_binary_xored { From 19ee9f3bbf9f46ddc177871de2628cce1b368c41 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:22:39 +0200 Subject: [PATCH 38/56] . --- Cargo.toml | 1 + mpc-core/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b3552e6c5..c9cf2de9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ serde_json = "1.0" serde_yaml = "0.9.27" sha2 = "0.10" sha3 = "0.10.8" +subtle = "2.6" thiserror = "1.0.59" tokio = { version = "1.34.0", features = [ "rt", diff --git a/mpc-core/Cargo.toml b/mpc-core/Cargo.toml index c1bd8eee7..6d6677bda 100644 --- a/mpc-core/Cargo.toml +++ b/mpc-core/Cargo.toml @@ -30,7 +30,7 @@ rand = { workspace = true } rand_chacha = { workspace = true } rayon = { workspace = true } scuttlebutt = { git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } -subtle = "2.6" +subtle = { workspace = true } serde = { workspace = true } sha3 = { workspace = true } tokio = { workspace = true } From 9e7fea8512a077b7a6557215afa69a5d187a684c Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:33:10 +0200 Subject: [PATCH 39/56] fix docu --- mpc-core/src/protocols/rep3/yao.rs | 6 +++--- mpc-core/src/protocols/rep3/yao/evaluator.rs | 6 +++--- mpc-core/src/protocols/rep3/yao/garbler.rs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index d8e3d3e04..b07e7ca9e 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -226,7 +226,7 @@ impl GCUtils { } } -/// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into three yao shares [x_1]^Y, [x_2]^Y, [x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. +/// Transforms an arithmetically shared input x = (x_1, x_2, x_3) into three yao shares x_1^Y, x_2^Y, x_3^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. pub fn joint_input_arithmetic( x: Rep3PrimeFieldShare, delta: Option, @@ -308,7 +308,7 @@ pub fn joint_input_arithmetic Ok([x0, x1, x2]) } -/// Transforms an arithmetically shared input [x] = (x_1, x_2, x_3) into two yao shares [x_1]^Y, [x_2 + x_3]^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. +/// Transforms an arithmetically shared input x = (x_1, x_2, x_3) into two yao shares x_1^Y, (x_2 + x_3)^Y. The used delta is an input to the function to allow for the same delta to be used for multiple conversions. pub fn joint_input_arithmetic_added( x: Rep3PrimeFieldShare, delta: Option, @@ -373,7 +373,7 @@ pub fn joint_input_arithmetic_added( x: &Rep3BigUintShare, delta: Option, diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 07d9eea3b..9687e511d 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -1,8 +1,8 @@ //! Evaluator //! -//! This module contains the implementation of the evaluator for the replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. +//! This module contains the implementation of the evaluator for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). //! -//! This file is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs +//! 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 crate::protocols::rep3::{ @@ -20,7 +20,7 @@ use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; -/// This struct implements the evaluator for replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. +/// This struct implements the evaluator for replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). pub struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 783993af1..400df3d17 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -1,8 +1,8 @@ //! Garbler //! -//! This module contains the implementation of the garbler for the replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. +//! This module contains the implementation of the garbler for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). //! -//! This implementation is heavily inspired by https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs +//! 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 crate::{ @@ -26,7 +26,7 @@ use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; use subtle::ConditionallySelectable; -/// This struct implements the garbler for replicated 3-party garbled circuits as described in https://eprint.iacr.org/2018/403.pdf. +/// This struct implements the garbler for replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). pub struct Rep3Garbler<'a, N: Rep3Network> { io_context: &'a mut IoContext, delta: WireMod2, From 5d0c2cf966a01645e86f4c49526de0aab66cc1f5 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:47:27 +0200 Subject: [PATCH 40/56] remove one dependency --- mpc-core/Cargo.toml | 1 - mpc-core/src/protocols/rep3/yao.rs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mpc-core/Cargo.toml b/mpc-core/Cargo.toml index 6d6677bda..39783e252 100644 --- a/mpc-core/Cargo.toml +++ b/mpc-core/Cargo.toml @@ -35,7 +35,6 @@ serde = { workspace = true } sha3 = { workspace = true } tokio = { workspace = true } tracing.workspace = true -vectoreyes = { git = "https://github.com/GaloisInc/swanky", rev = "586a6ba1efdb531542668d6b0afe5cacc302d434" } [dev-dependencies] ark-bn254 = { workspace = true } diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index b07e7ca9e..a48b08f02 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -17,7 +17,6 @@ use itertools::Itertools; use num_bigint::BigUint; use rand::{CryptoRng, Rng}; use scuttlebutt::Block; -use vectoreyes::SimdBase; /// A structure that contains both the garbler and the evaluators wires pub struct GCInputs { @@ -56,10 +55,10 @@ impl GCUtils { pub(crate) fn collabse_bundle_to_lsb_bits_as_biguint(input: BinaryBundle) -> BigUint { let mut res = BigUint::zero(); - let one = Block::set_lo(1); for wire in input.wires().iter().rev() { res <<= 1; - let lsb = (wire.as_block() & one) == one; + let lsb = wire.color(); + debug_assert!(lsb < 2); res += lsb as u64; } res From 38ac3460604fe59b3ab03f1b87886b2a11a81d4a Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:50:51 +0200 Subject: [PATCH 41/56] doc comment --- mpc-core/src/protocols/rep3/conversion.rs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index dabd4458f..a41a4265f 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -64,9 +64,9 @@ pub fn b2a_consume( } /// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementation currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. - -// Keep in mind: Only works if the input is actually a binary sharing of a valid field element -// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. +/// +/// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +/// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. pub fn b2a( x: &Rep3BigUintShare, io_context: &mut IoContext, @@ -204,9 +204,9 @@ pub fn a2y( } /// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x gets transformed into x = x_1 + x_2 + x_3. - -// Keep in mind: Only works if the input is actually a binary sharing of a valid field element -// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. +/// +/// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +/// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. pub fn y2a( x: BinaryBundle, @@ -289,9 +289,9 @@ pub fn y2a( } /// Transforms the replicated shared value x from a binary sharing to a yao sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x. - -// Keep in mind: Only works if the input is actually a binary sharing of a valid field element -// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. +/// +/// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +/// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. pub fn b2y( x: &Rep3BigUintShare, delta: Option, @@ -371,9 +371,9 @@ pub fn a2y2b( } /// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. - -// Keep in mind: Only works if the input is actually a binary sharing of a valid field element -// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. +/// +/// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +/// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. pub fn b2y2a( x: &Rep3BigUintShare, io_context: &mut IoContext, From bd1196e3ad9e918f7634f1cc2036701a64ad207a Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:52:06 +0200 Subject: [PATCH 42/56] grammar --- mpc-core/src/protocols/rep3/conversion.rs | 2 +- mpc-core/src/protocols/rep3/yao.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index a41a4265f..63e83731f 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -332,7 +332,7 @@ pub fn y2b( io_context: &mut IoContext, ) -> IoResult> { let bitlen = x.size(); - let collapsed = GCUtils::collabse_bundle_to_lsb_bits_as_biguint(x); + let collapsed = GCUtils::collapse_bundle_to_lsb_bits_as_biguint(x); let converted = match io_context.id { PartyID::ID0 => { diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index a48b08f02..bb94882f8 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -53,7 +53,7 @@ impl GCUtils { Ok(v) } - pub(crate) fn collabse_bundle_to_lsb_bits_as_biguint(input: BinaryBundle) -> BigUint { + pub(crate) fn collapse_bundle_to_lsb_bits_as_biguint(input: BinaryBundle) -> BigUint { let mut res = BigUint::zero(); for wire in input.wires().iter().rev() { res <<= 1; From c45ab6aec1ebb4390e16126da48b2b60f8075ca8 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:06:58 +0200 Subject: [PATCH 43/56] . --- mpc-core/src/protocols/rep3/yao/circuits.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index 5196dc5a7..cd085e16a 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -27,8 +27,8 @@ impl GarbledCircuits { } else { let z1 = a; let s = g.xor(z1, c)?; - let z3 = g.xor(a, c)?; - let z4 = g.and(z1, &z3)?; + let z3 = &s; + let z4 = g.and(z1, z3)?; let c = g.xor(&z4, a)?; (s, c) }; From 566ced45f86ec8ad9527d4cf4e1d3862358fcdeb Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:08:44 +0200 Subject: [PATCH 44/56] . --- mpc-core/src/protocols/rep3/yao.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index bb94882f8..d111b7fae 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -235,7 +235,8 @@ pub fn joint_input_arithmetic let id = io_context.id; let n_bits = F::MODULUS_BIT_SIZE as usize; - // x1 is known by both garblers, we can do a shortcut + // x1 is known by both garblers, we can do a shortcut to share it without communication. + // See https://eprint.iacr.org/2019/1168.pdf, p18, last paragraph of "Joint Yao Input". let mut x1 = (0..n_bits) .map(|_| WireMod2::from_block(io_context.rngs.generate_shared::(id), 2)) .collect_vec(); From 85af7e224e8342b8145a60129456f9421af8c9b7 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:16:34 +0200 Subject: [PATCH 45/56] make mux explicit circuit --- mpc-core/src/protocols/rep3/yao/circuits.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/circuits.rs b/mpc-core/src/protocols/rep3/yao/circuits.rs index cd085e16a..1ee3f5b83 100644 --- a/mpc-core/src/protocols/rep3/yao/circuits.rs +++ b/mpc-core/src/protocols/rep3/yao/circuits.rs @@ -85,6 +85,19 @@ impl GarbledCircuits { Ok((BinaryBundle::new(result), c)) } + /// If `b = 0` returns `x` else `y`. + fn mux( + g: &mut G, + b: &G::Item, + x: &G::Item, + y: &G::Item, + ) -> Result { + // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though + let xor = g.xor(x, y)?; + let and = g.and(b, &xor)?; + g.xor(&and, x) + } + /// Adds two field shared field elements mod p. The field elements are encoded as Yao shared wires pub fn adder_mod_p( g: &mut G, @@ -128,10 +141,7 @@ impl GarbledCircuits { let mut result = Vec::with_capacity(bitlen); for (s, a) in subtracted.iter().zip(added.iter()) { // CMUX - // let r = g.mux(&ov, s, a)?; // Has two ANDs, only need one though - let xor = g.xor(s, a)?; - let and = g.and(&ov, &xor)?; - let r = g.xor(&and, s)?; + let r = Self::mux(g, &ov, s, a)?; result.push(r); } From 9a4f708d8297427c2045df3ccee5eb786227de2b Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:18:05 +0200 Subject: [PATCH 46/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 63e83731f..d80581af1 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -384,7 +384,7 @@ pub fn b2y2a( y2a(y, delta, io_context, rng) } -/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. his implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. +/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. pub fn b2y2a_consume( x: Rep3BigUintShare, io_context: &mut IoContext, From 46efcb867d152bf803be7f19626a1660358daba2 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:27:31 +0200 Subject: [PATCH 47/56] put garbling/evaluating of and gates into GCUtils --- mpc-core/src/protocols/rep3/yao.rs | 73 +++++++++++++++++++- mpc-core/src/protocols/rep3/yao/evaluator.rs | 25 +------ mpc-core/src/protocols/rep3/yao/garbler.rs | 43 +----------- 3 files changed, 77 insertions(+), 64 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index d111b7fae..d931d9f80 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -12,11 +12,12 @@ use super::{ }; use crate::protocols::rep3::id::PartyID; use ark_ff::{PrimeField, Zero}; -use fancy_garbling::{BinaryBundle, WireLabel, WireMod2}; +use fancy_garbling::{hash_wires, util::tweak2, BinaryBundle, WireLabel, WireMod2}; use itertools::Itertools; use num_bigint::BigUint; use rand::{CryptoRng, Rng}; use scuttlebutt::Block; +use subtle::ConditionallySelectable; /// A structure that contains both the garbler and the evaluators wires pub struct GCInputs { @@ -32,6 +33,76 @@ pub struct GCInputs { pub struct GCUtils {} impl GCUtils { + pub(crate) fn evaluate_and_gate( + gate_num: usize, + a: &WireMod2, + b: &WireMod2, + gate0: &Block, + gate1: &Block, + ) -> WireMod2 { + let g = tweak2(gate_num as u64, 0); + + let [hash_a, hash_b] = hash_wires([a, b], g); + + // garbler's half gate + let l = WireMod2::from_block( + Block::conditional_select(&hash_a, &(hash_a ^ *gate0), (a.color() as u8).into()), + 2, + ); + + // evaluator's half gate + let r = WireMod2::from_block( + Block::conditional_select(&hash_b, &(hash_b ^ *gate1), (b.color() as u8).into()), + 2, + ); + + l.plus_mov(&r.plus_mov(&a.cmul(b.color()))) + } + + pub(crate) fn garble_and_gate( + gate_num: usize, + a: &WireMod2, + b: &WireMod2, + delta: &WireMod2, + ) -> (Block, Block, WireMod2) { + let q = 2; + let d = delta; + + let r = b.color(); // secret value known only to the garbler (ev knows r+b) + + let g = tweak2(gate_num as u64, 0); + + // X = H(A+aD) + arD such that a + A.color == 0 + let alpha = a.color(); // alpha = -A.color + let x1 = a.plus(&d.cmul(alpha)); + + // Y = H(B + bD) + (b + r)A such that b + B.color == 0 + let beta = (q - b.color()) % q; + let y1 = b.plus(&d.cmul(beta)); + + let ad = a.plus(d); + let bd = b.plus(d); + + // idx is always boolean for binary gates, so it can be represented as a `u8` + let a_selector = (a.color() as u8).into(); + let b_selector = (b.color() as u8).into(); + + let b = WireMod2::conditional_select(&bd, b, b_selector); + let new_a = WireMod2::conditional_select(&ad, a, a_selector); + let idx = u8::conditional_select(&(r as u8), &0u8, a_selector); + + let [hash_a, hash_b, hash_x, hash_y] = hash_wires([&new_a, &b, &x1, &y1], g); + + let x = WireMod2::hash_to_mod(hash_x, q).plus_mov(&d.cmul(alpha * r % q)); + let y = WireMod2::hash_to_mod(hash_y, q); + + let gate0 = + hash_a ^ Block::conditional_select(&x.as_block(), &x.plus(d).as_block(), idx.into()); + let gate1 = hash_b ^ y.plus(a).as_block(); + + (gate0, gate1, x.plus_mov(&y)) + } + pub(crate) fn garbled_circuits_error(input: Result) -> IoResult { input.or(Err(std::io::Error::new( std::io::ErrorKind::Other, diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 9687e511d..53a8ab19d 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -11,14 +11,11 @@ use crate::protocols::rep3::{ IoResult, }; use fancy_garbling::{ - errors::EvaluatorError, - hash_wires, - util::{output_tweak, tweak2}, - BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, + errors::EvaluatorError, util::output_tweak, BinaryBundle, Fancy, FancyBinary, WireLabel, + WireMod2, }; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; -use subtle::ConditionallySelectable; /// This struct implements the evaluator for replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). pub struct Rep3Evaluator<'a, N: Rep3Network> { @@ -203,23 +200,7 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { gate1: &Block, ) -> WireMod2 { let gate_num = self.current_gate(); - let g = tweak2(gate_num as u64, 0); - - let [hash_a, hash_b] = hash_wires([a, b], g); - - // garbler's half gate - let l = WireMod2::from_block( - Block::conditional_select(&hash_a, &(hash_a ^ *gate0), (a.color() as u8).into()), - 2, - ); - - // evaluator's half gate - let r = WireMod2::from_block( - Block::conditional_select(&hash_b, &(hash_b ^ *gate1), (b.color() as u8).into()), - 2, - ); - - l.plus_mov(&r.plus_mov(&a.cmul(b.color()))) + GCUtils::evaluate_and_gate(gate_num, a, b, gate0, gate1) } } diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 400df3d17..5b0781b03 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -16,15 +16,11 @@ use crate::{ use ark_ff::PrimeField; use core::panic; use fancy_garbling::{ - errors::GarblerError, - hash_wires, - util::{output_tweak, tweak2}, - BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, + errors::GarblerError, util::output_tweak, BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, }; use rand::SeedableRng; use scuttlebutt::Block; use sha3::{Digest, Sha3_256}; -use subtle::ConditionallySelectable; /// This struct implements the garbler for replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). pub struct Rep3Garbler<'a, N: Rep3Network> { @@ -213,43 +209,8 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { b: &WireMod2, delta: &WireMod2, ) -> (Block, Block, WireMod2) { - let q = 2; - let d = delta; let gate_num = self.current_gate(); - - let r = b.color(); // secret value known only to the garbler (ev knows r+b) - - let g = tweak2(gate_num as u64, 0); - - // X = H(A+aD) + arD such that a + A.color == 0 - let alpha = a.color(); // alpha = -A.color - let x1 = a.plus(&d.cmul(alpha)); - - // Y = H(B + bD) + (b + r)A such that b + B.color == 0 - let beta = (q - b.color()) % q; - let y1 = b.plus(&d.cmul(beta)); - - let ad = a.plus(d); - let bd = b.plus(d); - - // idx is always boolean for binary gates, so it can be represented as a `u8` - let a_selector = (a.color() as u8).into(); - let b_selector = (b.color() as u8).into(); - - let b = WireMod2::conditional_select(&bd, b, b_selector); - let new_a = WireMod2::conditional_select(&ad, a, a_selector); - let idx = u8::conditional_select(&(r as u8), &0u8, a_selector); - - let [hash_a, hash_b, hash_x, hash_y] = hash_wires([&new_a, &b, &x1, &y1], g); - - let x = WireMod2::hash_to_mod(hash_x, q).plus_mov(&d.cmul(alpha * r % q)); - let y = WireMod2::hash_to_mod(hash_y, q); - - let gate0 = - hash_a ^ Block::conditional_select(&x.as_block(), &x.plus(d).as_block(), idx.into()); - let gate1 = hash_b ^ y.plus(a).as_block(); - - (gate0, gate1, x.plus_mov(&y)) + GCUtils::garble_and_gate(gate_num, a, b, delta) } } From f56f082cf4e93a4e4c53cde0c00dbe72d8034f4b Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:31:01 +0200 Subject: [PATCH 48/56] . --- mpc-core/src/protocols/rep3/yao.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index d931d9f80..2bbb7c526 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -33,6 +33,11 @@ pub struct GCInputs { pub struct GCUtils {} impl GCUtils { + /// Evaluates an 'and' gate given two inputs wires and two half-gates from the garbler. + /// + /// Outputs C = A & B + /// + /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. pub(crate) fn evaluate_and_gate( gate_num: usize, a: &WireMod2, @@ -59,6 +64,12 @@ impl GCUtils { l.plus_mov(&r.plus_mov(&a.cmul(b.color()))) } + /// Garbles an 'and' gate given two input wires and the delta. + /// + /// Outputs a tuple consisting of the two gates (that should be transfered to the evaluator) + /// and the next wire label for the garbler. + /// + /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. pub(crate) fn garble_and_gate( gate_num: usize, a: &WireMod2, From 44882e675d7c5416668c879ad70d3633a31de1f6 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:32:59 +0200 Subject: [PATCH 49/56] . --- mpc-core/src/protocols/rep3/yao/garbler.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 5b0781b03..5740067fd 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -203,14 +203,9 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// and the next wire label for the garbler. /// /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. - fn garble_and_gate( - &mut self, - a: &WireMod2, - b: &WireMod2, - delta: &WireMod2, - ) -> (Block, Block, WireMod2) { + fn garble_and_gate(&mut self, a: &WireMod2, b: &WireMod2) -> (Block, Block, WireMod2) { let gate_num = self.current_gate(); - GCUtils::garble_and_gate(gate_num, a, b, delta) + GCUtils::garble_and_gate(gate_num, a, b, &self.delta) } } @@ -238,8 +233,7 @@ impl<'a, N: Rep3Network> Fancy for Rep3Garbler<'a, N> { impl<'a, N: Rep3Network> FancyBinary for Rep3Garbler<'a, N> { fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { - let delta = self.delta; - let (gate0, gate1, c) = self.garble_and_gate(a, b, &delta); + let (gate0, gate1, c) = self.garble_and_gate(a, b); self.send_block(&gate0)?; self.send_block(&gate1)?; Ok(c) From 153c15502862c1f4c7827e9c0fa8b3d00cd6275f Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:39:42 +0200 Subject: [PATCH 50/56] . --- mpc-core/src/protocols/rep3/conversion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index d80581af1..dc076c639 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -167,7 +167,7 @@ pub fn bit_inject( Ok(e) } -/// Transforms the replicated shared value x from an arithmetic sharing to a yao sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x. +/// Transforms the replicated shared value x from an arithmetic sharing to a yao sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x. pub fn a2y( x: Rep3PrimeFieldShare, delta: Option, @@ -203,7 +203,7 @@ pub fn a2y( Ok(converted) } -/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x gets transformed into x = x_1 + x_2 + x_3. +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x gets transformed into x = x_1 + x_2 + x_3. /// /// Keep in mind: Only works if the input is actually a binary sharing of a valid field element /// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. @@ -288,7 +288,7 @@ pub fn y2a( Ok(res) } -/// Transforms the replicated shared value x from a binary sharing to a yao sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x. +/// Transforms the replicated shared value x from a binary sharing to a yao sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x. /// /// Keep in mind: Only works if the input is actually a binary sharing of a valid field element /// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. @@ -326,7 +326,7 @@ pub fn b2y( Ok(converted) } -/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_x xor delta * x gets transformed into x = x_1 xor x_2 xor x_3. +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x gets transformed into x = x_1 xor x_2 xor x_3. pub fn y2b( x: BinaryBundle, io_context: &mut IoContext, From 88c973b3cd04a56802eedd129ff4a427ce268e9c Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:42:15 +0200 Subject: [PATCH 51/56] remove the consume version of b2a --- mpc-core/src/protocols/rep3/binary.rs | 2 +- mpc-core/src/protocols/rep3/conversion.rs | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/mpc-core/src/protocols/rep3/binary.rs b/mpc-core/src/protocols/rep3/binary.rs index ac066321e..78fac4632 100644 --- a/mpc-core/src/protocols/rep3/binary.rs +++ b/mpc-core/src/protocols/rep3/binary.rs @@ -146,7 +146,7 @@ pub fn shift_l_public_by_shared( (shared.a.clone() >> i) & BigUint::one(), (shared.b.clone() >> i) & BigUint::one(), ); - individual_bit_shares.push(conversion::b2a_consume(bit, context)?); + individual_bit_shares.push(conversion::b2a(&bit, context)?); } // v_i = 2^2^i * + 1 - let mut vs: Vec<_> = individual_bit_shares diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index dc076c639..61984bc99 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -55,14 +55,6 @@ pub fn a2b( detail::low_depth_binary_add_mod_p::(&x01, &x2, io_context, F::MODULUS_BIT_SIZE as usize) } -/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementation currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. -pub fn b2a_consume( - x: Rep3BigUintShare, - io_context: &mut IoContext, -) -> IoResult> { - b2a(&x, io_context) -} - /// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementation currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. /// /// Keep in mind: Only works if the input is actually a binary sharing of a valid field element @@ -383,12 +375,3 @@ pub fn b2y2a( let y = b2y(x, delta, io_context, rng)?; y2a(y, delta, io_context, rng) } - -/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. -pub fn b2y2a_consume( - x: Rep3BigUintShare, - io_context: &mut IoContext, - rng: &mut R, -) -> IoResult> { - b2y2a(&x, io_context, rng) -} From 56787cc37162c674cf02d92626dc9fea4c219949 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:34:45 +0200 Subject: [PATCH 52/56] buffered version of the garbler/evaluator --- mpc-core/src/protocols/rep3/conversion.rs | 12 +- mpc-core/src/protocols/rep3/yao/evaluator.rs | 122 +++++++++--------- mpc-core/src/protocols/rep3/yao/garbler.rs | 124 +++++++++++-------- tests/tests/mpc/rep3.rs | 11 +- 4 files changed, 152 insertions(+), 117 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 61984bc99..81b7c9309 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -171,10 +171,9 @@ pub fn a2y( let converted = match io_context.id { PartyID::ID0 => { let mut evaluator = Rep3Evaluator::new(io_context); + evaluator.receive_circuit()?; let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2); - let res = GCUtils::garbled_circuits_error(res)?; - evaluator.receive_hash()?; - res + GCUtils::garbled_circuits_error(res)? } PartyID::ID1 | PartyID::ID2 => { let delta = match delta { @@ -187,7 +186,7 @@ pub fn a2y( let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2); let res = GCUtils::garbled_circuits_error(res)?; - garbler.send_hash()?; + garbler.send_circuit()?; res } }; @@ -215,6 +214,7 @@ pub fn y2a( let x23 = input_field_id2::(None, None, io_context, rng)?; let mut evaluator = Rep3Evaluator::new(io_context); + evaluator.receive_circuit()?; let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x, &x23); let x1 = GCUtils::garbled_circuits_error(x1)?; let x1 = evaluator.output_to_id0_and_id1(x1.wires())?; @@ -296,9 +296,9 @@ pub fn b2y( let converted = match io_context.id { PartyID::ID0 => { let mut evaluator = Rep3Evaluator::new(io_context); + // evaluator.receive_circuit()?; // No network used here let res = GarbledCircuits::xor_many(&mut evaluator, &x01, &x2); GCUtils::garbled_circuits_error(res)? - // evaluator.receive_hash()?; // No network used here } PartyID::ID1 | PartyID::ID2 => { let delta = match delta { @@ -311,7 +311,7 @@ pub fn b2y( let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); let res = GarbledCircuits::xor_many(&mut garbler, &x01, &x2); GCUtils::garbled_circuits_error(res)? - // garbler.send_hash()?; // No network used here + // garbler.send_circuit()?; // No network used here } }; diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index 53a8ab19d..e7bcdec62 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -22,7 +22,8 @@ pub struct Rep3Evaluator<'a, N: Rep3Network> { io_context: &'a mut IoContext, current_output: usize, current_gate: usize, - hash: Sha3_256, // For the ID2 to match everything sent with one hash + circuit: Vec<[u8; 16]>, + current_circuit_element: usize, } impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { @@ -37,10 +38,50 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { io_context, current_output: 0, current_gate: 0, - hash: Sha3_256::default(), + circuit: Vec::new(), + current_circuit_element: 0, } } + /// Get a gate from the circuit. + fn get_block_from_circuit(&mut self) -> IoResult { + if self.current_circuit_element >= self.circuit.len() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Too few gates in circuits.", + )); + } + let mut block = Block::default(); + block + .as_mut() + .copy_from_slice(&self.circuit[self.current_circuit_element]); + self.current_circuit_element += 1; + Ok(block) + } + + /// Receive the garbled circuit from the garblers. + pub fn receive_circuit(&mut self) -> IoResult<()> { + debug_assert!(self.circuit.is_empty()); + self.circuit = self.io_context.network.recv_many(PartyID::ID1)?; + self.current_circuit_element = 0; + + let mut hasher = Sha3_256::default(); + for block in &self.circuit { + hasher.update(block); + } + let is_hash = hasher.finalize(); + let should_hash: Vec = self.io_context.network.recv(PartyID::ID2)?; + + if should_hash != is_hash.as_slice() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Inconsistent Garbled Circuits: Hashes do not match!", + )); + } + + Ok(()) + } + /// The current non-free gate index of the garbling computation. fn current_gate(&mut self) -> usize { let current = self.current_gate; @@ -84,19 +125,30 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { /// Outputs the values to the garbler. fn output_garbler(&mut self, x: &[WireMod2]) -> IoResult<()> { + let mut blocks = Vec::with_capacity(x.len()); for val in x { let block = val.as_block(); - self.send_block(&block)?; + let mut gate = [0; 16]; + gate.copy_from_slice(block.as_ref()); + blocks.push(gate); } + self.io_context.network.send_many(PartyID::ID1, &blocks)?; + self.io_context.network.send_many(PartyID::ID2, &blocks)?; + Ok(()) } /// Outputs the values to the garbler with id1. fn output_garbler_id1(&mut self, x: &[WireMod2]) -> IoResult<()> { + let mut blocks = Vec::with_capacity(x.len()); for val in x { let block = val.as_block(); - self.io_context.network.send(PartyID::ID1, block.as_ref())?; + let mut gate = [0; 16]; + gate.copy_from_slice(block.as_ref()); + blocks.push(gate); } + self.io_context.network.send_many(PartyID::ID1, &blocks)?; + Ok(()) } @@ -105,9 +157,6 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { // Garbler's to evaluator let res = self.output_evaluator(x)?; - // Check consistency with the second garbled circuit before releasing the result - self.receive_hash()?; - // Evaluator to garbler self.output_garbler(x)?; @@ -119,68 +168,29 @@ impl<'a, N: Rep3Network> Rep3Evaluator<'a, N> { // Garbler's to evaluator let res = self.output_evaluator(x)?; - // Check consistency with the second garbled circuit before releasing the result - self.receive_hash()?; - // Evaluator to garbler self.output_garbler_id1(x)?; Ok(res) } - /// Receive a hash of ID2 (the second garbler) to verify the garbled circuit. - pub fn receive_hash(&mut self) -> IoResult<()> { - let data: Vec = self.io_context.network.recv(PartyID::ID2)?; - let mut hash = Sha3_256::default(); - std::mem::swap(&mut hash, &mut self.hash); - let digest = hash.finalize(); - if data != digest.as_slice() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Inconsistent Garbled Circuits: Hashes do not match!", - )); - } - - Ok(()) - } - - /// Send a block over the network to the garblers. - fn send_block(&mut self, block: &Block) -> IoResult<()> { - self.io_context.network.send(PartyID::ID1, block.as_ref())?; - self.io_context.network.send(PartyID::ID2, block.as_ref())?; - Ok(()) - } - - /// Receive a block from a specific party. - fn receive_block_from(&mut self, id: PartyID) -> IoResult { - GCUtils::receive_block_from(&mut self.io_context.network, id) - } - - /// Send a block over the network to the evaluator. - fn receive_block(&mut self) -> IoResult { - let block = self.receive_block_from(PartyID::ID1)?; - self.hash.update(block.as_ref()); // "Receive" from ID2 - - Ok(block) - } - /// Read `n` `Block`s from the channel. #[inline(always)] - fn read_blocks(&mut self, n: usize) -> IoResult> { - (0..n).map(|_| self.receive_block()).collect() + fn read_blocks_from_circuit(&mut self, n: usize) -> IoResult> { + (0..n).map(|_| self.get_block_from_circuit()).collect() } /// Read a Wire from the reader. - pub fn read_wire(&mut self) -> IoResult { - let block = self.receive_block()?; + pub fn read_wire_from_circuit(&mut self) -> IoResult { + let block = self.get_block_from_circuit()?; Ok(WireMod2::from_block(block, 2)) } /// Receive a bundle of wires over the established channel. - pub fn receive_bundle(&mut self, n: usize) -> IoResult> { + pub fn receive_bundle_from_circuit(&mut self, n: usize) -> IoResult> { let mut wires = Vec::with_capacity(n); for _ in 0..n { - let wire = WireMod2::from_block(self.receive_block()?, 2); + let wire = WireMod2::from_block(self.get_block_from_circuit()?, 2); wires.push(wire); } @@ -209,7 +219,7 @@ impl<'a, N: Rep3Network> Fancy for Rep3Evaluator<'a, N> { type Error = EvaluatorError; fn constant(&mut self, _: u16, _q: u16) -> Result { - Ok(self.read_wire()?) + Ok(self.read_wire_from_circuit()?) } fn output(&mut self, x: &WireMod2) -> Result, EvaluatorError> { @@ -217,7 +227,7 @@ impl<'a, N: Rep3Network> Fancy for Rep3Evaluator<'a, N> { let i = self.current_output(); // Receive the output ciphertext from the garbler - let ct = self.read_blocks(q as usize)?; + let ct = self.read_blocks_from_circuit(q as usize)?; // Attempt to brute force x using the output ciphertext let mut decoded = None; @@ -248,8 +258,8 @@ impl<'a, N: Rep3Network> FancyBinary for Rep3Evaluator<'a, N> { } fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { - let gate0 = self.receive_block()?; - let gate1 = self.receive_block()?; + let gate0 = self.get_block_from_circuit()?; + let gate1 = self.get_block_from_circuit()?; Ok(self.evaluate_and_gate(a, b, &gate0, &gate1)) } } diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index 5740067fd..ce8206567 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -30,6 +30,7 @@ pub struct Rep3Garbler<'a, N: Rep3Network> { current_gate: usize, rng: RngType, hash: Sha3_256, // For the ID2 to match everything sent with one hash + circuit: Vec<[u8; 16]>, } impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { @@ -53,9 +54,54 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { current_gate: 0, rng, hash: Sha3_256::default(), + circuit: Vec::new(), } } + /// Add the gate to the circuit + fn add_block_to_circuit(&mut self, block: &Block) { + match self.io_context.id { + PartyID::ID0 => { + panic!("Garbler should not be PartyID::ID0"); + } + PartyID::ID1 => { + let mut gate = [0; 16]; + gate.copy_from_slice(block.as_ref()); + self.circuit.push(gate); + } + PartyID::ID2 => { + self.hash.update(block.as_ref()); + } + } + } + + /// Sends the circuit to the evaluator + pub fn send_circuit(&mut self) -> IoResult<()> { + match self.io_context.id { + PartyID::ID0 => { + panic!("Garbler should not be PartyID::ID0"); + } + PartyID::ID1 => { + // Send the prepared circuit over the network to the evaluator + let mut empty_circuit = Vec::new(); + std::mem::swap(&mut empty_circuit, &mut self.circuit); + self.io_context + .network + .send_many(PartyID::ID0, &empty_circuit)?; + } + PartyID::ID2 => { + // Send the hash of the circuit to the evaluator + let mut hash = Sha3_256::default(); + std::mem::swap(&mut hash, &mut self.hash); + let digest = hash.finalize(); + self.io_context + .network + .send(PartyID::ID0, digest.as_slice())?; + } + } + Ok(()) + } + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires pub fn encode_field(&mut self, field: F) -> GCInputs { GCUtils::encode_field(field, &mut self.rng, self.delta) @@ -91,7 +137,13 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { /// Outputs the values to the garbler. fn output_garbler(&mut self, x: &[WireMod2]) -> IoResult> { - let blocks = self.read_blocks(x.len())?; + let blocks = self.read_blocks()?; + if blocks.len() != x.len() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid number of blocks received", + )); + } let mut result = Vec::with_capacity(x.len()); for (block, zero) in blocks.into_iter().zip(x.iter()) { @@ -115,7 +167,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { self.output_evaluator(x)?; // Check consistency with the second garbled circuit before receiving the result - self.send_hash()?; + self.send_circuit()?; // Evaluator to garbler self.output_garbler(x) @@ -127,7 +179,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { self.output_evaluator(x)?; // Check consistency with the second garbled circuit before receiving the result - self.send_hash()?; + self.send_circuit()?; // Evaluator to garbler if self.io_context.id == PartyID::ID1 { @@ -137,59 +189,29 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } } - /// As ID2, send a hash of the sended data to the evaluator. - pub fn send_hash(&mut self) -> IoResult<()> { - if self.io_context.id == PartyID::ID2 { - let mut hash = Sha3_256::default(); - std::mem::swap(&mut hash, &mut self.hash); - let digest = hash.finalize(); - self.io_context - .network - .send(PartyID::ID0, digest.as_slice())?; - } - Ok(()) - } - - /// Send a block over the network to the evaluator. - fn send_block(&mut self, block: &Block) -> IoResult<()> { - match self.io_context.id { - PartyID::ID0 => { - panic!("Garbler should not be PartyID::ID0"); - } - PartyID::ID1 => { - self.io_context.network.send(PartyID::ID0, block.as_ref())?; - } - PartyID::ID2 => { - self.hash.update(block.as_ref()); - } - } - Ok(()) - } - - fn receive_block_from(&mut self, id: PartyID) -> IoResult { - GCUtils::receive_block_from(&mut self.io_context.network, id) - } - - /// Read `n` `Block`s from the channel. + // Read `n` `Block`s from the channel. #[inline(always)] - fn read_blocks(&mut self, n: usize) -> IoResult> { - (0..n) - .map(|_| self.receive_block_from(PartyID::ID0)) - .collect() + fn read_blocks(&mut self) -> IoResult> { + let rcv: Vec<[u8; 16]> = self.io_context.network.recv_many(PartyID::ID0)?; + let mut result = Vec::with_capacity(rcv.len()); + for block in rcv { + let mut v = Block::default(); + v.as_mut().copy_from_slice(&block); + result.push(v); + } + Ok(result) } /// Send a wire over the established channel. - fn send_wire(&mut self, wire: &WireMod2) -> IoResult<()> { - self.send_block(&wire.as_block())?; - Ok(()) + fn add_wire_to_circuit(&mut self, wire: &WireMod2) { + self.add_block_to_circuit(&wire.as_block()); } /// Send a bundle of wires over the established channel. - pub fn send_bundle(&mut self, wires: &BinaryBundle) -> IoResult<()> { + pub fn add_bundle_to_circuit(&mut self, wires: &BinaryBundle) { for wire in wires.wires() { - self.send_wire(wire)?; + self.add_wire_to_circuit(wire); } - Ok(()) } /// Encode a wire, producing the zero wire as well as the encoded value. @@ -216,7 +238,7 @@ impl<'a, N: Rep3Network> Fancy for Rep3Garbler<'a, 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)); - self.send_wire(&wire)?; + self.add_wire_to_circuit(&wire); Ok(zero) } @@ -225,7 +247,7 @@ impl<'a, N: Rep3Network> Fancy for Rep3Garbler<'a, N> { let d = self.delta; for k in 0..2 { let block = x.plus(&d.cmul(k)).hash(output_tweak(i, k)); - self.send_block(&block)?; + self.add_block_to_circuit(&block); } Ok(None) } @@ -234,8 +256,8 @@ impl<'a, N: Rep3Network> Fancy for Rep3Garbler<'a, N> { impl<'a, N: Rep3Network> FancyBinary for Rep3Garbler<'a, N> { fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { let (gate0, gate1, c) = self.garble_and_gate(a, b); - self.send_block(&gate0)?; - self.send_block(&gate1)?; + self.add_block_to_circuit(&gate0); + self.add_block_to_circuit(&gate1); Ok(c) } diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index e1e93e260..698f2c22d 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -691,8 +691,8 @@ mod field_share { let y_ = garbler.encode_field(y); // This is without OT, just a simulation - garbler.send_bundle(&x_.evaluator_wires).unwrap(); - garbler.send_bundle(&y_.evaluator_wires).unwrap(); + garbler.add_bundle_to_circuit(&x_.evaluator_wires); + garbler.add_bundle_to_circuit(&y_.evaluator_wires); let circuit_output = GarbledCircuits::adder_mod_p::<_, ark_bn254::Fr>( &mut garbler, @@ -715,8 +715,9 @@ mod field_share { let n_bits = ark_bn254::Fr::MODULUS_BIT_SIZE as usize; // This is without OT, just a simulation - let x_ = evaluator.receive_bundle(n_bits).unwrap(); - let y_ = evaluator.receive_bundle(n_bits).unwrap(); + evaluator.receive_circuit().unwrap(); + let x_ = evaluator.receive_bundle_from_circuit(n_bits).unwrap(); + let y_ = evaluator.receive_bundle_from_circuit(n_bits).unwrap(); let circuit_output = GarbledCircuits::adder_mod_p::<_, ark_bn254::Fr>(&mut evaluator, &x_, &y_).unwrap(); @@ -763,6 +764,7 @@ mod field_share { let output = match id { PartyID::ID0 => { let mut evaluator = Rep3Evaluator::new(&mut rep3); + evaluator.receive_circuit().unwrap(); evaluator.output_all_parties(converted.wires()).unwrap() } PartyID::ID1 | PartyID::ID2 => { @@ -850,6 +852,7 @@ mod field_share { let output = match id { PartyID::ID0 => { let mut evaluator = Rep3Evaluator::new(&mut rep3); + evaluator.receive_circuit().unwrap(); evaluator.output_all_parties(converted.wires()).unwrap() } PartyID::ID1 | PartyID::ID2 => { From eba8032106475aaa99b4439cbdce6797141c13d6 Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:44:23 +0200 Subject: [PATCH 53/56] also send at oncein yao.rs --- mpc-core/src/protocols/rep3/yao.rs | 55 ++++++++++++---------- mpc-core/src/protocols/rep3/yao/garbler.rs | 2 +- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 2bbb7c526..581a54fbe 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -121,20 +121,6 @@ impl GCUtils { ))) } - fn receive_block_from(network: &mut N, id: PartyID) -> IoResult { - let data: Vec = network.recv(id)?; - if data.len() != 16 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "To little elements received", - )); - } - let mut v = Block::default(); - v.as_mut().copy_from_slice(&data); - - Ok(v) - } - pub(crate) fn collapse_bundle_to_lsb_bits_as_biguint(input: BinaryBundle) -> BigUint { let mut res = BigUint::zero(); for wire in input.wires().iter().rev() { @@ -151,12 +137,35 @@ impl GCUtils { network: &mut N, id: PartyID, ) -> IoResult> { - let mut x = Vec::with_capacity(n_bits); - for _ in 0..n_bits { - let block = GCUtils::receive_block_from(network, id)?; - x.push(WireMod2::from_block(block, 2)); + let rcv: Vec<[u8; 16]> = network.recv_many(id)?; + if rcv.len() != n_bits { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid number of elements received", + )); } - Ok(BinaryBundle::new(x)) + let mut result = Vec::with_capacity(rcv.len()); + for block in rcv { + let mut v = Block::default(); + v.as_mut().copy_from_slice(&block); + result.push(WireMod2::from_block(v, 2)); + } + Ok(BinaryBundle::new(result)) + } + + fn send_bundle_to( + input: &BinaryBundle, + network: &mut N, + id: PartyID, + ) -> IoResult<()> { + let mut blocks = Vec::with_capacity(input.size()); + for val in input.iter() { + let block = val.as_block(); + let mut gate = [0; 16]; + gate.copy_from_slice(block.as_ref()); + blocks.push(gate); + } + network.send_many(id, &blocks) } fn send_inputs( @@ -164,12 +173,8 @@ impl GCUtils { network: &mut N, garbler_id: PartyID, ) -> IoResult<()> { - for val in input.garbler_wires.iter() { - network.send(garbler_id, val.as_block().as_ref())?; - } - for val in input.evaluator_wires.iter() { - network.send(PartyID::ID0, val.as_block().as_ref())?; - } + Self::send_bundle_to(&input.garbler_wires, network, garbler_id)?; + Self::send_bundle_to(&input.evaluator_wires, network, PartyID::ID0)?; Ok(()) } diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index ce8206567..a9cd676b0 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -189,7 +189,7 @@ impl<'a, N: Rep3Network> Rep3Garbler<'a, N> { } } - // Read `n` `Block`s from the channel. + // Read `Block`s from the channel. #[inline(always)] fn read_blocks(&mut self) -> IoResult> { let rcv: Vec<[u8; 16]> = self.io_context.network.recv_many(PartyID::ID0)?; From 75b40aa245fc42ffafe15d604f8f3e8ca3cc6cfe Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:01:05 +0200 Subject: [PATCH 54/56] add the streaming evaluator/garbler again --- deny.toml | 6 +- mpc-core/src/protocols/rep3/yao.rs | 16 ++ mpc-core/src/protocols/rep3/yao/evaluator.rs | 2 +- mpc-core/src/protocols/rep3/yao/garbler.rs | 2 +- .../protocols/rep3/yao/streaming_evaluator.rs | 255 ++++++++++++++++++ .../protocols/rep3/yao/streaming_garbler.rs | 254 +++++++++++++++++ tests/tests/mpc/rep3.rs | 70 +++++ 7 files changed, 602 insertions(+), 3 deletions(-) create mode 100644 mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs create mode 100644 mpc-core/src/protocols/rep3/yao/streaming_garbler.rs diff --git a/deny.toml b/deny.toml index eacc0feb7..be9be771c 100644 --- a/deny.toml +++ b/deny.toml @@ -281,7 +281,11 @@ unknown-git = "deny" # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories -allow-git = ["https://github.com/noir-lang/noir", "https://github.com/jfecher/chumsky"] +allow-git = [ + "https://github.com/noir-lang/noir", + "https://github.com/jfecher/chumsky", + "https://github.com/GaloisInc/swanky", +] [sources.allow-org] # 1 or more github.com organizations to allow git sources for diff --git a/mpc-core/src/protocols/rep3/yao.rs b/mpc-core/src/protocols/rep3/yao.rs index 581a54fbe..7f9cf4aaa 100644 --- a/mpc-core/src/protocols/rep3/yao.rs +++ b/mpc-core/src/protocols/rep3/yao.rs @@ -5,6 +5,8 @@ pub mod circuits; pub mod evaluator; pub mod garbler; +pub mod streaming_evaluator; +pub mod streaming_garbler; use super::{ network::{IoContext, Rep3Network}, @@ -132,6 +134,20 @@ impl GCUtils { res } + fn receive_block_from(network: &mut N, id: PartyID) -> IoResult { + let data: Vec = network.recv(id)?; + if data.len() != 16 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "To little elements received", + )); + } + let mut v = Block::default(); + v.as_mut().copy_from_slice(&data); + + Ok(v) + } + fn receive_bundle_from( n_bits: usize, network: &mut N, diff --git a/mpc-core/src/protocols/rep3/yao/evaluator.rs b/mpc-core/src/protocols/rep3/yao/evaluator.rs index e7bcdec62..4e5399c66 100644 --- a/mpc-core/src/protocols/rep3/yao/evaluator.rs +++ b/mpc-core/src/protocols/rep3/yao/evaluator.rs @@ -1,6 +1,6 @@ //! Evaluator //! -//! This module contains the implementation of the evaluator for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). +//! This module contains the implementation of the evaluator for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). Thereby, the whole garbled circuit is buffered before given to the network. //! //! This file is heavily inspired by [fancy-garbling](https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/evaluator.rs) diff --git a/mpc-core/src/protocols/rep3/yao/garbler.rs b/mpc-core/src/protocols/rep3/yao/garbler.rs index a9cd676b0..1edab6025 100644 --- a/mpc-core/src/protocols/rep3/yao/garbler.rs +++ b/mpc-core/src/protocols/rep3/yao/garbler.rs @@ -1,6 +1,6 @@ //! Garbler //! -//! This module contains the implementation of the garbler for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). +//! This module contains the implementation of the garbler for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). Thereby, the whole garbled circuit is buffered before given to the network. //! //! This implementation is heavily inspired by [fancy-garbling](https://github.com/GaloisInc/swanky/blob/dev/fancy-garbling/src/garble/garbler.rs) diff --git a/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs b/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs new file mode 100644 index 000000000..d64112eed --- /dev/null +++ b/mpc-core/src/protocols/rep3/yao/streaming_evaluator.rs @@ -0,0 +1,255 @@ +//! Streaming Evaluator +//! +//! This module contains the implementation of the evaluator for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). Thereby, the garbled gates are sent out as soon as they are prepared. +//! +//! 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 crate::protocols::rep3::{ + id::PartyID, + network::{IoContext, Rep3Network}, + IoResult, +}; +use fancy_garbling::{ + errors::EvaluatorError, util::output_tweak, BinaryBundle, Fancy, FancyBinary, WireLabel, + WireMod2, +}; +use scuttlebutt::Block; +use sha3::{Digest, Sha3_256}; + +/// This struct implements the evaluator for replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). +pub struct StreamingRep3Evaluator<'a, N: Rep3Network> { + io_context: &'a mut IoContext, + current_output: usize, + current_gate: usize, + hash: Sha3_256, // For the ID2 to match everything sent with one hash +} + +impl<'a, N: Rep3Network> StreamingRep3Evaluator<'a, N> { + /// Create a new garbler. + pub fn new(io_context: &'a mut IoContext) -> Self { + let id = io_context.id; + if id != PartyID::ID0 { + panic!("Garbler should be PartyID::ID0") + } + + Self { + io_context, + current_output: 0, + current_gate: 0, + hash: Sha3_256::default(), + } + } + + /// The current non-free gate index of the garbling computation. + fn current_gate(&mut self) -> usize { + let current = self.current_gate; + self.current_gate += 1; + current + } + + /// The current output index of the garbling computation. + fn current_output(&mut self) -> usize { + let current = self.current_output; + self.current_output += 1; + current + } + + /// Outputs the values to the evaluator. + fn output_evaluator(&mut self, x: &[WireMod2]) -> IoResult> { + let result = self.outputs(x).or(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Output failed", + )))?; + match result { + Some(outputs) => { + let mut res = Vec::with_capacity(outputs.len()); + for val in outputs { + if val >= 2 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Value is not a bool", + )); + } + res.push(val == 1); + } + Ok(res) + } + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "No output received", + )), + } + } + + /// Outputs the values to the garbler. + fn output_garbler(&mut self, x: &[WireMod2]) -> IoResult<()> { + for val in x { + let block = val.as_block(); + self.send_block(&block)?; + } + Ok(()) + } + + /// Outputs the values to the garbler with id1. + fn output_garbler_id1(&mut self, x: &[WireMod2]) -> IoResult<()> { + for val in x { + let block = val.as_block(); + self.io_context.network.send(PartyID::ID1, block.as_ref())?; + } + Ok(()) + } + + /// Outputs the value to all parties + pub fn output_all_parties(&mut self, x: &[WireMod2]) -> IoResult> { + // Garbler's to evaluator + let res = self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before releasing the result + self.receive_hash()?; + + // Evaluator to garbler + self.output_garbler(x)?; + + Ok(res) + } + + /// Outputs the value to parties ID0 and ID1 + pub fn output_to_id0_and_id1(&mut self, x: &[WireMod2]) -> IoResult> { + // Garbler's to evaluator + let res = self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before releasing the result + self.receive_hash()?; + + // Evaluator to garbler + self.output_garbler_id1(x)?; + + Ok(res) + } + + /// Receive a hash of ID2 (the second garbler) to verify the garbled circuit. + pub fn receive_hash(&mut self) -> IoResult<()> { + let data: Vec = self.io_context.network.recv(PartyID::ID2)?; + let mut hash = Sha3_256::default(); + std::mem::swap(&mut hash, &mut self.hash); + let digest = hash.finalize(); + if data != digest.as_slice() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Inconsistent Garbled Circuits: Hashes do not match!", + )); + } + + Ok(()) + } + + /// Send a block over the network to the garblers. + fn send_block(&mut self, block: &Block) -> IoResult<()> { + self.io_context.network.send(PartyID::ID1, block.as_ref())?; + self.io_context.network.send(PartyID::ID2, block.as_ref())?; + Ok(()) + } + + /// Receive a block from a specific party. + fn receive_block_from(&mut self, id: PartyID) -> IoResult { + GCUtils::receive_block_from(&mut self.io_context.network, id) + } + + /// Send a block over the network to the evaluator. + fn receive_block(&mut self) -> IoResult { + let block = self.receive_block_from(PartyID::ID1)?; + self.hash.update(block.as_ref()); // "Receive" from ID2 + + Ok(block) + } + + /// Read `n` `Block`s from the channel. + #[inline(always)] + fn read_blocks(&mut self, n: usize) -> IoResult> { + (0..n).map(|_| self.receive_block()).collect() + } + + /// Read a Wire from the reader. + pub fn read_wire(&mut self) -> IoResult { + let block = self.receive_block()?; + Ok(WireMod2::from_block(block, 2)) + } + + /// Receive a bundle of wires over the established channel. + pub fn receive_bundle(&mut self, n: usize) -> IoResult> { + let mut wires = Vec::with_capacity(n); + for _ in 0..n { + let wire = WireMod2::from_block(self.receive_block()?, 2); + wires.push(wire); + } + + Ok(BinaryBundle::new(wires)) + } + + /// Evaluates an 'and' gate given two inputs wires and two half-gates from the garbler. + /// + /// Outputs C = A & B + /// + /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. + fn evaluate_and_gate( + &mut self, + a: &WireMod2, + b: &WireMod2, + gate0: &Block, + gate1: &Block, + ) -> WireMod2 { + let gate_num = self.current_gate(); + GCUtils::evaluate_and_gate(gate_num, a, b, gate0, gate1) + } +} + +impl<'a, N: Rep3Network> Fancy for StreamingRep3Evaluator<'a, N> { + type Item = WireMod2; + type Error = EvaluatorError; + + fn constant(&mut self, _: u16, _q: u16) -> Result { + Ok(self.read_wire()?) + } + + fn output(&mut self, x: &WireMod2) -> Result, EvaluatorError> { + let q = 2; + let i = self.current_output(); + + // Receive the output ciphertext from the garbler + let ct = self.read_blocks(q as usize)?; + + // Attempt to brute force x using the output ciphertext + let mut decoded = None; + for k in 0..q { + let hashed_wire = x.hash(output_tweak(i, k)); + if hashed_wire == ct[k as usize] { + decoded = Some(k); + break; + } + } + + if let Some(output) = decoded { + Ok(Some(output)) + } else { + Err(EvaluatorError::DecodingFailed) + } + } +} + +impl<'a, N: Rep3Network> FancyBinary for StreamingRep3Evaluator<'a, N> { + /// Negate is a noop for the evaluator + fn negate(&mut self, x: &Self::Item) -> Result { + Ok(*x) + } + + fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Result { + Ok(x.plus(y)) + } + + fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { + let gate0 = self.receive_block()?; + let gate1 = self.receive_block()?; + Ok(self.evaluate_and_gate(a, b, &gate0, &gate1)) + } +} diff --git a/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs b/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs new file mode 100644 index 000000000..bcc21d4ba --- /dev/null +++ b/mpc-core/src/protocols/rep3/yao/streaming_garbler.rs @@ -0,0 +1,254 @@ +//! Streaming Garbler +//! +//! This module contains the implementation of the garbler for the replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). Thereby, the garbled gates are sent out as soon as they are prepared. +//! +//! 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 crate::{ + protocols::rep3::{ + id::PartyID, + network::{IoContext, Rep3Network}, + IoResult, + }, + RngType, +}; +use ark_ff::PrimeField; +use core::panic; +use fancy_garbling::{ + errors::GarblerError, util::output_tweak, BinaryBundle, Fancy, FancyBinary, WireLabel, WireMod2, +}; +use rand::SeedableRng; +use scuttlebutt::Block; +use sha3::{Digest, Sha3_256}; + +/// This struct implements the garbler for replicated 3-party garbled circuits as described in [ABY3](https://eprint.iacr.org/2018/403.pdf). +pub struct StreamingRep3Garbler<'a, N: Rep3Network> { + io_context: &'a mut IoContext, + delta: WireMod2, + current_output: usize, + current_gate: usize, + rng: RngType, + hash: Sha3_256, // For the ID2 to match everything sent with one hash +} + +impl<'a, N: Rep3Network> StreamingRep3Garbler<'a, N> { + /// Create a new garbler. + pub fn new(io_context: &'a mut IoContext) -> Self { + let mut res = Self::new_with_delta(io_context, WireMod2::default()); + res.delta = GCUtils::random_delta(&mut res.rng); + res + } + + /// Create a new garbler with existing delta. + pub fn new_with_delta(io_context: &'a mut IoContext, delta: WireMod2) -> Self { + let id = io_context.id; + let seed = io_context.rngs.generate_garbler_randomness(id); + let rng = RngType::from_seed(seed); + + Self { + io_context, + delta, + current_output: 0, + current_gate: 0, + rng, + hash: Sha3_256::default(), + } + } + + /// This puts the X_0 values into garbler_wires and X_c values into evaluator_wires + pub fn encode_field(&mut self, field: F) -> GCInputs { + GCUtils::encode_field(field, &mut self.rng, self.delta) + } + + /// Consumes the Garbler and returns the delta. + pub fn into_delta(self) -> WireMod2 { + self.delta + } + + /// The current non-free gate index of the garbling computation + fn current_gate(&mut self) -> usize { + let current = self.current_gate; + self.current_gate += 1; + current + } + + /// The current output index of the garbling computation. + fn current_output(&mut self) -> usize { + let current = self.current_output; + self.current_output += 1; + current + } + + /// Outputs the values to the evaluator. + fn output_evaluator(&mut self, x: &[WireMod2]) -> IoResult<()> { + self.outputs(x).or(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Output failed", + )))?; + Ok(()) + } + + /// Outputs the values to the garbler. + fn output_garbler(&mut self, x: &[WireMod2]) -> IoResult> { + let blocks = self.read_blocks(x.len())?; + + let mut result = Vec::with_capacity(x.len()); + for (block, zero) in blocks.into_iter().zip(x.iter()) { + if block == zero.as_block() { + result.push(false); + } else if block == zero.plus(&self.delta).as_block() { + result.push(true); + } else { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid block received", + )); + } + } + Ok(result) + } + + /// Outputs the value to all parties + pub fn output_all_parties(&mut self, x: &[WireMod2]) -> IoResult> { + // Garbler's to evaluator + self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before receiving the result + self.send_hash()?; + + // Evaluator to garbler + self.output_garbler(x) + } + + /// Outputs the value to parties ID0 and ID1 + pub fn output_to_id0_and_id1(&mut self, x: &[WireMod2]) -> IoResult>> { + // Garbler's to evaluator + self.output_evaluator(x)?; + + // Check consistency with the second garbled circuit before receiving the result + self.send_hash()?; + + // Evaluator to garbler + if self.io_context.id == PartyID::ID1 { + Ok(Some(self.output_garbler(x)?)) + } else { + Ok(None) + } + } + + /// As ID2, send a hash of the sended data to the evaluator. + pub fn send_hash(&mut self) -> IoResult<()> { + if self.io_context.id == PartyID::ID2 { + let mut hash = Sha3_256::default(); + std::mem::swap(&mut hash, &mut self.hash); + let digest = hash.finalize(); + self.io_context + .network + .send(PartyID::ID0, digest.as_slice())?; + } + Ok(()) + } + + /// Send a block over the network to the evaluator. + fn send_block(&mut self, block: &Block) -> IoResult<()> { + match self.io_context.id { + PartyID::ID0 => { + panic!("Garbler should not be PartyID::ID0"); + } + PartyID::ID1 => { + self.io_context.network.send(PartyID::ID0, block.as_ref())?; + } + PartyID::ID2 => { + self.hash.update(block.as_ref()); + } + } + Ok(()) + } + + fn receive_block_from(&mut self, id: PartyID) -> IoResult { + GCUtils::receive_block_from(&mut self.io_context.network, id) + } + + /// Read `n` `Block`s from the channel. + #[inline(always)] + fn read_blocks(&mut self, n: usize) -> IoResult> { + (0..n) + .map(|_| self.receive_block_from(PartyID::ID0)) + .collect() + } + + /// Send a wire over the established channel. + fn send_wire(&mut self, wire: &WireMod2) -> IoResult<()> { + self.send_block(&wire.as_block())?; + Ok(()) + } + + /// Send a bundle of wires over the established channel. + pub fn send_bundle(&mut self, wires: &BinaryBundle) -> IoResult<()> { + for wire in wires.wires() { + self.send_wire(wire)?; + } + Ok(()) + } + + /// Encode a wire, producing the zero wire as well as the encoded value. + pub fn encode_wire(&mut self, val: u16) -> (WireMod2, WireMod2) { + GCUtils::encode_wire(&mut self.rng, &self.delta, val) + } + + /// Garbles an 'and' gate given two input wires and the delta. + /// + /// Outputs a tuple consisting of the two gates (that should be transfered to the evaluator) + /// and the next wire label for the garbler. + /// + /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`. + fn garble_and_gate(&mut self, a: &WireMod2, b: &WireMod2) -> (Block, Block, WireMod2) { + let gate_num = self.current_gate(); + GCUtils::garble_and_gate(gate_num, a, b, &self.delta) + } +} + +impl<'a, N: Rep3Network> Fancy for StreamingRep3Garbler<'a, N> { + type Item = WireMod2; + type Error = GarblerError; + + 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)); + self.send_wire(&wire)?; + Ok(zero) + } + + fn output(&mut self, x: &WireMod2) -> Result, GarblerError> { + let i = self.current_output(); + let d = self.delta; + for k in 0..2 { + let block = x.plus(&d.cmul(k)).hash(output_tweak(i, k)); + self.send_block(&block)?; + } + Ok(None) + } +} + +impl<'a, N: Rep3Network> FancyBinary for StreamingRep3Garbler<'a, N> { + fn and(&mut self, a: &Self::Item, b: &Self::Item) -> Result { + let (gate0, gate1, c) = self.garble_and_gate(a, b); + self.send_block(&gate0)?; + self.send_block(&gate1)?; + Ok(c) + } + + fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Result { + Ok(x.plus(y)) + } + + /// We can negate by having garbler xor wire with Delta + /// + /// Since we treat all garbler wires as zero, + /// xoring with delta conceptually negates the value of the wire + fn negate(&mut self, x: &Self::Item) -> Result { + let delta = self.delta; + self.xor(&delta, x) + } +} diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index 698f2c22d..c587abd60 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -8,6 +8,8 @@ mod field_share { use mpc_core::protocols::rep3::yao::circuits::GarbledCircuits; use mpc_core::protocols::rep3::yao::evaluator::Rep3Evaluator; use mpc_core::protocols::rep3::yao::garbler::Rep3Garbler; + use mpc_core::protocols::rep3::yao::streaming_evaluator::StreamingRep3Evaluator; + use mpc_core::protocols::rep3::yao::streaming_garbler::StreamingRep3Garbler; use mpc_core::protocols::rep3::yao::GCUtils; use mpc_core::protocols::rep3::{self, arithmetic, network::IoContext}; use rand::thread_rng; @@ -737,6 +739,74 @@ mod field_share { assert_eq!(result3, should_result); } + #[test] + fn rep3_streaming_gc() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let y = ark_bn254::Fr::rand(&mut rng); + let should_result = x + y; + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + let [net1, net2, net3] = test_network.get_party_networks(); + + // Both Garblers + for (net, tx) in izip!([net2, net3], [tx2, tx3]) { + thread::spawn(move || { + let mut ctx = IoContext::init(net).unwrap(); + + let mut garbler = StreamingRep3Garbler::new(&mut ctx); + let x_ = garbler.encode_field(x); + let y_ = garbler.encode_field(y); + + // This is without OT, just a simulation + garbler.send_bundle(&x_.evaluator_wires).unwrap(); + garbler.send_bundle(&y_.evaluator_wires).unwrap(); + + let circuit_output = GarbledCircuits::adder_mod_p::<_, ark_bn254::Fr>( + &mut garbler, + &x_.garbler_wires, + &y_.garbler_wires, + ) + .unwrap(); + + let output = garbler.output_all_parties(circuit_output.wires()).unwrap(); + let add = GCUtils::bits_to_field::(output).unwrap(); + tx.send(add) + }); + } + + // The evaluator (ID0) + thread::spawn(move || { + let mut ctx = IoContext::init(net1).unwrap(); + + let mut evaluator = StreamingRep3Evaluator::new(&mut ctx); + let n_bits = ark_bn254::Fr::MODULUS_BIT_SIZE as usize; + + // This is without OT, just a simulation + let x_ = evaluator.receive_bundle(n_bits).unwrap(); + let y_ = evaluator.receive_bundle(n_bits).unwrap(); + + let circuit_output = + GarbledCircuits::adder_mod_p::<_, ark_bn254::Fr>(&mut evaluator, &x_, &y_).unwrap(); + + let output = evaluator + .output_all_parties(circuit_output.wires()) + .unwrap(); + let add = GCUtils::bits_to_field::(output).unwrap(); + tx1.send(add) + }); + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + assert_eq!(result1, should_result); + assert_eq!(result2, should_result); + assert_eq!(result3, should_result); + } + #[test] fn rep3_a2y() { let test_network = Rep3TestNetwork::default(); From 4963971bcc9803b4ef297156df78385c1229102d Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:42:31 +0200 Subject: [PATCH 55/56] add conversions for streaming and buffered garbler/evaluator --- mpc-core/src/protocols/rep3/conversion.rs | 211 +++++++++++++++++----- tests/tests/mpc/rep3.rs | 204 ++++++++++++++++++++- 2 files changed, 365 insertions(+), 50 deletions(-) diff --git a/mpc-core/src/protocols/rep3/conversion.rs b/mpc-core/src/protocols/rep3/conversion.rs index 81b7c9309..a03cad3ab 100644 --- a/mpc-core/src/protocols/rep3/conversion.rs +++ b/mpc-core/src/protocols/rep3/conversion.rs @@ -9,7 +9,9 @@ use super::{ id::PartyID, network::{IoContext, Rep3Network}, yao::{ - self, circuits::GarbledCircuits, evaluator::Rep3Evaluator, garbler::Rep3Garbler, GCUtils, + self, circuits::GarbledCircuits, evaluator::Rep3Evaluator, garbler::Rep3Garbler, + streaming_evaluator::StreamingRep3Evaluator, streaming_garbler::StreamingRep3Garbler, + GCUtils, }, IoResult, Rep3BigUintShare, Rep3PrimeFieldShare, }; @@ -194,6 +196,103 @@ pub fn a2y( Ok(converted) } +/// Transforms the replicated shared value x from an arithmetic sharing to a yao sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into wires, such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x. Uses the Streaming Garbler/Evaluator. +pub fn a2y_streaming( + x: Rep3PrimeFieldShare, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let [x01, x2] = yao::joint_input_arithmetic_added(x, delta, io_context, rng)?; + + let converted = match io_context.id { + PartyID::ID0 => { + let mut evaluator = StreamingRep3Evaluator::new(io_context); + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x01, &x2); + let res = GCUtils::garbled_circuits_error(res)?; + evaluator.receive_hash()?; + res + } + PartyID::ID1 | PartyID::ID2 => { + let delta = match delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + let mut garbler = StreamingRep3Garbler::new_with_delta(io_context, delta); + let res = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x01, &x2); + let res = GCUtils::garbled_circuits_error(res)?; + garbler.send_hash()?; + res + } + }; + + Ok(converted) +} + +macro_rules! y2a_impl_p1 { + ($garbler:ty,$x:expr,$delta:expr,$io_context:expr,$rng:expr,$res:expr) => {{ + let delta = match $delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + let k2 = $io_context.rngs.bitcomp1.random_fes_3keys::(); + $res.a = (k2.0 + k2.1 + k2.2).neg(); + let x23 = input_field_id2::(None, None, $io_context, $rng)?; + + let mut garbler = <$garbler>::new_with_delta($io_context, delta); + let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &$x, &x23); + let x1 = 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", + ))?, + }; + $res.b = GCUtils::bits_to_field(x1)?; + }}; +} + +macro_rules! y2a_impl_p2 { + ($garbler:ty,$x:expr,$delta:expr,$io_context:expr,$rng:expr,$res:expr) => {{ + let delta = match $delta { + Some(delta) => delta, + None => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No delta provided", + ))?, + }; + + let k2 = $io_context.rngs.bitcomp1.random_fes_3keys::(); + let k3 = $io_context.rngs.bitcomp2.random_fes_3keys::(); + let k2_comp = k2.0 + k2.1 + k2.2; + let k3_comp = k3.0 + k3.1 + k3.2; + let x23 = Some(k2_comp + k3_comp); + $res.a = k3_comp.neg(); + $res.b = k2_comp.neg(); + let x23 = input_field_id2(x23, Some(delta), $io_context, $rng)?; + + let mut garbler = <$garbler>::new_with_delta($io_context, delta); + let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &$x, &x23); + let x1 = 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", + ))?; + } + }}; +} + /// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x gets transformed into x = x_1 + x_2 + x_3. /// /// Keep in mind: Only works if the input is actually a binary sharing of a valid field element @@ -221,59 +320,46 @@ pub fn y2a( res.a = GCUtils::bits_to_field(x1)?; } PartyID::ID1 => { - let delta = match delta { - Some(delta) => delta, - None => Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "No delta provided", - ))?, - }; - - let k2 = io_context.rngs.bitcomp1.random_fes_3keys::(); - res.a = (k2.0 + k2.1 + k2.2).neg(); - let x23 = input_field_id2::(None, None, io_context, rng)?; - - let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); - let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); - let x1 = 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", - ))?, - }; - res.b = GCUtils::bits_to_field(x1)?; + y2a_impl_p1!(Rep3Garbler, x, delta, io_context, rng, res) } PartyID::ID2 => { - let delta = match delta { - Some(delta) => delta, - None => Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "No delta provided", - ))?, - }; + y2a_impl_p2!(Rep3Garbler, x, delta, io_context, rng, res) + } + }; - let k2 = io_context.rngs.bitcomp1.random_fes_3keys::(); + Ok(res) +} + +/// Transforms the shared value x from a yao sharing to an arithmetic sharing. I.e., the sharing such that the garbler have keys (k_0, delta) for each bit of x, while the evaluator has k_x = k_0 xor delta * x gets transformed into x = x_1 + x_2 + x_3. Uses the Streaming Garbler/Evaluator. +/// +/// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +/// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. These adaptions need to be encoded into a garbled circuit. + +pub fn y2a_streaming( + x: BinaryBundle, + delta: Option, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let mut res = Rep3PrimeFieldShare::zero_share(); + + match io_context.id { + PartyID::ID0 => { let k3 = io_context.rngs.bitcomp2.random_fes_3keys::(); - let k2_comp = k2.0 + k2.1 + k2.2; - let k3_comp = k3.0 + k3.1 + k3.2; - let x23 = Some(k2_comp + k3_comp); - res.a = k3_comp.neg(); - res.b = k2_comp.neg(); - let x23 = input_field_id2(x23, Some(delta), io_context, rng)?; + res.b = (k3.0 + k3.1 + k3.2).neg(); + let x23 = input_field_id2::(None, None, io_context, rng)?; - let mut garbler = Rep3Garbler::new_with_delta(io_context, delta); - let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut garbler, &x, &x23); + let mut evaluator = StreamingRep3Evaluator::new(io_context); + let x1 = GarbledCircuits::adder_mod_p::<_, F>(&mut evaluator, &x, &x23); let x1 = 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", - ))?; - } + let x1 = evaluator.output_to_id0_and_id1(x1.wires())?; + res.a = GCUtils::bits_to_field(x1)?; + } + PartyID::ID1 => { + y2a_impl_p1!(StreamingRep3Garbler, x, delta, io_context, rng, res) + } + PartyID::ID2 => { + y2a_impl_p2!(StreamingRep3Garbler, x, delta, io_context, rng, res) } }; @@ -295,12 +381,14 @@ pub fn b2y( let converted = match io_context.id { PartyID::ID0 => { + // There is no code difference between Rep3Evaluator and StreamingRep3Evaluator let mut evaluator = Rep3Evaluator::new(io_context); // evaluator.receive_circuit()?; // No network used here let res = GarbledCircuits::xor_many(&mut evaluator, &x01, &x2); GCUtils::garbled_circuits_error(res)? } PartyID::ID1 | PartyID::ID2 => { + // There is no code difference between Rep3Garbler and StreamingRep3Garbler let delta = match delta { Some(delta) => delta, None => Err(std::io::Error::new( @@ -351,7 +439,7 @@ pub fn y2b( Ok(converted) } -/// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. . +/// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. pub fn a2y2b( x: Rep3PrimeFieldShare, io_context: &mut IoContext, @@ -362,6 +450,17 @@ pub fn a2y2b( y2b(y, io_context) } +/// Transforms the replicated shared value x from an arithmetic sharing to a binary sharing. I.e., x = x_1 + x_2 + x_3 gets transformed into x = x'_1 xor x'_2 xor x'_3. Uses the Streaming Garbler/Evaluator. +pub fn a2y2b_streaming( + x: Rep3PrimeFieldShare, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let delta = io_context.rngs.generate_random_garbler_delta(io_context.id); + let y = a2y_streaming(x, delta, io_context, rng)?; + y2b(y, io_context) +} + /// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. /// /// Keep in mind: Only works if the input is actually a binary sharing of a valid field element @@ -375,3 +474,17 @@ pub fn b2y2a( let y = b2y(x, delta, io_context, rng)?; y2a(y, delta, io_context, rng) } + +/// Transforms the replicated shared value x from a binary sharing to an arithmetic sharing. I.e., x = x_1 xor x_2 xor x_3 gets transformed into x = x'_1 + x'_2 + x'_3. This implementations goes through the yao protocol and currently works only for a binary sharing of a valid field element, i.e., x = x_1 xor x_2 xor x_3 < p. Uses the Streaming Garbler/Evaluator. +/// +/// Keep in mind: Only works if the input is actually a binary sharing of a valid field element +/// If the input has the correct number of bits, but is >= P, then either x can be reduced with self.low_depth_sub_p_cmux(x) first, or self.low_depth_binary_add_2_mod_p(x, y) is extended to subtract 2P in parallel as well. The second solution requires another multiplexer in the end. +pub fn b2y2a_streaming( + x: &Rep3BigUintShare, + io_context: &mut IoContext, + rng: &mut R, +) -> IoResult> { + let delta = io_context.rngs.generate_random_garbler_delta(io_context.id); + let y = b2y(x, delta, io_context, rng)?; + y2a_streaming(y, delta, io_context, rng) +} diff --git a/tests/tests/mpc/rep3.rs b/tests/tests/mpc/rep3.rs index c587abd60..93f012178 100644 --- a/tests/tests/mpc/rep3.rs +++ b/tests/tests/mpc/rep3.rs @@ -613,6 +613,39 @@ mod field_share { assert_eq!(is_result_f, x); } + #[test] + fn rep3_a2y2b_streaming() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_field_element(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for ((net, tx), x) in test_network + .get_party_networks() + .into_iter() + .zip([tx1, tx2, tx3]) + .zip(x_shares.into_iter()) + { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let mut rng = thread_rng(); + tx.send(conversion::a2y2b_streaming(x, &mut rep3, &mut rng).unwrap()) + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_binary_element(result1, result2, result3); + + let should_result = x.into(); + assert_eq!(is_result, should_result); + let is_result_f: ark_bn254::Fr = is_result.into(); + assert_eq!(is_result_f, x); + } + #[test] fn rep3_b2a() { let test_network = Rep3TestNetwork::default(); @@ -670,6 +703,35 @@ mod field_share { assert_eq!(is_result, x); } + #[test] + fn rep3_b2y2a_streaming() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_biguint(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for ((net, tx), x) in test_network + .get_party_networks() + .into_iter() + .zip([tx1, tx2, tx3]) + .zip(x_shares.into_iter()) + { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let mut rng = thread_rng(); + tx.send(conversion::b2y2a_streaming(&x, &mut rep3, &mut rng).unwrap()) + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_element(result1, result2, result3); + assert_eq!(is_result, x); + } + #[test] fn rep3_gc() { let test_network = Rep3TestNetwork::default(); @@ -740,7 +802,7 @@ mod field_share { } #[test] - fn rep3_streaming_gc() { + fn rep3_gc_streaming() { let test_network = Rep3TestNetwork::default(); let mut rng = thread_rng(); let x = ark_bn254::Fr::rand(&mut rng); @@ -856,6 +918,55 @@ mod field_share { assert_eq!(result3, x); } + #[test] + fn rep3_a2y_streaming() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_field_element(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let id = rep3.network.id; + let delta = rep3.rngs.generate_random_garbler_delta(id); + + let mut rng = thread_rng(); + let converted = conversion::a2y_streaming(x, delta, &mut rep3, &mut rng).unwrap(); + + let output = match id { + PartyID::ID0 => { + let mut evaluator = StreamingRep3Evaluator::new(&mut rep3); + evaluator.output_all_parties(converted.wires()).unwrap() + } + PartyID::ID1 | PartyID::ID2 => { + let mut garbler = + StreamingRep3Garbler::new_with_delta(&mut rep3, delta.unwrap()); + garbler.output_all_parties(converted.wires()).unwrap() + } + }; + + tx.send(GCUtils::bits_to_field::(output).unwrap()) + .unwrap(); + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + assert_eq!(result1, x); + assert_eq!(result2, x); + assert_eq!(result3, x); + } + #[test] fn rep3_y2a() { let test_network = Rep3TestNetwork::default(); @@ -895,6 +1006,49 @@ mod field_share { assert_eq!(is_result, x); } + #[test] + fn rep3_y2a_streaming() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let delta = GCUtils::random_delta(&mut rng); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = GCUtils::encode_field(x, &mut rng, delta); + let x_shares = [ + x_shares.evaluator_wires, + x_shares.garbler_wires.to_owned(), + x_shares.garbler_wires, + ]; + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + for (net, tx, x) in izip!( + test_network.get_party_networks().into_iter(), + [tx1, tx2, tx3], + x_shares.into_iter() + ) { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let mut rng = thread_rng(); + let converted = conversion::y2a_streaming::( + x, + Some(delta), + &mut rep3, + &mut rng, + ) + .unwrap(); + tx.send(converted).unwrap(); + }); + } + + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + let is_result = rep3::combine_field_element(result1, result2, result3); + assert_eq!(is_result, x); + } + #[test] fn rep3_b2y() { let test_network = Rep3TestNetwork::default(); @@ -943,6 +1097,54 @@ mod field_share { assert_eq!(result3, x); } + #[test] + fn rep3_b2y_streaming() { + let test_network = Rep3TestNetwork::default(); + let mut rng = thread_rng(); + let x = ark_bn254::Fr::rand(&mut rng); + let x_shares = rep3::share_biguint(x, &mut rng); + + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + for ((net, tx), x) in test_network + .get_party_networks() + .into_iter() + .zip([tx1, tx2, tx3]) + .zip(x_shares.into_iter()) + { + thread::spawn(move || { + let mut rep3 = IoContext::init(net).unwrap(); + let id = rep3.network.id; + let delta = rep3.rngs.generate_random_garbler_delta(id); + + let mut rng = thread_rng(); + let converted = conversion::b2y(&x, delta, &mut rep3, &mut rng).unwrap(); + + let output = match id { + PartyID::ID0 => { + let mut evaluator = StreamingRep3Evaluator::new(&mut rep3); + evaluator.output_all_parties(converted.wires()).unwrap() + } + PartyID::ID1 | PartyID::ID2 => { + let mut garbler = + StreamingRep3Garbler::new_with_delta(&mut rep3, delta.unwrap()); + garbler.output_all_parties(converted.wires()).unwrap() + } + }; + + tx.send(GCUtils::bits_to_field::(output).unwrap()) + .unwrap(); + }); + } + let result1 = rx1.recv().unwrap(); + let result2 = rx2.recv().unwrap(); + let result3 = rx3.recv().unwrap(); + assert_eq!(result1, x); + assert_eq!(result2, x); + assert_eq!(result3, x); + } + #[test] fn rep3_y2b() { let test_network = Rep3TestNetwork::default(); From 10f5547ff6c1fa7a0c856452792aa156629858ed Mon Sep 17 00:00:00 2001 From: Roman Walch <9820846+rw0x0@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:47:39 +0200 Subject: [PATCH 56/56] add ff from galois inc --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index be9be771c..2d1094d8c 100644 --- a/deny.toml +++ b/deny.toml @@ -285,6 +285,7 @@ allow-git = [ "https://github.com/noir-lang/noir", "https://github.com/jfecher/chumsky", "https://github.com/GaloisInc/swanky", + "https://github.com/GaloisInc/ff", ] [sources.allow-org]