diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 16fdaa1..caff402 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -124,7 +124,7 @@ impl Cluster> { let mut fueltanks = beaver::BeaverTriple::fake_many(&context, shared_rng, 2000); let mut engine = Engine::<_, S, _>::new(context, network, private_rng); engine.add_fuel(&mut fueltanks[context.me.0]); - engine.execute(&script).await + engine.execute(&script).await.unwrap_single() }) .await } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index b499da5..85ba7b6 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -3,12 +3,12 @@ pub mod parsing; use std::future::Future; use ff::Field; -use itertools::Itertools; +use itertools::{Either, Itertools}; use rand::RngCore; use crate::{ algebra::math::Vector, - net::{network::Network, Id, SplitChannel}, + net::{agency::Broadcast, network::Network, Id, SplitChannel}, protocols::beaver::{beaver_multiply, BeaverTriple}, schemes::interactive::{InteractiveShared, InteractiveSharedMany}, }; @@ -19,6 +19,29 @@ pub enum Value { Vector(Vector), } +impl Value { + pub fn unwrap_single(self) -> F { + match self { + Value::Single(v) => v, + _ => panic!("Was vector and not a single!"), + } + } + + pub fn unwrap_vector(self) -> Vector { + match self { + Value::Vector(v) => v, + _ => panic!("Was single and not a vector!"), + } + } + + pub fn map(self, func: impl Fn(F) -> U) -> Value { + match self { + Value::Single(a) => Value::Single(func(a)), + Value::Vector(a) => Value::Vector(a.into_iter().map(func).collect()), + } + } +} + impl From for Value { fn from(value: F) -> Self { Value::Single(value) @@ -69,6 +92,7 @@ pub enum Instruction { Add, Mul, Sub, + Sum(usize), } #[derive(Clone, Debug)] @@ -127,6 +151,31 @@ impl Stack { _ => panic!("no valid value found"), } } + + pub fn take_singles(&mut self, n: usize) -> impl Iterator + '_ { + self.stack.drain(0..n).map(|v| match v { + SharedValue::Single(v) => v, + _ => panic!(), + }) + } + + pub fn take_vectors(&mut self, n: usize) -> impl Iterator + '_ { + self.stack.drain(0..n).map(|v| match v { + SharedValue::Vector(v) => v, + _ => panic!(), + }) + } + + pub fn take( + &mut self, + n: usize, + ) -> Either + '_, impl Iterator + '_> { + match self.stack.last() { + Some(SharedValue::Single(_)) => Either::Left(self.take_singles(n)), + Some(SharedValue::Vector(_)) => Either::Right(self.take_vectors(n)), + None => panic!(), + } + } } impl Engine @@ -151,9 +200,9 @@ where // TODO: Superscalar execution when awaiting. - pub async fn execute(&mut self, script: &Script) -> F { + pub async fn execute(&mut self, script: &Script) -> Value { let mut stack = Stack::new(); - let mut results = vec![]; + let mut results: Vec> = vec![]; let constants = &script.constants; for opcode in script.instructions.iter() { @@ -168,7 +217,7 @@ where async fn step( &mut self, stack: &mut Stack, - results: &mut Vec, + results: &mut Vec>, constants: &[Value], opcode: &Instruction, ) -> Result<(), S::Error> { @@ -214,9 +263,16 @@ where stack.push_vector(share) } Instruction::Recombine => { - let share = stack.pop_single(); - let f = S::recombine(ctx, share, &mut coms).await?; - results.push(f); + match stack.pop() { + SharedValue::Single(share) => { + let f = S::recombine(ctx, share, &mut coms).await?; + results.push(Value::Single(f)); + } + SharedValue::Vector(share) => { + let f = S::recombine_many(ctx, share, &mut coms).await?; + results.push(Value::Vector(f)); + } + }; } Instruction::Add => { let a = stack.pop(); @@ -268,13 +324,32 @@ where (SharedValue::Single(_), SharedValue::Vector(_)) => todo!(), }; } + Instruction::Sum(size) => { + // Zero is a sentinal value that represents the party size. + let size = if *size == 0 { + self.network.size() + } else { + *size + }; + let res = match stack.take(size) { + Either::Left(iter) => { + let res = iter.reduce(|s, acc| acc + s).unwrap(); + SharedValue::Single(res) + } + Either::Right(iter) => { + let res = iter.reduce(|s, acc| acc + &s).unwrap(); + SharedValue::Vector(res) + } + }; + stack.push(res) + } } Ok(()) } pub async fn raw(&mut self, routine: Func) -> Out where - Func: async Fn(&mut Network, &S::Context, &mut R) -> Out, + Func: async Fn(&mut Network, &mut S::Context, &mut R) -> Out, { // TODO: Add other resources. routine(&mut self.network, &mut self.context, &mut self.rng).await @@ -327,7 +402,7 @@ mod tests { Recombine, ], }; - let res: u32 = engine.execute(&script).await.into(); + let res: u32 = engine.execute(&script).await.unwrap_single().into(); engine.network.shutdown().await.unwrap(); res }) @@ -364,7 +439,7 @@ mod tests { .with_args([a, b]) .run_with_args(|net, script| async move { let mut engine = dumb_engine(net); - let res: u32 = engine.execute(&script).await.into(); + let res: u32 = engine.execute(&script).await.unwrap_single().into(); engine.network.shutdown().await.unwrap(); res }) diff --git a/src/vm/parsing.rs b/src/vm/parsing.rs index e9a5833..881b7da 100644 --- a/src/vm/parsing.rs +++ b/src/vm/parsing.rs @@ -2,6 +2,7 @@ use itertools::Itertools; /// Pseudo-parsing direct to an array-backed AST which just is a bytecode stack. use std::{ array, + iter::Sum, ops::{Add, Mul, Sub}, }; @@ -18,6 +19,13 @@ pub struct Exp { instructions: Vec, } +// A dynamicly sized list of expressions. +#[derive(Debug)] +pub struct ExpList { + constant: Value, +} + +// An opened expression (last step) #[derive(Debug)] pub struct Opened(Exp); @@ -49,6 +57,20 @@ impl Exp { } } + // This is slighty cursed. + pub fn symmetric_share(secret: impl Into) -> ExpList { + ExpList { + constant: Value::Single(secret.into()), + } + } + + // This is slighty cursed. + pub fn symmetric_share_vec(secret: impl Into>) -> ExpList { + ExpList { + constant: Value::Vector(secret.into()), + } + } + /// Secret share into a field value /// /// * `secret`: value to secret share @@ -109,6 +131,7 @@ impl Exp { | Instruction::RecvVec(_) | Instruction::Recombine | Instruction::Add + | Instruction::Sum(_) | Instruction::Mul | Instruction::Sub => (), } @@ -152,6 +175,35 @@ impl Opened { } } +impl ExpList { + /// Promise that the explist is `size` long + /// + /// This will then assume that there a `size` on the stack when executing. + pub fn concrete(self, own: usize, size: usize) -> Vec> { + let mut me = Some(Exp { + constants: vec![self.constant], + instructions: vec![Instruction::SymShare(Const(0))], + }); + (0..size) + .map(|id| { + if id == own { + me.take().unwrap() + } else { + Exp::empty() + } + }) + .collect() + } + + pub fn sum(self) -> Exp { + use Instruction as I; + Exp { + constants: vec![self.constant], + instructions: vec![I::SymShare(Const(0)), I::Sum(0)], + } + } +} + impl Add for Exp { type Output = Self; @@ -192,6 +244,17 @@ impl Sub for Exp { } } +impl Sum for Exp { + fn sum>(iter: I) -> Self { + let (mut exp, size) = iter.fold((Exp::empty(), 0usize), |(mut acc, count), exp| { + acc.append(exp); + (acc, count + 1) + }); + exp.instructions.push(Instruction::Sum(size)); + exp + } +} + #[cfg(test)] mod test { use crate::{ @@ -274,4 +337,76 @@ mod test { // 1 + 2 * 3 = 7 assert_eq!(res, vec![7u32.into(), 7u32.into(), 7u32.into()]); } + + #[tokio::test] + async fn explist() { + let inputs = [1, 2, 3u32]; + let res = Cluster::new(3) + .with_args( + (0..3) + .map(|id| { + let me = Id(id); + type E = Exp; + let exp = E::symmetric_share(inputs[id]); + let [a, b, c]: [E; 3] = exp.concrete(id, 3).try_into().unwrap(); + let sum = a + b * c; // no need to implement precedence! + let res = sum.open(); + res.finalize() + }) + .collect::>(), + ) + .execute_mock() + .await + .unwrap(); + + // 1 + 2 * 3 = 7 + assert_eq!(res, vec![7u32.into(), 7u32.into(), 7u32.into()]); + } + + #[tokio::test] + async fn sum() { + let inputs = [1, 2, 3u32]; + let res = Cluster::new(3) + .with_args( + (0..3) + .map(|id| { + let me = Id(id); + type E = Exp; + let [a, b, c] = E::share_and_receive(inputs[id], me); + let sum: E = [a, b, c].into_iter().sum(); + let res = sum.open(); + res.finalize() + }) + .collect::>(), + ) + .execute_mock() + .await + .unwrap(); + + // 1 + 2 + 3 = 6 + assert_eq!(res, vec![6u32.into(), 6u32.into(), 6u32.into()]); + } + + #[tokio::test] + async fn sum_explist() { + let inputs = [1, 2, 3u32]; + let res = Cluster::new(3) + .with_args( + (0..3) + .map(|id| { + type E = Exp; + let exp = E::symmetric_share(inputs[id]); + let sum = exp.sum(); + let res = sum.open(); + res.finalize() + }) + .collect::>(), + ) + .execute_mock() + .await + .unwrap(); + + // 1 + 2 + 3 = 6 + assert_eq!(res, vec![6u32.into(), 6u32.into(), 6u32.into()]); + } } diff --git a/wecare/Cargo.lock b/wecare/Cargo.lock index 4daceb9..02d4a81 100644 --- a/wecare/Cargo.lock +++ b/wecare/Cargo.lock @@ -1656,6 +1656,7 @@ name = "wecare" version = "0.1.0" dependencies = [ "caring", + "castaway", "criterion", "curve25519-dalek", "enum_dispatch", diff --git a/wecare/Cargo.toml b/wecare/Cargo.toml index 17f2382..727172b 100644 --- a/wecare/Cargo.toml +++ b/wecare/Cargo.toml @@ -13,6 +13,7 @@ rand = "0.8.5" fixed = "2.0.0-alpha.11" enum_dispatch = "0.3.13" ff = "0.13.0" +castaway = "0.2.3" [dev-dependencies] criterion = "0.5.1" diff --git a/wecare/benches/shamir-25519.rs b/wecare/benches/shamir-25519.rs new file mode 100644 index 0000000..22f1805 --- /dev/null +++ b/wecare/benches/shamir-25519.rs @@ -0,0 +1,101 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::time::Duration; +use std::{hint::black_box, thread}; +use wecare::*; + +fn build_shamir_engines() -> (Engine, Engine) { + print!("\nSetting up engines..."); + let (e1, e2) = thread::scope(|scope| { + let e2 = scope.spawn(|| { + Engine::setup("127.0.0.1:1234") + .add_participant("127.0.0.1:1235") + .threshold(2) + .build_shamir() + .unwrap() + }); + let e1 = scope.spawn(|| { + thread::sleep(Duration::from_millis(200)); + Engine::setup("127.0.0.1:1235") + .add_participant("127.0.0.1:1234") + .threshold(2) + .build_shamir() + .unwrap() + }); + (e1.join().unwrap(), e2.join().unwrap()) + }); + println!(" Complete!"); + (e1, e2) +} + +fn criterion_benchmark(c: &mut Criterion) { + let (mut e1, mut e2) = build_shamir_engines(); + c.bench_function("shamir single", |b| { + let input1 = vec![7.0]; + let input2 = vec![3.0]; + b.iter(|| { + thread::scope(|scope| { + let t1 = scope.spawn(|| { + black_box(e1.mpc_sum(&input1)); + }); + let t2 = scope.spawn(|| { + black_box(e2.mpc_sum(&input2)); + }); + t1.join().unwrap(); + t2.join().unwrap(); + }); + }); + }); + c.bench_function("shamir vec32", |b| { + let input1 = vec![7.0; 32]; + let input2 = vec![3.0; 32]; + b.iter(|| { + thread::scope(|scope| { + let t1 = scope.spawn(|| { + black_box(e1.mpc_sum(&input1)); + }); + let t2 = scope.spawn(|| { + black_box(e2.mpc_sum(&input2)); + }); + t1.join().unwrap(); + t2.join().unwrap(); + }); + }); + }); + c.bench_function("shamir vec64", |b| { + let input1 = vec![7.0; 64]; + let input2 = vec![3.0; 64]; + b.iter(|| { + thread::scope(|scope| { + let t1 = scope.spawn(|| { + black_box(e1.mpc_sum(&input1)); + }); + let t2 = scope.spawn(|| { + black_box(e2.mpc_sum(&input2)); + }); + t1.join().unwrap(); + t2.join().unwrap(); + }); + }); + }); + c.bench_function("shamir vec128", |b| { + let input1 = vec![7.0; 128]; + let input2 = vec![3.0; 128]; + b.iter(|| { + thread::scope(|scope| { + let t1 = scope.spawn(|| { + black_box(e1.mpc_sum(&input1)); + }); + let t2 = scope.spawn(|| { + black_box(e2.mpc_sum(&input2)); + }); + t1.join().unwrap(); + t2.join().unwrap(); + }); + }); + }); + e1.shutdown(); + e2.shutdown(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/wecare/src/vm.rs b/wecare/src/vm.rs index 1dd80e5..c272c61 100644 --- a/wecare/src/vm.rs +++ b/wecare/src/vm.rs @@ -12,7 +12,7 @@ use caring::{ interactive::{InteractiveShared, InteractiveSharedMany}, shamir, spdz, }, - vm::{self, parsing::Exp}, + vm::{self, parsing::{Exp, Opened}, Value}, }; use crate::Mapping; @@ -97,7 +97,7 @@ impl UnknownNumber { todo!() } - pub fn to_f64(self) -> u64 { + pub fn to_f64(self) -> f64 { match self { UnknownNumber::U32(val) => { let val = FixedI32::<16>::from_bits(val as i32); @@ -150,61 +150,42 @@ impl Engine { EngineBuilder::default() } - pub async fn execute(&mut self, expr: Expr) -> UnknownNumber { - let res: UnknownNumber = match self { + pub async fn execute(&mut self, expr: Opened) -> Value { + let res: Value = match self { Engine::Spdz25519(engine) => { - let res = engine.execute(&expr.open().try_finalize().unwrap()).await; - res.into() + let res = engine.execute(&expr.try_finalize().unwrap()).await; + res.map(|x| x.into()) } Engine::Shamir32(engine) => engine - .execute(&expr.open().try_finalize().unwrap()) + .execute(&expr.try_finalize().unwrap()) .await - .into(), + .map(|x|x.into()), Engine::Spdz32(engine) => { - let res = engine.execute(&expr.open().try_finalize().unwrap()).await; - res.into() + let res = engine.execute(&expr.try_finalize().unwrap()).await; + res.map(|x| x.into()) } Engine::Shamir25519(engine) => { - let res = engine.execute(&expr.open().try_finalize().unwrap()).await; - res.into() + let res = engine.execute(&expr.try_finalize().unwrap()).await; + res.map(|x| x.into()) } Engine::Feldman25519(engine) => { - let res = engine.execute(&expr.open().try_finalize().unwrap()).await; - res.into() + let res = engine.execute(&expr.try_finalize().unwrap()).await; + res.map(|x| x.into()) } }; res } - // pub async fn raw(&mut self, routine: Func) -> O - // where S: InteractiveShared, - // Func: async Fn(&mut TcpNetwork, &mut S::Context, &mut StdRng) -> O - // { - // match self { - // Engine::Spdz25519(e) => { e.raw(routine).await }, - // Engine::Spdz32(e) => e.raw(routine).await, - // Engine::Shamir25519(_) => todo!(), - // Engine::Shamir32(_) => todo!(), - // Engine::Feldman25519(_) => todo!(), - // } - // } - - // compatilbity. - // pub async fn mpc_sum>(&mut self, nums: &[f64]) -> Option> - // where - // ::VectorShare: std::iter::Sum, - // { - // self.raw(async |net, ctx, rng| { - // let nums: Vec<_> = nums.iter().map(|&num| F::from_f64(num)).collect(); - // let shares: Vec = - // S::symmetric_share_many(ctx, &nums, rng, net) .await .unwrap(); - // let sum: S::VectorShare = shares.into_iter().sum(); - // let res: Vector = S::recombine_many(ctx, sum, net).await.unwrap(); - // - // let res = res.into_iter().map(|x| F::into_f64(x)).collect(); - // Some(res) - // }).await - // } + + pub async fn sum(&mut self, nums: &[f64]) -> Vec { + let nums : Vector<_> = nums.into_iter().map(|v| Number::Float(*v)).collect(); + let program = { + let explist = Expr::symmetric_share_vec(nums); + explist.sum().open() + }; + self.execute(program).await.unwrap_vector().into_iter().map(|x| x.to_f64()).collect() + } + } pub enum FieldKind { @@ -332,6 +313,8 @@ impl<'a> EngineBuilder<'a> { } pub mod blocking { + use caring::vm::{parsing::Opened, Value}; + use crate::vm::{Expr, UnknownNumber}; pub struct Engine { @@ -374,9 +357,12 @@ pub mod blocking { } impl Engine { - pub fn execute(&mut self, expr: Expr) -> UnknownNumber { - self.runtime - .block_on(async { self.parent.execute(expr).await }) + pub fn execute(&mut self, expr: Opened) -> Value { + self.runtime.block_on(self.parent.execute(expr)) + } + + pub fn sum(&mut self, nums: &[f64]) -> Vec { + self.runtime.block_on( self.parent.sum(nums)) } } }