1use crate::{
19 keys::{
20 ed25519::PublicKey,
21 x25519::{diffie_hellman, X25519PublicKey, X25519SecretKey},
22 KeyPair,
23 },
24 rand::{self, UnspecifiedRandError},
25};
26use arrayvec::{ArrayString, ArrayVec};
27use std::convert::TryInto;
28use thiserror::Error;
29
30const MAGIC_VALUE: [u8; 2] = [0x27, 0xb6];
31const MAX_ENCRYPTED_LEN: usize = MAX_MSG_LEN + 1;
32const MAX_B64_LEN: usize = 255;
33const MAX_LEN: usize = MAX_MSG_LEN + META_LEN + 1;
34const MAX_MSG_LEN: usize = 170;
35const META_LEN: usize = 4 + NONCE_LEN;
36const NONCE_LEN: usize = 16;
37const V1: u8 = 1;
38
39type Nonce = [u8; NONCE_LEN];
40
41#[derive(Clone, Debug, Error)]
43pub enum DecryptTxCommentErr {
44 #[error("Invalid base 64 encoding: {0}")]
46 Base64Err(base64::DecodeError),
47 #[error("Too short data")]
49 TooShort,
50 #[error("Too long data")]
52 TooLong,
53 #[error("malicious input")]
55 MaliciousInput,
56 #[error("Invalid utf8 message: {0}")]
58 Utf8Err(std::str::Utf8Error),
59 #[error("Unsupported version")]
61 UnsupportedVersion,
62 #[error("Wrong format")]
64 WrongFormat,
65 #[error("Wrong magic value")]
67 WrongMagicValue,
68}
69
70#[allow(missing_copy_implementations)]
72pub struct SharedKey([u8; 32]);
73
74pub fn compute_shared_key<K: KeyPair>(my_keypair: &K, its_pubkey: &PublicKey) -> SharedKey {
76 let precomputed_key = diffie_hellman(
77 X25519SecretKey::from_bytes(my_keypair.scalar_bytes_without_normalization()),
78 X25519PublicKey::from(its_pubkey),
79 |shared_secret| {
80 let mut buffer = [0; 32];
81 cryptoxide::salsa20::hsalsa20(shared_secret, &[0; 16], &mut buffer);
82 buffer
83 },
84 );
85
86 let mut shared_key = SharedKey([0; 32]);
87 shared_key.0.copy_from_slice(&precomputed_key[..]);
88 shared_key
89}
90
91pub fn get_message_type(
93 encrypted_tx_comment: &ArrayString<MAX_B64_LEN>,
94) -> Result<u8, DecryptTxCommentErr> {
95 let (version, bytes) = verify_magic_value_and_read_version(encrypted_tx_comment)?;
96
97 if version != V1 {
99 return Err(DecryptTxCommentErr::UnsupportedVersion);
100 }
101
102 if bytes.len() <= META_LEN {
104 return Err(DecryptTxCommentErr::TooShort);
105 }
106
107 let message_type = bytes[3];
109
110 Ok(message_type)
111}
112
113pub fn decrypt_tx_comment<K: KeyPair>(
115 my_keypair: &K,
116 other_pubkey: &PublicKey,
117 encrypted_tx_comment: &ArrayString<MAX_B64_LEN>,
118) -> Result<ArrayString<MAX_MSG_LEN>, DecryptTxCommentErr> {
119 let shared_key = compute_shared_key::<K>(my_keypair, other_pubkey);
120 decrypt_tx_comment_with_shared_key(&shared_key, encrypted_tx_comment)
121}
122
123pub fn decrypt_tx_comment_with_shared_key(
125 shared_key: &SharedKey,
126 encrypted_tx_comment: &ArrayString<MAX_B64_LEN>,
127) -> Result<ArrayString<MAX_MSG_LEN>, DecryptTxCommentErr> {
128 let (version, bytes) = verify_magic_value_and_read_version(encrypted_tx_comment)?;
129
130 if version != V1 {
132 return Err(DecryptTxCommentErr::UnsupportedVersion);
133 }
134
135 if bytes.len() <= META_LEN {
137 return Err(DecryptTxCommentErr::TooShort);
138 }
139
140 let mut nonce = [0; NONCE_LEN];
142 nonce.copy_from_slice(&bytes[4..20]);
143
144 let encrypted_data = &bytes[META_LEN..];
146
147 let mut encryption_key = ArrayVec::<u8, MAX_ENCRYPTED_LEN>::new();
149 unsafe {
150 encryption_key.set_len(encrypted_data.len());
151 }
152 crate::scrypt::scrypt(
153 &shared_key.0[..],
154 &nonce[..],
155 &crate::scrypt::params::ScryptParams {
156 log_n: 10,
157 r: 8,
158 p: 1,
159 },
160 &mut encryption_key,
161 );
162
163 let mut decrypted_data = ArrayVec::<u8, MAX_ENCRYPTED_LEN>::new();
165 for i in 0..encrypted_data.len() {
166 decrypted_data.push(encryption_key[i] ^ encrypted_data[i]);
167 }
168
169 read_decrypted_data(decrypted_data.as_ref())
170}
171
172pub fn encrypt_tx_comment_v1<K: KeyPair>(
174 message_type: u8,
175 my_keypair: &K,
176 other_pubkey: &PublicKey,
177 tx_comment: &ArrayString<MAX_MSG_LEN>,
178) -> Result<ArrayString<MAX_B64_LEN>, UnspecifiedRandError> {
179 let message_len = tx_comment.len();
180 let message_len_plus_one = message_len + 1;
181
182 let mut data_to_encrypt = ArrayVec::<u8, MAX_ENCRYPTED_LEN>::new();
184 data_to_encrypt.push(message_len as u8);
185 data_to_encrypt
186 .try_extend_from_slice(tx_comment.as_bytes())
187 .unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() });
188
189 let extra_bytes_len = rand::gen_u8()? % (MAX_MSG_LEN - message_len) as u8;
191 let data_to_encrypt_len = message_len_plus_one + extra_bytes_len as usize;
192 unsafe {
193 data_to_encrypt.set_len(data_to_encrypt_len);
194 }
195 rand::gen_random_bytes(&mut data_to_encrypt[message_len_plus_one..])?;
196
197 let mut bytes = ArrayVec::<u8, MAX_LEN>::new();
199 bytes.push(MAGIC_VALUE[0]); bytes.push(MAGIC_VALUE[1]); bytes.push(V1);
202 bytes.push(message_type); let nonce = gen_nonce()?;
206 unsafe {
207 bytes.set_len(META_LEN);
208 }
209 bytes[4..].copy_from_slice(&nonce[..]);
210
211 let shared_key = compute_shared_key::<K>(my_keypair, other_pubkey);
213 let mut encryption_key = ArrayVec::<u8, MAX_ENCRYPTED_LEN>::new();
214 unsafe {
215 encryption_key.set_len(data_to_encrypt_len);
216 }
217 crate::scrypt::scrypt(
218 &shared_key.0[..],
219 &nonce[..],
220 &crate::scrypt::params::ScryptParams {
221 log_n: 10,
222 r: 8,
223 p: 1,
224 },
225 &mut encryption_key,
226 );
227
228 for i in 0..data_to_encrypt_len {
230 bytes.push(encryption_key[i] ^ data_to_encrypt[i]);
231 }
232
233 let mut b64_str = ArrayString::new();
234 unsafe {
235 b64_str.set_len(MAX_B64_LEN);
236 }
237 let bytes_written = base64::encode_config_slice(bytes, base64::STANDARD_NO_PAD, unsafe {
238 b64_str.as_bytes_mut()
239 });
240 unsafe {
241 b64_str.set_len(bytes_written);
242 }
243
244 Ok(b64_str)
245}
246
247#[cfg(not(test))]
248fn gen_nonce() -> Result<Nonce, UnspecifiedRandError> {
249 let mut nonce = [0u8; NONCE_LEN];
250 crate::rand::gen_random_bytes(&mut nonce[..])?;
251 Ok(nonce)
252}
253#[cfg(test)]
254#[allow(clippy::unnecessary_wraps)]
255fn gen_nonce() -> Result<Nonce, UnspecifiedRandError> {
256 Ok([
257 0x16, 0x74, 0x02, 0x85, 0x72, 0x1a, 0xde, 0xb1, 0x0d, 0xa0, 0x41, 0x06, 0xc9, 0xeb, 0x9e,
258 0x34,
259 ])
260}
261
262fn read_decrypted_data(
263 decrypted_data: &[u8],
264) -> Result<ArrayString<MAX_MSG_LEN>, DecryptTxCommentErr> {
265 let real_message_length = decrypted_data[0] as usize;
267
268 if real_message_length >= decrypted_data.len() {
270 return Err(DecryptTxCommentErr::MaliciousInput);
271 }
272
273 std::str::from_utf8(&decrypted_data[1..=real_message_length])
274 .map_err(DecryptTxCommentErr::Utf8Err)?
275 .try_into()
276 .map_err(|_| DecryptTxCommentErr::TooLong)
277}
278
279fn verify_magic_value_and_read_version(
280 encrypted_tx_comment: &ArrayString<MAX_B64_LEN>,
281) -> Result<(u8, ArrayVec<u8, MAX_LEN>), DecryptTxCommentErr> {
282 let mut bytes = ArrayVec::new();
284 unsafe {
285 bytes.set_len(MAX_LEN);
286 }
287 let bytes_written = base64::decode_config_slice(
288 encrypted_tx_comment.as_bytes(),
289 base64::STANDARD_NO_PAD,
290 &mut bytes,
291 )
292 .map_err(DecryptTxCommentErr::Base64Err)?;
293 unsafe {
294 bytes.set_len(bytes_written);
295 }
296
297 if bytes.len() < 3 {
299 return Err(DecryptTxCommentErr::TooShort);
300 }
301
302 if bytes[0] != MAGIC_VALUE[0] || bytes[1] != MAGIC_VALUE[1] {
304 return Err(DecryptTxCommentErr::WrongMagicValue);
305 }
306
307 Ok((bytes[2], bytes))
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::{
314 bases::b58::ToBase58,
315 keys::{
316 ed25519::{KeyPairFromSeed32Generator, PublicKey},
317 PublicKey as _,
318 },
319 seeds::Seed32,
320 };
321 use unwrap::unwrap;
322
323 #[test]
324 fn test_read_malicious_decrypted_data() {
325 let decrypted_data = &[42, 1];
326
327 assert!(read_decrypted_data(decrypted_data).is_err());
328 }
329
330 #[test]
331 fn test_decrypt_tx_comment() -> Result<(), DecryptTxCommentErr> {
332 let seed_bob = Seed32::new([
334 33, 73, 85, 181, 88, 199, 121, 50, 104, 88, 158, 85, 126, 218, 42, 182, 155, 82, 147,
335 183, 61, 57, 7, 248, 44, 130, 225, 45, 105, 196, 114, 33,
336 ]);
337 println!("seed_bob={}", hex::encode(&seed_bob));
338 let kp_bob = KeyPairFromSeed32Generator::generate(seed_bob);
339 assert_eq!(
340 &kp_bob.public_key().to_base58(),
341 "8txjWNFZhMJbKPijvnFybeksN1QpKaKJrM4jW8HhnFsX"
342 );
343
344 let pk_alice = unwrap!(PublicKey::from_base58(
346 "EVfy1VoZwbuN7L69kYiHxeosJLh5azkHV8G6TaSLy94r"
347 ));
348
349 let shared_key = compute_shared_key(&kp_bob, &pk_alice);
351
352 let encrypted_tx_comment = unwrap!(ArrayString::from("J7YBABZ0AoVyGt6xDaBBBsnrnjREXG3NgZKjIyN2lxEx+jbNmf/tZt6uMaelMGRx48kqEovl70uUU17l1J8FOy9i"));
354
355 let tx_comment = decrypt_tx_comment_with_shared_key(&shared_key, &encrypted_tx_comment)?;
357
358 assert_eq!(
359 tx_comment.as_str(),
360 "My taylor is rich ? Isn't it ? Un été 42..."
361 );
362
363 Ok(())
364 }
365
366 #[test]
367 fn test_encrypt_tx_comment() {
368 let seed_alice = Seed32::new([
370 12, 106, 21, 208, 0, 77, 36, 164, 15, 101, 3, 48, 13, 73, 113, 3, 47, 176, 87, 255,
371 125, 194, 41, 214, 81, 104, 59, 65, 60, 150, 162, 22,
372 ]);
373 println!("seed_alice={}", hex::encode(&seed_alice));
374 let kp_alice = KeyPairFromSeed32Generator::generate(seed_alice);
375 assert_eq!(
376 &kp_alice.public_key().to_base58(),
377 "EVfy1VoZwbuN7L69kYiHxeosJLh5azkHV8G6TaSLy94r"
378 );
379
380 let pk_bob = unwrap!(PublicKey::from_base58(
382 "8txjWNFZhMJbKPijvnFybeksN1QpKaKJrM4jW8HhnFsX"
383 ));
384
385 let tx_comment = unwrap!(ArrayString::from(
387 "My taylor is rich ? Isn't it ? Un été 42..."
388 ));
389 println!("tx_comment_len={}", tx_comment.len());
390
391 let encrypted_comment = unwrap!(encrypt_tx_comment_v1(0, &kp_alice, &pk_bob, &tx_comment));
393 println!("encrypted_comment={}", encrypted_comment);
394 assert_eq!(
395 &encrypted_comment[..88], "J7YBABZ0AoVyGt6xDaBBBsnrnjREXG3NgZKjIyN2lxEx+jbNmf/tZt6uMaelMGRx48kqEovl70uUU17l1J8FOy9i"
397 );
398
399 }
401
402 #[test]
403 fn test_cryptobox_shared_key() {
404 use crate::keys::ed25519::Ed25519KeyPair;
405 use crate::keys::inner::KeyPairInner;
406 use sodiumoxide::crypto::box_;
407 use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::PublicKey as SodiumPublicKey;
408 use sodiumoxide::crypto::box_::curve25519xsalsa20poly1305::SecretKey as SodiumSecretKey;
409
410 let kp1 = unwrap!(Ed25519KeyPair::generate_random());
411 let kp2 = unwrap!(Ed25519KeyPair::generate_random());
412
413 let sk1 = X25519SecretKey::from_bytes(kp1.scalar_bytes_without_normalization());
414 let sk2 = X25519SecretKey::from_bytes(kp2.scalar_bytes_without_normalization());
415 let pk1 = X25519PublicKey::from(&kp1.public_key());
416 let pk2 = X25519PublicKey::from(&kp2.public_key());
417
418 let our_precomputed_key =
419 box_::precompute(&SodiumPublicKey((pk2.0).0), &SodiumSecretKey(sk1.0));
420 let their_precomputed_key =
421 box_::precompute(&SodiumPublicKey((pk1.0).0), &SodiumSecretKey(sk2.0));
422
423 assert_eq!(our_precomputed_key.0, their_precomputed_key.0);
424
425 let shared_key = compute_shared_key(&kp1, &kp2.public_key());
426 assert_eq!(&shared_key.0[..], &their_precomputed_key.0[..]);
427 let shared_key = compute_shared_key(&kp2, &kp1.public_key());
428 assert_eq!(&shared_key.0[..], &their_precomputed_key.0[..]);
429
430 let precomputed_key = diffie_hellman(sk1, pk2, |shared_secret| {
431 let mut buffer = [0; 32];
432 cryptoxide::salsa20::hsalsa20(shared_secret, &[0; 16], &mut buffer);
433 buffer
434 });
435 assert_eq!(&precomputed_key[..], &their_precomputed_key.0[..]);
436 }
437}