dup_crypto/
encrypt_tx_comment.rs

1//  Copyright (C) 2020 Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Encrypt transaction comment
17
18use 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/// Decrypt transaction comment error
42#[derive(Clone, Debug, Error)]
43pub enum DecryptTxCommentErr {
44    /// Invalid base 64 encoding
45    #[error("Invalid base 64 encoding: {0}")]
46    Base64Err(base64::DecodeError),
47    /// Too short
48    #[error("Too short data")]
49    TooShort,
50    /// Too long
51    #[error("Too long data")]
52    TooLong,
53    /// Malicious input
54    #[error("malicious input")]
55    MaliciousInput,
56    /// Invalid utf8 message
57    #[error("Invalid utf8 message: {0}")]
58    Utf8Err(std::str::Utf8Error),
59    /// Unsupported version
60    #[error("Unsupported version")]
61    UnsupportedVersion,
62    /// Wrong format
63    #[error("Wrong format")]
64    WrongFormat,
65    /// Wrong magic value
66    #[error("Wrong magic value")]
67    WrongMagicValue,
68}
69
70/// Shared key
71#[allow(missing_copy_implementations)]
72pub struct SharedKey([u8; 32]);
73
74/// compute shared key
75pub 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
91/// Read message type of an encrypted tx comment
92pub 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    // Verify version
98    if version != V1 {
99        return Err(DecryptTxCommentErr::UnsupportedVersion);
100    }
101
102    // Verify length for meta data
103    if bytes.len() <= META_LEN {
104        return Err(DecryptTxCommentErr::TooShort);
105    }
106
107    // Read message type
108    let message_type = bytes[3];
109
110    Ok(message_type)
111}
112
113/// Decrypt transaction comment
114pub 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
123/// Decrypt transaction comment with specified shared key
124pub 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    // Verify version
131    if version != V1 {
132        return Err(DecryptTxCommentErr::UnsupportedVersion);
133    }
134
135    // Verify length for meta data
136    if bytes.len() <= META_LEN {
137        return Err(DecryptTxCommentErr::TooShort);
138    }
139
140    // Read nonce
141    let mut nonce = [0; NONCE_LEN];
142    nonce.copy_from_slice(&bytes[4..20]);
143
144    // Get encrypted data
145    let encrypted_data = &bytes[META_LEN..];
146
147    // Compute encryption key
148    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    // Decryption
164    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
172/// Encrypt transaction comment
173pub 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    // Prepare data to encrypt
183    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    // Generate random extra bytes in data_to_encrypt directly
190    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    // Write meta data
198    let mut bytes = ArrayVec::<u8, MAX_LEN>::new();
199    bytes.push(MAGIC_VALUE[0]); // prefix
200    bytes.push(MAGIC_VALUE[1]); // prefix
201    bytes.push(V1);
202    bytes.push(message_type); // message type
203
204    // Generate and write nonce
205    let nonce = gen_nonce()?;
206    unsafe {
207        bytes.set_len(META_LEN);
208    }
209    bytes[4..].copy_from_slice(&nonce[..]);
210
211    // Compute encryption key
212    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    // Encryption with XOR cipher
229    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    // Read real message length
266    let real_message_length = decrypted_data[0] as usize;
267
268    // Verify real message length
269    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    // Decode base64
283    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    // Verify length for  magic value and version
298    if bytes.len() < 3 {
299        return Err(DecryptTxCommentErr::TooShort);
300    }
301
302    // Verify magic value
303    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        // Generate bob keypair
333        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        // Get alice pubkey
345        let pk_alice = unwrap!(PublicKey::from_base58(
346            "EVfy1VoZwbuN7L69kYiHxeosJLh5azkHV8G6TaSLy94r"
347        ));
348
349        // Compute thared secret
350        let shared_key = compute_shared_key(&kp_bob, &pk_alice);
351
352        // Get encrypted comment
353        let encrypted_tx_comment = unwrap!(ArrayString::from("J7YBABZ0AoVyGt6xDaBBBsnrnjREXG3NgZKjIyN2lxEx+jbNmf/tZt6uMaelMGRx48kqEovl70uUU17l1J8FOy9i"));
354
355        // Decrypt comment
356        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        // Generate alice keypair
369        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        // Get bob pubkey
381        let pk_bob = unwrap!(PublicKey::from_base58(
382            "8txjWNFZhMJbKPijvnFybeksN1QpKaKJrM4jW8HhnFsX"
383        ));
384
385        // Create tx comment
386        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        // Encrypt comment
392        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], // 88 b64 chars encode 66 octets (21 mate data + 45 comment)
396            "J7YBABZ0AoVyGt6xDaBBBsnrnjREXG3NgZKjIyN2lxEx+jbNmf/tZt6uMaelMGRx48kqEovl70uUU17l1J8FOy9i"
397        );
398
399        //
400    }
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}