From b77227a6447bf85f290b9dc0da5b097efdf721c6 Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Thu, 11 Apr 2024 09:02:41 -0700 Subject: [PATCH] offline-phase: lowgear: shared-random: Implement F_rand subprotocol This subprotocol is a simple commit-reveal which allows the counterparties to agree on a set of shared random values. --- offline-phase/Cargo.toml | 3 +- offline-phase/src/error.rs | 3 + offline-phase/src/lowgear/mod.rs | 1 + offline-phase/src/lowgear/shared_random.rs | 89 ++++++++++++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 offline-phase/src/lowgear/shared_random.rs diff --git a/offline-phase/Cargo.toml b/offline-phase/Cargo.toml index c872f92..6218790 100644 --- a/offline-phase/Cargo.toml +++ b/offline-phase/Cargo.toml @@ -15,10 +15,11 @@ mp-spdz-rs = { path = "../mp-spdz-rs" } futures = "0.3" # === Misc Dependencies === # +itertools = "0.10" rand = "0.8" +sha3 = { version = "0.10" } [dev-dependencies] ark-bn254 = "0.4" ark-mpc = { path = "../online-phase", features = ["test_helpers"] } -itertools = "0.10" tokio = { version = "1", features = ["full"] } diff --git a/offline-phase/src/error.rs b/offline-phase/src/error.rs index 1d1cf3f..ae45d43 100644 --- a/offline-phase/src/error.rs +++ b/offline-phase/src/error.rs @@ -4,6 +4,8 @@ use std::{error::Error, fmt::Display}; /// The error types for the offline phase #[derive(Clone, Debug)] pub enum LowGearError { + /// An invalid commitment was provided in a commit/reveal phase + InvalidCommitment, /// Error exchanging keys KeyExchange(String), /// The lowgear setup params requested before setup @@ -17,6 +19,7 @@ pub enum LowGearError { impl Display for LowGearError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + LowGearError::InvalidCommitment => write!(f, "Received invalid commitment"), LowGearError::KeyExchange(e) => write!(f, "Key exchange error: {e}"), LowGearError::NotSetup => write!(f, "LowGear not setup"), LowGearError::SendMessage(e) => write!(f, "Error sending message: {e}"), diff --git a/offline-phase/src/lowgear/mod.rs b/offline-phase/src/lowgear/mod.rs index 2573d47..ac5c9ea 100644 --- a/offline-phase/src/lowgear/mod.rs +++ b/offline-phase/src/lowgear/mod.rs @@ -2,6 +2,7 @@ //! keys, authenticating inputs, etc pub mod setup; +pub mod shared_random; pub mod triplets; use ark_ec::CurveGroup; diff --git a/offline-phase/src/lowgear/shared_random.rs b/offline-phase/src/lowgear/shared_random.rs new file mode 100644 index 0000000..6648150 --- /dev/null +++ b/offline-phase/src/lowgear/shared_random.rs @@ -0,0 +1,89 @@ +//! Implements the F_rand functionality from the LowGear paper + +use ark_ec::CurveGroup; +use ark_mpc::{algebra::Scalar, network::MpcNetwork}; +use itertools::Itertools; +use rand::rngs::OsRng; +use sha3::{Digest, Sha3_256}; + +use crate::error::LowGearError; + +use super::LowGear; + +impl + Unpin + Send> LowGear { + /// Generate a single shared random value via commit/reveal + pub async fn get_shared_randomness(&mut self) -> Result, LowGearError> { + Ok(self.get_shared_randomness_vec(1).await?[0]) + } + + /// Generate a set of shared random values via commit/reveal + /// + /// 1. Generate local random values + /// 2. Commit to the random values + /// 3. Send & receive the commitments to/from the counterparty + /// 4. Send & receive the random values to/from the counterparty + /// 5. Verify commitments and construct shared value + pub async fn get_shared_randomness_vec( + &mut self, + n: usize, + ) -> Result>, LowGearError> { + // Generate local random values + let mut rng = OsRng; + let my_shares = (0..n).map(|_| Scalar::random(&mut rng)).collect_vec(); + + let my_comm = Self::commit_randomness(&my_shares); + self.send_network_payload(my_comm).await?; + let their_comm: Scalar = self.receive_network_payload().await?; + + self.send_network_payload(my_shares.clone()).await?; + let their_shares: Vec> = self.receive_network_payload().await?; + + // Verify commitments + let expected_comm = Self::commit_randomness(&their_shares); + if expected_comm != their_comm { + return Err(LowGearError::InvalidCommitment); + } + + let final_shares = my_shares + .iter() + .zip(their_shares.iter()) + .map(|(my_share, their_share)| my_share + their_share) + .collect_vec(); + Ok(final_shares) + } + + /// Hash commit to a set of random values + fn commit_randomness(values: &[Scalar]) -> Scalar { + let mut hasher = Sha3_256::new(); + for value in values.iter() { + hasher.update(value.to_bytes_be()); + } + let hash_output = hasher.finalize(); + + Scalar::::from_be_bytes_mod_order(&hash_output) + } +} + +#[cfg(test)] +mod tests { + use crate::test_helpers::mock_lowgear; + + use super::*; + + #[tokio::test] + async fn test_get_shared_randomness_vec() { + mock_lowgear(|mut lowgear| async move { + let n = 5; + let shares = lowgear.get_shared_randomness_vec(n).await.unwrap(); + + assert_eq!(shares.len(), n); + + // Send the shares to one another to verify they are the same + lowgear.send_network_payload(shares.clone()).await.unwrap(); + let their_shares: Vec> = lowgear.receive_network_payload().await.unwrap(); + + assert_eq!(shares, their_shares); + }) + .await; + } +}