use msgpack_tagged::{
EncodingStrategy, MsgpackTagged, Serializer as TaggedSerializer, TagRegistry,
msgpack_tagged_deserialize,
};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use strum_macros::EnumString;
const FORMAT_ENV_VAR: &str = "NOIR_SERIALIZATION_FORMAT";
#[derive(Debug, Default, Clone, Copy, IntoPrimitive, TryFromPrimitive, EnumString, PartialEq, Eq)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
#[strum(serialize_all = "kebab-case")]
#[repr(u8)]
pub enum Format {
Msgpack = 2,
#[default]
MsgpackCompact = 3,
MsgpackTagged = 4,
}
impl Format {
pub fn from_env() -> Result<Option<Self>, String> {
let Ok(format) = std::env::var(FORMAT_ENV_VAR) else {
return Ok(None);
};
Self::from_str(&format)
.map(Some)
.map_err(|e| format!("unknown format '{format}' in {FORMAT_ENV_VAR}: {e}"))
}
}
pub(crate) fn msgpack_serialize<T: Serialize>(
value: &T,
compact: bool,
) -> std::io::Result<Vec<u8>> {
let mut buf = Vec::new();
let serializer = rmp_serde::Serializer::new(&mut buf)
.with_bytes(rmp_serde::config::BytesMode::ForceIterables);
let result = if compact {
value.serialize(&mut serializer.with_struct_tuple())
} else {
value.serialize(&mut serializer.with_struct_map())
};
match result {
Ok(()) => Ok(buf),
Err(e) => Err(std::io::Error::other(e)),
}
}
pub(crate) fn msgpack_deserialize<T: for<'a> Deserialize<'a>>(buf: &[u8]) -> std::io::Result<T> {
rmp_serde::from_slice(buf).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
}
pub(crate) fn deserialize_any_format<T>(buf: &[u8]) -> std::io::Result<T>
where
T: for<'a> Deserialize<'a> + MsgpackTagged,
{
let Some(format_byte) = buf.first() else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "empty buffer"));
};
match Format::try_from(*format_byte) {
Ok(Format::Msgpack) | Ok(Format::MsgpackCompact) => msgpack_deserialize(&buf[1..]),
Ok(Format::MsgpackTagged) => msgpack_tagged_deserialize(&buf[1..]),
Err(msg) => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, msg.to_string())),
}
}
pub(crate) fn serialize_with_format<T>(value: &T, format: Format) -> std::io::Result<Vec<u8>>
where
T: Serialize + MsgpackTagged,
{
let mut buf = match format {
Format::Msgpack => msgpack_serialize(value, false)?,
Format::MsgpackCompact => msgpack_serialize(value, true)?,
Format::MsgpackTagged => msgpack_tagged_serialize_acir(value)?,
};
let mut res = vec![format.into()];
res.append(&mut buf);
Ok(res)
}
pub(crate) fn msgpack_tagged_serialize_acir<T>(value: &T) -> std::io::Result<Vec<u8>>
where
T: ?Sized + Serialize + MsgpackTagged,
{
let registry = TagRegistry::from_type::<T>();
let mut buf = Vec::new();
let mut serializer = TaggedSerializer::new(&mut buf, ®istry)
.with_default_strategy(EncodingStrategy::Array)
.with_strategy_for_name("Program", EncodingStrategy::Tagged)
.with_strategy_for_name("Circuit", EncodingStrategy::Tagged)
.with_strategy_for_name("BrilligBytecode", EncodingStrategy::Tagged);
value.serialize(&mut serializer).map_err(std::io::Error::other)?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use brillig::{BitSize, HeapArray, IntegerBitSize, ValueOrArray, lengths::SemiFlattenedLength};
use std::str::FromStr;
use crate::{
native_types::Witness,
serialization::{Format, msgpack_deserialize, msgpack_serialize},
};
mod version1 {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) enum Foo {
Case0 { d: u32 },
Case1 { a: u64, b: bool },
Case2 { a: i32 },
Case3 { a: bool },
Case4 { a: Box<Foo> },
Case5 { a: u32, b: Option<u32> },
}
}
mod version2 {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) enum Foo {
Case1 {
a: u64,
b: bool,
},
Case2 {
b: String,
a: i32,
},
Case3 {
a: bool,
b: String,
},
Case5 {
a: u32,
},
Case4 {
#[serde(rename = "a")]
c: Box<Foo>,
},
Case6 {
b: i64,
},
Case7 {
c: bool,
},
}
}
#[test]
fn msgpack_serialize_backwards_compatibility() {
let cases = vec![
(version2::Foo::Case1 { b: true, a: 1 }, version1::Foo::Case1 { b: true, a: 1 }),
(version2::Foo::Case2 { b: "prefix".into(), a: 2 }, version1::Foo::Case2 { a: 2 }),
(
version2::Foo::Case3 { a: true, b: "suffix".into() },
version1::Foo::Case3 { a: true },
),
(
version2::Foo::Case4 { c: Box::new(version2::Foo::Case1 { a: 4, b: false }) },
version1::Foo::Case4 { a: Box::new(version1::Foo::Case1 { a: 4, b: false }) },
),
(version2::Foo::Case5 { a: 5 }, version1::Foo::Case5 { a: 5, b: None }),
];
for (i, (v2, v1)) in cases.into_iter().enumerate() {
let bz = msgpack_serialize(&v2, false).unwrap();
let v = msgpack_deserialize::<version1::Foo>(&bz)
.unwrap_or_else(|e| panic!("case {i} failed: {e}"));
assert_eq!(v, v1);
}
}
#[test]
fn msgpack_serialize_compact_backwards_compatibility() {
let cases = vec![
(version2::Foo::Case1 { b: true, a: 1 }, version1::Foo::Case1 { b: true, a: 1 }, None),
(
version2::Foo::Case2 { b: "prefix".into(), a: 2 },
version1::Foo::Case2 { a: 2 },
Some("wrong msgpack marker FixStr(6)"),
),
(
version2::Foo::Case3 { a: true, b: "suffix".into() },
version1::Foo::Case3 { a: true },
Some("array had incorrect length, expected 1"),
),
(
version2::Foo::Case4 { c: Box::new(version2::Foo::Case1 { a: 4, b: false }) },
version1::Foo::Case4 { a: Box::new(version1::Foo::Case1 { a: 4, b: false }) },
None,
),
(
version2::Foo::Case5 { a: 5 },
version1::Foo::Case5 { a: 5, b: None },
Some("invalid length 1, expected struct variant Foo::Case5 with 2 elements"),
),
];
for (i, (v2, v1, ex)) in cases.into_iter().enumerate() {
let bz = msgpack_serialize(&v2, true).unwrap();
let res = msgpack_deserialize::<version1::Foo>(&bz);
match (res, ex) {
(Ok(v), None) => {
assert_eq!(v, v1);
}
(Ok(_), Some(ex)) => panic!("case {i} expected to fail with {ex}"),
(Err(e), None) => panic!("case {i} expected to pass; got {e}"),
(Err(e), Some(ex)) => {
let e = e.to_string();
if !e.contains(ex) {
panic!("case {i} error expected to contain {ex}; got {e}")
}
}
}
}
}
#[test]
fn msgpack_repr_enum_of_structs() {
use rmpv::Value;
let value = ValueOrArray::HeapArray(HeapArray {
pointer: brillig::MemoryAddress::Relative(0),
size: SemiFlattenedLength(3),
});
let bz = msgpack_serialize(&value, false).unwrap();
let msg = rmpv::decode::read_value::<&[u8]>(&mut bz.as_ref()).unwrap();
let Value::Map(fields) = msg else {
panic!("expected Map: {msg:?}");
};
assert_eq!(fields.len(), 1);
let Value::String(key) = &fields[0].0 else {
panic!("expected String key: {fields:?}");
};
assert_eq!(key.as_str(), Some("HeapArray"));
}
#[test]
fn msgpack_repr_enum_of_unit_structs() {
let value = IntegerBitSize::U1;
let bz = msgpack_serialize(&value, false).unwrap();
let msg = rmpv::decode::read_value::<&[u8]>(&mut bz.as_ref()).unwrap();
assert_eq!(msg.as_str(), Some("U1"));
}
#[test]
fn msgpack_repr_enum_of_mixed() {
let value = vec![BitSize::Field, BitSize::Integer(IntegerBitSize::U64)];
let bz = msgpack_serialize(&value, false).unwrap();
let msg = rmpv::decode::read_value::<&[u8]>(&mut bz.as_ref()).unwrap();
assert_eq!(format!("{msg}"), r#"["Field", {"Integer": "U64"}]"#);
}
#[test]
fn msgpack_repr_newtype() {
use rmpv::Value;
let value = Witness(1);
let bz = msgpack_serialize(&value, false).unwrap();
let msg = rmpv::decode::read_value::<&[u8]>(&mut bz.as_ref()).unwrap();
assert!(matches!(msg, Value::Integer(_)));
}
mod msgpack_repr_field_element {
use super::super::{Format, deserialize_any_format, serialize_with_format};
use acir_field::{AcirField, FieldElement};
use rmpv::Value;
fn encoded_msgpack<F: Into<Format>>(value: &FieldElement, format: F) -> Vec<u8> {
let mut bz = serialize_with_format(value, format.into()).unwrap();
bz.remove(0);
bz
}
fn assert_bin(bz: &[u8]) -> Vec<u8> {
let msg = rmpv::decode::read_value::<&[u8]>(&mut &bz[..]).unwrap();
match msg {
Value::Binary(v) => v,
other => panic!("expected msgpack Binary for FieldElement; got {other:?}"),
}
}
#[test]
fn msgpack_tagged_emits_bin() {
let v = FieldElement::from(1u128);
let bytes = assert_bin(&encoded_msgpack(&v, Format::MsgpackTagged));
assert_eq!(bytes.len(), v.to_be_bytes().len());
assert_eq!(bytes, v.to_be_bytes());
}
#[test]
fn msgpack_compact_emits_bin() {
let v = FieldElement::from(42u128);
let bytes = assert_bin(&encoded_msgpack(&v, Format::MsgpackCompact));
assert_eq!(bytes, v.to_be_bytes());
}
#[test]
fn msgpack_named_emits_bin() {
let v = FieldElement::from(99u128);
let bytes = assert_bin(&encoded_msgpack(&v, Format::Msgpack));
assert_eq!(bytes, v.to_be_bytes());
}
#[test]
fn round_trips_under_all_formats() {
for &format in &[Format::Msgpack, Format::MsgpackCompact, Format::MsgpackTagged] {
let v = FieldElement::from(7u128);
let bytes = serialize_with_format(&v, format).unwrap();
let decoded: FieldElement = deserialize_any_format(&bytes).unwrap();
assert_eq!(decoded, v, "round-trip failed under {format:?}");
}
}
}
#[test]
fn format_from_str() {
assert_eq!(Format::from_str("msgpack-compact").unwrap(), Format::MsgpackCompact);
assert_eq!(Format::from_str("msgpack-tagged").unwrap(), Format::MsgpackTagged);
}
#[test]
fn msgpack_tagged_format_roundtrip() {
use msgpack_tagged::MsgpackTagged;
#[derive(serde::Serialize, serde::Deserialize, MsgpackTagged, PartialEq, Eq, Debug)]
struct Foo {
#[tag(0)]
a: u32,
#[tag(1)]
b: bool,
}
let value = Foo { a: 7, b: true };
let bytes = super::serialize_with_format(&value, Format::MsgpackTagged).unwrap();
assert_eq!(bytes[0], Format::MsgpackTagged as u8);
let decoded: Foo = super::deserialize_any_format(&bytes).unwrap();
assert_eq!(decoded, value);
}
#[test]
fn msgpack_tagged_acir_policy_program_tagged_expression_array() {
use crate::circuit::{Circuit, Opcode, Program};
use crate::native_types::Expression;
use acir_field::FieldElement;
use rmpv::Value;
let expr: Expression<FieldElement> = Expression {
mul_terms: vec![],
linear_combinations: vec![],
q_c: FieldElement::from(1u128),
};
let circuit: Circuit<FieldElement> = Circuit {
function_name: "main".to_string(),
opcodes: vec![Opcode::AssertZero(expr)],
..Circuit::default()
};
let program =
Program::<FieldElement> { functions: vec![circuit], unconstrained_functions: vec![] };
let bytes = super::msgpack_tagged_serialize_acir(&program).expect("encode succeeds");
let value = rmpv::decode::read_value(&mut bytes.as_slice()).expect("valid msgpack");
let Value::Map(program_entries) = &value else {
panic!("expected fixmap for Program under the ACIR policy, got {value:?}");
};
assert_eq!(program_entries.len(), 2);
assert!(program_entries.iter().all(|(k, _)| matches!(k, Value::Integer(_))));
let functions = program_entries
.iter()
.find(|(k, _)| k.as_u64() == Some(0))
.map(|(_, v)| v)
.expect("functions tag present");
let Value::Array(functions) = functions else {
panic!("functions field should be a msgpack array, got {functions:?}");
};
let Value::Map(circuit_entries) = &functions[0] else {
panic!("expected fixmap for Circuit, got {:?}", functions[0]);
};
assert!(circuit_entries.iter().all(|(k, _)| matches!(k, Value::Integer(_))));
let opcodes_value = circuit_entries
.iter()
.find(|(k, _)| k.as_u64() == Some(1))
.map(|(_, v)| v)
.expect("opcodes tag present on Circuit wire");
let Value::Array(opcodes) = opcodes_value else {
panic!("opcodes should be a msgpack array, got {opcodes_value:?}");
};
let Value::Map(opcode) = &opcodes[0] else {
panic!("opcode should be a 1-entry map, got {:?}", opcodes[0]);
};
assert_eq!(opcode.len(), 1);
let expression_value = &opcode[0].1;
assert!(
matches!(expression_value, Value::Array(_)),
"expected fixarray for nested Expression under default Array policy, got \
{expression_value:?}",
);
}
#[test]
fn msgpack_tagged_acir_policy_program_roundtrips_through_format() {
use crate::circuit::Program;
use acir_field::FieldElement;
let program = Program::<FieldElement>::default();
let bytes = super::serialize_with_format(&program, Format::MsgpackTagged).unwrap();
let decoded: Program<FieldElement> = super::deserialize_any_format(&bytes).unwrap();
assert_eq!(decoded, program);
}
}