Skip to content

Commit e2191d5

Browse files
committed
feat: begin porting crypto
1 parent 11539e9 commit e2191d5

File tree

12 files changed

+1196
-27
lines changed

12 files changed

+1196
-27
lines changed

packages/sdk/lib/Cargo.lock

Lines changed: 171 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sdk/lib/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ namada_tx = { git = "https://github.com/anoma/namada", rev = "49a4a5d3260423df19
2222

2323
[dependencies]
2424
async-trait = {version = "0.1.51"}
25-
tiny-bip39 = "0.8.2"
25+
# tiny-bip39 = "0.8.2"
26+
tiny-bip39 = { git = "https://github.com/anoma/tiny-bip39", rev = "743d537349c8deab14409ce726b868dcde90fd8e" }
2627
chrono = "0.4.22"
2728
getrandom = { version = "0.2.7", features = ["js"] }
2829
gloo-utils = { version = "0.1.5", features = ["serde"] }
@@ -44,6 +45,12 @@ zeroize = "1.6.0"
4445
hex = "0.4.3"
4546
reqwest = "0.11.25"
4647
subtle-encoding = "0.5.1"
48+
aes-gcm = "0.10.1"
49+
argon2 = "0.4.1"
50+
slip10_ed25519 = "0.1.3"
51+
password-hash = "0.3.2"
52+
masp_primitives = { git = "https://github.com/anoma/masp", tag = "v1.1.0" }
53+
borsh-ext = { git = "https://github.com/heliaxdev/borsh-ext", tag = "v1.2.0" }
4754

4855
[dependencies.web-sys]
4956
version = "0.3.4"

packages/sdk/lib/src/crypto/aes.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::crypto::pointer_types::VecU8Pointer;
2+
use aes_gcm::{
3+
aead::{generic_array::GenericArray, Aead, KeyInit},
4+
Aes256Gcm, Nonce,
5+
};
6+
use thiserror::Error;
7+
use wasm_bindgen::prelude::*;
8+
use zeroize::Zeroize;
9+
10+
#[derive(Debug, Error)]
11+
pub enum AESError {
12+
#[error("Invalid key size! Minimum key size is 32.")]
13+
KeyLengthError,
14+
#[error("Invalid IV! Expected 96 bits (12 bytes)")]
15+
IVSizeError,
16+
}
17+
18+
#[wasm_bindgen]
19+
pub struct AES {
20+
cipher: Aes256Gcm,
21+
iv: [u8; 12],
22+
}
23+
24+
#[wasm_bindgen]
25+
impl AES {
26+
#[wasm_bindgen(constructor)]
27+
pub fn new(key: VecU8Pointer, iv: Vec<u8>) -> Result<AES, String> {
28+
if key.length < 32 {
29+
return Err(format!(
30+
"{} Received {}",
31+
AESError::KeyLengthError,
32+
key.length
33+
));
34+
}
35+
let mut key = GenericArray::from_iter(key.vec.clone().into_iter());
36+
let iv: [u8; 12] = match iv.try_into() {
37+
Ok(iv) => iv,
38+
Err(_) => {
39+
key.zeroize();
40+
return Err(AESError::IVSizeError.to_string());
41+
}
42+
};
43+
44+
let aes = AES {
45+
cipher: Aes256Gcm::new(&key),
46+
iv,
47+
};
48+
key.zeroize();
49+
Ok(aes)
50+
}
51+
52+
pub fn encrypt(&self, mut text: String) -> Result<Vec<u8>, String> {
53+
let nonce = Nonce::from_slice(&self.iv);
54+
let result = self
55+
.cipher
56+
.encrypt(nonce, text.as_ref())
57+
.map_err(|err| err.to_string());
58+
text.zeroize();
59+
result
60+
}
61+
62+
pub fn decrypt(&self, ciphertext: Vec<u8>) -> Result<VecU8Pointer, String> {
63+
let nonce = Nonce::from_slice(&self.iv);
64+
let plaintext = self
65+
.cipher
66+
.decrypt(nonce, ciphertext.as_ref())
67+
.map_err(|err| err.to_string())?;
68+
69+
Ok(VecU8Pointer::new(plaintext))
70+
}
71+
}
72+
73+
#[cfg(test)]
74+
mod tests {
75+
use super::*;
76+
use crate::crypto::rng::{ByteSize, Rng};
77+
use wasm_bindgen_test::*;
78+
79+
#[wasm_bindgen_test]
80+
fn can_encrypt_and_decrypt() {
81+
let key = Rng::generate_bytes(Some(ByteSize::N32))
82+
.expect("Generating random bytes should not fail");
83+
let iv = Rng::generate_bytes(Some(ByteSize::N12))
84+
.expect("Generating random bytes should not fail");
85+
let aes = AES::new(VecU8Pointer::new(key), iv).unwrap();
86+
let plaintext = "my secret message";
87+
let encrypted = aes
88+
.encrypt(String::from(plaintext))
89+
.expect("AES should not fail encrypting plaintext");
90+
91+
let decrypted: &[u8] = &aes
92+
.decrypt(encrypted)
93+
.expect("AES should not fail decrypting ciphertext")
94+
.vec;
95+
let decrypted = std::str::from_utf8(decrypted).expect("Should parse as string");
96+
97+
assert_eq!(decrypted, plaintext);
98+
}
99+
}

packages/sdk/lib/src/crypto/argon2.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use crate::crypto::pointer_types::VecU8Pointer;
2+
use password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
3+
use wasm_bindgen::prelude::*;
4+
use zeroize::{Zeroize, ZeroizeOnDrop};
5+
6+
#[wasm_bindgen]
7+
pub struct Argon2Params {
8+
m_cost: u32,
9+
t_cost: u32,
10+
p_cost: u32,
11+
}
12+
13+
#[wasm_bindgen]
14+
impl Argon2Params {
15+
#[wasm_bindgen(constructor)]
16+
pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
17+
Self {
18+
m_cost,
19+
t_cost,
20+
p_cost,
21+
}
22+
}
23+
24+
#[wasm_bindgen(getter)]
25+
pub fn m_cost(&self) -> u32 {
26+
self.m_cost
27+
}
28+
29+
#[wasm_bindgen(getter)]
30+
pub fn t_cost(&self) -> u32 {
31+
self.t_cost
32+
}
33+
34+
#[wasm_bindgen(getter)]
35+
pub fn p_cost(&self) -> u32 {
36+
self.p_cost
37+
}
38+
}
39+
40+
#[wasm_bindgen]
41+
#[derive(ZeroizeOnDrop)]
42+
pub struct Argon2 {
43+
#[zeroize(skip)]
44+
salt: SaltString,
45+
password: Vec<u8>,
46+
#[zeroize(skip)]
47+
params: argon2::Params,
48+
}
49+
50+
/// Argon2 password hashing
51+
#[wasm_bindgen]
52+
impl Argon2 {
53+
#[wasm_bindgen(constructor)]
54+
pub fn new(
55+
password: String,
56+
salt: Option<String>,
57+
params: Option<Argon2Params>,
58+
) -> Result<Argon2, String> {
59+
let password = Vec::from(password.as_bytes());
60+
let default_params = argon2::Params::default();
61+
62+
let salt = match salt {
63+
Some(salt) => SaltString::new(&salt).map_err(|err| err.to_string())?,
64+
None => SaltString::generate(&mut OsRng),
65+
};
66+
67+
let params = match params {
68+
Some(params) => argon2::Params::new(params.m_cost, params.t_cost, params.p_cost, None)
69+
.map_err(|err| err.to_string())?,
70+
None => default_params,
71+
};
72+
73+
Ok(Argon2 {
74+
salt,
75+
password,
76+
params,
77+
})
78+
}
79+
80+
pub fn to_hash(&self) -> Result<String, String> {
81+
let argon2 = argon2::Argon2::default();
82+
let bytes: &[u8] = &self.password;
83+
let params = &self.params;
84+
85+
// Hash password to PHC string ($argon2id$v=19$...)
86+
let password_hash = argon2
87+
.hash_password_customized(
88+
bytes,
89+
None, // Default alg_id = Argon2id
90+
None, // Default ver = v19
91+
params.to_owned(),
92+
&self.salt,
93+
)
94+
.map_err(|err| err.to_string())?
95+
.to_string();
96+
97+
Ok(password_hash)
98+
}
99+
100+
pub fn verify(&self, hash: String) -> Result<(), String> {
101+
let argon2 = argon2::Argon2::default();
102+
let bytes: &[u8] = &self.password;
103+
let parsed_hash = PasswordHash::new(&hash).map_err(|err| err.to_string())?;
104+
105+
match argon2.verify_password(bytes, &parsed_hash) {
106+
Ok(_) => Ok(()),
107+
Err(err) => Err(err.to_string()),
108+
}
109+
}
110+
111+
pub fn params(&self) -> Argon2Params {
112+
Argon2Params::new(
113+
self.params.m_cost(),
114+
self.params.t_cost(),
115+
self.params.p_cost(),
116+
)
117+
}
118+
119+
/// Convert PHC string to serialized key
120+
pub fn key(&self) -> Result<VecU8Pointer, String> {
121+
let mut hash = self.to_hash()?;
122+
let split = hash.split('$');
123+
let items: Vec<&str> = split.collect();
124+
125+
let key = items[items.len() - 1];
126+
let vec = Vec::from(key.as_bytes());
127+
hash.zeroize();
128+
129+
Ok(VecU8Pointer::new(vec))
130+
}
131+
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
use wasm_bindgen_test::*;
137+
138+
#[wasm_bindgen_test]
139+
fn can_hash_password() {
140+
let password = "unhackable";
141+
let argon2 = Argon2::new(password.into(), None, None)
142+
.expect("Creating instance with default params should not fail");
143+
let hash = argon2
144+
.to_hash()
145+
.expect("Hashing password with Argon2 should not fail!");
146+
147+
assert!(argon2.verify(hash).is_ok());
148+
}
149+
150+
#[wasm_bindgen_test]
151+
fn can_hash_password_with_custom_params() {
152+
// Memory cost
153+
let m_cost = 2048;
154+
// Iterations/Time cost:
155+
let t_cost = 2;
156+
// Degree of parallelism:
157+
let p_cost = 2;
158+
let params = Argon2Params::new(m_cost, t_cost, p_cost);
159+
let password = "unhackable";
160+
let argon2 = Argon2::new(password.into(), None, Some(params))
161+
.expect("Creating instance with custom params should not fail");
162+
163+
let hash = argon2
164+
.to_hash()
165+
.expect("Hashing password with Argon2 should not fail!");
166+
assert!(argon2.verify(hash).is_ok());
167+
}
168+
169+
#[wasm_bindgen_test]
170+
fn can_verify_stored_hash() {
171+
let password = "unhackable";
172+
let argon2 = Argon2::new(password.into(), None, None)
173+
.expect("Creating instance with default params should not fail");
174+
let stored_hash = "$argon2id$v=19$m=4096,t=3,p=1$0UUjc4ZBOJJLTPrS1mQr1w$orbgGGRzWC0GvplgJuteaDORldnQiJfVumhXSuwO3UE";
175+
176+
// With randomly generated salt, this should not create
177+
// an equivalent hash:
178+
assert_ne!(argon2.to_hash().unwrap(), stored_hash);
179+
assert!(argon2.verify(stored_hash.to_string()).is_ok());
180+
}
181+
182+
#[wasm_bindgen_test]
183+
fn can_verify_stored_hash_with_custom_salt() {
184+
let password = "unhackable";
185+
let salt = String::from("41oVKhMIBZ+oF4efwq7e0A");
186+
let argon2 = Argon2::new(password.into(), Some(salt), None)
187+
.expect("Creating instance with default params should not fail");
188+
let stored_hash = "$argon2id$v=19$m=4096,t=3,p=1$41oVKhMIBZ+oF4efwq7e0A$ec9kY153e/S6z9awayWdUTLdaQowoAxrdo7ZkTjhBl4";
189+
190+
// Providing salt, this should create an equivalent hash:
191+
assert_eq!(argon2.to_hash().unwrap(), stored_hash);
192+
assert!(argon2.verify(stored_hash.to_string()).is_ok());
193+
}
194+
195+
#[wasm_bindgen_test]
196+
fn can_get_key_and_params() {
197+
let password = "unhackable";
198+
let argon2 = Argon2::new(password.into(), None, None)
199+
.expect("Creating instance with default params should not fail");
200+
let hash = argon2
201+
.to_hash()
202+
.expect("Hashing password with Argon2 should not fail!");
203+
204+
assert!(argon2.verify(hash).is_ok());
205+
206+
let params = argon2.params();
207+
let key = argon2.key().expect("Creating key should not fail");
208+
209+
assert_eq!(params.m_cost(), 4096);
210+
assert_eq!(params.t_cost(), 3);
211+
assert_eq!(params.p_cost(), 1);
212+
assert_eq!(key.vec.len(), 43);
213+
}
214+
}

0 commit comments

Comments
 (0)