|
1 | 1 | //! A purely functional programming language with a Rust-like syntax that compiles to logic gates
|
2 | 2 | //! 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 | +//! ``` |
3 | 39 |
|
4 | 40 | #![deny(unsafe_code)]
|
5 | 41 | #![deny(missing_docs)]
|
6 | 42 | #![deny(rustdoc::broken_intra_doc_links)]
|
7 | 43 |
|
8 | 44 | use check::TypeError;
|
9 | 45 | use compile::CompilerError;
|
10 |
| -use eval::EvalError; |
| 46 | +use eval::{EvalError, Evaluator}; |
| 47 | +use literal::Literal; |
11 | 48 | use parse::ParseError;
|
12 | 49 | use scan::{scan, ScanError};
|
13 |
| -use std::fmt::Write as _; |
| 50 | +use std::fmt::{Display, Write as _}; |
14 | 51 | use token::MetaInfo;
|
15 | 52 |
|
16 | 53 | use ast::{Expr, FnDef, Pattern, Program, Stmt, Type, VariantExpr};
|
@@ -56,12 +93,92 @@ pub fn check(prg: &str) -> Result<TypedProgram, Error> {
|
56 | 93 | Ok(scan(prg)?.parse()?.type_check()?)
|
57 | 94 | }
|
58 | 95 |
|
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> { |
61 | 98 | 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, ¶m.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, ¶m.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 | + } |
65 | 182 | }
|
66 | 183 |
|
67 | 184 | /// Errors that can occur during compile time, while a program is scanned, parsed or type-checked.
|
|
0 commit comments