Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce message sizes #47

Merged
merged 4 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion synedrion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ crypto-bigint = { version = "0.5.3", features = ["serde"] }
crypto-primes = "0.5"

serde = { version = "1", features = ["derive"] }
rmp-serde = "1"
bincode = "1"
cfg-if = "1"
itertools = { version = "0.11", default-features = false, optional = true }

Expand All @@ -35,6 +35,7 @@ tokio = { version = "1", features = ["rt", "sync", "time", "macros"] }
rand = "0.8"
criterion = "0.5"
itertools = "0.11"
hex = "0.4"

[features]
bench-internals = ["itertools"] # makes some internal functions public to allow external benchmarks
Expand Down
2 changes: 1 addition & 1 deletion synedrion/src/cggmp21/sigma/aff_g.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl<P: SchemeParams> AffGProof<P> {
Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap());
let e_wide = e.into_wide();

let hat_cap_n = &aux_rp.public_key().modulus_nonzero();
let hat_cap_n = &aux_rp.public_key().modulus_bounded();

let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND);
let beta = Signed::random_bounded_bits(rng, P::LP_BOUND + P::EPS_BOUND);
Expand Down
2 changes: 1 addition & 1 deletion synedrion/src/cggmp21/sigma/dec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl<P: SchemeParams> DecProof<P> {
let e =
Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap());

let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$
let hat_cap_n = &aux_rp.public_key().modulus_bounded(); // $\hat{N}$

let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND);
let mu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n);
Expand Down
2 changes: 1 addition & 1 deletion synedrion/src/cggmp21/sigma/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl<P: SchemeParams> EncProof<P> {
Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap());

let pk = sk.public_key();
let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$
let hat_cap_n = &aux_rp.public_key().modulus_bounded(); // $\hat{N}$

// CHECK: should we instead sample in range $+- 2^{\ell + \eps} - q 2^\ell$?
// This will ensure that the range check on the prover side will pass.
Expand Down
9 changes: 5 additions & 4 deletions synedrion/src/cggmp21/sigma/fac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::paillier::{
SecretKeyPaillierPrecomputed,
};
use crate::tools::hashing::{Chain, Hashable, XofHash};
use crate::uint::{HasWide, Integer, NonZero, Signed};
use crate::uint::{Bounded, Integer, NonZero, Signed};

const HASH_TAG: &[u8] = b"P_fac";

Expand Down Expand Up @@ -45,12 +45,13 @@ impl<P: SchemeParams> FacProof<P> {
let e_wide = e.into_wide();

let pk = sk.public_key();
let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$
let hat_cap_n = &aux_rp.public_key().modulus_bounded(); // $\hat{N}$

// CHECK: using `2^(Paillier::PRIME_BITS - 1)` as $\sqrt{N_0}$ (which is its lower bound)
let sqrt_cap_n = NonZero::new(
let sqrt_cap_n = Bounded::new(
<P::Paillier as PaillierParams>::Uint::ONE
<< (<P::Paillier as PaillierParams>::PRIME_BITS - 1),
<P::Paillier as PaillierParams>::PRIME_BITS as u32,
)
.unwrap();

Expand All @@ -60,7 +61,7 @@ impl<P: SchemeParams> FacProof<P> {
let nu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n);

// N_0 \hat{N}
let scale = NonZero::new(pk.modulus().mul_wide(hat_cap_n.as_ref())).unwrap();
let scale = pk.modulus_bounded().mul_wide(hat_cap_n);

let sigma =
Signed::<<P::Paillier as PaillierParams>::Uint>::random_bounded_bits_scaled_wide(
Expand Down
2 changes: 1 addition & 1 deletion synedrion/src/cggmp21/sigma/log_star.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl<P: SchemeParams> LogStarProof<P> {
let e =
Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap());

let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$
let hat_cap_n = &aux_rp.public_key().modulus_bounded(); // $\hat{N}$

let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND);
let mu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n);
Expand Down
2 changes: 1 addition & 1 deletion synedrion/src/cggmp21/sigma/mul_star.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl<P: SchemeParams> MulStarProof<P> {
let e =
Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap());

let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$
let hat_cap_n = &aux_rp.public_key().modulus_bounded(); // $\hat{N}$

let r = RandomizerMod::random(rng, pk);
let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND);
Expand Down
4 changes: 2 additions & 2 deletions synedrion/src/sessions/type_erased.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ use crate::cggmp21::{
use crate::tools::collections::{HoleRange, HoleVec, HoleVecAccum};

pub(crate) fn serialize_message(message: &impl Serialize) -> Result<Box<[u8]>, LocalError> {
rmp_serde::encode::to_vec(message)
bincode::serialize(message)
.map(|serialized| serialized.into_boxed_slice())
.map_err(|err| LocalError(format!("Failed to serialize: {err:?}")))
}

pub(crate) fn deserialize_message<M: for<'de> Deserialize<'de>>(
message_bytes: &[u8],
) -> Result<M, String> {
rmp_serde::decode::from_slice(message_bytes).map_err(|err| err.to_string())
bincode::deserialize(message_bytes).map_err(|err| err.to_string())
}

pub(crate) enum FinalizeOutcome<Res: ProtocolResult> {
Expand Down
8 changes: 8 additions & 0 deletions synedrion/src/uint/bounded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ impl<T: UintLike + HasWide> Bounded<T> {
bound: self.bound,
}
}

pub fn mul_wide(&self, rhs: &Self) -> Bounded<T::Wide> {
let result = self.value.mul_wide(&rhs.value);
Bounded {
value: result,
bound: self.bound + rhs.bound,
}
}
}

