cardano_serialization_lib/
emip3.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use super::error::JsError;
use super::*;

use cryptoxide::chacha20poly1305::ChaCha20Poly1305;
use cryptoxide::hmac::Hmac;
use cryptoxide::pbkdf2::pbkdf2;
use cryptoxide::sha2::Sha512;
use hex::ToHex;

use std::iter::repeat;

// taken from js-cardano-wasm

mod password_encryption_parameter {
    pub const ITER: u32 = 19_162;
    pub const SALT_SIZE: usize = 32;
    pub const NONCE_SIZE: usize = 12;
    pub const KEY_SIZE: usize = 32;
    pub const TAG_SIZE: usize = 16;

    pub const METADATA_SIZE: usize = SALT_SIZE + NONCE_SIZE + TAG_SIZE;

    pub const SALT_START: usize = 0;
    pub const SALT_END: usize = SALT_START + SALT_SIZE;
    pub const NONCE_START: usize = SALT_END;
    pub const NONCE_END: usize = NONCE_START + NONCE_SIZE;
    pub const TAG_START: usize = NONCE_END;
    pub const TAG_END: usize = TAG_START + TAG_SIZE;
    pub const ENCRYPTED_START: usize = TAG_END;
}

#[wasm_bindgen]
pub fn encrypt_with_password(
    password: &str,
    salt: &str,
    nonce: &str,
    data: &str,
) -> Result<String, JsError> {
    use password_encryption_parameter::*;

    let password = hex::decode(password).map_err(|e| JsError::from_str(&e.to_string()))?;
    let salt = hex::decode(salt).map_err(|e| JsError::from_str(&e.to_string()))?;
    let nonce = hex::decode(nonce).map_err(|e| JsError::from_str(&e.to_string()))?;
    let data = hex::decode(data).map_err(|e| JsError::from_str(&e.to_string()))?;

    if salt.len() != SALT_SIZE {
        return Err(JsError::from_str(&format!(
            "salt len must be {}, found {} bytes",
            SALT_SIZE,
            salt.len()
        )));
    }
    if nonce.len() != NONCE_SIZE {
        return Err(JsError::from_str(&format!(
            "nonce len must be {}, found {} bytes",
            NONCE_SIZE,
            nonce.len()
        )));
    }
    if password.len() == 0 {
        return Err(JsError::from_str("Password len cannot be 0"));
    }

    let key = {
        let mut mac = Hmac::new(Sha512::new(), &password);
        let mut key: Vec<u8> = repeat(0).take(KEY_SIZE).collect();
        pbkdf2(&mut mac, &salt[..], ITER, &mut key);
        key
    };

    let mut tag = [0; TAG_SIZE];
    let mut encrypted: Vec<u8> = repeat(0).take(data.len()).collect();
    {
        ChaCha20Poly1305::new(&key, &nonce, &[]).encrypt(&data, &mut encrypted, &mut tag);
    }

    let mut output = Vec::with_capacity(data.len() + METADATA_SIZE);
    output.extend_from_slice(&salt);
    output.extend_from_slice(&nonce);
    output.extend_from_slice(&tag);
    output.extend_from_slice(&encrypted);

    Ok(output.encode_hex::<String>())
}

#[wasm_bindgen]
pub fn decrypt_with_password(password: &str, data: &str) -> Result<String, JsError> {
    use password_encryption_parameter::*;
    let password = hex::decode(password).map_err(|e| JsError::from_str(&e.to_string()))?;
    let data = hex::decode(data).map_err(|e| JsError::from_str(&e.to_string()))?;

    if data.len() <= METADATA_SIZE {
        // not enough input to decrypt.
        return Err(JsError::from_str("Missing input data"));
    }

    let salt = &data[SALT_START..SALT_END];
    let nonce = &data[NONCE_START..NONCE_END];
    let tag = &data[TAG_START..TAG_END];
    let encrypted = &data[ENCRYPTED_START..];

    let key = {
        let mut mac = Hmac::new(Sha512::new(), &password);
        let mut key: Vec<u8> = repeat(0).take(KEY_SIZE).collect();
        pbkdf2(&mut mac, &salt[..], ITER, &mut key);
        key
    };

    let mut decrypted: Vec<u8> = repeat(0).take(encrypted.len()).collect();
    let decryption_succeed =
        { ChaCha20Poly1305::new(&key, &nonce, &[]).decrypt(&encrypted, &mut decrypted, &tag) };

    if decryption_succeed {
        Ok(decrypted.encode_hex::<String>())
    } else {
        Err(JsError::from_str("Decryption error"))
    }
}