Skip to content

Commit 85b783e

Browse files
committed
Hide low-level type details when using compile + GarbleProgram
1 parent c227ec2 commit 85b783e

File tree

6 files changed

+383
-291
lines changed

6 files changed

+383
-291
lines changed

src/eval.rs

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ pub enum EvalError {
4343
UnexpectedNumberOfInputsFromParty(usize),
4444
/// An input literal could not be parsed.
4545
LiteralParseError(CompileTimeError),
46+
/// The circuit does not have an input argument with the given index.
47+
InvalidArgIndex(usize),
4648
/// The literal is not of the expected parameter type.
4749
InvalidLiteralType(Literal, Type),
4850
/// The number of output bits does not match the expected type.
@@ -68,6 +70,9 @@ impl std::fmt::Display for EvalError {
6870
EvalError::LiteralParseError(err) => {
6971
err.fmt(f)
7072
}
73+
EvalError::InvalidArgIndex(i) => {
74+
f.write_fmt(format_args!("The circuit does not an input argument with index {i}"))
75+
}
7176
EvalError::InvalidLiteralType(literal, ty) => {
7277
f.write_fmt(format_args!("The argument literal is not of type {ty}: '{literal}'"))
7378
}

src/lib.rs

+124-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,53 @@
11
//! A purely functional programming language with a Rust-like syntax that compiles to logic gates
22
//! for secure multi-party computation.
3+
//!
4+
//! Garble programs always terminate and are compiled into a combination of boolean AND / XOR / NOT
5+
//! gates. These boolean circuits can either be executed directly (mostly for testing purposes) or
6+
//! passed to a multi-party computation engine.
7+
//!
8+
//! ```rust
9+
//! use garble_lang::{compile, literal::Literal, token::UnsignedNumType::U32};
10+
//!
11+
//! // Compile and type-check a simple program to add the inputs of 3 parties:
12+
//! let code = "pub fn main(x: u32, y: u32, z: u32) -> u32 { x + y + z }";
13+
//! let prg = compile(code).map_err(|e| e.prettify(&code)).unwrap();
14+
//!
15+
//! // We can evaluate the circuit directly, useful for testing purposes:
16+
//! let mut eval = prg.evaluator();
17+
//! eval.set_u32(2);
18+
//! eval.set_u32(10);
19+
//! eval.set_u32(100);
20+
//! let output = eval.run().map_err(|e| e.prettify(&code)).unwrap();
21+
//! assert_eq!(u32::try_from(output).map_err(|e| e.prettify(&code)).unwrap(), 2 + 10 + 100);
22+
//!
23+
//! // Or we can run the compiled circuit in an MPC engine, simulated using `prg.circuit.eval()`:
24+
//! let x = prg.parse_arg(0, "2u32").unwrap().as_bits();
25+
//! let y = prg.parse_arg(1, "10u32").unwrap().as_bits();
26+
//! let z = prg.parse_arg(2, "100u32").unwrap().as_bits();
27+
//! let output = prg.circuit.eval(&[x, y, z]); // use your own MPC engine here instead
28+
//! let result = prg.parse_output(&output).unwrap();
29+
//! assert_eq!("112u32", result.to_string());
30+
//!
31+
//! // Input arguments can also be constructed directly as literals:
32+
//! let x = prg.literal_arg(0, Literal::NumUnsigned(2, U32)).unwrap().as_bits();
33+
//! let y = prg.literal_arg(1, Literal::NumUnsigned(10, U32)).unwrap().as_bits();
34+
//! let z = prg.literal_arg(2, Literal::NumUnsigned(100, U32)).unwrap().as_bits();
35+
//! let output = prg.circuit.eval(&[x, y, z]); // use your own MPC engine here instead
36+
//! let result = prg.parse_output(&output).unwrap();
37+
//! assert_eq!(Literal::NumUnsigned(112, U32), result);
38+
//! ```
339
440
#![deny(unsafe_code)]
541
#![deny(missing_docs)]
642
#![deny(rustdoc::broken_intra_doc_links)]
743

844
use check::TypeError;
945
use compile::CompilerError;
10-
use eval::EvalError;
46+
use eval::{EvalError, Evaluator};
47+
use literal::Literal;
1148
use parse::ParseError;
1249
use scan::{scan, ScanError};
13-
use std::fmt::Write as _;
50+
use std::fmt::{Display, Write as _};
1451
use token::MetaInfo;
1552

1653
use ast::{Expr, FnDef, Pattern, Program, Stmt, Type, VariantExpr};
@@ -56,12 +93,92 @@ pub fn check(prg: &str) -> Result<TypedProgram, Error> {
5693
Ok(scan(prg)?.parse()?.type_check()?)
5794
}
5895

59-
/// Scans, parses, type-checks and then compiles a program to a circuit of gates.
60-
pub fn compile(prg: &str, fn_name: &str) -> Result<(TypedProgram, TypedFnDef, Circuit), Error> {
96+
/// Scans, parses, type-checks and then compiles the `"main"` fn of a program to a boolean circuit.
97+
pub fn compile(prg: &str) -> Result<GarbleProgram, Error> {
6198
let program = check(prg)?;
62-
let (circuit, main_fn) = program.compile(fn_name)?;
63-
let main_fn = main_fn.clone();
64-
Ok((program, main_fn, circuit))
99+
let (circuit, main) = program.compile("main")?;
100+
let main = main.clone();
101+
Ok(GarbleProgram {
102+
program,
103+
main,
104+
circuit,
105+
})
106+
}
107+
108+
/// The result of type-checking and compiling a Garble program.
109+
#[derive(Debug, Clone)]
110+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
111+
pub struct GarbleProgram {
112+
/// The type-checked represenation of the full program.
113+
pub program: TypedProgram,
114+
/// The function to be executed as a circuit.
115+
pub main: TypedFnDef,
116+
/// The compilation output, as a circuit of boolean gates.
117+
pub circuit: Circuit,
118+
}
119+
120+
/// An input argument for a Garble program and circuit.
121+
#[derive(Debug, Clone)]
122+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
123+
pub struct GarbleArgument<'a>(Literal, &'a TypedProgram);
124+
125+
impl GarbleProgram {
126+
/// Returns an evaluator that can be used to run the compiled circuit.
127+
pub fn evaluator(&self) -> Evaluator<'_> {
128+
Evaluator::new(&self.program, &self.main, &self.circuit)
129+
}
130+
131+
/// Type-checks and uses the literal as the circuit input argument with the given index.
132+
pub fn literal_arg(
133+
&self,
134+
arg_index: usize,
135+
literal: Literal,
136+
) -> Result<GarbleArgument<'_>, EvalError> {
137+
let Some(param) = self.main.params.get(arg_index) else {
138+
return Err(EvalError::InvalidArgIndex(arg_index));
139+
};
140+
if !literal.is_of_type(&self.program, &param.ty) {
141+
return Err(EvalError::InvalidLiteralType(literal, param.ty.clone()));
142+
}
143+
Ok(GarbleArgument(literal, &self.program))
144+
}
145+
146+
/// Tries to parse the string as the circuit input argument with the given index.
147+
pub fn parse_arg(
148+
&self,
149+
arg_index: usize,
150+
literal: &str,
151+
) -> Result<GarbleArgument<'_>, EvalError> {
152+
let Some(param) = self.main.params.get(arg_index) else {
153+
return Err(EvalError::InvalidArgIndex(arg_index));
154+
};
155+
let literal = Literal::parse(&self.program, &param.ty, literal)
156+
.map_err(EvalError::LiteralParseError)?;
157+
Ok(GarbleArgument(literal, &self.program))
158+
}
159+
160+
/// Tries to convert the circuit output back to a Garble literal.
161+
pub fn parse_output(&self, bits: &[bool]) -> Result<Literal, EvalError> {
162+
Literal::from_result_bits(&self.program, &self.main.ty, bits)
163+
}
164+
}
165+
166+
impl GarbleArgument<'_> {
167+
/// Converts the argument to input bits for the compiled circuit.
168+
pub fn as_bits(&self) -> Vec<bool> {
169+
self.0.as_bits(self.1)
170+
}
171+
172+
/// Converts the argument to a Garble literal.
173+
pub fn as_literal(&self) -> Literal {
174+
self.0.clone()
175+
}
176+
}
177+
178+
impl Display for GarbleArgument<'_> {
179+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180+
self.0.fmt(f)
181+
}
65182
}
66183

