Skip to content

Commit

Permalink
Send hashes in the echo round instead of the full messages
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Feb 2, 2025
1 parent f4c9515 commit 6ed1729
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 91 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions manul/src/dev/session_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl digest::Update for TestHasher {
// A very simple algorithm for testing, just xor the data in buffer-sized chunks.
for byte in data {
*self.buffer.get_mut(self.cursor).expect("index within bounds") ^= byte;
self.cursor = (self.cursor + 1) % 32;
self.cursor = (self.cursor + 1) % self.buffer.len();
}
}
}
Expand All @@ -84,7 +84,7 @@ impl digest::FixedOutput for TestHasher {
}

impl digest::OutputSizeUser for TestHasher {
type OutputSize = typenum::U8;
type OutputSize = typenum::U32;
}

/// An implementation of [`SessionParameters`] using the testing signer/verifier types.
Expand Down
55 changes: 16 additions & 39 deletions manul/src/session/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use tracing::debug;

use super::{
message::{MessageVerificationError, SignedMessagePart},
message::{MessageVerificationError, SignedMessageHash, SignedMessagePart},
session::{EchoRoundInfo, SessionParameters},
LocalError,
};
Expand Down Expand Up @@ -40,9 +40,8 @@ pub(crate) enum EchoRoundError<Id> {
/// This is the fault of the sender of that specific broadcast.
MismatchedBroadcasts {
guilty_party: Id,
error: MismatchedBroadcastsError,
we_received: SignedMessagePart<EchoBroadcast>,
echoed_to_us: SignedMessagePart<EchoBroadcast>,
echoed_to_us: SignedMessageHash,
},
}

Expand All @@ -57,17 +56,10 @@ impl<Id> EchoRoundError<Id> {
}
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub(crate) enum MismatchedBroadcastsError {
/// The originally received message and the echoed one had different payloads.
DifferentPayloads,
/// The originally received message and the echoed one had different signatures.
DifferentSignatures,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct EchoRoundMessage<SP: SessionParameters> {
pub(super) echo_broadcasts: SerializableMap<SP::Verifier, SignedMessagePart<EchoBroadcast>>,
/// Signatures of echo broadcasts from respective nodes.
pub(super) message_hashes: SerializableMap<SP::Verifier, SignedMessageHash>,
}

/// Each protocol round can contain one `EchoRound` with "echo messages" that are sent to all
Expand All @@ -90,16 +82,12 @@ where
{
pub fn new(
verifier: SP::Verifier,
my_echo_broadcast: SignedMessagePart<EchoBroadcast>,
echo_broadcasts: BTreeMap<SP::Verifier, SignedMessagePart<EchoBroadcast>>,
echo_round_info: EchoRoundInfo<SP::Verifier>,
main_round: BoxedRound<SP::Verifier, P>,
payloads: BTreeMap<SP::Verifier, Payload>,
artifacts: BTreeMap<SP::Verifier, Artifact>,
) -> Self {
let mut echo_broadcasts = echo_broadcasts;
echo_broadcasts.insert(verifier.clone(), my_echo_broadcast);

debug!("{:?}: initialized echo round with {:?}", verifier, echo_round_info);
Self {
verifier,
Expand Down Expand Up @@ -166,9 +154,13 @@ where
)));
}

let message = EchoRoundMessage::<SP> {
echo_broadcasts: echo_broadcasts.into(),
};
let message_hashes = echo_broadcasts
.iter()
.map(|(id, echo_broadcast)| (id.clone(), echo_broadcast.to_signed_hash::<SP>()))
.collect::<BTreeMap<_, _>>()
.into();

let message = EchoRoundMessage::<SP> { message_hashes };
NormalBroadcast::new(serializer, message)
}

Expand Down Expand Up @@ -199,7 +191,7 @@ where
// We don't expect the node to send its echo the second time.
expected_keys.remove(from);

let message_keys = message.echo_broadcasts.keys().cloned().collect::<BTreeSet<_>>();
let message_keys = message.message_hashes.keys().cloned().collect::<BTreeSet<_>>();

