mixnet/core/sphinx/
peel.rs

1// Copyright 2022 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21//! Sphinx packet peeling.
22
23use super::{build::SurbPayloadEncryptionKeys, crypto::*, delay::Delay, packet::*, target::Target};
24use arrayref::{array_mut_ref, array_ref, array_refs};
25use subtle::ConstantTimeEq;
26
27/// Returns a reference to the key-exchange public key in `packet`.
28pub fn kx_public(packet: &Packet) -> &KxPublic {
29	array_ref![packet, 0, KX_PUBLIC_SIZE]
30}
31
32/// Action to take with a peeled packet.
33#[derive(Debug, PartialEq, Eq)]
34pub enum Action {
35	/// The packet in `out` should be forwarded to `target` after `delay`.
36	ForwardTo { target: Target, delay: Delay },
37	/// The payload data in `out[..PAYLOAD_DATA_SIZE]` should be delivered locally.
38	DeliverRequest,
39	/// The reply payload in `out[..PAYLOAD_SIZE]` should be decrypted according to `surb_id` and
40	/// then delivered locally.
41	DeliverReply { surb_id: SurbId },
42	/// The packet was a cover packet with the specified ID. There is no payload.
43	DeliverCover { cover_id: Option<CoverId> },
44}
45
46#[derive(Debug, thiserror::Error, PartialEq, Eq)]
47pub enum PeelErr {
48	#[error("Bad MAC in header")]
49	Mac,
50	#[error("Bad action in header")]
51	Action,
52	#[error("Bad payload tag")]
53	PayloadTag,
54}
55
56fn check_payload_tag(tag: &PayloadTag) -> Result<(), PeelErr> {
57	let tag_ok: bool = tag.ct_eq(&PAYLOAD_TAG).into();
58	if tag_ok {
59		Ok(())
60	} else {
61		Err(PeelErr::PayloadTag)
62	}
63}
64
65/// Attempt to peel a layer off `packet` using `kx_shared_secret`. `kx_shared_secret` should be
66/// derived from [`kx_public(packet)`](kx_public) and this node's secret key.
67pub fn peel(
68	out: &mut Packet,
69	packet: &Packet,
70	kx_shared_secret: &SharedSecret,
71) -> Result<Action, PeelErr> {
72	// (kx_public, mac, actions, payload) correspond to (alpha, gamma, beta, delta) in the Sphinx
73	// paper
74	let (kx_public, mac, actions, payload) =
75		array_refs![packet, KX_PUBLIC_SIZE, MAC_SIZE, ACTIONS_SIZE, PAYLOAD_SIZE];
76
77	let sds = SmallDerivedSecrets::new(kx_shared_secret);
78
79	// Verify the MAC
80	if !mac_ok(mac, actions, sds.mac_key()) {
81		return Err(PeelErr::Mac)
82	}
83
84	// Decrypt the routing actions and generate padding for length invariance. Try to get the
85	// decrypted actions in the right place in the output. The most likely case is that we will be
86	// forwarding the packet to a mixnode, so assume this. We could save some work in the deliver
87	// case by decrypting just the first few bytes to start with to see if we need to decrypt the
88	// rest. This would complicate things and as the forward case is much more common it might
89	// ultimately not make things any faster, so don't bother for now.
90	let decrypted_actions =
91		array_mut_ref![out, KX_PUBLIC_SIZE - RAW_ACTION_SIZE, ACTIONS_SIZE + MAX_ACTIONS_PAD_SIZE];
92	*array_mut_ref![decrypted_actions, 0, ACTIONS_SIZE] = *actions;
93	*array_mut_ref![decrypted_actions, ACTIONS_SIZE, MAX_ACTIONS_PAD_SIZE] =
94		[0; MAX_ACTIONS_PAD_SIZE]; // Padding is generated by encrypting zeroes
95	apply_actions_encryption_keystream(decrypted_actions, sds.actions_encryption_key());
96
97	let raw_action = RawAction::from_le_bytes(*array_ref![decrypted_actions, 0, RAW_ACTION_SIZE]);
98	Ok(match raw_action {
99		RAW_ACTION_DELIVER_REQUEST => {
100			// Peel off the final layer of payload encryption
101			let out = array_mut_ref![out, 0, PAYLOAD_SIZE];
102			*out = *payload;
103			decrypt_payload(out, &derive_payload_encryption_key(kx_shared_secret));
104
105			check_payload_tag(array_ref![out, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE])?;
106
107			Action::DeliverRequest
108		},
109		RAW_ACTION_DELIVER_REPLY => {
110			// Pull the SURB ID out
111			let surb_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, SURB_ID_SIZE];
112
113			// Copy the payload across but don't do anything with it yet; the caller will need to
114			// fetch the keys corresponding to the SURB ID and then call decrypt_reply_payload()
115			*array_mut_ref![out, 0, PAYLOAD_SIZE] = *payload;
116
117			Action::DeliverReply { surb_id }
118		},
119		RAW_ACTION_DELIVER_COVER => Action::DeliverCover { cover_id: None },
120		RAW_ACTION_DELIVER_COVER_WITH_ID => {
121			// Pull the cover ID out
122			let cover_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, COVER_ID_SIZE];
123
124			Action::DeliverCover { cover_id: Some(cover_id) }
125		},
126		_ => {
127			// Forward. Determine target...
128			let target = if raw_action == RAW_ACTION_FORWARD_TO_PEER_ID {
129				// Copy out peer ID and move rest down
130				let peer_id = *array_ref![decrypted_actions, RAW_ACTION_SIZE, PEER_ID_SIZE];
131				decrypted_actions.copy_within(
132					RAW_ACTION_SIZE + PEER_ID_SIZE..
133						RAW_ACTION_SIZE + PEER_ID_SIZE + MAC_SIZE + ACTIONS_SIZE,
134					RAW_ACTION_SIZE,
135				);
136				Target::PeerId(peer_id)
137			} else {
138				Target::MixnodeIndex(raw_action.try_into().map_err(|_| PeelErr::Action)?)
139			};
140
141			// Determine the forwarding delay
142			let delay = Delay::exp(sds.delay_seed());
143
144			// Blind the key-exchange public key
145			*array_mut_ref![out, 0, KX_PUBLIC_SIZE] = blind_kx_public(kx_public, kx_shared_secret);
146
147			// The next MAC and routing actions are already in the right place in out
148
149			// Peel off one layer of payload encryption
150			let out_payload = array_mut_ref![out, HEADER_SIZE, PAYLOAD_SIZE];
151			*out_payload = *payload;
152			decrypt_payload(out_payload, &derive_payload_encryption_key(kx_shared_secret));
153
154			Action::ForwardTo { target, delay }
155		},
156	})
157}
158
159/// Decrypts a reply payload given the encryption keys of the corresponding SURB. On success, the
160/// payload data is left in `payload[..PAYLOAD_DATA_SIZE]`.
161pub fn decrypt_reply_payload(
162	payload: &mut Payload,
163	keys: &SurbPayloadEncryptionKeys,
164) -> Result<(), PeelErr> {
165	for key in keys.iter().rev() {
166		encrypt_payload(payload, key);
167	}
168
169	check_payload_tag(array_ref![payload, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE])
170}