mixnet 0.7.0

A mix network based on Loopix
Documentation
// Copyright 2022 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! Sphinx packet peeling.

use super::{build::SurbPayloadEncryptionKeys, crypto::*, delay::Delay, packet::*, target::Target};
use arrayref::{array_mut_ref, array_ref, array_refs};
use subtle::ConstantTimeEq;

/// Returns a reference to the key-exchange public key in `packet`.
pub fn kx_public(packet: &Packet) -> &KxPublic {
	array_ref![packet, 0, KX_PUBLIC_SIZE]
}

/// Action to take with a peeled packet.
#[derive(Debug, PartialEq, Eq)]
pub enum Action {
	/// The packet in `out` should be forwarded to `target` after `delay`.
	ForwardTo { target: Target, delay: Delay },
	/// The payload data in `out[..PAYLOAD_DATA_SIZE]` should be delivered locally.
	DeliverRequest,
	/// The reply payload in `out[..PAYLOAD_SIZE]` should be decrypted according to `surb_id` and
	/// then delivered locally.
	DeliverReply { surb_id: SurbId },
	/// The packet was a cover packet with the specified ID. There is no payload.
	DeliverCover { cover_id: Option<CoverId> },
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PeelErr {
	#[error("Bad MAC in header")]
	Mac,
	#[error("Bad action in header")]
	Action,
	#[error("Bad payload tag")]
	PayloadTag,
}

fn check_payload_tag(tag: &PayloadTag) -> Result<(), PeelErr> {
	let tag_ok: bool = tag.ct_eq(&PAYLOAD_TAG).into();
	if tag_ok {
		Ok(())
	} else {
		Err(PeelErr::PayloadTag)
	}
}

/// Attempt to peel a layer off `packet` using `kx_shared_secret`. `kx_shared_secret` should be
/// derived from [`kx_public(packet)`](kx_public) and this node's secret key.
pub fn peel(
	out: &mut Packet,
	packet: &Packet,
	kx_shared_secret: &SharedSecret,
) -> Result<Action, PeelErr> {
	// (kx_public, mac, actions, payload) correspond to (alpha, gamma, beta, delta) in the Sphinx
	// paper
	let (kx_public, mac, actions, payload) =
		array_refs![packet, KX_PUBLIC_SIZE, MAC_SIZE, ACTIONS_SIZE, PAYLOAD_SIZE];

	let sds = SmallDerivedSecrets::new(kx_shared_secret);

	// Verify the MAC
	if !mac_ok(mac, actions, sds.mac_key()) {
		return Err(PeelErr::Mac)
	}

	// Decrypt the routing actions and generate padding for length invariance. Try to get the
	// decrypted actions in the right place in the output. The most likely case is that we will be
	// forwarding the packet to a mixnode, so assume this. We could save some work in the deliver
	// case by decrypting just the first few bytes to start with to see if we need to decrypt the
	// rest. This would complicate things and as the forward case is much more common it might
	// ultimately not make things any faster, so don't bother for now.
	let decrypted_actions =
		array_mut_ref![out, KX_PUBLIC_SIZE - RAW_ACTION_SIZE, ACTIONS_SIZE + MAX_ACTIONS_PAD_SIZE];
	*array_mut_ref![decrypted_actions, 0, ACTIONS_SIZE] = *actions;
	*array_mut_ref![decrypted_actions, ACTIONS_SIZE, MAX_ACTIONS_PAD_SIZE] =
		[0; MAX_ACTIONS_PAD_SIZE]; // Padding is generated by encrypting zeroes
	apply_actions_encryption_keystream(decrypted_actions, sds.actions_encryption_key());

	let raw_action = RawAction::from_le_bytes(*array_ref![decrypted_actions, 0, RAW_ACTION_SIZE]);
	Ok(match raw_action {
		RAW_ACTION_DELIVER_REQUEST => {
			// Peel off the final layer of payload encryption
			let out = array_mut_ref![out, 0, PAYLOAD_SIZE];
			*out = *payload;
			decrypt_payload(out, &derive_payload_encryption_key(kx_shared_secret));

			check_payload_tag(array_ref![out, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE])?;

			Action::DeliverRequest
		},
		RAW_ACTION_DELIVER_REPLY => {
			// Pull the SURB ID out
			let surb_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, SURB_ID_SIZE];

			// Copy the payload across but don't do anything with it yet; the caller will need to
			// fetch the keys corresponding to the SURB ID and then call decrypt_reply_payload()
			*array_mut_ref![out, 0, PAYLOAD_SIZE] = *payload;

			Action::DeliverReply { surb_id }
		},
		RAW_ACTION_DELIVER_COVER => Action::DeliverCover { cover_id: None },
		RAW_ACTION_DELIVER_COVER_WITH_ID => {
			// Pull the cover ID out
			let cover_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, COVER_ID_SIZE];

			Action::DeliverCover { cover_id: Some(cover_id) }
		},
		_ => {
			// Forward. Determine target...
			let target = if raw_action == RAW_ACTION_FORWARD_TO_PEER_ID {
				// Copy out peer ID and move rest down
				let peer_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, PEER_ID_SIZE];
				decrypted_actions.copy_within(
					RAW_ACTION_SIZE + PEER_ID_SIZE..
						RAW_ACTION_SIZE + PEER_ID_SIZE + MAC_SIZE + ACTIONS_SIZE,
					RAW_ACTION_SIZE,
				);
				Target::PeerId(peer_id)
			} else {
				Target::MixnodeIndex(raw_action.try_into().map_err(|_| PeelErr::Action)?)
			};

			// Determine the forwarding delay
			let delay = Delay::exp(sds.delay_seed());

			// Blind the key-exchange public key
			*array_mut_ref![out, 0, KX_PUBLIC_SIZE] = blind_kx_public(kx_public, kx_shared_secret);

			// The next MAC and routing actions are already in the right place in out

			// Peel off one layer of payload encryption
			let out_payload = array_mut_ref![out, HEADER_SIZE, PAYLOAD_SIZE];
			*out_payload = *payload;
			decrypt_payload(out_payload, &derive_payload_encryption_key(kx_shared_secret));

			Action::ForwardTo { target, delay }
		},
	})
}

/// Decrypts a reply payload given the encryption keys of the corresponding SURB. On success, the
/// payload data is left in `payload[..PAYLOAD_DATA_SIZE]`.
pub fn decrypt_reply_payload(
	payload: &mut Payload,
	keys: &SurbPayloadEncryptionKeys,
) -> Result<(), PeelErr> {
	for key in keys.iter().rev() {
		encrypt_payload(payload, key);
	}

	check_payload_tag(array_ref![payload, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE])
}