impl<T: UintLike + FromScalar> FromScalar for Bounded<T> {
Expand Down
114 changes: 57 additions & 57 deletions synedrion/src/uint/signed.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
use core::fmt;
use core::marker::PhantomData;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use core::ops::{Add, Mul, Neg, Not, Sub};

use digest::XofReader;
use rand_core::CryptoRngCore;
use serde::{
de, de::Error, ser::SerializeTupleStruct, Deserialize, Deserializer, Serialize, Serializer,
};
use serde::{Deserialize, Serialize};

use super::{
subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption},
Bounded, CheckedAdd, CheckedMul, FromScalar, HasWide, Integer, NonZero, UintLike, UintModLike,
};
use crate::curve::{Scalar, ORDER};

/// A wrapper over unsigned integers that treats two's complement numbers as negative.
// In principle, Bounded could be separate from Signed, but we only use it internally,
// and pretty much every time we need a bounded value, it's also signed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Signed<T: UintLike> {
/// bound on the bit size of the absolute value
use crate::tools::serde_bytes;

/// A packed representation for serializing Signed objects.
/// Usually they have the bound much lower than the full size of the integer,
/// so thiw way we avoid serializing a bunch of zeros.
#[derive(Serialize, Deserialize)]
struct PackedSigned {
is_negative: bool,
bound: u32,
value: T,
#[serde(with = "serde_bytes::as_hex")]
bytes: Box<[u8]>,
}

impl<'de, T: UintLike + Deserialize<'de>> Deserialize<'de> for Signed<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SignedVisitor<T: UintLike>(PhantomData<T>);

impl<'de, T: UintLike + Deserialize<'de>> de::Visitor<'de> for SignedVisitor<T> {
type Value = Signed<T>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a tuple struct Signed")
}
impl<T: UintLike> From<Signed<T>> for PackedSigned {
fn from(val: Signed<T>) -> Self {
let repr = val.abs().to_be_bytes();
let bound_bytes = (val.bound() + 7) / 8;
let slice = &repr.as_ref()[(repr.as_ref().len() - bound_bytes as usize)..];
Self {
is_negative: val.is_negative().into(),
bound: val.bound(),
bytes: slice.into(),
}
}
}

fn visit_seq<A>(self, mut seq: A) -> Result<Signed<T>, A::Error>
where
A: de::SeqAccess<'de>,
{
let bound: u32 = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let value: T = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;

Signed::new_from_unsigned(value, bound)
.ok_or_else(|| A::Error::custom("The integer is over the declared bound"))
}
impl<T: UintLike> TryFrom<PackedSigned> for Signed<T> {
type Error = String;
fn try_from(val: PackedSigned) -> Result<Self, Self::Error> {
let mut repr = T::ZERO.to_be_bytes();
let bytes_len: usize = val.bytes.len();
let repr_len: usize = repr.as_ref().len();

if repr_len < bytes_len {
return Err(format!(
"The bytestring of length {} does not fit the expected integer size {}",
bytes_len, repr_len
));
}

deserializer.deserialize_tuple_struct("Signed", 2, SignedVisitor::<T>(PhantomData))
repr.as_mut()[(repr_len - bytes_len)..].copy_from_slice(&val.bytes);
let abs_value = T::from_be_bytes(repr);

Self::new_from_abs(abs_value, val.bound, Choice::from(val.is_negative as u8))
.ok_or_else(|| "Invalid values for the signed integer".into())
}
}

impl<T: UintLike + Serialize> Serialize for Signed<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// TODO: save just the `bound` bytes? That will save some bandwidth.
let mut ts = serializer.serialize_tuple_struct("Signed", 2)?;
ts.serialize_field(&self.bound)?;
ts.serialize_field(&self.value)?;
ts.end()
}
/// A wrapper over unsigned integers that treats two's complement numbers as negative.
// In principle, Bounded could be separate from Signed, but we only use it internally,
// and pretty much every time we need a bounded value, it's also signed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "PackedSigned", into = "PackedSigned")]
pub struct Signed<T: UintLike> {
/// bound on the bit size of the absolute value
bound: u32,
value: T,
}

