Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Commit

Permalink
refactor messages
Browse files Browse the repository at this point in the history
Summary:
========

1. add a `Header` type for the common fields
2. add a `LengthValueBuffer` type to handle the variable length fields
3. decouple the crypto and parsing parts
4. add a `Message` type that wraps sum, update and sum2 messages
5. have an `Owned` and a `Borrowed` version of every type
7. small bug fixes & improvements:
     - not using `usize` as a field since it's platform dependent
     - detection of truncated local seed dictionary
     - `XxxBuffer` does not allocate a `Vec`
     - remove impls for specific types (`impl TryFrom<Vec<u8>> for
       XxxBuffer`, `impl XxxBuffer<Vec<u8>>` etc)
     - make the `XxxBuffer()` APIs more consistent, opening the door
       to code generation via macros
     - use a custom `DecodeError` type that contains the whole stack
       of errors, which makes debugging easier

Details:
========

1. Introduce a `Header` type for the common fields
--------------------------------------------------

All the messages share common fields. In networking protocol, these
common fields are usually handled separately from the rest of the
message. They are usually called headers, and the rest of the message
is the payload. We defined the following header:

```rust
/// A header, common to all the message
pub struct Header<CK, PK, C> {
    /// Type of message
    pub tag: Tag,
    /// Coordinator public key
    pub coordinator_pk: CK,
    /// Participant public key
    pub participant_pk: PK,
    /// A certificate that identifies the author of the message
    pub certificate: Option<C>,
}
```

Currently the code to handle these common fields lives in the
`MessageBuffer` trait which is implemented for each message type, thus
reducing boilerplate for parsing these fields. I can see several
downsides to using a trait for this though:

a. the `serialize()` and `deserialize()` methods must call the
   `MessageBuffer` methods for each of these common fields.
b. it is difficult to handle variable length fields in the
   header (which maybe is why the optional certificate is handled by
   each message separately?)
c. tests require full messages

`a.` is not too much of a problem, and I'm not sure to what extent
`b.` holds true. But removing the header logic does lead to much
simpler tests.

2. Add a `LengthValueBuffer` type to handle the variable length fields
----------------------------------------------------------------------

This reduces error prone boilerplate, like we had in `update.rs`:

```rust
if buffer.len() >= Self::LOCAL_SEED_DICT_LEN_RANGE.end {
    buffer.certificate_range = Self::LOCAL_SEED_DICT_LEN_RANGE.end
        ..Self::LOCAL_SEED_DICT_LEN_RANGE.end
            + usize::from_le_bytes(buffer.certificate_len().try_into().unwrap());
    buffer.masked_model_range = buffer.certificate_range.end
        ..buffer.certificate_range.end
            + usize::from_le_bytes(buffer.masked_model_len().try_into().unwrap());
    buffer.local_seed_dict_range = buffer.masked_model_range.end
        ..buffer.masked_model_range.end
            + usize::from_le_bytes(buffer.local_seed_dict_len().try_into().unwrap());
}
```

This also allows us to automate the serialization/deserialization of
length-variable types like masks, certificates and masked models (see
`impl_traits_for_length_value_types!`)

3. decouple the crypto and parsing parts
----------------------------------------

The message signature and encryption is the very last/first step for
every message, and is always exactly the same, so it makes sense to
keep it separate. Therefore we removed the `open()` and `seal()`
methods for the messages themself and moved the logic to two dedicated
types: `MessageSeal` for signing and encrypting messages, and
`MessageOpener` for decrypting and verifying message signatures.

With this, we could now move the crypto logic to the _transport_
layer, and only handle fully fledged messages in the business logic
layer.

4. add a `Message` type that wraps sum, update and sum2 messages
----------------------------------------------------------------

```rust
pub struct Message {
    pub header: Header,
    pub payload: Payload,
}

pub enum Payload {
    Sum(SumMessage),
    Sum2(Sum2Message),
    Update(UpdateMessage),
}
```

This is actually needed if we want to move message
serialization/deserialization to Tokio.

By doing so, we don't have to repeat the same code for the fields that
are common to all the messages.

5. have an `Owned` and a `Borrowed` version of every type
---------------------------------------------------------

Each type comes in two flavours: `Owned` and `Borrowed`. An `XxxOwned`
type _owns_ their fields, while an `XxxBorrowed` type may only have
references to some of the fields.

The `Borrowed` variant is needed because we have some potentially
large fields that we don't want to clone when emitting a message. For
instance, it would be wasteful for an update participant sending an
update message with a large local seed dictionary to clone that
dictionary.
  • Loading branch information
little-dude committed May 18, 2020
1 parent aa8a4a5 commit e7ea51d
Show file tree
Hide file tree
Showing 24 changed files with 2,273 additions and 1,876 deletions.
1 change: 1 addition & 0 deletions rust/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
imports_layout = "HorizontalVertical"
merge_imports = true
format_code_in_doc_comments = true
24 changes: 15 additions & 9 deletions rust/src/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use crate::{
PetError,
SeedDict,
SumDict,
SumParticipantEphemeralPublicKey,
SumParticipantPublicKey,
UpdateParticipantPublicKey,
};
Expand Down Expand Up @@ -199,6 +198,13 @@ pub trait Coordinators: Sized {
self.events_mut().pop_front()
}