let missing_keys = expected_keys.difference(&message_keys).collect::<Vec<_>>();
if !missing_keys.is_empty() {
Expand All @@ -221,7 +213,7 @@ where
// If there's a difference, it's a provable fault,
// since we have both messages signed by `from`.

for (sender, echo) in message.echo_broadcasts.iter() {
for (sender, echo) in message.message_hashes.iter() {
// We expect the key to be there since
// `message.echo_broadcasts.keys()` is within `self.destinations`
// which was constructed as `self.echo_broadcasts.keys()`.
Expand All @@ -230,10 +222,6 @@ where
.get(sender)
.expect("the key is present by construction");

if echo == previously_received_echo {
continue;
}

let verified_echo = match echo.clone().verify::<SP>(sender) {
Ok(echo) => echo,
Err(MessageVerificationError::Local(error)) => return Err(error.into()),
Expand All @@ -253,28 +241,17 @@ where
return Err(EchoRoundError::InvalidEcho(sender.clone()).into());
}

// `sender` sent us and `from` messages with different payloads.
// `sender` sent us and `from` messages with different payloads,
// but with correct signatures and the same metadata.
// Provable fault of `sender`.
if verified_echo.payload() != previously_received_echo.payload() {
if !verified_echo.is_hash_of::<SP, _>(previously_received_echo) {
return Err(EchoRoundError::MismatchedBroadcasts {
guilty_party: sender.clone(),
error: MismatchedBroadcastsError::DifferentPayloads,
we_received: previously_received_echo.clone(),
echoed_to_us: echo.clone(),
}
.into());
}

// At this point, we know that the echoed broadcast is not identical to what we initially received,
// but somehow they both have the correct metadata, and correct signatures.
// Something strange is going on.
return Err(EchoRoundError::MismatchedBroadcasts {
guilty_party: sender.clone(),
error: MismatchedBroadcastsError::DifferentSignatures,
we_received: previously_received_echo.clone(),
echoed_to_us: echo.clone(),
}
.into());
}

Ok(Payload::empty())
Expand Down
88 changes: 52 additions & 36 deletions manul/src/session/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use core::fmt::Debug;
use serde::{Deserialize, Serialize};

use super::{
echo::{EchoRound, EchoRoundError, EchoRoundMessage, MismatchedBroadcastsError},
message::{MessageVerificationError, SignedMessagePart},
echo::{EchoRound, EchoRoundError, EchoRoundMessage},
message::{MessageVerificationError, SignedMessageHash, SignedMessagePart},
session::{SessionId, SessionParameters},
transcript::Transcript,
LocalError,
Expand Down Expand Up @@ -129,13 +129,18 @@ where
}
}

let mut combined_echos = BTreeMap::new();
let mut echo_hashes = BTreeMap::new();
let mut other_echo_broadcasts = BTreeMap::new();
if let Some(required_combined_echos) = required_messages.combined_echos {
for round_id in required_combined_echos {
combined_echos.insert(
echo_hashes.insert(
round_id.clone(),
transcript.get_normal_broadcast(&round_id.echo(), verifier)?,
);
other_echo_broadcasts.insert(
round_id.clone(),
transcript.get_other_echo_broadcasts(&round_id, verifier)?.into(),
);
}
}

Expand All @@ -152,7 +157,8 @@ where
direct_messages: direct_messages.into(),
echo_broadcasts: echo_broadcasts.into(),
normal_broadcasts: normal_broadcasts.into(),
combined_echos: combined_echos.into(),
other_echo_broadcasts: other_echo_broadcasts.into(),
echo_hashes: echo_hashes.into(),
}),
})
}
Expand All @@ -174,14 +180,12 @@ where
}),
EchoRoundError::MismatchedBroadcasts {
guilty_party,
error,
we_received,
echoed_to_us,
} => Ok(Self {
guilty_party,
description,
evidence: EvidenceEnum::MismatchedBroadcasts(MismatchedBroadcastsEvidence {
error,
we_received,
echoed_to_us,
}),
Expand Down Expand Up @@ -285,7 +289,7 @@ where
let verified = self.normal_broadcast.clone().verify::<SP>(verifier)?;
let deserialized = verified.payload().deserialize::<EchoRoundMessage<SP>>(deserializer)?;
let invalid_echo = deserialized
.echo_broadcasts
.message_hashes
.get(&self.invalid_echo_sender)
.ok_or_else(|| {
EvidenceError::InvalidEvidence(format!(
Expand Down Expand Up @@ -316,9 +320,8 @@ where

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MismatchedBroadcastsEvidence {
error: MismatchedBroadcastsError,
we_received: SignedMessagePart<EchoBroadcast>,
echoed_to_us: SignedMessagePart<EchoBroadcast>,
echoed_to_us: SignedMessageHash,
}

impl MismatchedBroadcastsEvidence {
Expand All @@ -329,18 +332,7 @@ impl MismatchedBroadcastsEvidence {
let we_received = self.we_received.clone().verify::<SP>(verifier)?;
let echoed_to_us = self.echoed_to_us.clone().verify::<SP>(verifier)?;

let evidence_is_valid = match self.error {
MismatchedBroadcastsError::DifferentPayloads => {
we_received.metadata() == echoed_to_us.metadata() && we_received.payload() != echoed_to_us.payload()
}
MismatchedBroadcastsError::DifferentSignatures => {
self.we_received != self.echoed_to_us
&& we_received.metadata() == echoed_to_us.metadata()
&& we_received.payload() == echoed_to_us.payload()
}
};

if evidence_is_valid {
if we_received.metadata() == echoed_to_us.metadata() && !echoed_to_us.is_hash_of::<SP, _>(&self.we_received) {
Ok(())
} else {
Err(EvidenceError::InvalidEvidence(
Expand Down Expand Up @@ -427,15 +419,16 @@ impl InvalidNormalBroadcastEvidence {

#[derive_where::derive_where(Debug)]
#[derive(Clone, Serialize, Deserialize)]
struct ProtocolEvidence<Id, P: Protocol<Id>> {
struct ProtocolEvidence<Id: Debug + Clone + Ord, P: Protocol<Id>> {
error: P::ProtocolError,
direct_message: Option<SignedMessagePart<DirectMessage>>,
echo_broadcast: Option<SignedMessagePart<EchoBroadcast>>,
normal_broadcast: Option<SignedMessagePart<NormalBroadcast>>,
direct_messages: SerializableMap<RoundId, SignedMessagePart<DirectMessage>>,
echo_broadcasts: SerializableMap<RoundId, SignedMessagePart<EchoBroadcast>>,
normal_broadcasts: SerializableMap<RoundId, SignedMessagePart<NormalBroadcast>>,
combined_echos: SerializableMap<RoundId, SignedMessagePart<NormalBroadcast>>,
other_echo_broadcasts: SerializableMap<RoundId, SerializableMap<Id, SignedMessagePart<EchoBroadcast>>>,
echo_hashes: SerializableMap<RoundId, SignedMessagePart<NormalBroadcast>>,
}

fn verify_message_parts<SP, T>(
Expand Down Expand Up @@ -488,7 +481,7 @@ where

impl<Id, P> ProtocolEvidence<Id, P>
where
Id: Clone + Ord,
Id: Debug + Clone + Ord,
P: Protocol<Id>,
{
fn verify<SP>(
Expand Down Expand Up @@ -527,30 +520,53 @@ where
let mut normal_broadcasts = verify_message_parts::<SP, _>(verifier, session_id, &self.normal_broadcasts)?;

let mut combined_echos = BTreeMap::new();
for (round_id, combined_echo) in self.combined_echos.iter() {
let verified_combined_echo = combined_echo.clone().verify::<SP>(verifier)?;
let metadata = verified_combined_echo.metadata();
for (round_id, echo_hashes) in self.echo_hashes.iter() {
let metadata = echo_hashes.metadata();
if metadata.session_id() != session_id || &metadata.round_id().non_echo() != round_id {
return Err(EvidenceError::InvalidEvidence(
"Invalid attached message metadata".into(),
));
}
let echo_set = verified_combined_echo

let verified_echo_hashes = echo_hashes.clone().verify::<SP>(verifier)?;
let echo_round_payload = verified_echo_hashes
.payload()
.deserialize::<EchoRoundMessage<SP>>(deserializer)?;

let mut verified_echo_set = BTreeMap::new();
for (other_verifier, echo_broadcast) in echo_set.echo_broadcasts.iter() {
let verified_echo_broadcast = echo_broadcast.clone().verify::<SP>(other_verifier)?;
let metadata = verified_echo_broadcast.metadata();
let signed_echo_broadcasts = self
.other_echo_broadcasts
.get(round_id)
.ok_or_else(|| EvidenceError::InvalidEvidence(format!("Missing {round_id} echo broadcasts")))?;

let mut echo_messages = BTreeMap::new();
for (other_verifier, echo_hash) in echo_round_payload.message_hashes.iter() {
let metadata = echo_hash.metadata();
if metadata.session_id() != session_id || metadata.round_id() != round_id {
return Err(EvidenceError::InvalidEvidence("Invalid echo hash metadata".into()));
}

let verified_echo_hash = echo_hash.clone().verify::<SP>(other_verifier)?;

let echo_broadcast = signed_echo_broadcasts.get(other_verifier).ok_or_else(|| {
EvidenceError::InvalidEvidence(format!("Missing {round_id} echo broadcast from {other_verifier:?}"))
})?;

let metadata = echo_broadcast.metadata();
if metadata.session_id() != session_id || metadata.round_id() != round_id {
return Err(EvidenceError::InvalidEvidence("Invalid echo broadcast metadata".into()));
}

if !verified_echo_hash.is_hash_of::<SP, _>(echo_broadcast) {
return Err(EvidenceError::InvalidEvidence(
"Invalid attached message metadata".into(),
"Mismatch between the echoed hash and the original echo broadcast".into(),
));
}
verified_echo_set.insert(other_verifier.clone(), verified_echo_broadcast.into_payload());

let verified_echo_broadcast = echo_broadcast.clone().verify::<SP>(other_verifier)?;

echo_messages.insert(other_verifier.clone(), verified_echo_broadcast.into_payload());
}
combined_echos.insert(round_id.clone(), verified_echo_set);
combined_echos.insert(round_id.clone(), echo_messages);
}

// Merge message parts
Expand Down
Loading

0 comments on commit 6ed1729

Please sign in to comment.