Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Crypto]: Replace scrypt.c with crypto_scrypt implemented in Rust #3489

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"tw_evm",
"tw_hash",
"tw_keypair",
"tw_keystore",
"tw_memory",
"tw_misc",
"tw_move_parser",
Expand All @@ -21,11 +22,14 @@ members = [

[profile.wasm-test]
inherits = "release"
# Fixes an incredibly slow compilation of `curve25519-dalek` package.
opt-level = 1
opt-level = 3
debug = true
debug-assertions = true
overflow-checks = true

# Fixes an incredibly slow compilation of `curve25519-dalek` package.
[profile.wasm-test.package.curve25519-dalek]
opt-level = 1

[profile.release.package.curve25519-dalek]
opt-level = 2
12 changes: 12 additions & 0 deletions rust/tw_keystore/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "tw_keystore"
version = "0.1.0"
edition = "2021"

[dependencies]
pbkdf2 = "0.12.1"
salsa20 = "0.10.2"
sha2 = "0.10.6"
tw_encoding = { path = "../tw_encoding" }
tw_memory = { path = "../tw_memory" }
tw_misc = { path = "../tw_misc" }
5 changes: 5 additions & 0 deletions rust/tw_keystore/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
target
corpus
artifacts
coverage
Cargo.lock
27 changes: 27 additions & 0 deletions rust/tw_keystore/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "tw_keystore-fuzz"
version = "0.0.0"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] }

[dependencies.tw_keystore]
path = ".."

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[profile.release]
debug = 1

[[bin]]
name = "crypto_scrypt"
path = "fuzz_targets/crypto_scrypt.rs"
test = false
doc = false
36 changes: 36 additions & 0 deletions rust/tw_keystore/fuzz/fuzz_targets/crypto_scrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#![no_main]

use libfuzzer_sys::{arbitrary, fuzz_target};
use tw_keystore::crypto_scrypt::{params::Params, scrypt};

#[derive(arbitrary::Arbitrary, Debug)]
struct ScryptInput<'a> {
password: &'a [u8],
salt: &'a [u8],
log_n: u8,
r: u32,
p: u32,
desired_len: usize,
}

fuzz_target!(|input: ScryptInput<'_>| {
// Greater r, p parameters make `scrypt` incredibly slow.
if input.r > 16 || input.p > 16 {
return;
}

let params = Params {
log_n: input.log_n,
r: input.r,
p: input.p,
desired_len: input.desired_len,
};

let _ = scrypt(input.password, input.salt, &params);
});
42 changes: 42 additions & 0 deletions rust/tw_keystore/src/crypto_scrypt/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

use crate::crypto_scrypt::params::{InvalidParams, Params};
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;

pub mod params;
mod romix;

/// The scrypt key derivation function.
/// Original: https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/lib.rs#L89
///
/// The only reason we should have rewritten the function is that it does unnecessary `log_n >= r * 16` check:
/// https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/params.rs#L67-L72
pub fn scrypt(password: &[u8], salt: &[u8], params: &Params) -> Result<Vec<u8>, InvalidParams> {
params.check_params()?;

// The checks in the `Params::check_params` guarantee
// that the following is safe:
let n = params.n as usize;
let r128 = (params.r as usize) * 128;
let pr128 = (params.p as usize) * r128;
let nr128 = n * r128;

let mut b = vec![0u8; pr128];
pbkdf2_hmac::<Sha256>(password, salt, 1, &mut b);

let mut v = vec![0u8; nr128];
let mut t = vec![0u8; r128];

for chunk in &mut b.chunks_mut(r128) {
romix::scrypt_ro_mix(chunk, &mut v, &mut t, n);
}

let mut output = vec![0u8; params.desired_len];
pbkdf2_hmac::<Sha256>(password, &b, 1, &mut output);
Ok(output)
}
94 changes: 94 additions & 0 deletions rust/tw_keystore/src/crypto_scrypt/params.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

use std::mem::size_of;

#[derive(Clone, Copy, Debug)]
pub struct InvalidParams;

pub struct Params {
/// Scrypt parameter `N`: CPU/memory cost.
/// Must be a power of 2.
pub n: u32,
/// Scrypt parameter `r`: block size.
pub r: u32,
/// Scrypt parameter `p`: parallelism.
pub p: u32,
/// Scrypt parameter `Key length`.
pub desired_len: usize,
}

impl Params {
/// Create a new instance of [`Params`].
///
/// # Conditions
/// - `log_n` must be less than `64`
/// - `r` must be greater than `0` and less than or equal to `4294967295`
/// - `p` must be greater than `0` and less than `4294967295`
/// - `desired_len` must be greater than `9` and less than or equal to `64`
///
/// Original: https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/params.rs#L44
///
/// The only reason we should have rewritten the function is that it does unnecessary `log_n >= r * 16` check:
/// https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/params.rs#L67-L72
pub fn check_params(&self) -> Result<(), InvalidParams> {
let log_n = self.try_log_n()?;

let cond1 = (log_n as usize) < usize::BITS as usize;
let cond2 = size_of::<usize>() >= size_of::<u32>();
let cond3 = self.r <= usize::MAX as u32 && self.p < usize::MAX as u32;
let cond4 = (10..=64).contains(&self.desired_len);
if !(self.r > 0 && self.p > 0 && cond1 && (cond2 || cond3) && cond4) {
return Err(InvalidParams);
}

let r = self.r as usize;
let p = self.p as usize;
let n = self.n as usize;

// check that r * 128 doesn't overflow
let r128 = r.checked_mul(128).ok_or(InvalidParams)?;

// check that n * r * 128 doesn't overflow
r128.checked_mul(n).ok_or(InvalidParams)?;

// check that p * r * 128 doesn't overflow
r128.checked_mul(p).ok_or(InvalidParams)?;

// This check required by Scrypt:
// check: p <= ((2^32-1) * 32) / (128 * r)
// It takes a bit of re-arranging to get the check above into this form,
// but it is indeed the same.
if r * p >= 0x4000_0000 {
return Err(InvalidParams);
}

// The following checks are copied from C++:
// https://github.com/trustwallet/wallet-core/blob/b530432921d7a9709428b0162673e0ab72de1c3d/src/Keystore/ScryptParameters.cpp#L27-L42

if (n & (n - 1)) != 0 || n < 2 {
// Invalid cost factor.
return Err(InvalidParams);
}

let max_r = u32::MAX as usize / 128_usize / p;
let max_n = u32::MAX as usize / 128 / r;
if r > max_r || n > max_n {
return Err(InvalidParams);
}

Ok(())
}

fn try_log_n(&self) -> Result<u8, InvalidParams> {
let log_n = self.n.checked_ilog2().ok_or(InvalidParams)?;
// `Params::n` must be equal to 2^log_n.
if 1 << log_n != self.n {
return Err(InvalidParams);
}
log_n.try_into().map_err(|_| InvalidParams)
}
}
Loading