1
0
mirror of synced 2025-01-11 00:05:48 +00:00

modularization

This commit is contained in:
Youngjoon Lee 2024-11-20 17:22:21 +09:00
parent bfa4fe617f
commit 94dbd35fea
No known key found for this signature in database
GPG Key ID: 303963A54A81DD4D
16 changed files with 196 additions and 95 deletions

View File

@ -60,6 +60,7 @@ mix:
cryptographic_processor:
private_key: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
num_mix_layers: 1
message_settings:
temporal_processor:
max_delay_seconds: 5
membership:

View File

@ -9,15 +9,16 @@ pub struct CryptographicProcessor<R, M>
where
M: MixMessage,
{
settings: CryptographicProcessorSettings<M::PrivateKey>,
settings: CryptographicProcessorSettings<M::PrivateKey, M::Settings>,
membership: Membership<M>,
rng: R,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CryptographicProcessorSettings<K> {
pub struct CryptographicProcessorSettings<K, S> {
pub private_key: K,
pub num_mix_layers: usize,
pub message_settings: S,
}
impl<R, M> CryptographicProcessor<R, M>
@ -27,7 +28,7 @@ where
M::PublicKey: Clone + PartialEq,
{
pub fn new(
settings: CryptographicProcessorSettings<M::PrivateKey>,
settings: CryptographicProcessorSettings<M::PrivateKey, M::Settings>,
membership: Membership<M>,
rng: R,
) -> Self {
@ -38,7 +39,7 @@ where
}
}
pub fn wrap_message(&mut self, message: &[u8]) -> Result<Vec<u8>, nomos_mix_message::Error> {
pub fn wrap_message(&mut self, message: &[u8]) -> Result<Vec<u8>, M::Error> {
// TODO: Use the actual Sphinx encoding instead of mock.
let public_keys = self
.membership
@ -47,13 +48,14 @@ where
.map(|node| node.public_key.clone())
.collect::<Vec<_>>();
M::build_message(message, &public_keys)
M::build_message(message, &public_keys, &self.settings.message_settings)
}
pub fn unwrap_message(
&self,
message: &[u8],
) -> Result<(Vec<u8>, bool), nomos_mix_message::Error> {
M::unwrap_message(message, &self.settings.private_key)
pub fn unwrap_message(&self, message: &[u8]) -> Result<(Vec<u8>, bool), M::Error> {
M::unwrap_message(
message,
&self.settings.private_key,
&self.settings.message_settings,
)
}
}

View File

@ -4,6 +4,7 @@ pub mod temporal;
pub use crypto::CryptographicProcessorSettings;
use futures::{Stream, StreamExt};
use rand::RngCore;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
@ -25,8 +26,9 @@ pub struct MessageBlendSettings<M>
where
M: MixMessage,
M::PrivateKey: Serialize + DeserializeOwned,
M::Settings: Serialize + DeserializeOwned,
{
pub cryptographic_processor: CryptographicProcessorSettings<M::PrivateKey>,
pub cryptographic_processor: CryptographicProcessorSettings<M::PrivateKey, M::Settings>,
pub temporal_processor: TemporalSchedulerSettings,
}
@ -52,6 +54,8 @@ where
M: MixMessage,
M::PrivateKey: Serialize + DeserializeOwned,
M::PublicKey: Clone + PartialEq,
M::Settings: Serialize + DeserializeOwned,
M::Error: Debug,
Scheduler: Stream<Item = ()> + Unpin + Send + Sync + 'static,
{
pub fn new(
@ -91,9 +95,6 @@ where
tracing::error!("Failed to send message to the outbound channel: {e:?}");
}
}
Err(nomos_mix_message::Error::MsgUnwrapNotAllowed) => {
tracing::debug!("Message cannot be unwrapped by this node");
}
Err(e) => {
tracing::error!("Failed to unwrap message: {:?}", e);
}
@ -108,6 +109,8 @@ where
M: MixMessage + Unpin,
M::PrivateKey: Serialize + DeserializeOwned + Unpin,
M::PublicKey: Clone + PartialEq + Unpin,
M::Settings: Serialize + DeserializeOwned + Unpin,
M::Error: Debug,
Scheduler: Stream<Item = ()> + Unpin + Send + Sync + 'static,
{
type Item = MixOutgoingMessage;
@ -126,6 +129,8 @@ where
M: MixMessage,
M::PrivateKey: Serialize + DeserializeOwned,
M::PublicKey: Clone + PartialEq,
M::Settings: Serialize + DeserializeOwned,
M::Error: Debug,
Scheduler: Stream<Item = ()> + Unpin + Send + Sync + 'static,
{
fn blend(
@ -155,6 +160,8 @@ where
M: MixMessage,
M::PrivateKey: Clone + Serialize + DeserializeOwned + PartialEq,
M::PublicKey: Clone + Serialize + DeserializeOwned + PartialEq,
M::Settings: Clone + Serialize + DeserializeOwned,
M::Error: Debug,
S: Stream<Item = ()> + Unpin + Send + Sync + 'static,
{
}

View File

@ -5,6 +5,7 @@ edition = "2021"
[dependencies]
rand_chacha = "0.3"
serde = { version = "1", features = ["derive"] }
sha2 = "0.10"
sphinx-packet = "0.2"
thiserror = "1.0.65"

View File

@ -1,22 +0,0 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Invalid mix message format")]
InvalidMixMessage,
#[error("Payload is too large")]
PayloadTooLarge,
#[error("Invalid number of layers")]
InvalidNumberOfLayers,
#[error("Sphinx packet error: {0}")]
SphinxPacketError(#[from] sphinx_packet::Error),
#[error("Invalid packet")]
InvalidPacket,
#[error("Invalid routing flag: {0}")]
InvalidRoutingFlag(u8),
#[error("Invalid routing length: {0}")]
InvalidEncryptedRoutingInfoLength(usize),
#[error("ConsistentLengthLayeredEncryptionError: {0}")]
ConsistentLengthLayeredEncryptionError(#[from] crate::layered_cipher::Error),
#[error("Unwrapping a message is not allowed to this node")]
/// e.g. the message cannot be unwrapped using the private key provided
MsgUnwrapNotAllowed,
}

View File

@ -1,17 +1,18 @@
mod error;
mod layered_cipher;
pub mod mock;
pub mod packet;
mod routing;
pub use error::Error;
pub mod sphinx;
pub trait MixMessage {
type PublicKey;
type PrivateKey;
type Settings;
type Error;
const DROP_MESSAGE: &'static [u8];
fn build_message(payload: &[u8], public_keys: &[Self::PublicKey]) -> Result<Vec<u8>, Error>;
fn build_message(
payload: &[u8],
public_keys: &[Self::PublicKey],
settings: &Self::Settings,
) -> Result<Vec<u8>, Self::Error>;
/// Unwrap the message one layer.
///
/// This function returns the unwrapped message and a boolean indicating whether the message was fully unwrapped.
@ -22,31 +23,9 @@ pub trait MixMessage {
fn unwrap_message(
message: &[u8],
private_key: &Self::PrivateKey,
) -> Result<(Vec<u8>, bool), Error>;
settings: &Self::Settings,
) -> Result<(Vec<u8>, bool), Self::Error>;
fn is_drop_message(message: &[u8]) -> bool {
message == Self::DROP_MESSAGE
}
}
pub(crate) fn concat_bytes(bytes_list: &[&[u8]]) -> Vec<u8> {
let mut buf = Vec::with_capacity(bytes_list.iter().map(|bytes| bytes.len()).sum());
bytes_list
.iter()
.for_each(|bytes| buf.extend_from_slice(bytes));
buf
}
pub(crate) fn parse_bytes<'a>(data: &'a [u8], sizes: &[usize]) -> Result<Vec<&'a [u8]>, String> {
let mut i = 0;
sizes
.iter()
.map(|&size| {
if i + size > data.len() {
return Err("The sum of sizes exceeds the length of the input slice".to_string());
}
let slice = &data[i..i + size];
i += size;
Ok(slice)
})
.collect()
}

View File

@ -0,0 +1,12 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Invalid mix message format")]
InvalidMixMessage,
#[error("Payload is too large")]
PayloadTooLarge,
#[error("Invalid number of layers")]
InvalidNumberOfLayers,
#[error("Unwrapping a message is not allowed to this node")]
/// e.g. the message cannot be unwrapped using the private key provided
MsgUnwrapNotAllowed,
}

View File

@ -1,4 +1,8 @@
use crate::{Error, MixMessage};
pub mod error;
use error::Error;
use crate::MixMessage;
// TODO: Remove all the mock below once the actual implementation is integrated to the system.
//
/// A mock implementation of the Sphinx encoding.
@ -17,13 +21,19 @@ pub struct MockMixMessage;
impl MixMessage for MockMixMessage {
type PublicKey = [u8; NODE_ID_SIZE];
type PrivateKey = [u8; NODE_ID_SIZE];
type Settings = ();
type Error = Error;
const DROP_MESSAGE: &'static [u8] = &[0; MESSAGE_SIZE];
/// The length of the encoded message is fixed to [`MESSAGE_SIZE`] bytes.
/// The [`MAX_LAYERS`] number of [`NodeId`]s are concatenated in front of the payload.
/// The payload is zero-padded to the end.
///
fn build_message(payload: &[u8], public_keys: &[Self::PublicKey]) -> Result<Vec<u8>, Error> {
fn build_message(
payload: &[u8],
public_keys: &[Self::PublicKey],
_: &Self::Settings,
) -> Result<Vec<u8>, Self::Error> {
// In this mock, we don't encrypt anything. So, we use public key as just a node ID.
let node_ids = public_keys;
if node_ids.is_empty() || node_ids.len() > MAX_LAYERS {
@ -54,7 +64,8 @@ impl MixMessage for MockMixMessage {
fn unwrap_message(
message: &[u8],
private_key: &Self::PrivateKey,
) -> Result<(Vec<u8>, bool), Error> {
_: &Self::Settings,
) -> Result<(Vec<u8>, bool), Self::Error> {
if message.len() != MESSAGE_SIZE {
return Err(Error::InvalidMixMessage);
}
@ -96,21 +107,21 @@ mod tests {
fn message() {
let node_ids = (0..3).map(|i| [i; NODE_ID_SIZE]).collect::<Vec<_>>();
let payload = [7; 10];
let message = MockMixMessage::build_message(&payload, &node_ids).unwrap();
let message = MockMixMessage::build_message(&payload, &node_ids, &()).unwrap();
assert_eq!(message.len(), MESSAGE_SIZE);
let (message, is_fully_unwrapped) =
MockMixMessage::unwrap_message(&message, &node_ids[0]).unwrap();
MockMixMessage::unwrap_message(&message, &node_ids[0], &()).unwrap();
assert!(!is_fully_unwrapped);
assert_eq!(message.len(), MESSAGE_SIZE);
let (message, is_fully_unwrapped) =
MockMixMessage::unwrap_message(&message, &node_ids[1]).unwrap();
MockMixMessage::unwrap_message(&message, &node_ids[1], &()).unwrap();
assert!(!is_fully_unwrapped);
assert_eq!(message.len(), MESSAGE_SIZE);
let (unwrapped_payload, is_fully_unwrapped) =
MockMixMessage::unwrap_message(&message, &node_ids[2]).unwrap();
MockMixMessage::unwrap_message(&message, &node_ids[2], &()).unwrap();
assert!(is_fully_unwrapped);
assert_eq!(unwrapped_payload, payload);
}

View File

@ -0,0 +1,13 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Sphinx packet error: {0}")]
SphinxPacketError(#[from] sphinx_packet::Error),
#[error("Invalid packet bytes")]
InvalidPacketBytes,
#[error("Invalid routing flag: {0}")]
InvalidRoutingFlag(u8),
#[error("Invalid routing length: {0}")]
InvalidEncryptedRoutingInfoLength(usize),
#[error("ConsistentLengthLayeredEncryptionError: {0}")]
ConsistentLengthLayeredEncryptionError(#[from] super::layered_cipher::Error),
}

View File

@ -13,14 +13,14 @@ use sphinx_packet::{
},
};
use crate::{concat_bytes, parse_bytes};
use super::{concat_bytes, parse_bytes};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Invalid data length")]
InvalidDataLength,
#[error("Invalid params")]
InvalidParams,
#[error("Invalid cipher text length")]
InvalidCipherTextLength,
#[error("Invalid encryption param")]
InvalidEncryptionParam,
#[error("Integrity MAC verification failed")]
IntegrityMacVerificationFailed,
}
@ -96,7 +96,7 @@ impl<D: ConsistentLengthLayeredCipherData> ConsistentLengthLayeredCipher<D> {
/// Perform the layered encryption.
pub fn encrypt(&self, params: &[EncryptionParam<D>]) -> Result<(Vec<u8>, HeaderIntegrityMac)> {
if params.is_empty() || params.len() > self.max_layers {
return Err(Error::InvalidParams);
return Err(Error::InvalidEncryptionParam);
}
params
@ -147,13 +147,16 @@ impl<D: ConsistentLengthLayeredCipherData> ConsistentLengthLayeredCipher<D> {
&self,
params: &[EncryptionParam<D>],
) -> Result<(Vec<u8>, HeaderIntegrityMac)> {
let last_param = params.last().ok_or(Error::InvalidParams)?;
let last_param = params.last().ok_or(Error::InvalidEncryptionParam)?;
// Build fillers that will be appended to the last data.
// The number of fillers must be the same as the number of intermediate layers
// that will be decrypted later.
// (excluding the last layer) that will be decrypted later.
let fillers = self.build_fillers(&params[..params.len() - 1]);
// Random bytes is used to fill the space between data and fillers.
// Header integrity MAC doesn't need to be included in the last layer
// because there is no next encrypted layer.
// Instead, random bytes are used to fill the space between data and fillers.
// The size of random bytes depends on the [`self.max_layers`].
let random_bytes = random_bytes(self.total_size() - D::size() - fillers.len());
// First, concat the data and the random bytes, and encrypt it.
@ -195,7 +198,7 @@ impl<D: ConsistentLengthLayeredCipherData> ConsistentLengthLayeredCipher<D> {
key: &Key,
) -> Result<(Vec<u8>, HeaderIntegrityMac, Vec<u8>)> {
if encrypted_total_data.len() != self.total_size() {
return Err(Error::InvalidDataLength);
return Err(Error::InvalidCipherTextLength);
}
// If a wrong key is used, the decryption should fail.
if !mac.verify(key.integrity_mac_key, encrypted_total_data) {

View File

@ -0,0 +1,87 @@
use error::Error;
use packet::{Packet, UnpackedPacket};
use serde::{Deserialize, Serialize};
use crate::MixMessage;
pub mod error;
mod layered_cipher;
pub mod packet;
mod routing;
pub struct SphinxMessage;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SphinxMessageSettings {
pub max_layers: usize,
pub max_payload_size: usize,
}
const ASYM_KEY_SIZE: usize = 32;
impl MixMessage for SphinxMessage {
type PublicKey = [u8; ASYM_KEY_SIZE];
type PrivateKey = [u8; ASYM_KEY_SIZE];
type Settings = SphinxMessageSettings;
type Error = Error;
// TODO: Remove DROP_MESSAGE. Currently, an arbitrary size (2048) is used,
// but we've decided to remove drop messages from the spec.
const DROP_MESSAGE: &'static [u8] = &[0; 2048];
fn build_message(
payload: &[u8],
public_keys: &[Self::PublicKey],
settings: &Self::Settings,
) -> Result<Vec<u8>, Self::Error> {
let packet = Packet::build(
&public_keys
.iter()
.map(|k| x25519_dalek::PublicKey::from(*k))
.collect::<Vec<_>>(),
settings.max_layers,
payload,
settings.max_payload_size,
)?;
Ok(packet.to_bytes())
}
fn unwrap_message(
message: &[u8],
private_key: &Self::PrivateKey,
settings: &Self::Settings,
) -> Result<(Vec<u8>, bool), Self::Error> {
let packet = Packet::from_bytes(message, settings.max_layers)?;
let unpacked_packet = packet.unpack(
&x25519_dalek::StaticSecret::from(*private_key),
settings.max_layers,
)?;
match unpacked_packet {
UnpackedPacket::ToForward(packet) => Ok((packet.to_bytes(), false)),
UnpackedPacket::FullyUnpacked(payload) => Ok((payload, true)),
}
}
}
fn concat_bytes(bytes_list: &[&[u8]]) -> Vec<u8> {
let mut buf = Vec::with_capacity(bytes_list.iter().map(|bytes| bytes.len()).sum());
bytes_list
.iter()
.for_each(|bytes| buf.extend_from_slice(bytes));
buf
}
fn parse_bytes<'a>(data: &'a [u8], sizes: &[usize]) -> Result<Vec<&'a [u8]>, String> {
let mut i = 0;
sizes
.iter()
.map(|&size| {
if i + size > data.len() {
return Err("The sum of sizes exceeds the length of the input slice".to_string());
}
let slice = &data[i..i + size];
i += size;
Ok(slice)
})
.collect()
}

View File

@ -1,4 +1,3 @@
use crate::{concat_bytes, parse_bytes, routing::EncryptedRoutingInformation, Error};
use sphinx_packet::{
constants::NODE_ADDRESS_LENGTH,
header::{
@ -8,6 +7,10 @@ use sphinx_packet::{
payload::Payload,
};
use crate::sphinx::ASYM_KEY_SIZE;
use super::{concat_bytes, error::Error, parse_bytes, routing::EncryptedRoutingInformation};
/// A packet that contains a header and a payload.
/// The header and payload are encrypted for the selected recipients.
/// This packet can be serialized and sent over the network.
@ -33,7 +36,7 @@ impl Packet {
recipient_pubkeys: &[x25519_dalek::PublicKey],
max_layers: usize,
payload: &[u8],
payload_size: usize,
max_payload_size: usize,
) -> Result<Self, Error> {
// Derive `[sphinx_packet::header::keys::KeyMaterial]` for all recipients.
let ephemeral_privkey = x25519_dalek::StaticSecret::random();
@ -52,7 +55,7 @@ impl Packet {
let payload = sphinx_packet::payload::Payload::encapsulate_message(
payload,
&payload_keys,
payload_size,
max_payload_size,
)?;
Ok(Packet {
@ -157,7 +160,7 @@ impl Packet {
}
pub fn from_bytes(data: &[u8], max_layers: usize) -> Result<Self, Error> {
let ephemeral_public_key_size = 32;
let ephemeral_public_key_size = ASYM_KEY_SIZE;
let encrypted_routing_info_size = EncryptedRoutingInformation::size(max_layers);
let parsed = parse_bytes(
data,
@ -167,7 +170,7 @@ impl Packet {
data.len() - ephemeral_public_key_size - encrypted_routing_info_size,
],
)
.map_err(|_| Error::InvalidPacket)?;
.map_err(|_| Error::InvalidPacketBytes)?;
Ok(Packet {
header: Header {
@ -269,13 +272,13 @@ mod tests {
// Build a packet
let max_layers = 5;
let payload = [10u8; 512];
let packet = Packet::build(&recipient_pubkeys, max_layers, &payload, 1024).unwrap();
let max_payload_size = 1024;
let packet =
Packet::build(&recipient_pubkeys, max_layers, &payload, max_payload_size).unwrap();
// Calculate the expected packet size
let pubkey_size = 32;
let payload_size = 1024;
let packet_size =
pubkey_size + EncryptedRoutingInformation::size(max_layers) + payload_size;
ASYM_KEY_SIZE + EncryptedRoutingInformation::size(max_layers) + max_payload_size;
// The serialized packet size must be the same as the expected size.
assert_eq!(packet.to_bytes().len(), packet_size);

View File

@ -7,12 +7,13 @@ use sphinx_packet::{
},
};
use crate::{
use super::{
concat_bytes,
error::Error,
layered_cipher::{
ConsistentLengthLayeredCipher, ConsistentLengthLayeredCipherData, EncryptionParam, Key,
},
parse_bytes, Error,
parse_bytes,
};
/// A routing information that will be contained in a packet header

View File

@ -224,6 +224,7 @@ pub fn new_node(
cryptographic_processor: CryptographicProcessorSettings {
private_key: mix_config.private_key.to_bytes(),
num_mix_layers: 1,
message_settings: (),
},
temporal_processor: TemporalSchedulerSettings {
max_delay_seconds: 2,

View File

@ -161,6 +161,7 @@ pub fn create_executor_config(config: GeneralConfig) -> Config {
cryptographic_processor: CryptographicProcessorSettings {
private_key: config.mix_config.private_key.to_bytes(),
num_mix_layers: 1,
message_settings: (),
},
temporal_processor: TemporalSchedulerSettings {
max_delay_seconds: 2,

View File

@ -247,6 +247,7 @@ pub fn create_validator_config(config: GeneralConfig) -> Config {
cryptographic_processor: CryptographicProcessorSettings {
private_key: config.mix_config.private_key.to_bytes(),
num_mix_layers: 1,
message_settings: (),
},
temporal_processor: TemporalSchedulerSettings {
max_delay_seconds: 2,