Skip to content
Draft
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
28 changes: 24 additions & 4 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,31 @@ impl BuiltHeader {
.collect()
}

pub(crate) fn payload_key_seeds(&self) -> Vec<PayloadKeySeed> {
/// Returns the key used to add the innermost layer of payload encryption,
/// i.e. the layer belonging to the last hop of the route.
///
/// This is the only payload key material a SURB may hand out to whoever uses it: the
/// remaining layers get added by each hop processing, and
/// only the SURB's original creator - who retains every derived key locally - can remove
/// them all again. Handing out every layer's key would let the SURB user precompute every
/// intermediate ciphertext and, by colluding with the last hop, deanonymize the reply route.
pub(crate) fn legacy_first_layer_payload_key(&self) -> PayloadKey {
*self.last_hop_secret().legacy_payload_key()
}

/// Seed variant of [`Self::legacy_first_layer_payload_key`].
pub(crate) fn first_layer_payload_key_seed(&self) -> PayloadKeySeed {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't it the case that if we use the seed variant (i.e. the "legacy" mode), we need all seeds for compatibility?

*self.last_hop_secret().payload_key_seed()
}

#[allow(clippy::expect_used)]
fn last_hop_secret(&self) -> &ExpandedSharedSecret {
// `build_header` already panics on an empty route (slicing `expanded_shared_secrets`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's quite a bad justification from Claude. it panics elsewhere so panic here is fine. ideally we shouldn't allow panics anywhere by guarding against degenerative cases

// by `route.len() - 1` to build the filler), so a `BuiltHeader` is never constructed
// with an empty `expanded_secrets` in the first place
self.expanded_secrets
.iter()
.map(|s| *s.payload_key_seed())
.collect()
.last()
.expect("BuiltHeader is always constructed with a non-empty route")
}

pub(crate) fn into_header(self) -> SphinxHeader {
Expand Down
10 changes: 8 additions & 2 deletions src/header/shared_secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ impl ExpandedSharedSecret {

/// Legacy output of the hπ random oracle
// NOTE: currently we expand it to full PRP key
pub(crate) fn legacy_payload_key(&self) -> &PayloadKey {
//
// Public because a SURB's original creator has to be able to re-derive every hop's payload
// key for itself (e.g. via `KeyMaterial::derive`) in order to remove the layers that got
// added to the payload as it transited the network.
pub fn legacy_payload_key(&self) -> &PayloadKey {
array_ref!(
&self.0,
STREAM_CIPHER_KEY_SIZE + INTEGRITY_MAC_KEY_SIZE,
Expand All @@ -77,7 +81,9 @@ impl ExpandedSharedSecret {
}

/// Output of the hπ random oracle
pub(crate) fn payload_key_seed(&self) -> &[u8; PAYLOAD_KEY_SEED_SIZE] {
///
/// Public for the same reason as [`Self::legacy_payload_key`].
pub fn payload_key_seed(&self) -> &[u8; PAYLOAD_KEY_SEED_SIZE] {
array_ref!(
&self.0,
STREAM_CIPHER_KEY_SIZE + INTEGRITY_MAC_KEY_SIZE,
Expand Down
9 changes: 8 additions & 1 deletion src/payload/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,14 @@ impl Payload {
}

/// Tries to add an additional layer of encryption onto self.
fn add_encryption_layer<P: Borrow<PayloadKey>>(mut self, payload_key: P) -> Result<Self> {
///
/// Besides being used internally by [`Self::encapsulate_message`], this is also what lets a
/// SURB's original creator undo the layers each mix node added to a SURB reply's payload as

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it though? wouldn't the SURB's original creator need to call unwrap to undo the layers? I know that for all intents and purposes encrypt and decrypt are identical, but isn't this comment very much misleading?

/// it transited the network: since every hop always *removes* a layer with its own key
/// regardless of whether the packet is a forward packet or a SURB reply, re-*adding* those
/// same layers (in the same, per-hop order) with the independently re-derived hop keys
/// exactly reverses that transit-time processing.
pub fn add_encryption_layer<P: Borrow<PayloadKey>>(mut self, payload_key: P) -> Result<Self> {
let lioness_cipher = NymLioness::new(payload_key.borrow().into());

if let Err(err) = lioness_cipher.encrypt_block(&mut self.0) {
Expand Down
Loading
Loading