From 38e3ae89838ba69fea28e504939f5491ac5c7b1a Mon Sep 17 00:00:00 2001 From: Frederic Kettelhoit Date: Tue, 7 Jan 2025 11:14:02 +0000 Subject: [PATCH 1/5] Add test for Garble serialization --- .github/workflows/test.yml | 2 +- Cargo.lock | 25 ++++++++ Cargo.toml | 2 + tests/literal.rs | 123 +++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 tests/literal.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ebd2e14..30c4a96 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,5 +22,5 @@ jobs: ~/.rustup key: ${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }} - run: cargo build --features="bin" - - run: cargo test + - run: cargo test --features="serde" - run: cargo clippy -- -Dwarnings diff --git a/Cargo.lock b/Cargo.lock index f095ded..7dc8dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,6 +414,7 @@ dependencies = [ "quickcheck", "quickcheck_macros", "serde", + "serde_json", ] [[package]] @@ -486,6 +487,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -776,6 +783,12 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "same-file" version = "1.0.6" @@ -811,6 +824,18 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 02b96bc..e4bb495 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,5 @@ plot = ["plotters"] [dev-dependencies] quickcheck = "1" quickcheck_macros = "1" +serde_json = "1.0.134" +serde = { version = "1.0", features = ["derive"] } diff --git a/tests/literal.rs b/tests/literal.rs new file mode 100644 index 0000000..63d9eb2 --- /dev/null +++ b/tests/literal.rs @@ -0,0 +1,123 @@ +#![cfg(feature = "serde")] +use garble_lang::{ + literal::{Literal, VariantLiteral}, + token::{SignedNumType, UnsignedNumType}, +}; + +#[test] +fn serde_true() -> Result<(), String> { + let literal = Literal::True; + let expected = "\"True\""; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_int_unsigned() -> Result<(), String> { + let literal = Literal::NumUnsigned(200, UnsignedNumType::U32); + let expected = "{\"NumUnsigned\":[200,\"U32\"]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_int_signed() -> Result<(), String> { + let literal = Literal::NumSigned(-200, SignedNumType::Unspecified); + let expected = "{\"NumSigned\":[-200,\"Unspecified\"]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_array_repeat() -> Result<(), String> { + let literal = Literal::ArrayRepeat(Box::new(Literal::True), 3); + let expected = "{\"ArrayRepeat\":[\"True\",3]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_array() -> Result<(), String> { + let literal = Literal::Array(vec![Literal::True, Literal::False]); + let expected = "{\"Array\":[\"True\",\"False\"]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_tuple() -> Result<(), String> { + let literal = Literal::Tuple(vec![ + Literal::True, + Literal::False, + Literal::NumUnsigned(10, UnsignedNumType::U8), + ]); + let expected = "{\"Tuple\":[\"True\",\"False\",{\"NumUnsigned\":[10,\"U8\"]}]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_struct() -> Result<(), String> { + let literal = Literal::Struct( + "FooBar".to_string(), + vec![ + ("Foo".to_string(), Literal::True), + ("Bar".to_string(), Literal::False), + ], + ); + let expected = "{\"Struct\":[\"FooBar\",[[\"Foo\",\"True\"],[\"Bar\",\"False\"]]]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_enum_unit() -> Result<(), String> { + let literal = Literal::Enum( + "FooBar".to_string(), + "Foo".to_string(), + VariantLiteral::Unit, + ); + let expected = "{\"Enum\":[\"FooBar\",\"Foo\",\"Unit\"]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_enum_tuple() -> Result<(), String> { + let literal = Literal::Enum( + "FooBar".to_string(), + "Bar".to_string(), + VariantLiteral::Tuple(vec![Literal::True, Literal::False]), + ); + let expected = "{\"Enum\":[\"FooBar\",\"Bar\",{\"Tuple\":[\"True\",\"False\"]}]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} + +#[test] +fn serde_range() -> Result<(), String> { + let literal = Literal::Range((2, UnsignedNumType::U8), (10, UnsignedNumType::U8)); + let expected = "{\"Range\":[[2,\"U8\"],[10,\"U8\"]]}"; + let json = serde_json::to_string(&literal).unwrap(); + assert_eq!(json, expected); + assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + Ok(()) +} From 6c1fcbfe6af73e2a3e74f09e22af72fed11a65dd Mon Sep 17 00:00:00 2001 From: Frederic Kettelhoit Date: Tue, 7 Jan 2025 17:18:38 +0000 Subject: [PATCH 2/5] Simplify `Range` Expr in AST --- src/ast.rs | 2 +- src/check.rs | 18 +++--------------- src/compile.rs | 4 ++-- src/literal.rs | 16 ++++++++-------- src/parse.rs | 19 +++++++++++++++---- tests/literal.rs | 4 ++-- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 86709ca..726d31d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -365,7 +365,7 @@ pub enum ExprEnum { /// Explicit cast of an expression to the specified type. Cast(Type, Box>), /// Range of numbers from the specified min (inclusive) to the specified max (exclusive). - Range((u64, UnsignedNumType), (u64, UnsignedNumType)), + Range(u64, u64, UnsignedNumType), } /// The different kinds of variant literals. diff --git a/src/check.rs b/src/check.rs index e288152..29a4516 100644 --- a/src/check.rs +++ b/src/check.rs @@ -103,8 +103,6 @@ pub enum TypeErrorEnum { }, /// The two types are incompatible, (e.g. incompatible number types in a `+` operation). TypeMismatch(Type, Type), - /// The specified range has different min and max types. - RangeTypeMismatch(UnsignedNumType, UnsignedNumType), /// The specified range has invalid min or max values. InvalidRange(u64, u64), /// The specified pattern does not match the type of the matched expression. @@ -201,9 +199,6 @@ impl std::fmt::Display for TypeErrorEnum { TypeErrorEnum::TypeMismatch(x, y) => f.write_fmt(format_args!( "The arguments have incompatible types; {x} vs {y}" )), - TypeErrorEnum::RangeTypeMismatch(from, to) => f.write_fmt(format_args!( - "Start and end of range do not have the same type; {from} vs {to}" - )), TypeErrorEnum::InvalidRange(_, _) => f.write_str("Invalid range"), TypeErrorEnum::PatternDoesNotMatchType(ty) => { f.write_fmt(format_args!("The pattern does not match the type {ty}")) @@ -1156,20 +1151,13 @@ impl UntypedExpr { expect_bool_or_num_type(&ty, meta)?; (ExprEnum::Cast(ty.clone(), Box::new(expr)), ty) } - ExprEnum::Range((from, from_suffix), (to, to_suffix)) => { + ExprEnum::Range(from, to, num_ty) => { if from >= to || (to - from) > u32::MAX as u64 { let e = TypeErrorEnum::InvalidRange(*from, *to); return Err(vec![Some(TypeError(e, meta))]); } - if from_suffix != to_suffix { - let e = TypeErrorEnum::RangeTypeMismatch(*from_suffix, *to_suffix); - return Err(vec![Some(TypeError(e, meta))]); - } - let ty = Type::Array(Box::new(Type::Unsigned(*from_suffix)), (to - from) as usize); - ( - ExprEnum::Range((*from, *from_suffix), (*to, *to_suffix)), - ty, - ) + let ty = Type::Array(Box::new(Type::Unsigned(*num_ty)), (to - from) as usize); + (ExprEnum::Range(*from, *to, *num_ty), ty) } ExprEnum::EnumLiteral(identifier, variant_name, variant) => { if let Some(enum_def) = defs.enums.get(identifier.as_str()) { diff --git a/src/compile.rs b/src/compile.rs index 1b27684..6d697c3 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1016,10 +1016,10 @@ impl TypedExpr { } } } - ExprEnum::Range((from, elem_ty), (to, _)) => { + ExprEnum::Range(from, to, num_ty) => { let size = (to - from) as usize; let elem_bits = - Type::Unsigned(*elem_ty).size_in_bits_for_defs(prg, circuit.const_sizes()); + Type::Unsigned(*num_ty).size_in_bits_for_defs(prg, circuit.const_sizes()); let mut array = Vec::with_capacity(elem_bits * size); for i in *from..*to { for b in (0..elem_bits).rev() { diff --git a/src/literal.rs b/src/literal.rs index 973fab0..479741b 100644 --- a/src/literal.rs +++ b/src/literal.rs @@ -45,7 +45,7 @@ pub enum Literal { /// Enum literal of the specified variant, possibly with fields. Enum(String, String, VariantLiteral), /// Range of numbers from the specified min (inclusive) to the specified max (exclusive). - Range((u64, UnsignedNumType), (u64, UnsignedNumType)), + Range(u64, u64, UnsignedNumType), } /// A variant literal (either of unit type or containing fields), used by [`Literal::Enum`]. @@ -158,8 +158,8 @@ impl Literal { } false } - (Literal::Range((min, min_ty), (max, _)), Type::Array(elem_ty, size)) => { - elem_ty.as_ref() == &Type::Unsigned(*min_ty) && max - min == *size as u64 + (Literal::Range(min, max, num_ty), Type::Array(elem_ty, size)) => { + elem_ty.as_ref() == &Type::Unsigned(*num_ty) && max - min == *size as u64 } _ => false, } @@ -423,9 +423,9 @@ impl Literal { } wires } - Literal::Range((min, min_ty), (max, _)) => { + Literal::Range(min, max, num_ty) => { let elems: Vec = (*min as usize..*max as usize).collect(); - let elem_size = Type::Unsigned(*min_ty).size_in_bits_for_defs(checked, const_sizes); + let elem_size = Type::Unsigned(*num_ty).size_in_bits_for_defs(checked, const_sizes); let mut bits = Vec::with_capacity(elems.len() * elem_size); for elem in elems { unsigned_to_bits(elem as u64, elem_size, &mut bits); @@ -494,8 +494,8 @@ impl Display for Literal { write!(f, ")") } }, - Literal::Range((min, min_ty), (max, max_ty)) => { - write!(f, "{min}{min_ty}..{max}{max_ty}") + Literal::Range(min, max, num_ty) => { + write!(f, "{min}{num_ty}..{max}{num_ty}") } } } @@ -554,7 +554,7 @@ impl TypedExpr { }; Literal::Enum(name, variant_name.clone(), variant) } - ExprEnum::Range(min, max) => Literal::Range(min, max), + ExprEnum::Range(min, max, num_ty) => Literal::Range(min, max, num_ty), _ => unreachable!("This should result in a literal parse error instead"), } } diff --git a/src/parse.rs b/src/parse.rs index 960f055..ed122da 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -32,6 +32,8 @@ pub enum ParseErrorEnum { InvalidLiteral, /// Expected a const expr, but found a non-const or invalid expr. InvalidConstExpr, + /// The specified range has different min and max types. + InvalidRangeTypes(UnsignedNumType, UnsignedNumType), /// Expected a type, but found a non-type token. ExpectedType, /// Expected a statement, but found a non-statement token. @@ -62,6 +64,9 @@ impl std::fmt::Display for ParseErrorEnum { ParseErrorEnum::InvalidPattern => f.write_str("Invalid pattern"), ParseErrorEnum::InvalidLiteral => f.write_str("Invalid literal"), ParseErrorEnum::InvalidConstExpr => f.write_str("Invalid const expr"), + ParseErrorEnum::InvalidRangeTypes(from, to) => f.write_fmt(format_args!( + "Start and end of range do not have the same type; {from} vs {to}" + )), ParseErrorEnum::ExpectedType => f.write_str("Expected a type"), ParseErrorEnum::ExpectedStmt => f.write_str("Expected a statement"), ParseErrorEnum::ExpectedExpr => f.write_str("Expected an expression"), @@ -1322,10 +1327,16 @@ impl Parser { let meta_end = *meta_end; self.advance(); let meta = join_meta(meta, meta_end); - Expr::untyped( - ExprEnum::Range((n, n_suffix), (range_end, range_end_suffix)), - meta, - ) + let num_ty = match (n_suffix, range_end_suffix) { + (UnsignedNumType::Unspecified, ty) + | (ty, UnsignedNumType::Unspecified) => ty, + (ty1, ty2) if ty1 == ty2 => ty1, + (ty1, ty2) => { + self.push_error(ParseErrorEnum::InvalidRangeTypes(ty1, ty2), meta); + ty1 + } + }; + Expr::untyped(ExprEnum::Range(n, range_end, num_ty), meta) } else { self.push_error_for_next(ParseErrorEnum::InvalidRangeExpr); return Err(()); diff --git a/tests/literal.rs b/tests/literal.rs index 63d9eb2..b951000 100644 --- a/tests/literal.rs +++ b/tests/literal.rs @@ -114,8 +114,8 @@ fn serde_enum_tuple() -> Result<(), String> { #[test] fn serde_range() -> Result<(), String> { - let literal = Literal::Range((2, UnsignedNumType::U8), (10, UnsignedNumType::U8)); - let expected = "{\"Range\":[[2,\"U8\"],[10,\"U8\"]]}"; + let literal = Literal::Range(2, 10, UnsignedNumType::U8); + let expected = "{\"Range\":[2,10,\"U8\"]}"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); From f29ca7d54fcdd6c8ef6000aba67323b3ba070936 Mon Sep 17 00:00:00 2001 From: Frederic Kettelhoit Date: Wed, 8 Jan 2025 13:33:45 +0000 Subject: [PATCH 3/5] Add ABNF grammar + examples for Garble literals to docs --- garble_docs/src/SUMMARY.md | 1 + garble_docs/src/serde.md | 52 +++++++++++++++++++++++++++++++ garble_docs/theme/css/general.css | 37 ++++++++++++++++++++++ tests/literal.rs | 26 ++++++++++++++-- 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 garble_docs/src/serde.md diff --git a/garble_docs/src/SUMMARY.md b/garble_docs/src/SUMMARY.md index ff04024..aa42d7c 100644 --- a/garble_docs/src/SUMMARY.md +++ b/garble_docs/src/SUMMARY.md @@ -11,4 +11,5 @@ - [Const Parameters](./guide/const.md) - [Circuits and Termination](./guide/computational_model.md) - [MPC Engine Integration](./integration.md) +- [(De-)Serializing Garble Values](./serde.md) - [Contributing](./contributing.md) diff --git a/garble_docs/src/serde.md b/garble_docs/src/serde.md new file mode 100644 index 0000000..19b8520 --- /dev/null +++ b/garble_docs/src/serde.md @@ -0,0 +1,52 @@ +# (De-)Serializing Garble Values + +If the optional `"serde"` crate feature is enabled, Garble supports serializing literal values to/from formats supported via [serde](https://serde.rs/). The following [ABNF](https://en.wikipedia.org/wiki/Augmented_Backus%E2%80%93Naur_form) grammar shows how Garble literals are represented in JSON (using `serde_json`): + +
+literal = "\"True\"" /
+          "\"False\"" /
+          "{\"NumUnsigned\":[" uint "," uint-ty "]}" /
+          "{\"NumSigned\":[" int "," int-ty "]}" /
+          "{\"ArrayRepeat\":[" literal "," uint "]}" /
+          "{\"Array\":[" [literal *("," literal)] "]}" /
+          "{\"Tuple\":[" [literal *("," literal)] "]}" /
+          "{\"Enum\":[\"" string "\",\"" string "\"," variant "]}" /
+          "{\"Range\":[" uint "," uint "," uint-type "]}"
+
+uint    = 1*DIGIT
+
+uint-ty = "\"Usize\"" /
+          "\"U8\"" /
+          "\"U16\"" /
+          "\"U32\"" /
+          "\"U64\"" /
+          "\"Unspecified\""
+
+int     = ["-"] uint
+
+int-ty  = "\"I8\"" /
+          "\"I16\"" /
+          "\"I32\"" /
+          "\"I64\"" /
+          "\"Unspecified\""
+
+string  = 1*ALPHA
+
+variant = "\"Unit\"" /
+          "{\"Tuple\":[" [literal *("," literal)] "]}"
+
+ +Here are some example Garble literals and how they would be serialized as JSON: + +| Garble Literal | Serialized as JSON | +| -------------------------------- | -------------------------------------------------------- | +| `true` | `"True"` | +| `200u32` | `{"NumUnsigned":[200,"U32"]}` | +| `-200` | `{"NumSigned":[-200,"Unspecified"]}` | +| `[true; 3]` | `{"ArrayRepeat":["True",3]}` | +| `[true, false]` | `{"Array":["True","False"]}` | +| `(true, false, 10)` | `{"Tuple":["True","False",{"NumUnsigned":[10,"U8"]}]}` | +| `FooBar {foo: true, bar: false}` | `{"Struct":["FooBar",[["foo","True"],["bar","False"]]]}` | +| `FooBar::Foo` | `{"Enum":["FooBar","Foo","Unit"]}` | +| `FooBar::Bar(true, false)` | `{"Enum":["FooBar","Bar",{"Tuple":["True","False"]}]}` | +| `2u8..10u8` | `{"Range":[2,10,"U8"]}` | diff --git a/garble_docs/theme/css/general.css b/garble_docs/theme/css/general.css index 6499ae5..8f1b738 100644 --- a/garble_docs/theme/css/general.css +++ b/garble_docs/theme/css/general.css @@ -359,4 +359,41 @@ a, #sidebar a { font-weight: normal; +} + +.abnf, +.abnf>* { + font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace !important; + line-height: 1.8; +} + +.abnf .str { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; + border: 1px solid #333; + background-color: #fff; + line-height: 1.8; +} + +.table-wrapper { + overflow-x: auto; +} + +table { + margin: 0; + border: none; +} + +table tbody tr:nth-child(2n) { + background-color: unset !important; +} + +table thead th { + text-align: left; + padding: 6px 6px; +} + +table td { + padding: 6px 4px; } \ No newline at end of file diff --git a/tests/literal.rs b/tests/literal.rs index b951000..523f819 100644 --- a/tests/literal.rs +++ b/tests/literal.rs @@ -8,9 +8,11 @@ use garble_lang::{ fn serde_true() -> Result<(), String> { let literal = Literal::True; let expected = "\"True\""; + let as_garble_code = "true"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -18,9 +20,11 @@ fn serde_true() -> Result<(), String> { fn serde_int_unsigned() -> Result<(), String> { let literal = Literal::NumUnsigned(200, UnsignedNumType::U32); let expected = "{\"NumUnsigned\":[200,\"U32\"]}"; + let as_garble_code = "200"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -28,9 +32,11 @@ fn serde_int_unsigned() -> Result<(), String> { fn serde_int_signed() -> Result<(), String> { let literal = Literal::NumSigned(-200, SignedNumType::Unspecified); let expected = "{\"NumSigned\":[-200,\"Unspecified\"]}"; + let as_garble_code = "-200"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -38,9 +44,11 @@ fn serde_int_signed() -> Result<(), String> { fn serde_array_repeat() -> Result<(), String> { let literal = Literal::ArrayRepeat(Box::new(Literal::True), 3); let expected = "{\"ArrayRepeat\":[\"True\",3]}"; + let as_garble_code = "[true; 3]"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -48,9 +56,11 @@ fn serde_array_repeat() -> Result<(), String> { fn serde_array() -> Result<(), String> { let literal = Literal::Array(vec![Literal::True, Literal::False]); let expected = "{\"Array\":[\"True\",\"False\"]}"; + let as_garble_code = "[true, false]"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -62,9 +72,11 @@ fn serde_tuple() -> Result<(), String> { Literal::NumUnsigned(10, UnsignedNumType::U8), ]); let expected = "{\"Tuple\":[\"True\",\"False\",{\"NumUnsigned\":[10,\"U8\"]}]}"; + let as_garble_code = "(true, false, 10)"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -73,14 +85,16 @@ fn serde_struct() -> Result<(), String> { let literal = Literal::Struct( "FooBar".to_string(), vec![ - ("Foo".to_string(), Literal::True), - ("Bar".to_string(), Literal::False), + ("foo".to_string(), Literal::True), + ("bar".to_string(), Literal::False), ], ); - let expected = "{\"Struct\":[\"FooBar\",[[\"Foo\",\"True\"],[\"Bar\",\"False\"]]]}"; + let expected = "{\"Struct\":[\"FooBar\",[[\"foo\",\"True\"],[\"bar\",\"False\"]]]}"; + let as_garble_code = "FooBar {foo: true, bar: false}"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -92,9 +106,11 @@ fn serde_enum_unit() -> Result<(), String> { VariantLiteral::Unit, ); let expected = "{\"Enum\":[\"FooBar\",\"Foo\",\"Unit\"]}"; + let as_garble_code = "FooBar::Foo"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -106,9 +122,11 @@ fn serde_enum_tuple() -> Result<(), String> { VariantLiteral::Tuple(vec![Literal::True, Literal::False]), ); let expected = "{\"Enum\":[\"FooBar\",\"Bar\",{\"Tuple\":[\"True\",\"False\"]}]}"; + let as_garble_code = "FooBar::Bar(true, false)"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } @@ -116,8 +134,10 @@ fn serde_enum_tuple() -> Result<(), String> { fn serde_range() -> Result<(), String> { let literal = Literal::Range(2, 10, UnsignedNumType::U8); let expected = "{\"Range\":[2,10,\"U8\"]}"; + let as_garble_code = "2u8..10u8"; let json = serde_json::to_string(&literal).unwrap(); assert_eq!(json, expected); assert_eq!(literal, serde_json::from_str::(&json).unwrap()); + assert_eq!(literal.to_string(), as_garble_code); Ok(()) } From 2b66604c037e7836a40980daba1a02aadc95784f Mon Sep 17 00:00:00 2001 From: Frederic Kettelhoit Date: Wed, 8 Jan 2025 13:44:11 +0000 Subject: [PATCH 4/5] Add info about (de-)serialization as doc comments --- src/literal.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/literal.rs b/src/literal.rs index 479741b..9f3adde 100644 --- a/src/literal.rs +++ b/src/literal.rs @@ -1,5 +1,8 @@ //! A subset of [`crate::ast::Expr`] that is used as input / output by an //! [`crate::eval::Evaluator`]. +//! +//! See [`crate::literal::Literal`] for examples on how to (de-)serialize to/from Garble literals +//! using `serde`. use std::{ collections::{HashMap, HashSet}, @@ -23,6 +26,59 @@ use crate::{ /// A subset of [`crate::ast::Expr`] that is used as input / output by an /// [`crate::eval::Evaluator`]. +/// +/// If the `serde` crate feature is enabled, literals can be (de-)serialized to any format supported +/// by `serde`. The following ABNF grammar shows how literals are represented when serialized using +/// `serde_json`: +/// +/// ```asci +/// literal = "\"True\"" / +/// "\"False\"" / +/// "{\"NumUnsigned\":[" uint "," uint-ty "]}" / +/// "{\"NumSigned\":[" int "," int-ty "]}" / +/// "{\"ArrayRepeat\":[" literal "," uint "]}" / +/// "{\"Array\":[" [literal *("," literal)] "]}" / +/// "{\"Tuple\":[" [literal *("," literal)] "]}" / +/// "{\"Enum\":[\"" string "\",\"" string "\"," variant "]}" / +/// "{\"Range\":[" uint "," uint "," uint-type "]}" +/// +/// uint = 1*DIGIT +/// +/// uint-ty = "\"Usize\"" / +/// "\"U8\"" / +/// "\"U16\"" / +/// "\"U32\"" / +/// "\"U64\"" / +/// "\"Unspecified\"" +/// +/// int = ["-"] uint +/// +/// int-ty = "\"I8\"" / +/// "\"I16\"" / +/// "\"I32\"" / +/// "\"I64\"" / +/// "\"Unspecified\"" +/// +/// string = 1*ALPHA +/// +/// variant = "\"Unit\"" / +/// "{\"Tuple\":[" [literal *("," literal)] "]}" +/// ``` +/// +/// Here are some example Garble literals and how they would be serialized as JSON: +/// +/// | Garble Literal | Serialized as JSON | +/// | -------------------------------- | -------------------------------------------------------- | +/// | `true` | `"True"` | +/// | `200u32` | `{"NumUnsigned":[200,"U32"]}` | +/// | `-200` | `{"NumSigned":[-200,"Unspecified"]}` | +/// | `[true; 3]` | `{"ArrayRepeat":["True",3]}` | +/// | `[true, false]` | `{"Array":["True","False"]}` | +/// | `(true, false, 10)` | `{"Tuple":["True","False",{"NumUnsigned":[10,"U8"]}]}` | +/// | `FooBar {foo: true, bar: false}` | `{"Struct":["FooBar",[["foo","True"],["bar","False"]]]}` | +/// | `FooBar::Foo` | `{"Enum":["FooBar","Foo","Unit"]}` | +/// | `FooBar::Bar(true, false)` | `{"Enum":["FooBar","Bar",{"Tuple":["True","False"]}]}` | +/// | `2u8..10u8` | `{"Range":[2,10,"U8"]}` | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Literal { From 6829fe6872570b5a2a4eddf686ad12fadb4a7b92 Mon Sep 17 00:00:00 2001 From: Frederic Kettelhoit Date: Wed, 8 Jan 2025 13:59:37 +0000 Subject: [PATCH 5/5] Move explanation about circuits + termination to separate doc page --- garble_docs/src/SUMMARY.md | 2 +- garble_docs/src/{guide => }/computational_model.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename garble_docs/src/{guide => }/computational_model.md (85%) diff --git a/garble_docs/src/SUMMARY.md b/garble_docs/src/SUMMARY.md index aa42d7c..8a09c14 100644 --- a/garble_docs/src/SUMMARY.md +++ b/garble_docs/src/SUMMARY.md @@ -9,7 +9,7 @@ - [If/Else and Pattern Matching](./guide/control_flow.md) - [For Loops and For-Join Loops](./guide/loops.md) - [Const Parameters](./guide/const.md) - - [Circuits and Termination](./guide/computational_model.md) +- [Circuits and Termination](./computational_model.md) - [MPC Engine Integration](./integration.md) - [(De-)Serializing Garble Values](./serde.md) - [Contributing](./contributing.md) diff --git a/garble_docs/src/guide/computational_model.md b/garble_docs/src/computational_model.md similarity index 85% rename from garble_docs/src/guide/computational_model.md rename to garble_docs/src/computational_model.md index cb14810..643845f 100644 --- a/garble_docs/src/guide/computational_model.md +++ b/garble_docs/src/computational_model.md @@ -1,10 +1,10 @@ # Circuits and Termination -Garble programs are Boolean _circuits_ consisting of a graph of logic gates, not a sequentially executed program of instructions on a von Neumann architecture with main memory and CPU. This has deep consequences for the programming style that leads to efficient Garble programs, with programs that would be efficient in "normal" programming languages resulting in highly inefficient circuits and vice versa. +Garble programs are Boolean _circuits_ consisting of a graph of logic gates, not sequentially executed programs of instructions on a von Neumann architecture with main memory and CPU. This has deep consequences for the programming style that leads to efficient Garble programs, with programs that would be efficient in "normal" programming languages resulting in highly inefficient circuits and vice versa. ## Copying is Free -One example has already been mentioned: Copying whole arrays in Garble is essentially free, because arrays (and their elements) are just a collection of output wires from a bunch of Boolean logic gates. Duplicating these wires does not increase the complexity of the circuit, because no additional logic gates are required. +One example has already been mentioned in the context of [arrays](./guide/data_types.md#arrays-and-ranges): Copying whole arrays in Garble is essentially free, because arrays (and their elements) are just a collection of output wires from a bunch of Boolean logic gates. Duplicating these wires does not increase the complexity of the circuit, because no additional logic gates are required. Replacing the element at a _constant_ index in an array with a new value is equally cheap, because the Garble compiler can just duplicate the output wires of all the other elements and only has to use the wires of the replacement element where previously the old element was being used. In contrast, replacing the element at a _non-constant_ index (i.e. an index that depends on a runtime value) is a much more expensive operation in a Boolean circuit than it would be on a normal computer, because the Garble compiler has to generate a nested multiplexer circuit.