impl<T: UintLike> Signed<T> {
Expand Down Expand Up @@ -258,7 +258,7 @@ impl<T: UintLike + HasWide> Signed<T> {
pub fn random_bounded_bits_scaled(
rng: &mut impl CryptoRngCore,
bound_bits: usize,
scale: &NonZero<T>,
scale: &Bounded<T>,
) -> Signed<T::Wide> {
assert!(bound_bits < <T as Integer>::BITS - 1);
let bound = T::ONE.shl_vartime(bound_bits);
Expand All @@ -269,7 +269,7 @@ impl<T: UintLike + HasWide> Signed<T> {
let scaled_bound = scale.as_ref().into_wide().shl_vartime(bound_bits);

Signed {
bound: (bound_bits + scale.bits_vartime()) as u32,
bound: bound_bits as u32 + scale.bound(),
value: scaled_positive_result.wrapping_sub(&scaled_bound),
}
}
Expand Down Expand Up @@ -300,7 +300,7 @@ where
pub fn random_bounded_bits_scaled_wide(
rng: &mut impl CryptoRngCore,
bound_bits: usize,
scale: &NonZero<<T as HasWide>::Wide>,
scale: &Bounded<<T as HasWide>::Wide>,
) -> Signed<<<T as HasWide>::Wide as HasWide>::Wide> {
assert!(bound_bits < <T as Integer>::BITS - 1);
let bound = T::ONE.shl_vartime(bound_bits);
Expand All @@ -309,11 +309,11 @@ where

let positive_result = positive_result.into_wide();

let scaled_positive_result = positive_result.mul_wide(scale);
let scaled_positive_result = positive_result.mul_wide(scale.as_ref());
let scaled_bound = scale.as_ref().into_wide().shl_vartime(bound_bits);

Signed {
bound: (bound_bits + scale.bits_vartime()) as u32,
bound: bound_bits as u32 + scale.bound(),
value: scaled_positive_result.wrapping_sub(&scaled_bound),
}
}
Expand Down
1 change: 1 addition & 0 deletions synedrion/src/uint/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub(crate) const fn upcast_uint<const N1: usize, const N2: usize>(value: Uint<N1

pub trait UintLike:
Integer
+ Encoding
+ JacobiSymbolTrait
+ Hashable
+ RandomPrimeWithRng
Expand Down
31 changes: 24 additions & 7 deletions synedrion/tests/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ use synedrion::{
type MessageOut = (VerifyingKey, VerifyingKey, SignedMessage<Signature>);
type MessageIn = (VerifyingKey, SignedMessage<Signature>);

fn key_to_str(key: &VerifyingKey) -> String {
hex::encode(&key.to_encoded_point(true).as_bytes()[1..5])
}

async fn run_session<Res: ProtocolResult>(
tx: mpsc::Sender<MessageOut>,
rx: mpsc::Receiver<MessageIn>,
Expand All @@ -28,9 +32,13 @@ async fn run_session<Res: ProtocolResult>(
let mut cached_messages = Vec::<(VerifyingKey, SignedMessage<Signature>)>::new();

let key = session.verifier();
let key_str = key_to_str(&key);

loop {
println!("*** {key:?}: starting round {:?}", session.current_round());
println!(
"{key_str}: *** starting round {:?} ***",
session.current_round()
);

// This is kept in the main task since it's mutable,
// and we don't want to bother with synchronization.
Expand All @@ -45,7 +53,10 @@ async fn run_session<Res: ProtocolResult>(
// In production usage, this will happen in a spawned task
let message = session.make_broadcast(&mut OsRng).unwrap();
for destination in destinations.iter() {
println!("{key:?}: sending a broadcast to {destination:?}");
println!(
"{key_str}: sending a broadcast to {}",
key_to_str(destination)
);
tx.send((key, *destination, message.clone())).await.unwrap();
}
}
Expand All @@ -60,7 +71,10 @@ async fn run_session<Res: ProtocolResult>(
let (message, artefact) = session
.make_direct_message(&mut OsRng, destination)
.unwrap();
println!("{key:?}: sending a direct message to {destination:?}");
println!(
"{key_str}: sending a direct message to {}",
key_to_str(destination)
);
tx.send((key, *destination, message)).await.unwrap();

// This will happen in a host task
Expand All @@ -70,29 +84,32 @@ async fn run_session<Res: ProtocolResult>(

for (from, message) in cached_messages {
// In production usage, this will happen in a spawned task.
println!("{key:?}: applying a cached message from {from:?}");
println!(
"{key_str}: applying a cached message from {}",
key_to_str(&from)
);
let result = session.verify_message(&from, message).unwrap();

// This will happen in a host task.
accum.add_processed_message(result).unwrap().unwrap();
}

while !session.can_finalize(&accum).unwrap() {
println!("{key:?}: waiting for a message");
println!("{key_str}: waiting for a message");
let (from, message) = rx.recv().await.unwrap();

// TODO: check here that the message from this origin hasn't been already processed
// if accum.already_processed(message) { ... }

// In production usage, this will happen in a spawned task.
println!("{key:?}: applying a message from {from:?}");
println!("{key_str}: applying a message from {}", key_to_str(&from));
let result = session.verify_message(&from, message).unwrap();

// This will happen in a host task.
accum.add_processed_message(result).unwrap().unwrap();
}

println!("{key:?}: finalizing the round");
println!("{key_str}: finalizing the round");

match session.finalize_round(&mut OsRng, accum).unwrap() {
FinalizeOutcome::Success(res) => break res,
Expand Down