fn message_open(&self) -> MessageOpen<'_, '_> {
MessageOpen {
recipient_pk: &self.pk,
recipient_sk: &self.sk,
}
}

/// Validate and handle a sum, update or sum2 message.
fn handle_message(&mut self, bytes: &[u8]) -> Result<(), PetError> {
match self.phase() {
Expand Down Expand Up @@ -240,8 +246,8 @@ pub trait Coordinators: Sized {
/// Validate a sum signature and its implied task.
fn validate_sum_task(
&self,
sum_signature: &ParticipantTaskSignature,
pk: &SumParticipantPublicKey,
sum_signature: &ParticipantTaskSignature,
) -> Result<(), PetError> {
if pk.verify_detached(sum_signature, &[self.seed().as_slice(), b"sum"].concat())
&& sum_signature.is_eligible(*self.sum())
Expand All @@ -255,9 +261,9 @@ pub trait Coordinators: Sized {
/// Validate an update signature and its implied task.
fn validate_update_task(
&self,
pk: &UpdateParticipantPublicKey,
sum_signature: &ParticipantTaskSignature,
update_signature: &ParticipantTaskSignature,
pk: &UpdateParticipantPublicKey,
) -> Result<(), PetError> {
if pk.verify_detached(sum_signature, &[self.seed().as_slice(), b"sum"].concat())
&& pk.verify_detached(
Expand Down Expand Up @@ -621,7 +627,7 @@ impl MaskCoordinators<i64> for Coordinator<i64> {
}
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct RoundParameters {
/// The coordinator public key for encryption.
pub pk: CoordinatorPublicKey,
Expand Down Expand Up @@ -704,7 +710,7 @@ mod tests {
148, 45, 56, 85, 65, 75, 128, 178, 175, 101, 93, 241, 162,
]);
assert_eq!(
coord.validate_sum_task(&sum_signature, &pk).unwrap_err(),
coord.validate_sum_task(&pk, &sum_signature).unwrap_err(),
PetError::InvalidMessage,
);
}
Expand Down Expand Up @@ -738,7 +744,7 @@ mod tests {
]);
assert_eq!(
coord
.validate_update_task(&sum_signature, &update_signature, &pk)
.validate_update_task(&pk, &sum_signature, &update_signature)
.unwrap(),
(),
);
Expand All @@ -762,7 +768,7 @@ mod tests {
]);
assert_eq!(
coord
.validate_update_task(&sum_signature, &update_signature, &pk)
.validate_update_task(&pk, &sum_signature, &update_signature)
.unwrap_err(),
PetError::InvalidMessage,
);
Expand All @@ -786,7 +792,7 @@ mod tests {
]);
assert_eq!(
coord
.validate_update_task(&sum_signature, &update_signature, &pk)
.validate_update_task(&pk, &sum_signature, &update_signature)
.unwrap_err(),
PetError::InvalidMessage,
);
Expand All @@ -810,7 +816,7 @@ mod tests {
]);
assert_eq!(
coord
.validate_update_task(&sum_signature, &update_signature, &pk)
.validate_update_task(&pk, &sum_signature, &update_signature)
.unwrap_err(),
PetError::InvalidMessage,
);
Expand Down
17 changes: 14 additions & 3 deletions rust/src/crypto/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use super::ByteObject;
use derive_more::{AsMut, AsRef, From};
use sodiumoxide::crypto::{box_, sealedbox};

/// Number of additional bytes in a ciphertext compared to the
/// corresponding plaintext.
pub const SEALBYTES: usize = sealedbox::SEALBYTES;

/// Generate a new random key pair
pub fn generate_encrypt_key_pair() -> (PublicEncryptKey, SecretEncryptKey) {
let (pk, sk) = box_::gen_keypair();
Expand Down Expand Up @@ -41,9 +45,10 @@ impl ByteObject for PublicEncryptKey {
}
}

pub const SEALBYTES: usize = sealedbox::SEALBYTES;

impl PublicEncryptKey {
/// Length in bytes of a [`PublicEncryptKey`]
pub const LENGTH: usize = box_::PUBLICKEYBYTES;

/// Encrypt a message `m` with this public key. The resulting
/// ciphertext length is [`SEALBYTES`] + `m.len()`.
///
Expand All @@ -60,6 +65,9 @@ impl PublicEncryptKey {
pub struct SecretEncryptKey(box_::SecretKey);

impl SecretEncryptKey {
/// Length in bytes of a [`SecretEncryptKey`]
pub const LENGTH: usize = box_::SECRETKEYBYTES;

/// Decrypt the ciphertext `c` using this secret key and the
/// associated public key, and return the decrypted message.
///
Expand Down Expand Up @@ -94,6 +102,9 @@ impl ByteObject for SecretEncryptKey {
pub struct EncryptKeySeed(box_::Seed);

impl EncryptKeySeed {
/// Length in bytes of a [`EncryptKeySeed`]
pub const LENGTH: usize = box_::SEEDBYTES;

/// Deterministically derive a new key pair from this seed
pub fn derive_encrypt_key_pair(&self) -> (PublicEncryptKey, SecretEncryptKey) {
let (pk, sk) = box_::keypair_from_seed(self.as_ref());
Expand All @@ -107,7 +118,7 @@ impl ByteObject for EncryptKeySeed {
}

fn zeroed() -> Self {
Self(box_::Seed([0; box_::PUBLICKEYBYTES]))
Self(box_::Seed([0; box_::SEEDBYTES]))
}

fn as_slice(&self) -> &[u8] {
Expand Down
45 changes: 45 additions & 0 deletions rust/src/crypto/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use super::ByteObject;

use sodiumoxide::crypto::hash::sha256;

use derive_more::{AsMut, AsRef, From};

#[derive(
AsRef,
AsMut,
From,
Serialize,
Deserialize,
Hash,
Eq,
Ord,
PartialEq,
Copy,
Clone,
PartialOrd,
Debug,
)]
pub struct Sha256(sha256::Digest);

impl ByteObject for Sha256 {
fn zeroed() -> Self {
Self(sha256::Digest([0_u8; sha256::DIGESTBYTES]))
}

fn as_slice(&self) -> &[u8] {
self.0.as_ref()
}

fn from_slice(bytes: &[u8]) -> Option<Self> {
sha256::Digest::from_slice(bytes).map(Self)
}
}

impl Sha256 {
/// Length in bytes of a [`Sha256`]
pub const LENGTH: usize = 32;

pub fn hash(m: &[u8]) -> Self {
Self(sha256::hash(m))
}
}
1 change: 1 addition & 0 deletions rust/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! primitives.
mod encrypt;
mod hash;
mod sign;

use num::{bigint::BigUint, traits::identities::Zero};
Expand Down
12 changes: 12 additions & 0 deletions rust/src/crypto/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub fn generate_signing_key_pair() -> (PublicSigningKey, SecretSigningKey) {
pub struct PublicSigningKey(sign::PublicKey);

impl PublicSigningKey {
/// Length in bytes of a [`PublicSigningKey`]
pub const LENGTH: usize = sign::PUBLICKEYBYTES;
/// Verify the signature `s` against the message `m` and the
/// signer's public key `&self`.
///
Expand Down Expand Up @@ -62,6 +64,8 @@ impl ByteObject for PublicSigningKey {
pub struct SecretSigningKey(sign::SecretKey);

impl SecretSigningKey {
/// Length in bytes of a [`SecretSigningKey`]
pub const LENGTH: usize = sign::SECRETKEYBYTES;
/// Sign a message `m`
pub fn sign_detached(&self, m: &[u8]) -> Signature {
sign::sign_detached(m, self.as_ref()).into()
Expand Down Expand Up @@ -105,6 +109,11 @@ impl ByteObject for SecretSigningKey {
)]
pub struct Signature(sign::Signature);

impl Signature {
/// Length in bytes of a [`Signature`]
pub const LENGTH: usize = sign::SIGNATUREBYTES;
}

impl ByteObject for Signature {
fn zeroed() -> Self {
Self(sign::Signature([0_u8; sign::SIGNATUREBYTES]))
Expand Down Expand Up @@ -146,6 +155,9 @@ impl Signature {
pub struct SigningKeySeed(sign::Seed);

impl SigningKeySeed {
/// Length in bytes of a [`SigningKeySeed`]
pub const LENGTH: usize = sign::SEEDBYTES;

/// Deterministically derive a new signing key pair from this seed
pub fn derive_signing_key_pair(&self) -> (PublicSigningKey, SecretSigningKey) {
let (pk, sk) = sign::keypair_from_seed(&self.0);
Expand Down
4 changes: 2 additions & 2 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#[macro_use]
extern crate tracing;
#[macro_use]
extern crate serde;
extern crate tracing;

#[macro_use]
mod macros;
Expand Down Expand Up @@ -82,7 +82,7 @@ pub type ParticipantTaskSignature = Signature;

/// A dictionary created during the sum phase of the protocol. It maps the public key of every sum
/// participant to the ephemeral public key generated by that sum participant.
type SumDict = HashMap<SumParticipantPublicKey, SumParticipantEphemeralPublicKey>;
pub type SumDict = HashMap<SumParticipantPublicKey, SumParticipantEphemeralPublicKey>;

/// Local seed dictionaries are sent by update participants. They contain the participant's masking
/// seed, encrypted with the ephemeral public key of each sum participant.
Expand Down
Loading

0 comments on commit e7ea51d

Please sign in to comment.