Skip to content

Commit c07f856

Browse files
authored
Merge pull request #180 from sine-fdn/single-array-param-as-multiple-parties
Treat a main fn with a single array param as multiple parties
2 parents d32690f + f7a1da6 commit c07f856

File tree

7 files changed

+147
-14
lines changed

7 files changed

+147
-14
lines changed

garble_docs/src/SUMMARY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
- [What is Garble?](./landing.md)
44
- [A Guide to Garble](./guide/intro.md)
55
- [Installation](./guide/installation.md)
6-
- [Functions](./guide/basics.md)
6+
- [Functions](./guide/functions.md)
77
- [Variables and Mutability](./guide/variables.md)
88
- [Data Types](./guide/data_types.md)
99
- [If/Else and Pattern Matching](./guide/control_flow.md)

garble_docs/src/guide/const.md

+22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub fn main(x: u16) -> u16 {
1515
}
1616
```
1717

18+
## Min & Max
19+
1820
Garble also supports taking the `min()` / `max()` of several constants as part of the declaration of a constant, which, for instance, can be useful to set the size of a collection to the size of the biggest collection provided by different parties:
1921

2022
```rust
@@ -37,4 +39,24 @@ pub fn main(x: u16) -> u16 {
3739

3840
> Garble currently does not support type inference in const declarations, which is why you always have to use type suffixes even for simple number literals, e.g. use `2u16` instead of `2`.
3941
42+
## A Dynamic Number of Parties
43+
44+
Using `const` declarations is especially useful for writing Garble programs that work for any number of parties. Simply use a single array as the argument of the main function, Garble will then assume that each array element is provided by a different party:
45+
46+
```rust
47+
const PARTIES: usize = PARTIES::TOTAL;
48+
49+
pub fn main(array: [u16; PARTIES]) -> u16 {
50+
let mut result = 0u16;
51+
for elem in array {
52+
result = result + elem;
53+
}
54+
result
55+
}
56+
```
57+
58+
The above program can be type checked without specifying the exact number of parties, but all parties need to agree on the number of parties during compilation (if each party is compiling the program locally), otherwise the same program (but with different `PARTIES::TOTAL` constants) would be compiled into incompatible circuits.
59+
60+
## Compiling Consts
61+
4062
Garble programs can be type checked without providing concrete values for the declared constants, but all const values must be provided during compilation, using [`garble_lang::compile_with_constants`](https://docs.rs/garble_lang/latest/garble_lang/fn.compile_with_constants.html). See [MPC Engine Integration](../integration.md) for more details.
File renamed without changes.

garble_docs/src/guide/intro.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Garble is a programming language for [MPC](https://en.wikipedia.org/wiki/Secure_multi-party_computation) that is quite easy to learn and mostly works like Rust, except that it is much simpler and only implements a subset of Rust (without the borrow checker). The following guide introduces the main features of Garble.
44

5-
A minimal Garble program is a public function (conventionally called `main`), with each function argument conceptually belonging to a different party in an MPC protocol based on Boolean circuits, e.g. [Garbled Circuits](https://en.wikipedia.org/wiki/Garbled_circuit). For example, the following program computes the Boolean `and` of two parties:
5+
A minimal Garble program is a public function (conventionally called `main`), with each argument coming from a different party in an MPC protocol based on Boolean circuits, e.g. [Garbled Circuits](https://en.wikipedia.org/wiki/Garbled_circuit). For example, the following program computes the Boolean `and` of two parties:
66

77
```rust
88
pub fn main(party_a: bool, party_b: bool) -> bool {
@@ -17,3 +17,11 @@ pub fn main(a: u32, b: u32, c: u32) -> u32 {
1717
a + b + c
1818
}
1919
```
20+
21+
It is also possible to use a single array as the argument of the main function, Garble will then assume that each array element is coming from a different party. So we could also write the above as:
22+
23+
```rust
24+
pub fn main(parties: [u32; 3]) -> u32 {
25+
parties[0] + parties[1] + parties[2]
26+
}
27+
```

garble_docs/src/landing.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ pub fn main(a: u64, b: u64) -> Richest {
2222
}
2323
```
2424

25-
Garble is developed by the [SINE Foundation](https://sine.foundation/) and is [open source](https://github.com/sine-fdn/garble-lang). We developed Garble for our own MPC engine, but you can use Garble in any [MPC engine](./integration.md) that executes Boolean circuits.
25+
Garble is developed by the [SINE Foundation](https://sine.foundation/) and is [open source](https://github.com/sine-fdn/garble-lang). Garble is integrated into [Polytune](https://github.com/sine-fdn/polytune), our own MPC engine, but you can use Garble in [any MPC engine](./integration.md) that executes Boolean circuits.

src/compile.rs

+32-7
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,40 @@ impl TypedProgram {
239239
let Some(fn_def) = self.fn_defs.get(fn_name) else {
240240
return Err(vec![CompilerError::FnNotFound(fn_name.to_string())]);
241241
};
242-
for param in fn_def.params.iter() {
243-
let type_size = param.ty.size_in_bits_for_defs(self, &const_sizes);
244-
let mut wires = Vec::with_capacity(type_size);
245-
for _ in 0..type_size {
246-
wires.push(wire);
247-
wire += 1;
242+
let single_array_as_multiple_parties = if fn_def.params.len() == 1 {
243+
let param = &fn_def.params[0];
244+
match &param.ty {
245+
Type::Array(elem_ty, size) => Some((param, elem_ty, size)),
246+
Type::ArrayConst(elem_ty, size) => {
247+
Some((param, elem_ty, const_sizes.get(size).unwrap()))
248+
}
249+
_ => None,
250+
}
251+
} else {
252+
None
253+
};
254+
if let Some((param, elem_ty, size)) = single_array_as_multiple_parties {
255+
let mut wires = vec![];
256+
for _ in 0..*size {
257+
let type_size = elem_ty.size_in_bits_for_defs(self, &const_sizes);
258+
for _ in 0..type_size {
259+
wires.push(wire);
260+
wire += 1;
261+
}
262+
input_gates.push(type_size);
248263
}
249-
input_gates.push(type_size);
250264
env.let_in_current_scope(param.name.clone(), wires);
265+
} else {
266+
for param in fn_def.params.iter() {
267+
let type_size = param.ty.size_in_bits_for_defs(self, &const_sizes);
268+
let mut wires = Vec::with_capacity(type_size);
269+
for _ in 0..type_size {
270+
wires.push(wire);
271+
wire += 1;
272+
}
273+
input_gates.push(type_size);
274+
env.let_in_current_scope(param.name.clone(), wires);
275+
}
251276
}
252277
let mut circuit = CircuitBuilder::new(input_gates, const_sizes.clone());
253278
for (const_name, const_def) in self.const_defs.iter() {

tests/compile.rs

+82-4
Original file line numberDiff line numberDiff line change
@@ -1321,8 +1321,8 @@ pub fn main(i: usize) -> i32 {
13211321
#[test]
13221322
fn compile_main_with_array_io() -> Result<(), Error> {
13231323
let prg = "
1324-
pub fn main(nums: [u8; 5]) -> [u8; 5] {
1325-
let mut sum = 0u16;
1324+
pub fn main(nums: [u8; 5], init: u16) -> [u8; 5] {
1325+
let mut sum = init;
13261326
for n in nums {
13271327
sum = sum + n as u16;
13281328
}
@@ -1338,6 +1338,7 @@ pub fn main(nums: [u8; 5]) -> [u8; 5] {
13381338
let mut eval = compiled.evaluator();
13391339
let input = compiled.parse_arg(0, "[1, 2, 3, 4, 5]")?;
13401340
eval.set_literal(input.as_literal())?;
1341+
eval.set_u16(0);
13411342
let output = eval.run().map_err(|e| pretty_print(e, prg))?;
13421343
let r = output.into_literal().map_err(|e| pretty_print(e, prg))?;
13431344
assert_eq!(r.to_string(), "[15, 15, 15, 15, 15]");
@@ -2009,7 +2010,7 @@ pub fn main(x: u16) -> u16 {
20092010
fn compile_const_size_in_fn_param() -> Result<(), Error> {
20102011
let prg = "
20112012
const MY_CONST: usize = max(PARTY_0::MY_CONST, PARTY_1::MY_CONST);
2012-
pub fn main(array: [u16; MY_CONST]) -> u16 {
2013+
pub fn main(array: [u16; MY_CONST], _: u8) -> u16 {
20132014
array[1]
20142015
}
20152016
";
@@ -2032,6 +2033,7 @@ pub fn main(array: [u16; MY_CONST]) -> u16 {
20322033
let compiled = compile_with_constants(prg, consts).map_err(|e| pretty_print(e, prg))?;
20332034
let mut eval = compiled.evaluator();
20342035
eval.parse_literal("[7, 8]").unwrap();
2036+
eval.set_u8(0);
20352037
let output = eval.run().map_err(|e| pretty_print(e, prg))?;
20362038
assert_eq!(u16::try_from(output).map_err(|e| pretty_print(e, prg))?, 8);
20372039
Ok(())
@@ -2041,7 +2043,7 @@ pub fn main(array: [u16; MY_CONST]) -> u16 {
20412043
fn compile_const_size_for_each_loop() -> Result<(), Error> {
20422044
let prg = "
20432045
const MY_CONST: usize = max(PARTY_0::MY_CONST, PARTY_1::MY_CONST);
2044-
pub fn main(array: [u16; MY_CONST]) -> u16 {
2046+
pub fn main(array: [u16; MY_CONST], _: u8) -> u16 {
20452047
let mut result = 0u16;
20462048
for elem in array {
20472049
result = result + elem;
@@ -2068,6 +2070,7 @@ pub fn main(array: [u16; MY_CONST]) -> u16 {
20682070
let compiled = compile_with_constants(prg, consts).map_err(|e| pretty_print(e, prg))?;
20692071
let mut eval = compiled.evaluator();
20702072
eval.parse_literal("[7, 8]").unwrap();
2073+
eval.set_u8(0);
20712074
let output = eval.run().map_err(|e| pretty_print(e, prg))?;
20722075
assert_eq!(u16::try_from(output).map_err(|e| pretty_print(e, prg))?, 15);
20732076
Ok(())
@@ -2345,3 +2348,78 @@ pub fn main(_x: i32) -> i32 {
23452348
}
23462349
Ok(())
23472350
}
2351+
2352+
#[test]
2353+
fn compile_single_array_as_3_parties() -> Result<(), Error> {
2354+
let prg = "
2355+
pub fn main(parties: [u32; 3]) -> u32 {
2356+
parties[0] + parties[1] + parties[2]
2357+
}";
2358+
let compiled = compile(prg).map_err(|e| pretty_print(e, prg))?;
2359+
let mut eval = compiled.evaluator();
2360+
eval.set_u32(2);
2361+
eval.set_u32(4);
2362+
eval.set_u32(6);
2363+
let output = eval.run().map_err(|e| pretty_print(e, prg))?;
2364+
assert_eq!(
2365+
u32::try_from(output).map_err(|e| pretty_print(e, prg))?,
2366+
2 + 4 + 6,
2367+
);
2368+
Ok(())
2369+
}
2370+
2371+
#[test]
2372+
fn compile_single_array_as_4_parties() -> Result<(), Error> {
2373+
let prg = "
2374+
pub fn main(parties: [u32; 4]) -> u32 {
2375+
let mut result = 0u32;
2376+
for p in parties {
2377+
result += p
2378+
}
2379+
result
2380+
}";
2381+
let compiled = compile(prg).map_err(|e| pretty_print(e, prg))?;
2382+
let mut eval = compiled.evaluator();
2383+
eval.set_u32(2);
2384+
eval.set_u32(4);
2385+
eval.set_u32(6);
2386+
eval.set_u32(8);
2387+
let output = eval.run().map_err(|e| pretty_print(e, prg))?;
2388+
assert_eq!(
2389+
u32::try_from(output).map_err(|e| pretty_print(e, prg))?,
2390+
2 + 4 + 6 + 8,
2391+
);
2392+
Ok(())
2393+
}
2394+
2395+
#[test]
2396+
fn compile_single_array_with_const_size_as_multiple_parties() -> Result<(), Error> {
2397+
let prg = "
2398+
const PARTIES: usize = PARTIES::TOTAL;
2399+
pub fn main(array: [u16; PARTIES]) -> u16 {
2400+
let mut result = 0u16;
2401+
for elem in array {
2402+
result = result + elem;
2403+
}
2404+
result
2405+
}
2406+
";
2407+
let consts = HashMap::from_iter(vec![(
2408+
"PARTIES".to_string(),
2409+
HashMap::from_iter(vec![(
2410+
"TOTAL".to_string(),
2411+
Literal::NumUnsigned(3, UnsignedNumType::Usize),
2412+
)]),
2413+
)]);
2414+
let compiled = compile_with_constants(prg, consts).map_err(|e| pretty_print(e, prg))?;
2415+
let mut eval = compiled.evaluator();
2416+
eval.set_u16(2);
2417+
eval.set_u16(4);
2418+
eval.set_u16(6);
2419+
let output = eval.run().map_err(|e| pretty_print(e, prg))?;
2420+
assert_eq!(
2421+
u16::try_from(output).map_err(|e| pretty_print(e, prg))?,
2422+
2 + 4 + 6
2423+
);
2424+
Ok(())
2425+
}

0 commit comments

Comments
 (0)