67184
/// Errors that can occur during compile time, while a program is scanned, parsed or type-checked.

tests/circuit.rs

+36-18
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ pub fn main(_x: bool) -> bool {
1212
true
1313
}
1414
";
15-
let (_, _, unoptimized) = compile(unoptimized, "main").map_err(|e| e.prettify(unoptimized))?;
16-
let (_, _, optimized) = compile(optimized, "main").map_err(|e| e.prettify(optimized))?;
15+
let unoptimized = compile(unoptimized).map_err(|e| e.prettify(unoptimized))?;
16+
let optimized = compile(optimized).map_err(|e| e.prettify(optimized))?;
1717

18-
assert_eq!(unoptimized.gates.len(), optimized.gates.len());
18+
assert_eq!(
19+
unoptimized.circuit.gates.len(),
20+
optimized.circuit.gates.len()
21+
);
1922
Ok(())
2023
}
2124

@@ -31,10 +34,13 @@ pub fn main(_x: i32) -> i32 {
3134
10i32
3235
}
3336
";
34-
let (_, _, unoptimized) = compile(unoptimized, "main").map_err(|e| e.prettify(unoptimized))?;
35-
let (_, _, optimized) = compile(optimized, "main").map_err(|e| e.prettify(optimized))?;
37+
let unoptimized = compile(unoptimized).map_err(|e| e.prettify(unoptimized))?;
38+
let optimized = compile(optimized).map_err(|e| e.prettify(optimized))?;
3639

