mixnet/core/sphinx/
build.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 building.
22
23use super::{
24	crypto::*,
25	delay::Delay,
26	packet::*,
27	target::{MixnodeIndex, Target},
28};
29use arrayref::{array_mut_ref, array_refs, mut_array_refs};
30use arrayvec::ArrayVec;
31use rand::{CryptoRng, Rng};
32
33fn mut_arr_at<T, const N: usize>(slice: &mut [T], offset: usize) -> &mut [T; N] {
34	(&mut slice[offset..offset + N])
35		.try_into()
36		.expect("Slice length is fixed and matches array length")
37}
38
39enum PacketKind<'a> {
40	Request,
41	Reply(&'a SurbId),
42	Cover(Option<&'a CoverId>),
43}
44
45/// Build a Sphinx header. `targets` should not include the first hop. At most one target may be a
46/// peer ID; all others should be mixnode indices. Returns the total forwarding delay across all
47/// hops.
48fn build_header(
49	header: &mut Header,
50	kx_shared_secrets: &mut ArrayVec<SharedSecret, MAX_HOPS>,
51	rng: &mut (impl Rng + CryptoRng),
52	targets: &[Target],
53	their_kx_publics: &[KxPublic],
54	kind: PacketKind,
55) -> Delay {
56	debug_assert_eq!(targets.len() + 1, their_kx_publics.len());
57	debug_assert!(their_kx_publics.len() <= MAX_HOPS);
58
59	let (kx_public, mac_plus_actions) =
60		mut_array_refs![header, KX_PUBLIC_SIZE, MAC_SIZE + ACTIONS_SIZE];
61
62	gen_kx_public_and_shared_secrets(kx_public, kx_shared_secrets, rng, their_kx_publics);
63
64	// Routing actions, and current write offset into
65	let actions = array_mut_ref![mac_plus_actions, MAC_SIZE, ACTIONS_SIZE];
66	let mut offset = 0;
67
68	// Total forwarding delay across all hops
69	let mut total_delay = Delay::zero();
70
71	// We loop over the hops forward and then backward. Data that is generated by the first pass
72	// for the second pass is stashed here. The last hop is handled specially so is excluded here.
73	struct Hop {
74		mac_key: MacKey,
75		keystream: [u8; ACTIONS_SIZE + MAX_ACTIONS_PAD_SIZE],
76		start_offset: u16, // Starting offset of hop in actions
77	}
78	let mut hops: ArrayVec<Hop, { MAX_HOPS - 1 }> = ArrayVec::new();
79
80	// Padding for length invariance, generated from the keystreams. This is only needed for
81	// computing the MACs.
82	let mut pad = [0; ACTIONS_SIZE - RAW_ACTION_SIZE];
83
84	// Loop over hops forward (excluding the last hop)
85	for (target, kx_shared_secret) in targets.iter().zip(kx_shared_secrets.iter()) {
86		// Write forward action
87		let start_offset = offset;
88		offset += RAW_ACTION_SIZE;
89		let raw_action = match target {
90			Target::MixnodeIndex(mixnode_index) => mixnode_index.get(),
91			Target::PeerId(peer_id) => {
92				*mut_arr_at(actions, offset) = *peer_id;
93				offset += PEER_ID_SIZE;
94				RAW_ACTION_FORWARD_TO_PEER_ID
95			},
96		};
97		*mut_arr_at(actions, start_offset) = raw_action.to_le_bytes();
98
99		// The MAC for the next hop can't be computed yet. Leave a gap for it. Note that this is
100		// always the last part of the action; this is assumed by the backward loop.
101		offset += MAC_SIZE;
102
103		let sds = SmallDerivedSecrets::new(kx_shared_secret);
104
105		total_delay += Delay::exp(sds.delay_seed());
106
107		hops.push(Hop {
108			mac_key: *sds.mac_key(),
109			keystream: [0; ACTIONS_SIZE + MAX_ACTIONS_PAD_SIZE],
110			start_offset: start_offset as u16,
111		});
112		let keystream = &mut hops.last_mut().expect("Just pushed, so not empty").keystream;
113		apply_actions_encryption_keystream(keystream, sds.actions_encryption_key());
114
115		// At the end of the loop, pad will contain the padding as seen by the last hop (before
116		// decryption)
117		apply_keystream(&mut pad[..offset], &keystream[ACTIONS_SIZE - start_offset..]);
118	}
119
120	// Handle the last hop
121	{
122		// Write deliver action
123		let start_offset = offset;
124		offset += RAW_ACTION_SIZE;
125		let raw_action = match kind {
126			PacketKind::Request => RAW_ACTION_DELIVER_REQUEST,
127			PacketKind::Reply(surb_id) => {
128				*mut_arr_at(actions, offset) = *surb_id;
129				offset += SURB_ID_SIZE;
130				RAW_ACTION_DELIVER_REPLY
131			},
132			PacketKind::Cover(None) => RAW_ACTION_DELIVER_COVER,
133			PacketKind::Cover(Some(cover_id)) => {
134				*mut_arr_at(actions, offset) = *cover_id;
135				offset += COVER_ID_SIZE;
136				RAW_ACTION_DELIVER_COVER_WITH_ID
137			},
138		};
139		*mut_arr_at(actions, start_offset) = raw_action.to_le_bytes();
140
141		// Fill the remainder of the routing actions field with random bytes, so the last hop
142		// cannot determine the path length
143		rng.fill_bytes(&mut actions[offset..]);
144
145		let sds =
146			SmallDerivedSecrets::new(kx_shared_secrets.last().expect("There is at least one hop"));
147
148		// Encrypt the deliver action (and the random bytes, although this isn't really necessary).
149		// Note that the padding is not touched here; it is generated entirely from the keystreams
150		// for earlier hops, and effectively gets scrambled even further when the last hop
151		// "decrypts" it.
152		apply_actions_encryption_keystream(
153			&mut actions[start_offset..],
154			sds.actions_encryption_key(),
155		);
156
157		// Compute the MAC for the last hop and place it in the appropriate place (right before the
158		// deliver action)
159		*mut_arr_at(mac_plus_actions, start_offset) =
160			compute_mac(&actions[start_offset..], &pad[..start_offset], sds.mac_key());
161	}
162
163	// Loop over hops backward (excluding the last hop, which has already been handled)
164	for hop in hops.iter().rev() {
165		let start_offset = hop.start_offset as usize;
166
167		// Encrypt the actions and padding for the hop
168		apply_keystream(&mut mac_plus_actions[MAC_SIZE + start_offset..], &hop.keystream);
169		apply_keystream(&mut pad[..start_offset], &hop.keystream[ACTIONS_SIZE - start_offset..]);
170
171		// Compute the MAC for the hop and place it in the appropriate place (right before the hop
172		// action)
173		*mut_arr_at(mac_plus_actions, start_offset) = compute_mac(
174			&mac_plus_actions[MAC_SIZE + start_offset..],
175			&pad[..start_offset],
176			&hop.mac_key,
177		);
178	}
179
180	total_delay
181}
182
183/// Returns a mutable reference to the payload data in `packet`. This is only really useful for
184/// filling in the payload data prior to calling [`complete_request_packet`] or
185/// [`complete_reply_packet`].
186pub fn mut_payload_data(packet: &mut Packet) -> &mut PayloadData {
187	array_mut_ref![packet, HEADER_SIZE, PAYLOAD_DATA_SIZE]
188}
189
190/// Complete a Sphinx request packet. The unencrypted payload data should be written to
191/// [`mut_payload_data(packet)`](mut_payload_data) before calling this function. `targets` should
192/// not include the first hop. At most one target may be a peer ID; all others should be mixnode
193/// indices. Returns the total forwarding delay across all hops.
194pub fn complete_request_packet(
195	packet: &mut Packet,
196	rng: &mut (impl Rng + CryptoRng),
197	targets: &[Target],
198	their_kx_publics: &[KxPublic],
199) -> Delay {
200	debug_assert_eq!(targets.len() + 1, their_kx_publics.len());
201	debug_assert!(their_kx_publics.len() <= MAX_HOPS);
202
203	let (header, payload) = mut_array_refs![packet, HEADER_SIZE, PAYLOAD_SIZE];
204
205	// Build the header
206	let mut kx_shared_secrets = ArrayVec::new();
207	let total_delay = build_header(
208		header,
209		&mut kx_shared_secrets,
210		rng,
211		targets,
212		their_kx_publics,
213		PacketKind::Request,
214	);
215
216	// Force the payload tag
217	*array_mut_ref![payload, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE] = PAYLOAD_TAG;
218
219	// Encrypt the payload
220	for kx_shared_secret in kx_shared_secrets.iter().rev() {
221		encrypt_payload(payload, &derive_payload_encryption_key(kx_shared_secret));
222	}
223
224	total_delay
225}
226
227/// Size in bytes of a [`Surb`].
228pub const SURB_SIZE: usize = RAW_MIXNODE_INDEX_SIZE + HEADER_SIZE + SHARED_SECRET_SIZE;
229/// A "single-use reply block". This should be treated as an opaque type.
230pub type Surb = [u8; SURB_SIZE];
231
232pub type SurbPayloadEncryptionKeys = ArrayVec<PayloadEncryptionKey, MAX_HOPS>;
233
234/// Build a SURB. Note that unlike in the Sphinx paper, the last hop (which should be this node)
235/// decrypts the payload, rather than adding another layer of encryption and forwarding to the
236/// "destination". So the number of payload encryption keys matches the number of hops. The first
237/// hop must have a mixnode index, specified by `first_mixnode_index`. `targets` specifies the
238/// remaining hops. At most one target may be a peer ID; all others should be mixnode indices.
239/// Returns the total forwarding delay across all hops.
240pub fn build_surb(
241	surb: &mut Surb,
242	payload_encryption_keys: &mut SurbPayloadEncryptionKeys,
243	rng: &mut (impl Rng + CryptoRng),
244	first_mixnode_index: MixnodeIndex,
245	targets: &[Target],
246	their_kx_publics: &[KxPublic],
247	id: &SurbId,
248) -> Delay {
249	debug_assert_eq!(targets.len() + 1, their_kx_publics.len());
250	debug_assert!(their_kx_publics.len() <= MAX_HOPS);
251
252	let (raw_first_mixnode_index, header, shared_secret) =
253		mut_array_refs![surb, RAW_MIXNODE_INDEX_SIZE, HEADER_SIZE, SHARED_SECRET_SIZE];
254
255	*raw_first_mixnode_index = first_mixnode_index.get().to_le_bytes();
256
257	// Build the header
258	let mut kx_shared_secrets = ArrayVec::new();
259	let total_delay = build_header(
260		header,
261		&mut kx_shared_secrets,
262		rng,
263		targets,
264		their_kx_publics,
265		PacketKind::Reply(id),
266	);
267
268	// Generate the payload encryption keys. The first key is derived from a totally random shared
269	// secret, the rest are derived from the key-exchange shared secrets. Note that we _could_ just
270	// return the shared secrets here and derive the encryption keys in decrypt_reply_payload. This
271	// wouldn't save much time/space though, and it seems better from a security perspective to not
272	// keep the shared secrets around.
273	rng.fill_bytes(shared_secret);
274	payload_encryption_keys.push(derive_payload_encryption_key(shared_secret));
275	kx_shared_secrets.pop(); // Last hop does not encrypt
276	for kx_shared_secret in &kx_shared_secrets {
277		payload_encryption_keys.push(derive_payload_encryption_key(kx_shared_secret));
278	}
279
280	total_delay
281}
282
283/// Complete a Sphinx reply packet. The unencrypted payload data should be written to
284/// [`mut_payload_data(packet)`](mut_payload_data) before calling this function. `surb` should be a
285/// SURB built by the receiving node using [`build_surb`]. The mixnode index of the first hop is
286/// returned. Will only return [`None`] if the SURB is malformed.
287pub fn complete_reply_packet(packet: &mut Packet, surb: &Surb) -> Option<MixnodeIndex> {
288	let (header, payload) = mut_array_refs![packet, HEADER_SIZE, PAYLOAD_SIZE];
289	let (raw_first_mixnode_index, surb_header, shared_secret) =
290		array_refs![surb, RAW_MIXNODE_INDEX_SIZE, HEADER_SIZE, SHARED_SECRET_SIZE];
291
292	// Copy the header from the SURB across as-is. We can't really check it; we just have to trust
293	// it.
294	*header = *surb_header;
295
296	// Force the payload tag
297	*array_mut_ref![payload, PAYLOAD_DATA_SIZE, PAYLOAD_TAG_SIZE] = PAYLOAD_TAG;
298
299	// Encrypt the payload. Actually "decrypt" to make decrypt_reply_payload slightly simpler.
300	decrypt_payload(payload, &derive_payload_encryption_key(shared_secret));
301
302	// Return the mixnode index of the first hop from the SURB
303	let raw_first_mixnode_index = RawMixnodeIndex::from_le_bytes(*raw_first_mixnode_index);
304	raw_first_mixnode_index.try_into().ok()
305}
306
307/// Build a Sphinx cover packet. `targets` should not include the first hop. At most one target may
308/// be a peer ID; all others should be mixnode indices. Returns the total forwarding delay across
309/// all hops.
310pub fn build_cover_packet(
311	packet: &mut Packet,
312	rng: &mut (impl Rng + CryptoRng),
313	targets: &[Target],
314	their_kx_publics: &[KxPublic],
315	id: Option<&CoverId>,
316) -> Delay {
317	debug_assert_eq!(targets.len() + 1, their_kx_publics.len());
318	debug_assert!(their_kx_publics.len() <= MAX_HOPS);
319
320	let (header, payload) = mut_array_refs![packet, HEADER_SIZE, PAYLOAD_SIZE];
321
322	// Build the header
323	let mut kx_shared_secrets = ArrayVec::new();
324	let total_delay = build_header(
325		header,
326		&mut kx_shared_secrets,
327		rng,
328		targets,
329		their_kx_publics,
330		PacketKind::Cover(id),
331	);
332
333	// Randomise the payload. It will be ignored by the destination, but needs to be
334	// indistinguishable from a normal encrypted payload.
335	rng.fill_bytes(payload);
336
337	total_delay
338}