mixnet/core/sphinx/
crypto.rs1use 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
49pub const SHARED_SECRET_SIZE: usize = 32;
51pub type SharedSecret = [u8; SHARED_SECRET_SIZE];
53
54pub type KxSecret = [u8; 32];
60
61pub 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
68pub fn derive_kx_public(kx_secret: &KxSecret) -> KxPublic {
70 MontgomeryPoint::mul_base_clamped(*kx_secret).to_bytes()
71}
72
73fn 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
82pub 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
93pub 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 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
123fn derive_secret(derived: &mut [u8], shared_secret: &SharedSecret, personal: &[u8; 16]) {
128 for (i, chunk) in derived.chunks_mut(64).enumerate() {
129 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
183pub 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
200pub fn apply_actions_encryption_keystream(data: &mut [u8], key: &ActionsEncryptionKey) {
205 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
216pub 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}