From 5bf7e11abacf9ea42b50aed11ab86893047b427e Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Thu, 5 Dec 2024 09:47:51 +0100 Subject: [PATCH 1/8] Included ID in cointossing to prevent parties from cancelling out the randomness of other parties --- src/faand.rs | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index 784b3ad..8a05ed3 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -112,6 +112,14 @@ pub(crate) async fn broadcast_and_verify< if vec.is_empty() { return Err(Error::EmptyVector); } + let mut hash_vecs: Vec<[u128; 2]> = vec![[0; 2]; n]; + for k in (0..n).filter(|k| *k != i) { + if let Ok(hash) = hash_vec(&vec[k]) { + hash_vecs[k] = hash; + } else { + return Err(Error::EmptyVector); + } + } // Step 1: Send the vector to all parties that does not included its already sent value // (for index i) and the value it received from the party it is sending to (index k). for k in (0..n).filter(|k| *k != i) { @@ -120,7 +128,7 @@ pub(crate) async fn broadcast_and_verify< if vec[j].is_empty() { return Err(Error::EmptyVector); } - modified_vec[j] = Some(hash_vec(&vec[j])?); + modified_vec[j] = Some(hash_vecs[j]); } send_to(channel, k, phase, &modified_vec).await?; } @@ -132,7 +140,7 @@ pub(crate) async fn broadcast_and_verify< for j in (0..n).filter(|j| *j != i && *j != k) { if vec_k[j].is_none() { return Err(Error::EmptyVector); - } else if vec_k[j] != Some(hash_vec(&vec[j])?) { + } else if vec_k[j] != Some(hash_vecs[j]) { return Err(Error::BroadcastError); } } @@ -154,21 +162,22 @@ pub(crate) async fn shared_rng( n: usize, ) -> Result<(ChaCha20Rng, Vec>>), Error> { // Step 1 a) Generate a random 256-bit seed for multi-party cointossing and commit to it. - let r = [random::(), random::()]; - let mut buf = [0u8; 32]; - buf[..16].copy_from_slice(&r[0].to_be_bytes()); - buf[16..].copy_from_slice(&r[1].to_be_bytes()); - let commitment = commit(&buf); + let buf = random::<[u8; 32]>(); + let mut buf_id = [0u8; 33]; + buf_id[..32].copy_from_slice(&buf); + // Set the last byte to the party ID to ensure unique commitments. + buf_id[32] = i as u8; + let commitment = commit(&buf_id); // Step 1 b) Generate a random 256-bit seed for every other party for the pairwise // cointossing and commit to it. - let mut bufvec = vec![[0u8; 32]; n]; + let bufvec: Vec<[u8; 32]> = (0..n).map(|_| random::<[u8; 32]>()).collect(); + let mut bufvec_id: Vec<[u8; 33]> = vec![[0; 33]; n]; let mut commitment_vec = vec![Commitment([0; 32]); n]; for k in (0..n).filter(|k| *k != i) { - let r = [random::(), random::()]; - bufvec[k][..16].copy_from_slice(&r[0].to_be_bytes()); - bufvec[k][16..].copy_from_slice(&r[1].to_be_bytes()); - commitment_vec[k] = commit(&bufvec[k]); + bufvec_id[k][..32].copy_from_slice(&bufvec[k]); + bufvec_id[k][32] = i as u8; + commitment_vec[k] = commit(&bufvec_id[k]); } // Step 2) Send the commitments to all parties, first being the one for multi-party cointossing, next @@ -190,18 +199,23 @@ pub(crate) async fn shared_rng( send_to(channel, k, "RNG ver", &[(buf, bufvec[k])]).await?; } let mut bufs = vec![([0; 32], [0; 32]); n]; + let mut bufs_id = vec![([0; 33], [0; 33]); n]; for k in (0..n).filter(|k| *k != i) { bufs[k] = recv_from::<([u8; 32], [u8; 32])>(channel, k, "RNG ver") .await? .pop() .ok_or(Error::EmptyMsg)?; + bufs_id[k].0[..32].copy_from_slice(&bufs[k].0); + bufs_id[k].0[32] = k as u8; + bufs_id[k].1[..32].copy_from_slice(&bufs[k].1); + bufs_id[k].1[32] = k as u8; } // Step 4) Verify the decommitments and calculate multi-party seed. let mut buf_xor = buf; for k in (0..n).filter(|k| *k != i) { - if !open_commitment(&commitments[k].0, &bufs[k].0) - || !open_commitment(&commitments[k].1, &bufs[k].1) + if !open_commitment(&commitments[k].0, &bufs_id[k].0) + || !open_commitment(&commitments[k].1, &bufs_id[k].1) { return Err(Error::CommitmentCouldNotBeOpened); } From f3d4725a33bfba0e6176bb0220eabf81a52d31ad Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Thu, 5 Dec 2024 09:51:59 +0100 Subject: [PATCH 2/8] Broadcast the commitment to the multi-party cointossing --- src/faand.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/faand.rs b/src/faand.rs index 8a05ed3..9bf19ad 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -193,6 +193,11 @@ pub(crate) async fn shared_rng( .ok_or(Error::EmptyMsg)?; } + // Broadcast multi-party commitments + let commitments_multi: Vec> = + commitments.iter().map(|(c0, _)| vec![*c0]).collect(); + broadcast_and_verify(channel, i, n, "broadcast comm_multi", &commitments_multi).await?; + // Step 3) Send the decommitments to all parties, first being the one for multi-party cointossing, next // the one for pairwise cointossing. for k in (0..n).filter(|k| *k != i) { From 9d3cbb0023e910004332bfdb17d79505c1a15f65 Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Tue, 10 Dec 2024 13:25:03 +0100 Subject: [PATCH 3/8] Added check for avoiding low-entropy inputs to commitment --- src/faand.rs | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index 9bf19ad..8278ae9 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -63,17 +63,21 @@ impl From for Error { struct Commitment(pub(crate) [u8; 32]); /// Commits to a value using the BLAKE3 cryptographic hash function. -fn commit(value: &[u8]) -> Commitment { - let result = blake3::hash(value).into(); - Commitment(result) +fn commit(value: &[u8]) -> Result { + let min_len = RHO / 8 + usize::from(RHO % 8 != 0); + if value.len() < min_len { + return Err(Error::InvalidLength); + } + Ok(Commitment(blake3::hash(value).into())) } /// Verifies if a given value matches a previously generated commitment. -fn open_commitment(commitment: &Commitment, value: &[u8]) -> bool { - if value.is_empty() { - return false; +fn open_commitment(commitment: &Commitment, value: &[u8]) -> Result { + let min_len = RHO / 8 + usize::from(RHO % 8 != 0); + if value.len() < min_len { + return Err(Error::InvalidLength); } - blake3::hash(value).as_bytes() == &commitment.0 + Ok(blake3::hash(value).as_bytes() == &commitment.0) } /// Hashes a Vec using blake3 and returns the resulting hash as `[u128; 2]`. @@ -167,7 +171,7 @@ pub(crate) async fn shared_rng( buf_id[..32].copy_from_slice(&buf); // Set the last byte to the party ID to ensure unique commitments. buf_id[32] = i as u8; - let commitment = commit(&buf_id); + let commitment = commit(&buf_id)?; // Step 1 b) Generate a random 256-bit seed for every other party for the pairwise // cointossing and commit to it. @@ -177,7 +181,7 @@ pub(crate) async fn shared_rng( for k in (0..n).filter(|k| *k != i) { bufvec_id[k][..32].copy_from_slice(&bufvec[k]); bufvec_id[k][32] = i as u8; - commitment_vec[k] = commit(&bufvec_id[k]); + commitment_vec[k] = commit(&bufvec_id[k])?; } // Step 2) Send the commitments to all parties, first being the one for multi-party cointossing, next @@ -219,8 +223,8 @@ pub(crate) async fn shared_rng( // Step 4) Verify the decommitments and calculate multi-party seed. let mut buf_xor = buf; for k in (0..n).filter(|k| *k != i) { - if !open_commitment(&commitments[k].0, &bufs_id[k].0) - || !open_commitment(&commitments[k].1, &bufs_id[k].1) + if !open_commitment(&commitments[k].0, &bufs_id[k].0)? + || !open_commitment(&commitments[k].1, &bufs_id[k].1)? { return Err(Error::CommitmentCouldNotBeOpened); } @@ -427,9 +431,9 @@ pub(crate) async fn fashare( dm.extend(&mac.0.to_be_bytes()); } d1[r] = d0[r] ^ delta.0; - let c0 = commit(&d0[r].to_be_bytes()); - let c1 = commit(&d1[r].to_be_bytes()); - let cm = commit(&dm); + let c0 = commit(&d0[r].to_be_bytes())?; + let c1 = commit(&d1[r].to_be_bytes())?; + let cm = commit(&dm)?; c0_c1_cm.push((c0, c1, cm)); dmvec.push(dm); @@ -509,7 +513,7 @@ pub(crate) async fn fashare( for k in (0..n).filter(|k| *k != i) { let d_bj = &di_bi_k[k][r].to_be_bytes(); let commitments = &c0_c1_cm_k[k][r]; - if !open_commitment(&commitments.0, d_bj) && !open_commitment(&commitments.1, d_bj) { + if !open_commitment(&commitments.0, d_bj)? && !open_commitment(&commitments.1, d_bj)? { return Err(Error::CommitmentCouldNotBeOpened); } if xor_xk_macs[k][r] != di_bi_k[k][r] { @@ -684,7 +688,7 @@ async fn flaand( hi[ll] ^= mk_zi.0 ^ ki_zk.0 ^ ki_xj_phi[k][ll]; } hi[ll] ^= (xshares[ll].0 as u128 * phi[ll]) ^ (zshares[ll].0 as u128 * delta.0); - commhi.push(commit(&hi[ll].to_be_bytes())); + commhi.push(commit(&hi[ll].to_be_bytes())?); } drop(phi); drop(ki_xj_phi); @@ -712,7 +716,7 @@ async fn flaand( for k in (0..n).filter(|k| *k != i) { for (ll, (xh, hi_k)) in xor_all_hi.iter_mut().zip(hi_k[k].clone()).enumerate() { - if !open_commitment(&commhi_k[k][ll], &hi_k.to_be_bytes()) { + if !open_commitment(&commhi_k[k][ll], &hi_k.to_be_bytes())? { return Err(Error::CommitmentCouldNotBeOpened); } *xh ^= hi_k; From 1cc24d5561b82bc16e8108a89694413d892e0c13 Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Wed, 11 Dec 2024 11:51:13 +0100 Subject: [PATCH 4/8] Decouple multi-party and pairwise cointossing (the multi-party cointossing seed should not be known before its needed) --- src/faand.rs | 130 +++++++++++++++++++++++++++++------------------- src/protocol.rs | 9 ++-- 2 files changed, 84 insertions(+), 55 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index 8278ae9..6eabeb0 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -164,8 +164,8 @@ pub(crate) async fn shared_rng( channel: &mut impl Channel, i: usize, n: usize, -) -> Result<(ChaCha20Rng, Vec>>), Error> { - // Step 1 a) Generate a random 256-bit seed for multi-party cointossing and commit to it. +) -> Result { + // Step 1 Generate a random 256-bit seed for multi-party cointossing and commit to it. let buf = random::<[u8; 32]>(); let mut buf_id = [0u8; 33]; buf_id[..32].copy_from_slice(&buf); @@ -173,6 +173,61 @@ pub(crate) async fn shared_rng( buf_id[32] = i as u8; let commitment = commit(&buf_id)?; + // Step 2) a) Send the commitments to all parties for multi-party cointossing. + for k in (0..n).filter(|k| *k != i) { + send_to(channel, k, "RNG comm", &[commitment]).await?; + } + let mut commitments = vec![Commitment([0; 32]); n]; + for k in (0..n).filter(|k| *k != i) { + commitments[k] = recv_from::(channel, k, "RNG comm") + .await? + .pop() + .ok_or(Error::EmptyMsg)?; + } + + // Broadcast multi-party commitments. + let commitments_multi: Vec> = commitments.iter().map(|c| vec![*c]).collect(); + broadcast_and_verify(channel, i, n, "broadcast comm_multi", &commitments_multi).await?; + + // Step 3) Send the decommitments to all parties for multi-party cointossing. + for k in (0..n).filter(|k| *k != i) { + send_to(channel, k, "RNG ver", &[buf]).await?; + } + let mut bufs = vec![[0; 32]; n]; + let mut bufs_id = vec![[0; 33]; n]; + for k in (0..n).filter(|k| *k != i) { + bufs[k] = recv_from::<[u8; 32]>(channel, k, "RNG ver") + .await? + .pop() + .ok_or(Error::EmptyMsg)?; + bufs_id[k][..32].copy_from_slice(&bufs[k]); + bufs_id[k][32] = k as u8; + } + + // Step 4) Verify the decommitments and calculate multi-party seed. + let mut buf_xor = buf; + for k in (0..n).filter(|k| *k != i) { + if !open_commitment(&commitments[k], &bufs_id[k])? { + return Err(Error::CommitmentCouldNotBeOpened); + } + buf_xor + .iter_mut() + .zip(&bufs[k]) + .for_each(|(buf_xor_byte, buf_byte)| *buf_xor_byte ^= *buf_byte); + } + + Ok(ChaCha20Rng::from_seed(buf_xor)) +} + +/// Pairwise two-party coin tossing to generate shared randomness in a secure, distributed manner. +/// +/// This function generates a shared random number generator (RNG) between every two parties using +/// two-party coin tossing for the two-party KOS OT protocol. +pub(crate) async fn shared_rng_pairwise( + channel: &mut impl Channel, + i: usize, + n: usize, +) -> Result>>, Error> { // Step 1 b) Generate a random 256-bit seed for every other party for the pairwise // cointossing and commit to it. let bufvec: Vec<[u8; 32]> = (0..n).map(|_| random::<[u8; 32]>()).collect(); @@ -184,67 +239,50 @@ pub(crate) async fn shared_rng( commitment_vec[k] = commit(&bufvec_id[k])?; } - // Step 2) Send the commitments to all parties, first being the one for multi-party cointossing, next - // the one for pairwise cointossing. + // Step 2) Send the commitments to all parties for pairwise cointossing. for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "RNG comm", &[(commitment, commitment_vec[k])]).await?; + send_to(channel, k, "RNG comm", &[commitment_vec[k]]).await?; } - let mut commitments = vec![(Commitment([0; 32]), Commitment([0; 32])); n]; + let mut commitments = vec![Commitment([0; 32]); n]; for k in (0..n).filter(|k| *k != i) { - commitments[k] = recv_from::<(Commitment, Commitment)>(channel, k, "RNG comm") + commitments[k] = recv_from::(channel, k, "RNG comm") .await? .pop() .ok_or(Error::EmptyMsg)?; } - // Broadcast multi-party commitments - let commitments_multi: Vec> = - commitments.iter().map(|(c0, _)| vec![*c0]).collect(); - broadcast_and_verify(channel, i, n, "broadcast comm_multi", &commitments_multi).await?; - - // Step 3) Send the decommitments to all parties, first being the one for multi-party cointossing, next - // the one for pairwise cointossing. + // Step 3) Send the decommitments to all parties for pairwise cointossing. for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "RNG ver", &[(buf, bufvec[k])]).await?; + send_to(channel, k, "RNG ver", &[bufvec[k]]).await?; } - let mut bufs = vec![([0; 32], [0; 32]); n]; - let mut bufs_id = vec![([0; 33], [0; 33]); n]; + let mut bufs = vec![[0; 32]; n]; + let mut bufs_id = vec![[0; 33]; n]; for k in (0..n).filter(|k| *k != i) { - bufs[k] = recv_from::<([u8; 32], [u8; 32])>(channel, k, "RNG ver") + bufs[k] = recv_from::<[u8; 32]>(channel, k, "RNG ver") .await? .pop() .ok_or(Error::EmptyMsg)?; - bufs_id[k].0[..32].copy_from_slice(&bufs[k].0); - bufs_id[k].0[32] = k as u8; - bufs_id[k].1[..32].copy_from_slice(&bufs[k].1); - bufs_id[k].1[32] = k as u8; + bufs_id[k][..32].copy_from_slice(&bufs[k]); + bufs_id[k][32] = k as u8; } - // Step 4) Verify the decommitments and calculate multi-party seed. - let mut buf_xor = buf; + // Step 4) Verify the decommitments. for k in (0..n).filter(|k| *k != i) { - if !open_commitment(&commitments[k].0, &bufs_id[k].0)? - || !open_commitment(&commitments[k].1, &bufs_id[k].1)? - { + if !open_commitment(&commitments[k], &bufs_id[k])? { return Err(Error::CommitmentCouldNotBeOpened); } - buf_xor - .iter_mut() - .zip(&bufs[k].0) - .for_each(|(buf_xor_byte, buf_byte)| *buf_xor_byte ^= *buf_byte); } // Step 5) Set up shared RNGs for pairwise cointossing let mut shared_two_by_two: Vec>> = vec![vec![None; n]; n]; - // Step 1: Set up shared RNGs for pairs for k in (0..n).filter(|&k| k != i) { let (a, b) = if i < k { (i, k) } else { (k, i) }; shared_two_by_two[a][b] = Some(ChaCha20Rng::from_seed(std::array::from_fn(|i| { - bufvec[k][i] ^ bufs[k].1[i] + bufvec[k][i] ^ bufs[k][i] }))); } - Ok((ChaCha20Rng::from_seed(buf_xor), shared_two_by_two)) + Ok(shared_two_by_two) } /// Protocol PI_aBit^n that performs F_aBit^n from the paper @@ -262,9 +300,8 @@ async fn fabitn( i: usize, n: usize, l: usize, - shared_rand: &mut ChaCha20Rng, mut shared_two_by_two: Vec>>, -) -> Result, Error> { +) -> Result<(Vec, ChaCha20Rng), Error> { // Step 1) Pick random bit-string x of length lprime. let two_rho = 2 * RHO; let lprime = l + two_rho; @@ -303,8 +340,9 @@ async fn fabitn( // Step 3) Verification of MACs and keys. // Step 3 a) Sample 2 * RHO random l'-bit strings r. + let mut multi_shared_rand = shared_rng(channel, i, n).await?; let r: Vec> = (0..two_rho) - .map(|_| (0..lprime).map(|_| shared_rand.gen()).collect()) + .map(|_| (0..lprime).map(|_| multi_shared_rand.gen()).collect()) .collect(); // Step 3 b) Compute xj and xjmac for each party, broadcast xj. @@ -377,7 +415,7 @@ async fn fabitn( } res.push(Share(*xi, Auth(authvec))); } - Ok(res) + Ok((res, multi_shared_rand)) } /// Protocol PI_aShare that performs F_aShare from the paper @@ -398,21 +436,13 @@ pub(crate) async fn fashare( i: usize, n: usize, l: usize, - shared_rand: &mut ChaCha20Rng, shared_two_by_two: Vec>>, -) -> Result, Error> { +) -> Result<(Vec, ChaCha20Rng), Error> { // Step 1) Pick random bit-string x (input). // Step 2) Run Pi_aBit^n to compute shares. - let mut xishares = fabitn( - (channel, delta), - i, - n, - l + RHO, - shared_rand, - shared_two_by_two, - ) - .await?; + let (mut xishares, multi_shared_rand) = + fabitn((channel, delta), i, n, l + RHO, shared_two_by_two).await?; // Step 3) Compute commitments and verify consistency. // Step 3 a) Compute d0, d1, dm, c0, c1, cm and broadcast commitments to all parties. @@ -524,7 +554,7 @@ pub(crate) async fn fashare( // Step 4) Return first l objects. xishares.truncate(l); - Ok(xishares) + Ok((xishares, multi_shared_rand)) } /// Protocol Pi_HaAND that performs F_HaAND from the paper diff --git a/src/protocol.rs b/src/protocol.rs index 8d5e3d7..70a099f 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -9,7 +9,7 @@ use tokio::{runtime::Runtime, task::JoinSet}; use crate::{ channel::{self, recv_from, recv_vec_from, send_to, Channel, SimpleChannel}, data_types::{Auth, Delta, GarbledGate, Key, Label, Mac, Share}, - faand::{self, beaver_aand, broadcast_and_verify, bucket_size, fashare, shared_rng}, + faand::{self, beaver_aand, broadcast_and_verify, bucket_size, fashare, shared_rng_pairwise}, fpre::fpre, garble::{self, decrypt, encrypt, GarblingKey}, }; @@ -307,18 +307,17 @@ pub async fn mpc( random_shares = recv_vec_from(channel, p_fpre, "random shares", secret_bits).await?; } else { delta = Delta(random()); - let (multi_shared_rand, shared_two_by_two) = shared_rng(channel, p_own, p_max).await?; - shared_rand = multi_shared_rand; + let shared_two_by_two = shared_rng_pairwise(channel, p_own, p_max).await?; - let rand_shares = fashare( + let (rand_shares, multi_shared_rand) = fashare( (channel, delta), p_own, p_max, secret_bits + 3 * lprime, - &mut shared_rand, shared_two_by_two, ) .await?; + shared_rand = multi_shared_rand; let (random_shares_vec, xyzbits_vec) = rand_shares.split_at(secret_bits); random_shares = random_shares_vec.to_vec(); From d967ed5def259fb834cf29cbfb15d3d8c075c798 Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Sun, 22 Dec 2024 15:49:04 +0100 Subject: [PATCH 5/8] Couple broadcasts into a single function to avoid mistakes --- src/faand.rs | 166 +++++++++++++++++++++++------------------------- src/protocol.rs | 17 ++--- 2 files changed, 83 insertions(+), 100 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index 6eabeb0..67e497a 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -100,8 +100,8 @@ pub fn hash_vec(data: &Vec) -> Result<[u128; 2], Error> { Ok([hash1, hash2]) } -/// Implements broadcast with abort based on Goldwasser and Lindell's protocol. -pub(crate) async fn broadcast_and_verify< +/// Implements the verification step of broadcast with abort based on Goldwasser and Lindell's protocol. +pub(crate) async fn broadcast_verification< T: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, >( channel: &mut impl Channel, @@ -153,6 +153,64 @@ pub(crate) async fn broadcast_and_verify< Ok(()) } +// Implements broadcast with abort based on Goldwasser and Lindell's protocol +// for all parties at once, where each party sends its vector to all others. +// The function returns the vector received and verified by broadcast. +pub(crate) async fn broadcast< + T: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, +>( + channel: &mut impl Channel, + i: usize, + n: usize, + phase: &str, + vec: &[T], + len: usize, +) -> Result>, Error> { + for k in (0..n).filter(|k| *k != i) { + send_to(channel, k, phase, vec).await?; + } + + // 3 b) Receiving all commitments. + let mut res_vec = vec![vec![]; n]; + for k in (0..n).filter(|k| *k != i) { + res_vec[k] = recv_vec_from::(channel, k, phase, len).await?; + } + let string = "broadcast "; + broadcast_verification(channel, i, n, &(string.to_owned() + phase), &res_vec).await?; + Ok(res_vec) +} + +// Implements same broadcast with abort as broadcast, but only the first element of the tuple in +// the vector is broadcasted, the second element is simply sent to all parties. +pub(crate) async fn broadcast_first_send_second< + T: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, + S: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, +>( + channel: &mut impl Channel, + i: usize, + n: usize, + phase: &str, + vec: &[Vec<(T, S)>], + len: usize, +) -> Result>, Error> { + for k in (0..n).filter(|k| *k != i) { + send_to(channel, k, phase, &vec[k]).await?; + } + + // 3 b) Receiving all commitments. + let mut recv_vec = vec![vec![]; n]; + for k in (0..n).filter(|k| *k != i) { + recv_vec[k] = recv_vec_from::<(T, S)>(channel, k, phase, len).await?; + } + let first_vec: Vec> = recv_vec + .iter() + .map(|inner_vec| inner_vec.iter().map(|(a, _)| a.clone()).collect()) + .collect(); + let string = "broadcast "; + broadcast_verification(channel, i, n, &(string.to_owned() + phase), &first_vec).await?; + Ok(recv_vec) +} + /// Multi-party coin tossing to generate shared randomness in a secure, distributed manner. /// /// This function generates a shared random number generator (RNG) using multi-party @@ -174,20 +232,9 @@ pub(crate) async fn shared_rng( let commitment = commit(&buf_id)?; // Step 2) a) Send the commitments to all parties for multi-party cointossing. - for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "RNG comm", &[commitment]).await?; - } - let mut commitments = vec![Commitment([0; 32]); n]; - for k in (0..n).filter(|k| *k != i) { - commitments[k] = recv_from::(channel, k, "RNG comm") - .await? - .pop() - .ok_or(Error::EmptyMsg)?; - } - // Broadcast multi-party commitments. - let commitments_multi: Vec> = commitments.iter().map(|c| vec![*c]).collect(); - broadcast_and_verify(channel, i, n, "broadcast comm_multi", &commitments_multi).await?; + let comm = vec![commitment]; + let commitments = broadcast(channel, i, n, "RNG comm", &comm, 1).await?; // Step 3) Send the decommitments to all parties for multi-party cointossing. for k in (0..n).filter(|k| *k != i) { @@ -207,7 +254,7 @@ pub(crate) async fn shared_rng( // Step 4) Verify the decommitments and calculate multi-party seed. let mut buf_xor = buf; for k in (0..n).filter(|k| *k != i) { - if !open_commitment(&commitments[k], &bufs_id[k])? { + if !open_commitment(&commitments[k][0], &bufs_id[k])? { return Err(Error::CommitmentCouldNotBeOpened); } buf_xor @@ -357,8 +404,8 @@ async fn fabitn( } // Step 3 b continued) Send xj and its corresponding MACs to all parties except self. + let mut xj_xjmac = vec![vec![]; n]; for k in (0..n).filter(|k| *k != i) { - let mut xj_xjmac = Vec::with_capacity(two_rho); for (rbits, xj) in r.iter().zip(xj.iter()) { let mut xjmac = 0; for (j, &rbit) in rbits.iter().enumerate() { @@ -366,22 +413,12 @@ async fn fabitn( xjmac ^= macs[k][j]; } } - xj_xjmac.push((*xj, xjmac)); + xj_xjmac[k].push((*xj, xjmac)); } - send_to(channel, k, "fabitn", &xj_xjmac).await?; - } - - let mut xj_xjmac_k: Vec> = vec![vec![]; n]; - for k in (0..n).filter(|k| *k != i) { - xj_xjmac_k[k] = recv_vec_from(channel, k, "fabitn", two_rho).await?; } - let xj_k: Vec> = xj_xjmac_k - .iter() - .map(|inner_vec| inner_vec.iter().map(|(b, _)| *b).collect()) - .collect(); - // Part for broadcast - broadcast_and_verify(channel, i, n, "broadcast xj", &xj_k).await?; + let xj_xjmac_k = + broadcast_first_send_second(channel, i, n, "fabitn", &xj_xjmac, two_rho).await?; // Step 3 c) Compute keys. for (j, rbits) in r.iter().enumerate() { @@ -469,31 +506,12 @@ pub(crate) async fn fashare( dmvec.push(dm); } - for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "fashare comm", &c0_c1_cm).await?; - } - - // 3 b) Receiving all commitments. - let mut c0_c1_cm_k = vec![vec![]; n]; - for k in (0..n).filter(|k| *k != i) { - c0_c1_cm_k[k] = - recv_vec_from::<(Commitment, Commitment, Commitment)>(channel, k, "fashare comm", RHO) - .await?; - } - broadcast_and_verify(channel, i, n, "broadcast c0 c1 cm", &c0_c1_cm_k).await?; + let mut c0_c1_cm_k = broadcast(channel, i, n, "fashare comm", &c0_c1_cm, RHO).await?; c0_c1_cm_k[i] = c0_c1_cm; // 3 b) Pi broadcasts decommitment for macs. - for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "fashare ver", &dmvec).await?; - } - let mut dm_k = vec![vec![]; n]; - for k in (0..n).filter(|k| *k != i) { - dm_k[k] = recv_vec_from::>(channel, k, "fashare ver", RHO).await?; - } - broadcast_and_verify(channel, i, n, "broadcast dm", &dm_k).await?; - + let mut dm_k = broadcast(channel, i, n, "fashare ver", &dmvec, RHO).await?; dm_k[i] = dmvec; // 3 c) Compute bi to determine di_bi and send to all parties. @@ -508,14 +526,8 @@ pub(crate) async fn fashare( } di_bi[r] = if bi[r] { d1[r] } else { d0[r] }; } - for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "fashare di_bi", &di_bi).await?; - } - let mut di_bi_k = vec![vec![0; RHO]; n]; - for k in (0..n).filter(|k| *k != i) { - di_bi_k[k] = recv_vec_from::(channel, k, "fashare di_bi", RHO).await?; - } - broadcast_and_verify(channel, i, n, "broadcast di_bi", &di_bi_k).await?; + + let di_bi_k = broadcast(channel, i, n, "fashare di_bi", &di_bi, RHO).await?; // 3 d) Consistency check of macs: open commitment of xor of keys and check if it equals to the xor of all macs. let mut xor_xk_macs = vec![vec![0; RHO]; n]; @@ -674,25 +686,17 @@ async fn flaand( // Step 5) Compute uij and send to all parties along with e from Step 3). // Receive uij from all parties and compute mi_xj_phi. let mut ki_xj_phi = vec![vec![0; l]; n]; + let mut ei_uij = vec![vec![]; n]; for j in (0..n).filter(|j| *j != i) { - let mut ei_uij = Vec::with_capacity(l); for (ll, phi_l) in phi.iter().enumerate().take(l) { let (_, ki_xj) = xshares[ll].1 .0[j]; ki_xj_phi[j][ll] = hash128(ki_xj.0)?; let uij = hash128(ki_xj.0 ^ delta.0)? ^ ki_xj_phi[j][ll] ^ *phi_l; - ei_uij.push((e[ll], uij)); + ei_uij[j].push((e[ll], uij)); } - send_to(channel, j, "flaand", &ei_uij).await?; - } - let mut ei_uij_k: Vec> = vec![vec![]; n]; - for j in (0..n).filter(|j| *j != i) { - ei_uij_k[j] = recv_vec_from::<(bool, u128)>(channel, j, "flaand", l).await?; } - let ej_k: Vec> = ei_uij_k - .iter() - .map(|inner_vec| inner_vec.iter().map(|(b, _)| *b).collect()) - .collect(); - broadcast_and_verify(channel, i, n, "broadcast ej", &ej_k).await?; + + let ei_uij_k = broadcast_first_send_second(channel, i, n, "flaand", &ei_uij, l).await?; for j in (0..n).filter(|j| *j != i) { for (ll, xbit) in xshares.iter().enumerate().take(l) { @@ -724,26 +728,12 @@ async fn flaand( drop(ki_xj_phi); // All parties first broadcast the commitment of Hi. - for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "flaand comm", &commhi).await?; - } - let mut commhi_k: Vec> = vec![vec![]; n]; - for k in (0..n).filter(|k| *k != i) { - commhi_k[k] = recv_vec_from::(channel, k, "flaand comm", l).await?; - } - broadcast_and_verify(channel, i, n, "broadcast commhi", &commhi_k).await?; + let commhi_k = broadcast(channel, i, n, "flaand comm", &commhi, l).await?; // Then all parties broadcast Hi. - for k in (0..n).filter(|k| *k != i) { - send_to(channel, k, "flaand hash", &hi).await?; - } - let mut xor_all_hi = hi; // XOR for all parties, including p_own - let mut hi_k: Vec> = vec![vec![]; n]; - for k in (0..n).filter(|k| *k != i) { - hi_k[k] = recv_vec_from::(channel, k, "flaand hash", l).await?; - } - broadcast_and_verify(channel, i, n, "broadcast hash", &hi_k).await?; + let hi_k = broadcast(channel, i, n, "flaand hash", &hi, l).await?; + let mut xor_all_hi = hi; // XOR for all parties, including p_own for k in (0..n).filter(|k| *k != i) { for (ll, (xh, hi_k)) in xor_all_hi.iter_mut().zip(hi_k[k].clone()).enumerate() { if !open_commitment(&commhi_k[k][ll], &hi_k.to_be_bytes())? { diff --git a/src/protocol.rs b/src/protocol.rs index 70a099f..526a7ba 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -9,7 +9,7 @@ use tokio::{runtime::Runtime, task::JoinSet}; use crate::{ channel::{self, recv_from, recv_vec_from, send_to, Channel, SimpleChannel}, data_types::{Auth, Delta, GarbledGate, Key, Label, Mac, Share}, - faand::{self, beaver_aand, broadcast_and_verify, bucket_size, fashare, shared_rng_pairwise}, + faand::{self, beaver_aand, broadcast, bucket_size, fashare, shared_rng_pairwise}, fpre::fpre, garble::{self, decrypt, encrypt, GarblingKey}, }; @@ -512,20 +512,13 @@ pub async fn mpc( } } } - for p in (0..p_max).filter(|p| *p != p_own) { - send_to(channel, p, "masked inputs", &masked_inputs).await?; - } - let mut masked_inputs_from_other_party = vec![vec![None; num_gates]; p_max]; - for p in (0..p_max).filter(|p| *p != p_own) { - masked_inputs_from_other_party[p] = - recv_vec_from::>(channel, p, "masked inputs", num_gates).await?; - } - broadcast_and_verify( + let masked_inputs_from_other_party = broadcast( channel, p_own, p_max, - "broadcast masked inputs", - &masked_inputs_from_other_party, + "masked inputs", + &masked_inputs, + num_gates, ) .await?; From 65362626353c044a9c922bca057e72fb7c678afc Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Sun, 22 Dec 2024 16:01:47 +0100 Subject: [PATCH 6/8] Allow ID to be 16 instead of 8 bits long (not restrict number of parties to 256) --- src/faand.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index 67e497a..3340c9f 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -225,10 +225,11 @@ pub(crate) async fn shared_rng( ) -> Result { // Step 1 Generate a random 256-bit seed for multi-party cointossing and commit to it. let buf = random::<[u8; 32]>(); - let mut buf_id = [0u8; 33]; + let mut buf_id = [0u8; 34]; // with 16 bits for the id buf_id[..32].copy_from_slice(&buf); - // Set the last byte to the party ID to ensure unique commitments. - buf_id[32] = i as u8; + // Set the last two bytes to the party ID to ensure unique commitments. + let id_bytes = (i as u16).to_be_bytes(); + buf_id[32..].copy_from_slice(&id_bytes); let commitment = commit(&buf_id)?; // Step 2) a) Send the commitments to all parties for multi-party cointossing. @@ -241,14 +242,15 @@ pub(crate) async fn shared_rng( send_to(channel, k, "RNG ver", &[buf]).await?; } let mut bufs = vec![[0; 32]; n]; - let mut bufs_id = vec![[0; 33]; n]; + let mut bufs_id = vec![[0; 34]; n]; for k in (0..n).filter(|k| *k != i) { bufs[k] = recv_from::<[u8; 32]>(channel, k, "RNG ver") .await? .pop() .ok_or(Error::EmptyMsg)?; bufs_id[k][..32].copy_from_slice(&bufs[k]); - bufs_id[k][32] = k as u8; + let id_bytes = (k as u16).to_be_bytes(); + bufs_id[k][32..].copy_from_slice(&id_bytes); } // Step 4) Verify the decommitments and calculate multi-party seed. @@ -278,11 +280,12 @@ pub(crate) async fn shared_rng_pairwise( // Step 1 b) Generate a random 256-bit seed for every other party for the pairwise // cointossing and commit to it. let bufvec: Vec<[u8; 32]> = (0..n).map(|_| random::<[u8; 32]>()).collect(); - let mut bufvec_id: Vec<[u8; 33]> = vec![[0; 33]; n]; + let mut bufvec_id: Vec<[u8; 34]> = vec![[0; 34]; n]; let mut commitment_vec = vec![Commitment([0; 32]); n]; for k in (0..n).filter(|k| *k != i) { bufvec_id[k][..32].copy_from_slice(&bufvec[k]); - bufvec_id[k][32] = i as u8; + let id_bytes = (i as u16).to_be_bytes(); + bufvec_id[k][32..].copy_from_slice(&id_bytes); commitment_vec[k] = commit(&bufvec_id[k])?; } @@ -303,14 +306,15 @@ pub(crate) async fn shared_rng_pairwise( send_to(channel, k, "RNG ver", &[bufvec[k]]).await?; } let mut bufs = vec![[0; 32]; n]; - let mut bufs_id = vec![[0; 33]; n]; + let mut bufs_id = vec![[0; 34]; n]; for k in (0..n).filter(|k| *k != i) { bufs[k] = recv_from::<[u8; 32]>(channel, k, "RNG ver") .await? .pop() .ok_or(Error::EmptyMsg)?; bufs_id[k][..32].copy_from_slice(&bufs[k]); - bufs_id[k][32] = k as u8; + let id_bytes = (k as u16).to_be_bytes(); + bufs_id[k][32..].copy_from_slice(&id_bytes); } // Step 4) Verify the decommitments. From 3fa840a4dcdeacc3c7f5f3523379ba7e701f1b54 Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Sun, 22 Dec 2024 19:15:41 +0100 Subject: [PATCH 7/8] Added comment about commitment implementation --- src/faand.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index 3340c9f..a5d7845 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -63,15 +63,13 @@ impl From for Error { struct Commitment(pub(crate) [u8; 32]); /// Commits to a value using the BLAKE3 cryptographic hash function. +/// This is not a general-purpose commitment scheme, the input value is assumed to have high entropy. fn commit(value: &[u8]) -> Result { - let min_len = RHO / 8 + usize::from(RHO % 8 != 0); - if value.len() < min_len { - return Err(Error::InvalidLength); - } Ok(Commitment(blake3::hash(value).into())) } /// Verifies if a given value matches a previously generated commitment. +/// This is not a general-purpose commitment scheme, the input value is assumed to have high entropy. fn open_commitment(commitment: &Commitment, value: &[u8]) -> Result { let min_len = RHO / 8 + usize::from(RHO % 8 != 0); if value.len() < min_len { @@ -153,9 +151,9 @@ pub(crate) async fn broadcast_verification< Ok(()) } -// Implements broadcast with abort based on Goldwasser and Lindell's protocol -// for all parties at once, where each party sends its vector to all others. -// The function returns the vector received and verified by broadcast. +/// Implements broadcast with abort based on Goldwasser and Lindell's protocol +/// for all parties at once, where each party sends its vector to all others. +/// The function returns the vector received and verified by broadcast. pub(crate) async fn broadcast< T: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, >( @@ -180,8 +178,8 @@ pub(crate) async fn broadcast< Ok(res_vec) } -// Implements same broadcast with abort as broadcast, but only the first element of the tuple in -// the vector is broadcasted, the second element is simply sent to all parties. +/// Implements same broadcast with abort as broadcast, but only the first element of the tuple in +/// the vector is broadcasted, the second element is simply sent to all parties. pub(crate) async fn broadcast_first_send_second< T: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, S: Clone + Serialize + DeserializeOwned + std::fmt::Debug + PartialEq, From f5f309ed4a81209f31bc057674a450eeab1f9ad1 Mon Sep 17 00:00:00 2001 From: Agnes Kiss Date: Mon, 23 Dec 2024 10:22:30 +0100 Subject: [PATCH 8/8] Removed returning Error from commitment and open_commitment --- src/faand.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/faand.rs b/src/faand.rs index a5d7845..d71df9e 100644 --- a/src/faand.rs +++ b/src/faand.rs @@ -64,18 +64,14 @@ struct Commitment(pub(crate) [u8; 32]); /// Commits to a value using the BLAKE3 cryptographic hash function. /// This is not a general-purpose commitment scheme, the input value is assumed to have high entropy. -fn commit(value: &[u8]) -> Result { - Ok(Commitment(blake3::hash(value).into())) +fn commit(value: &[u8]) -> Commitment { + Commitment(blake3::hash(value).into()) } /// Verifies if a given value matches a previously generated commitment. /// This is not a general-purpose commitment scheme, the input value is assumed to have high entropy. -fn open_commitment(commitment: &Commitment, value: &[u8]) -> Result { - let min_len = RHO / 8 + usize::from(RHO % 8 != 0); - if value.len() < min_len { - return Err(Error::InvalidLength); - } - Ok(blake3::hash(value).as_bytes() == &commitment.0) +fn open_commitment(commitment: &Commitment, value: &[u8]) -> bool { + blake3::hash(value).as_bytes() == &commitment.0 } /// Hashes a Vec using blake3 and returns the resulting hash as `[u128; 2]`. @@ -228,7 +224,7 @@ pub(crate) async fn shared_rng( // Set the last two bytes to the party ID to ensure unique commitments. let id_bytes = (i as u16).to_be_bytes(); buf_id[32..].copy_from_slice(&id_bytes); - let commitment = commit(&buf_id)?; + let commitment = commit(&buf_id); // Step 2) a) Send the commitments to all parties for multi-party cointossing. // Broadcast multi-party commitments. @@ -254,7 +250,7 @@ pub(crate) async fn shared_rng( // Step 4) Verify the decommitments and calculate multi-party seed. let mut buf_xor = buf; for k in (0..n).filter(|k| *k != i) { - if !open_commitment(&commitments[k][0], &bufs_id[k])? { + if !open_commitment(&commitments[k][0], &bufs_id[k]) { return Err(Error::CommitmentCouldNotBeOpened); } buf_xor @@ -284,7 +280,7 @@ pub(crate) async fn shared_rng_pairwise( bufvec_id[k][..32].copy_from_slice(&bufvec[k]); let id_bytes = (i as u16).to_be_bytes(); bufvec_id[k][32..].copy_from_slice(&id_bytes); - commitment_vec[k] = commit(&bufvec_id[k])?; + commitment_vec[k] = commit(&bufvec_id[k]); } // Step 2) Send the commitments to all parties for pairwise cointossing. @@ -317,7 +313,7 @@ pub(crate) async fn shared_rng_pairwise( // Step 4) Verify the decommitments. for k in (0..n).filter(|k| *k != i) { - if !open_commitment(&commitments[k], &bufs_id[k])? { + if !open_commitment(&commitments[k], &bufs_id[k]) { return Err(Error::CommitmentCouldNotBeOpened); } } @@ -500,9 +496,9 @@ pub(crate) async fn fashare( dm.extend(&mac.0.to_be_bytes()); } d1[r] = d0[r] ^ delta.0; - let c0 = commit(&d0[r].to_be_bytes())?; - let c1 = commit(&d1[r].to_be_bytes())?; - let cm = commit(&dm)?; + let c0 = commit(&d0[r].to_be_bytes()); + let c1 = commit(&d1[r].to_be_bytes()); + let cm = commit(&dm); c0_c1_cm.push((c0, c1, cm)); dmvec.push(dm); @@ -557,7 +553,7 @@ pub(crate) async fn fashare( for k in (0..n).filter(|k| *k != i) { let d_bj = &di_bi_k[k][r].to_be_bytes(); let commitments = &c0_c1_cm_k[k][r]; - if !open_commitment(&commitments.0, d_bj)? && !open_commitment(&commitments.1, d_bj)? { + if !open_commitment(&commitments.0, d_bj) && !open_commitment(&commitments.1, d_bj) { return Err(Error::CommitmentCouldNotBeOpened); } if xor_xk_macs[k][r] != di_bi_k[k][r] { @@ -724,7 +720,7 @@ async fn flaand( hi[ll] ^= mk_zi.0 ^ ki_zk.0 ^ ki_xj_phi[k][ll]; } hi[ll] ^= (xshares[ll].0 as u128 * phi[ll]) ^ (zshares[ll].0 as u128 * delta.0); - commhi.push(commit(&hi[ll].to_be_bytes())?); + commhi.push(commit(&hi[ll].to_be_bytes())); } drop(phi); drop(ki_xj_phi); @@ -738,7 +734,7 @@ async fn flaand( let mut xor_all_hi = hi; // XOR for all parties, including p_own for k in (0..n).filter(|k| *k != i) { for (ll, (xh, hi_k)) in xor_all_hi.iter_mut().zip(hi_k[k].clone()).enumerate() { - if !open_commitment(&commhi_k[k][ll], &hi_k.to_be_bytes())? { + if !open_commitment(&commhi_k[k][ll], &hi_k.to_be_bytes()) { return Err(Error::CommitmentCouldNotBeOpened); } *xh ^= hi_k;