mixnet/core/sphinx/
crypto.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//! Key exchange, secret derivation, MAC computation, and encryption.
22
23use super::{
24	delay::{DelaySeed, DELAY_SEED_SIZE},
25	packet::{Actions, KxPublic, Mac, Payload, MAX_HOPS},
26};
27use arrayref::array_refs;
28use arrayvec::ArrayVec;
29use blake2::{
30	digest::{
31		consts::{U16, U32, U64},
32		generic_array::{sequence::Concat, GenericArray},
33		FixedOutput, Mac as DigestMac,
34	},
35	Blake2bMac,
36};
37use c2_chacha::{
38	stream_cipher::{NewStreamCipher, SyncStreamCipher},
39	ChaCha20,
40};
41use curve25519_dalek::{scalar::clamp_integer, MontgomeryPoint, Scalar};
42use lioness::LionessDefault;
43use rand::{CryptoRng, Rng};
44
45const KX_BLINDING_FACTOR_PERSONAL: &[u8; 16] = b"sphinx-blind-fac";
46const SMALL_DERIVED_SECRETS_PERSONAL: &[u8; 16] = b"sphinx-small-d-s";
47const PAYLOAD_ENCRYPTION_KEY_PERSONAL: &[u8; 16] = b"sphinx-pl-en-key";
48
49/// Size in bytes of a [`SharedSecret`].
50pub const SHARED_SECRET_SIZE: usize = 32;
51/// Either produced by key exchange or shared in a SURB.
52pub type SharedSecret = [u8; SHARED_SECRET_SIZE];
53
54////////////////////////////////////////////////////////////////////////////////
55// Key exchange
56////////////////////////////////////////////////////////////////////////////////
57
58/// An _unclamped_ key-exchange secret key.
59pub type KxSecret = [u8; 32];
60
61/// Generate an _unclamped_ key-exchange secret key.
62pub fn gen_kx_secret(rng: &mut (impl Rng + CryptoRng)) -> KxSecret {
63	let mut secret = [0; 32];
64	rng.fill_bytes(&mut secret);
65	secret
66}
67
68/// Derive the public key corresponding to a secret key.
69pub fn derive_kx_public(kx_secret: &KxSecret) -> KxPublic {
70	MontgomeryPoint::mul_base_clamped(*kx_secret).to_bytes()
71}
72
73/// Returns the _unclamped_ blinding factor.
74fn derive_kx_blinding_factor(kx_public: &KxPublic, kx_shared_secret: &SharedSecret) -> [u8; 32] {
75	let kx_public: &GenericArray<_, _> = kx_public.into();
76	let key = kx_public.concat((*kx_shared_secret).into());
77	let h = Blake2bMac::<U32>::new_with_salt_and_personal(&key, b"", KX_BLINDING_FACTOR_PERSONAL)
78		.expect("Key, salt, and personalisation sizes are fixed and small enough");
79	h.finalize().into_bytes().into()
80}
81
82/// Apply the blinding factor to `kx_public`.
83pub fn blind_kx_public(kx_public: &KxPublic, kx_shared_secret: &SharedSecret) -> KxPublic {
84	MontgomeryPoint(*kx_public)
85		.mul_clamped(derive_kx_blinding_factor(kx_public, kx_shared_secret))
86		.to_bytes()
87}
88
89pub fn derive_kx_shared_secret(kx_public: &KxPublic, kx_secret: &KxSecret) -> SharedSecret {
90	MontgomeryPoint(*kx_public).mul_clamped(*kx_secret).to_bytes()
91}
92
93/// Generate a public key to go in a packet and the corresponding shared secrets for each hop.
94pub fn gen_kx_public_and_shared_secrets(
95	kx_public: &mut KxPublic,
96	kx_shared_secrets: &mut ArrayVec<SharedSecret, MAX_HOPS>,
97	rng: &mut (impl Rng + CryptoRng),
98	their_kx_publics: &[KxPublic],
99) {
100	let kx_secret = gen_kx_secret(rng);
101	*kx_public = derive_kx_public(&kx_secret);
102
103	let mut kx_secret = Scalar::from_bytes_mod_order(clamp_integer(kx_secret));
104	let mut kx_public = *kx_public;
105	for (i, their_kx_public) in their_kx_publics.iter().enumerate() {
106		if i != 0 {
107			if i != 1 {
108				// An alternative would be to use blind_kx_public, but this is much cheaper
109				kx_public = MontgomeryPoint::mul_base(&kx_secret).to_bytes();
110			}
111			let kx_shared_secret = kx_shared_secrets.last().expect(
112				"On at least second iteration of loop, shared secret pushed every iteration",
113			);
114			kx_secret *= Scalar::from_bytes_mod_order(clamp_integer(derive_kx_blinding_factor(
115				&kx_public,
116				kx_shared_secret,
117			)));
118		}
119		kx_shared_secrets.push((MontgomeryPoint(*their_kx_public) * kx_secret).to_bytes());
120	}
121}
122
123////////////////////////////////////////////////////////////////////////////////
124// Additional secret derivation
125////////////////////////////////////////////////////////////////////////////////
126
127fn derive_secret(derived: &mut [u8], shared_secret: &SharedSecret, personal: &[u8; 16]) {
128	for (i, chunk) in derived.chunks_mut(64).enumerate() {
129		// This is the construction libsodium uses for crypto_kdf_derive_from_key; see
130		// https://doc.libsodium.org/key_derivation/
131		let h = Blake2bMac::<U64>::new_with_salt_and_personal(
132			shared_secret,
133			&i.to_le_bytes(),
134			personal,
135		)
136		.expect("Key, salt, and personalisation sizes are fixed and small enough");
137		h.finalize_into(GenericArray::from_mut_slice(chunk));
138	}
139}
140
141const MAC_KEY_SIZE: usize = 16;
142pub type MacKey = [u8; MAC_KEY_SIZE];
143const ACTIONS_ENCRYPTION_KEY_SIZE: usize = 32;
144pub type ActionsEncryptionKey = [u8; ACTIONS_ENCRYPTION_KEY_SIZE];
145const SMALL_DERIVED_SECRETS_SIZE: usize =
146	MAC_KEY_SIZE + ACTIONS_ENCRYPTION_KEY_SIZE + DELAY_SEED_SIZE;
147
148pub struct SmallDerivedSecrets([u8; SMALL_DERIVED_SECRETS_SIZE]);
149
150impl SmallDerivedSecrets {
151	pub fn new(shared_secret: &SharedSecret) -> Self {
152		let mut derived = [0; SMALL_DERIVED_SECRETS_SIZE];
153		derive_secret(&mut derived, shared_secret, SMALL_DERIVED_SECRETS_PERSONAL);
154		Self(derived)
155	}
156
157	fn split(&self) -> (&MacKey, &ActionsEncryptionKey, &DelaySeed) {
158		array_refs![&self.0, MAC_KEY_SIZE, ACTIONS_ENCRYPTION_KEY_SIZE, DELAY_SEED_SIZE]
159	}
160
161	pub fn mac_key(&self) -> &MacKey {
162		self.split().0
163	}
164
165	pub fn actions_encryption_key(&self) -> &ActionsEncryptionKey {
166		self.split().1
167	}
168
169	pub fn delay_seed(&self) -> &DelaySeed {
170		self.split().2
171	}
172}
173
174pub const PAYLOAD_ENCRYPTION_KEY_SIZE: usize = 192;
175pub type PayloadEncryptionKey = [u8; PAYLOAD_ENCRYPTION_KEY_SIZE];
176
177pub fn derive_payload_encryption_key(shared_secret: &SharedSecret) -> PayloadEncryptionKey {
178	let mut derived = [0; PAYLOAD_ENCRYPTION_KEY_SIZE];
179	derive_secret(&mut derived, shared_secret, PAYLOAD_ENCRYPTION_KEY_PERSONAL);
180	derived
181}
182
183////////////////////////////////////////////////////////////////////////////////
184// MAC computation
185////////////////////////////////////////////////////////////////////////////////
186
187pub fn compute_mac(actions: &[u8], pad: &[u8], key: &MacKey) -> Mac {
188	let mut h = Blake2bMac::<U16>::new_from_slice(key).expect("Key size is fixed and small enough");
189	h.update(actions);
190	h.update(pad);
191	h.finalize().into_bytes().into()
192}
193
194pub fn mac_ok(mac: &Mac, actions: &Actions, key: &MacKey) -> bool {
195	let mut h = Blake2bMac::<U16>::new_from_slice(key).expect("Key size is fixed and small enough");
196	h.update(actions);
197	h.verify(mac.into()).is_ok()
198}
199
200////////////////////////////////////////////////////////////////////////////////
201// Actions encryption
202////////////////////////////////////////////////////////////////////////////////
203
204pub fn apply_actions_encryption_keystream(data: &mut [u8], key: &ActionsEncryptionKey) {
205	// Key is only used once, so fine for nonce to be 0
206	let mut c = ChaCha20::new(key.into(), &[0; 8].into());
207	c.apply_keystream(data);
208}
209
210pub fn apply_keystream(data: &mut [u8], keystream: &[u8]) {
211	for (d, k) in data.iter_mut().zip(keystream) {
212		*d ^= *k;
213	}
214}
215
216////////////////////////////////////////////////////////////////////////////////
217// Payload encryption
218////////////////////////////////////////////////////////////////////////////////
219
220pub fn encrypt_payload(payload: &mut Payload, key: &PayloadEncryptionKey) {
221	let l = LionessDefault::new_raw(key);
222	l.encrypt(payload).expect("Payload size is fixed and large enough");
223}
224
225pub fn decrypt_payload(payload: &mut Payload, key: &PayloadEncryptionKey) {
226	let l = LionessDefault::new_raw(key);
227	l.decrypt(payload).expect("Payload size is fixed and large enough");
228}