37-
assert_eq!(unoptimized.gates.len(), optimized.gates.len());
40+
assert_eq!(
41+
unoptimized.circuit.gates.len(),
42+
optimized.circuit.gates.len()
43+
);
3844
Ok(())
3945
}
4046

@@ -51,9 +57,12 @@ pub fn main(b: bool, x: i32) -> bool {
5157
if b { y } else { y }
5258
}
5359
";
54-
let (_, _, unoptimized) = compile(unoptimized, "main").map_err(|e| e.prettify(unoptimized))?;
55-
let (_, _, optimized) = compile(optimized, "main").map_err(|e| e.prettify(optimized))?;
56-
assert_eq!(unoptimized.gates.len(), optimized.gates.len());
60+
let unoptimized = compile(unoptimized).map_err(|e| e.prettify(unoptimized))?;
61+
let optimized = compile(optimized).map_err(|e| e.prettify(optimized))?;
62+
assert_eq!(
63+
unoptimized.circuit.gates.len(),
64+
optimized.circuit.gates.len()
65+
);
5766
Ok(())
5867
}
5968

@@ -69,9 +78,12 @@ pub fn main(b: bool) -> bool {
6978
b
7079
}
7180
";
72-
let (_, _, unoptimized) = compile(unoptimized, "main").map_err(|e| e.prettify(unoptimized))?;
73-
let (_, _, optimized) = compile(optimized, "main").map_err(|e| e.prettify(optimized))?;
74-
assert_eq!(unoptimized.gates.len(), optimized.gates.len());
81+
let unoptimized = compile(unoptimized).map_err(|e| e.prettify(unoptimized))?;
82+
let optimized = compile(optimized).map_err(|e| e.prettify(optimized))?;
83+
assert_eq!(
84+
unoptimized.circuit.gates.len(),
85+
optimized.circuit.gates.len()
86+
);
7587
Ok(())
7688
}
7789

@@ -98,9 +110,12 @@ pub fn main(arr1: [u8; 8], arr2: [u8; 8], choice: bool) -> [u8; 8] {
98110
arr
99111
}
100112
";
101-
let (_, _, unoptimized) = compile(unoptimized, "main").map_err(|e| e.prettify(unoptimized))?;
102-
let (_, _, optimized) = compile(optimized, "main").map_err(|e| e.prettify(optimized))?;
103-
assert_eq!(unoptimized.gates.len(), optimized.gates.len());
113+
let unoptimized = compile(unoptimized).map_err(|e| e.prettify(unoptimized))?;
114+
let optimized = compile(optimized).map_err(|e| e.prettify(optimized))?;
115+
assert_eq!(
116+
unoptimized.circuit.gates.len(),
117+
optimized.circuit.gates.len()
118+
);
104119
Ok(())
105120
}
106121

@@ -127,8 +142,11 @@ pub fn main(arr1: [u8; 8], arr2: [u8; 8], choice: bool) -> [u8; 8] {
127142
arr
128143
}
129144
";
130-
let (_, _, unoptimized) = compile(unoptimized, "main").map_err(|e| e.prettify(unoptimized))?;
131-
let (_, _, optimized) = compile(optimized, "main").map_err(|e| e.prettify(optimized))?;
132-
assert_eq!(unoptimized.gates.len(), optimized.gates.len());
145+
let unoptimized = compile(unoptimized).map_err(|e| e.prettify(unoptimized))?;
146+
let optimized = compile(optimized).map_err(|e| e.prettify(optimized))?;
147+
assert_eq!(
148+
unoptimized.circuit.gates.len(),
149+
optimized.circuit.gates.len()
150+
);
133151
Ok(())
134152
}

0 commit comments

Comments
 (0)