diff --git a/src/lib.rs b/src/lib.rs index cb94753..65ba674 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -799,23 +799,18 @@ impl MPCParameters { for (base, projective) in bases.iter_mut().zip(projective.iter_mut()) { *projective = wnaf.base(base.into_projective(), 1).scalar(coeff); } + + C::Projective::batch_normalization(projective); + projective + .iter() + .zip(bases.iter_mut()) + .for_each(|(projective, affine)| { + *affine = projective.into_affine(); + }); }); } }) .unwrap(); - - // Perform batch normalization - projective - .par_chunks_mut(chunk_size) - .for_each(|p| C::Projective::batch_normalization(p)); - - // Turn it all back into affine points - projective - .par_iter() - .zip(bases.par_iter_mut()) - .for_each(|(projective, affine)| { - *affine = projective.into_affine(); - }); } let delta_inv = privkey.delta.inverse().expect("nonzero"); @@ -1131,7 +1126,14 @@ impl MPCParameters { "small params are internally inconsistent wrt. G1 deltas" ); - let MPCSmall { delta_g1, delta_g2, h, l, contributions, .. } = contrib; + let MPCSmall { + delta_g1, + delta_g2, + h, + l, + contributions, + .. + } = contrib; self.params.vk.delta_g1 = delta_g1; self.params.vk.delta_g2 = delta_g2; self.params.h = Arc::new(h); diff --git a/src/small.rs b/src/small.rs index 5c20178..54cbb95 100644 --- a/src/small.rs +++ b/src/small.rs @@ -1,21 +1,22 @@ use std::fmt::{self, Debug, Formatter}; use std::fs::File; -use std::io::{self, BufReader, Read, Seek, SeekFrom, Write}; +use std::io::{self, BufReader, BufWriter, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use ff::{Field, PrimeField}; use groupy::{CurveAffine, CurveProjective, EncodedPoint, Wnaf}; use log::{error, info}; -use paired::bls12_381::{Fr, G1 as G1Projective, G1Affine, G1Uncompressed, G2Affine, G2Uncompressed}; +use paired::bls12_381::{ + Fr, G1Affine, G1Uncompressed, G2Affine, G2Uncompressed, G1 as G1Projective, +}; use rand::Rng; -use rayon::prelude::*; -use crate::{hash_to_g2, HashWriter, merge_pairs, PrivateKey, PublicKey, same_ratio}; +use crate::{hash_to_g2, merge_pairs, same_ratio, HashWriter, PrivateKey, PublicKey}; #[derive(Clone)] pub struct MPCSmall { - // The Groth16 verification-key's deltas G1 and G2. For all non-initial parameters + // The Groth16 verification-key's deltas G1 and G2. For all non-initial parameters // `delta_g1 == contributions.last().delta_after`. pub(crate) delta_g1: G1Affine, pub(crate) delta_g2: G2Affine, @@ -29,6 +30,364 @@ pub struct MPCSmall { pub(crate) contributions: Vec, } +pub struct Streamer<'a> { + delta_g1: G1Affine, + delta_g2: G2Affine, + h_len_offset: u64, + cs_hash: [u8; 64], + contributions: Vec, + path: &'a str, + read_raw: bool, + write_raw: bool, +} + +impl<'a> Streamer<'a> { + // Create a new `Streamer` from small params file. + pub fn new(path: &'a str, read_raw: bool, write_raw: bool) -> io::Result { + let mut file = File::open(path)?; + + let delta_g1: G1Affine = read_g1(&mut file)?; + let delta_g2: G2Affine = read_g2(&mut file)?; + let g1_size = size_of::(); + let g2_size = size_of::(); + + let chunk_element_read_size = if read_raw { + G1Affine::raw_fmt_size() + } else { + size_of::() + }; + let h_len_offset = g1_size + g2_size; + let h_len = file.read_u32::()? as usize; + file.seek(SeekFrom::Current((h_len * chunk_element_read_size) as i64))?; + + let l_len = file.read_u32::()? as usize; + file.seek(SeekFrom::Current((l_len * chunk_element_read_size) as i64))?; + let mut cs_hash = [0u8; 64]; + file.read_exact(&mut cs_hash)?; + + let contributions_len = file.read_u32::()? as usize; + let mut contributions = Vec::::with_capacity(contributions_len); + for _ in 0..contributions_len { + contributions.push(PublicKey::read(&mut file)?); + } + + let streamer = Streamer { + delta_g1, + delta_g2, + h_len_offset: h_len_offset as u64, + cs_hash, + contributions, + path, + read_raw, + write_raw, + }; + + Ok(streamer) + } + + // Create a new `Streamer` from large params file. + pub fn new_from_large_file( + path: &'a str, + read_raw: bool, + write_raw: bool, + ) -> io::Result { + let mut file = File::open(path)?; + + /* + `MPCParameters` are serialized in the order: + vk.alpha_g1 + vk.beta_g1 + vk.beta_g2 + vk.gamma_g2 + vk.delta_g1 + vk.delta_g2 + vk.ic length (4 bytes) + vk.ic (G1) + h length (4 bytes) + h (G1) + l length (4 bytes) + l (G1) + a length (4 bytes) + a (G1) + b_g1 length (4 bytes) + b_g1 (G1) + b_g2 length (4 bytes) + b_g2 (G2) + cs_hash (64 bytes) + contributions length (4 bytes) + contributions (544 bytes per PublicKey) + */ + + let g1_size = size_of::() as u64; // 96 bytes + let g2_size = size_of::() as u64; // 192 bytes + + let chunk_element_read_size = if read_raw { + G1Affine::raw_fmt_size() as u64 + } else { + size_of::() as u64 + }; + + // Read delta_g1, delta_g2, and ic's length. + let delta_g1_offset = g1_size + g1_size + g2_size + g2_size; // + vk.alpha_g1 + vk.beta_g1 + vk.beta_g2 + vk.gamma_g2 + file.seek(SeekFrom::Start(delta_g1_offset)).unwrap(); + let delta_g1 = read_g1(&mut file)?; + let delta_g2 = read_g2(&mut file)?; + let ic_len = file.read_u32::()? as u64; + + // Read h's length. + let h_len_offset = delta_g1_offset + g1_size + g2_size + 4 + ic_len * g1_size; // + vk.delta_g1 + vk.delta_g2 + ic length + ic + file.seek(SeekFrom::Start(h_len_offset)).unwrap(); + let h_len = file.read_u32::()? as u64; + + // Read l's length. + let l_len_offset = h_len_offset + 4 + h_len * chunk_element_read_size; // + h length + h + file.seek(SeekFrom::Start(l_len_offset)).unwrap(); + let l_len = file.read_u32::()? as u64; + + // Read a's length. + let a_len_offset = l_len_offset + 4 + l_len * chunk_element_read_size; // + l length + l + file.seek(SeekFrom::Start(a_len_offset)).unwrap(); + let a_len = file.read_u32::()? as u64; + + // Read b_g1's length. + let b_g1_len_offset = a_len_offset + 4 + a_len * g1_size; // + a length + a + file.seek(SeekFrom::Start(b_g1_len_offset)).unwrap(); + let b_g1_len = file.read_u32::()? as u64; + + // Read b_g2's length. + let b_g2_len_offset = b_g1_len_offset + 4 + b_g1_len * g1_size; // + b_g1 length + b_g1 + file.seek(SeekFrom::Start(b_g2_len_offset)).unwrap(); + let b_g2_len = file.read_u32::()? as u64; + + // Read cs_hash. + let cs_hash_offset = b_g2_len_offset + 4 + b_g2_len * g2_size; // + b_g2 length + b_g2 + file.seek(SeekFrom::Start(cs_hash_offset)).unwrap(); + let mut cs_hash = [0u8; 64]; + file.read_exact(&mut cs_hash)?; + + // Read contribution's length. + let contributions_len = file.read_u32::()? as u64; + + // Read the contributions. + let contributions_offset = cs_hash_offset + 64 + 4; // + 64-byte cs_hash + contributions length + file.seek(SeekFrom::Start(contributions_offset)).unwrap(); + let mut contributions = Vec::::with_capacity(contributions_len as usize); + for _ in 0..contributions_len { + contributions.push(PublicKey::read(&mut file)?); + } + + let streamer = Streamer { + delta_g1, + delta_g2, + h_len_offset, + cs_hash, + contributions, + path, + read_raw, + write_raw, + }; + + Ok(streamer) + } + + pub fn contribute( + &mut self, + rng: &mut RR, + out_file: File, + chunk_size: usize, + ) -> io::Result<[u8; 64]> { + let chunk_element_read_size = if self.read_raw { + G1Affine::raw_fmt_size() + } else { + size_of::() + }; + let chunk_element_write_size = if self.write_raw { + G1Affine::raw_fmt_size() + } else { + size_of::() + }; + + let read_buf_size = chunk_element_read_size * chunk_size; + let write_buf_size = chunk_element_write_size * chunk_size; + + let file = File::open(self.path)?; + let mut reader = BufReader::with_capacity(read_buf_size, file); + let mut writer = BufWriter::with_capacity(write_buf_size, out_file); + + let (pubkey, privkey) = keypair(rng, &self.cs_hash, &self.contributions, &self.delta_g1); + + self.delta_g1 = self.delta_g1.mul(privkey.delta).into_affine(); + self.delta_g2 = self.delta_g2.mul(privkey.delta).into_affine(); + + let delta_inv = privkey.delta.inverse().expect("nonzero"); + + writer.write(self.delta_g1.into_uncompressed().as_ref())?; + writer.write(self.delta_g2.into_uncompressed().as_ref())?; + + { + reader.seek(SeekFrom::Start(self.h_len_offset))?; + let h_len = reader.read_u32::()?; + writer.write_u32::(h_len)?; + + let chunks_to_read = h_len as usize; + let mut chunks_read = 0; + let mut this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + + let mut h_chunk = Vec::::with_capacity(this_chunk_size); + + info!("phase2::MPCParameters::contribute() beginning streaming h"); + while this_chunk_size > 0 { + for _ in 0..this_chunk_size { + h_chunk.push(load_g1(&mut reader, self.read_raw)?); + } + chunks_read += this_chunk_size; + + batch_exp(&mut h_chunk, delta_inv); + + for h in &h_chunk { + dump_g1(&mut writer, h, self.write_raw)?; + } + + this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + h_chunk.truncate(0); + } + info!("phase2::MPCParameters::contribute() finished streaming h"); + } + { + let l_len = reader.read_u32::()?; + writer.write_u32::(l_len)?; + + let chunks_to_read = l_len as usize; + let mut chunks_read = 0; + let mut this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + + let mut l_chunk = Vec::::new(); + info!("phase2::MPCParameters::contribute() beginning streaming l"); + while this_chunk_size > 0 { + for _ in 0..this_chunk_size { + l_chunk.push(load_g1(&mut reader, self.read_raw)?); + } + chunks_read += this_chunk_size; + + batch_exp(&mut l_chunk, delta_inv); + + for l in &l_chunk { + dump_g1(&mut writer, l, self.write_raw)?; + } + + this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + l_chunk.truncate(0); + } + info!("phase2::MPCParameters::contribute() finished streaming l"); + } + + self.contributions.push(pubkey.clone()); + + writer.write(&self.cs_hash)?; + + writer.write_u32::(self.contributions.len() as u32)?; + + for pubkey in &self.contributions { + pubkey.write(&mut writer)?; + } + + { + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + pubkey.write(&mut sink).unwrap(); + Ok(sink.into_hash()) + } + } + + /// Read from self and write out to `writer`, respecting own `read_raw` and `write_raw` flags but without otherwise changing data. + /// Useful for converting to and from raw format. + pub fn process(&mut self, out_file: File, chunk_size: usize) -> io::Result<()> { + let chunk_element_read_size = if self.read_raw { + G1Affine::raw_fmt_size() + } else { + size_of::() + }; + let chunk_element_write_size = if self.write_raw { + G1Affine::raw_fmt_size() + } else { + size_of::() + }; + + let read_buf_size = chunk_element_read_size * chunk_size; + let write_buf_size = chunk_element_write_size * chunk_size; + + let file = File::open(self.path)?; + let mut reader = BufReader::with_capacity(read_buf_size, file); + let mut writer = BufWriter::with_capacity(write_buf_size, out_file); + + writer.write(self.delta_g1.into_uncompressed().as_ref())?; + writer.write(self.delta_g2.into_uncompressed().as_ref())?; + + reader.seek(SeekFrom::Start(self.h_len_offset))?; + { + let h_len = reader.read_u32::()?; + writer.write_u32::(h_len)?; + + let chunks_to_read = h_len as usize; + let mut chunks_read = 0; + let mut this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + + let mut h_chunk = Vec::::with_capacity(this_chunk_size); + + info!("phase2::MPCParameters::convert() beginning streaming h"); + while this_chunk_size > 0 { + for _ in 0..this_chunk_size { + h_chunk.push(load_g1(&mut reader, self.read_raw)?); + } + chunks_read += this_chunk_size; + + for h in &h_chunk { + dump_g1(&mut writer, h, self.write_raw)?; + } + + this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + h_chunk.truncate(0); + } + info!("phase2::MPCParameters::convert() finished streaming h"); + } + + { + let l_len = reader.read_u32::()?; + writer.write_u32::(l_len)?; + + let chunks_to_read = l_len as usize; + let mut chunks_read = 0; + let mut this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + + let mut l_chunk = Vec::::new(); + info!("phase2::MPCParameters::convert() beginning streaming l"); + while this_chunk_size > 0 { + for _ in 0..this_chunk_size { + l_chunk.push(load_g1(&mut reader, self.read_raw)?); + } + chunks_read += this_chunk_size; + + for l in &l_chunk { + dump_g1(&mut writer, l, self.write_raw)?; + } + + this_chunk_size = usize::min(chunk_size, chunks_to_read - chunks_read); + l_chunk.truncate(0); + } + info!("phase2::MPCParameters::convert() finished streaming l"); + } + + writer.write(&self.cs_hash)?; + + writer.write_u32::(self.contributions.len() as u32)?; + + for pubkey in &self.contributions { + pubkey.write(&mut writer)?; + } + Ok(()) + } +} + // Required by `assert_eq!()`. impl Debug for MPCSmall { fn fmt(&self, f: &mut Formatter) -> fmt::Result { @@ -38,7 +397,10 @@ impl Debug for MPCSmall { .field("h", &format!("", self.h.len())) .field("l", &format!("", self.l.len())) .field("cs_hash", &self.cs_hash.to_vec()) - .field("contributions", &format!("", self.contributions.len())) + .field( + "contributions", + &format!("", self.contributions.len()), + ) .finish() } } @@ -56,7 +418,7 @@ impl PartialEq for MPCSmall { impl MPCSmall { pub fn contribute(&mut self, rng: &mut R) -> [u8; 64] { - let (pubkey, privkey) = keypair(rng, self); + let (pubkey, privkey) = keypair(rng, &self.cs_hash, &self.contributions, &self.delta_g1); self.delta_g1 = self.delta_g1.mul(privkey.delta).into_affine(); self.delta_g2 = self.delta_g2.mul(privkey.delta).into_affine(); @@ -82,20 +444,20 @@ impl MPCSmall { } /// Deserialize these parameters. - pub fn read(mut reader: R) -> io::Result { + pub fn read(mut reader: R, raw: bool) -> io::Result { let delta_g1: G1Affine = read_g1(&mut reader)?; let delta_g2: G2Affine = read_g2(&mut reader)?; - + let h_len = reader.read_u32::()? as usize; let mut h = Vec::::with_capacity(h_len); for _ in 0..h_len { - h.push(read_g1(&mut reader)?); + h.push(load_g1(&mut reader, raw)?); } let l_len = reader.read_u32::()? as usize; let mut l = Vec::::with_capacity(l_len); for _ in 0..l_len { - l.push(read_g1(&mut reader)?); + l.push(load_g1(&mut reader, raw)?); } let mut cs_hash = [0u8; 64]; @@ -126,7 +488,7 @@ impl MPCSmall { pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.delta_g1.into_uncompressed().as_ref())?; - writer.write_all(self.delta_g2.into_uncompressed().as_ref())?; + writer.write_all(self.delta_g2.into_uncompressed().as_ref())?; writer.write_u32::(self.h.len() as u32)?; for h in &*self.h { @@ -149,7 +511,12 @@ impl MPCSmall { } } -fn keypair(rng: &mut R, prev: &MPCSmall) -> (PublicKey, PrivateKey) { +fn keypair( + rng: &mut R, + prev_cs_hash: &[u8; 64], + prev_contributions: &[PublicKey], + prev_delta_g1: &G1Affine, +) -> (PublicKey, PrivateKey) { // Sample random delta let delta: Fr = Fr::random(rng); @@ -162,8 +529,8 @@ fn keypair(rng: &mut R, prev: &MPCSmall) -> (PublicKey, PrivateKey) { let sink = io::sink(); let mut sink = HashWriter::new(sink); - sink.write_all(&prev.cs_hash[..]).unwrap(); - for pubkey in &prev.contributions { + sink.write_all(&prev_cs_hash[..]).unwrap(); + for pubkey in prev_contributions { pubkey.write(&mut sink).unwrap(); } sink.write_all(s.into_uncompressed().as_ref()).unwrap(); @@ -183,7 +550,7 @@ fn keypair(rng: &mut R, prev: &MPCSmall) -> (PublicKey, PrivateKey) { ( PublicKey { - delta_after: prev.delta_g1.mul(delta).into_affine(), + delta_after: prev_delta_g1.mul(delta).into_affine(), s, s_delta, r_delta, @@ -219,23 +586,19 @@ fn batch_exp(bases: &mut [G1Affine], coeff: Fr) { for (base, products) in bases.iter_mut().zip(products.iter_mut()) { *products = wnaf.base(base.into_projective(), 1).scalar(coeff); } + // Normalize the projective products. + G1Projective::batch_normalization(products); + + bases + .iter_mut() + .zip(products.iter()) + .for_each(|(affine, projective)| { + *affine = projective.into_affine(); + }); }); } }) .unwrap(); - - // Normalize the projective products. - products - .par_chunks_mut(chunk_size) - .for_each(|batch| G1Projective::batch_normalization(batch)); - - // Convert the normalized projective points back to affine. - bases - .par_iter_mut() - .zip(products.par_iter()) - .for_each(|(affine, projective)| { - *affine = projective.into_affine(); - }); } pub fn verify_contribution_small(before: &MPCSmall, after: &MPCSmall) -> Result<[u8; 64], ()> { @@ -263,7 +626,9 @@ pub fn verify_contribution_small(before: &MPCSmall, after: &MPCSmall) -> Result< // Check that the before params' `delta_g1` and `delta_after` are the same value. if before_is_initial { if before.delta_g1 != G1Affine::one() || before.delta_g2 != G2Affine::one() { - error!("phase2::verify_contribution_small() initial params do not have identity deltas"); + error!( + "phase2::verify_contribution_small() initial params do not have identity deltas" + ); } } else { let before_pubkey = before.contributions.last().unwrap(); @@ -301,10 +666,12 @@ pub fn verify_contribution_small(before: &MPCSmall, after: &MPCSmall) -> Result< for pubkey in &before.contributions { pubkey.write(&mut sink).unwrap(); } - sink.write_all(after_pubkey.s.into_uncompressed().as_ref()).unwrap(); - sink.write_all(after_pubkey.s_delta.into_uncompressed().as_ref()).unwrap(); + sink.write_all(after_pubkey.s.into_uncompressed().as_ref()) + .unwrap(); + sink.write_all(after_pubkey.s_delta.into_uncompressed().as_ref()) + .unwrap(); let calculated_after_transcript = sink.into_hash(); - + // Check the after params transcript against its calculated transcript. if &after_pubkey.transcript[..] != calculated_after_transcript.as_ref() { error!("phase2::verify_contribution_small() inconsistent transcript"); @@ -315,19 +682,28 @@ pub fn verify_contribution_small(before: &MPCSmall, after: &MPCSmall) -> Result< // Check the signature of knowledge. Check that the participant's r and s were shifted by the // same factor. - if !same_ratio((after_r, after_pubkey.r_delta), (after_pubkey.s, after_pubkey.s_delta)) { + if !same_ratio( + (after_r, after_pubkey.r_delta), + (after_pubkey.s, after_pubkey.s_delta), + ) { error!("phase2::verify_contribution_small() participant's r and s were shifted by different deltas"); return Err(()); } // Check that delta_g1 and r were shifted by the same factor. - if !same_ratio((before.delta_g1, after.delta_g1), (after_r, after_pubkey.r_delta)) { + if !same_ratio( + (before.delta_g1, after.delta_g1), + (after_r, after_pubkey.r_delta), + ) { error!("phase2::verify_contribution_small() participant's delta_g1 and r where shifted by different deltas"); return Err(()); } // Check that delta_g1 and delta_g2 were shifted by the same factor. - if !same_ratio((G1Affine::one(), after.delta_g1), (G2Affine::one(), after.delta_g2)) { + if !same_ratio( + (G1Affine::one(), after.delta_g1), + (G2Affine::one(), after.delta_g2), + ) { error!("phase2::verify_contribution_small() delta_g1 and delta_g2 were shifted by different deltas"); return Err(()); } @@ -347,7 +723,7 @@ pub fn verify_contribution_small(before: &MPCSmall, after: &MPCSmall) -> Result< error!("phase2::verify_contribution_small() l was not updated by delta^-1"); return Err(()); } - + // Calculate the "after" participant's contribution hash. let sink = io::sink(); let mut sink = HashWriter::new(sink); @@ -359,7 +735,8 @@ pub fn verify_contribution_small(before: &MPCSmall, after: &MPCSmall) -> Result< pub fn read_g1(mut reader: R) -> io::Result { let mut affine_bytes = G1Uncompressed::empty(); reader.read_exact(affine_bytes.as_mut())?; - let affine = affine_bytes.into_affine() + let affine = affine_bytes + .into_affine() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; if affine.is_zero() { @@ -373,11 +750,30 @@ pub fn read_g1(mut reader: R) -> io::Result { } } +#[inline] +fn load_g1(mut reader: R, raw: bool) -> io::Result { + if raw { + G1Affine::read_raw(&mut reader) + } else { + read_g1(reader) + } +} + +#[inline] +fn dump_g1(mut writer: W, g1: &G1Affine, raw: bool) -> io::Result { + if raw { + g1.write_raw(&mut writer) + } else { + writer.write(g1.into_uncompressed().as_ref()) + } +} + #[inline] pub fn read_g2(mut reader: R) -> io::Result { let mut affine_bytes = G2Uncompressed::empty(); reader.read_exact(affine_bytes.as_mut())?; - let affine = affine_bytes.into_affine() + let affine = affine_bytes + .into_affine() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; if affine.is_zero() { diff --git a/tests/small.rs b/tests/small.rs index 53a51e4..f14b1c4 100644 --- a/tests/small.rs +++ b/tests/small.rs @@ -1,15 +1,15 @@ mod mimc; -use std::fs::{File, remove_file}; +use std::fs::{remove_file, File}; use std::io::{BufReader, BufWriter}; use std::path::Path; use bellperson::groth16::{create_random_proof, prepare_verifying_key, verify_proof}; use ff::Field; use paired::bls12_381::{Bls12, Fr}; -use phase21::{MPCParameters, verify_contribution}; -use phase21::small::{MPCSmall, read_small_params_from_large_file, verify_contribution_small}; -use rand::{SeedableRng, thread_rng}; +use phase21::small::{read_small_params_from_large_file, verify_contribution_small, MPCSmall}; +use phase21::{verify_contribution, MPCParameters}; +use rand::{thread_rng, SeedableRng}; use rand_chacha::ChaChaRng; use mimc::{mimc as mimc_hash, MiMCDemo, MIMC_ROUNDS}; @@ -203,12 +203,12 @@ fn test_small_file_io() { let mut writer = BufWriter::with_capacity(1024 * 1024, file); small_params.write(&mut writer).unwrap(); } - + // Test small param deserialisation. { let file = File::open(SMALL_PATH).unwrap(); let mut reader = BufReader::with_capacity(1024 * 1024, file); - let small_read = MPCSmall::read(&mut reader).unwrap(); + let small_read = MPCSmall::read(&mut reader, false).unwrap(); assert_eq!(small_read, small_params); assert!(large_params.has_last_contrib(&small_read)); };