Skip to content

Commit

Permalink
feat(ton): Remove TonMnemonic structure, replace with a `validate_m…
Browse files Browse the repository at this point in the history
…nemonic_words` function
  • Loading branch information
satoshiotomakan committed Sep 9, 2024
1 parent 7640110 commit d819e4d
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 58 deletions.
1 change: 1 addition & 0 deletions rust/Cargo.lock

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

2 changes: 1 addition & 1 deletion rust/tw_hd_wallet/src/bip39/bip39_english.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ lazy_static! {
/// https://github.com/trustwallet/wallet-core/blob/43c92837db9f5d773f2545473f29c8a597d86de5/trezor-crypto/include/TrezorCrypto/bip39_english.h#L24-L367
/// https://github.com/dvc94ch/rust-bip39/blob/master/src/language/english.rs
#[rustfmt::skip]
pub const BIP39_WORDS_LIST: [&'static str; 2048] = [
pub const BIP39_WORDS_LIST: [&str; 2048] = [
"abandon",
"ability",
"able",
Expand Down
4 changes: 4 additions & 0 deletions rust/tw_hd_wallet/src/bip39/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
// Copyright © 2017 Trust Wallet.

pub mod bip39_english;

pub fn normalize_mnemonic(mnemonic: &str) -> String {
mnemonic.trim().to_string()
}
66 changes: 28 additions & 38 deletions rust/tw_hd_wallet/src/ton/mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,37 @@

use crate::bip39::bip39_english::BIP39_WORDS_MAP;
use crate::{WalletError, WalletResult};
use zeroize::ZeroizeOnDrop;

#[derive(Debug, ZeroizeOnDrop)]
pub struct TonMnemonic(String);

impl TonMnemonic {
pub const WORDS_LEN: usize = 24;

/// Creates `TonMnemonic` from a 24-words string.
///
/// Please note there can be a passphrase required to be used to derive a key-pair.
/// See [`TonWallet::new`].
pub fn new(mnemonic: &str) -> WalletResult<TonMnemonic> {
let normalized_mnemonic = mnemonic.trim().to_string();

let mut invalid = false;
let mut words_count = 0;
for word in normalized_mnemonic.split(" ") {
words_count += 1;

// Although this operation is not security-critical, we aim for constant-time operation here as well
// (i.e., no early exit on match)
//
// We expect words in lowercase only.
// It's a responsibility of the WalletCore user to transform the mnemonic if needed.
if !BIP39_WORDS_MAP.contains_key(word) {
invalid = true;
}
}

if invalid {
return Err(WalletError::InvalidMnemonicUnknownWord);
}
if words_count != TonMnemonic::WORDS_LEN {
return Err(WalletError::InvalidMnemonicWordCount);
pub const WORDS_LEN: usize = 24;

/// Validates the given `mnemonic` string if it consists of 24 known words (see BIP39 words list).
///
/// Please note there this function doesn't validate the mnemonic but it words only.
/// See [`TonWallet::new`].
pub fn validate_mnemonic_words(mnemonic: &str) -> WalletResult<()> {
let normalized_mnemonic = mnemonic.trim().to_string();

let mut invalid = false;
let mut words_count = 0;
for word in normalized_mnemonic.split(" ") {
words_count += 1;

// Although this operation is not security-critical, we aim for constant-time operation here as well
// (i.e., no early exit on match)
//
// We expect words in lowercase only.
// It's a responsibility of the WalletCore user to transform the mnemonic if needed.
if !BIP39_WORDS_MAP.contains_key(word) {
invalid = true;
}

Ok(TonMnemonic(normalized_mnemonic))
}

pub fn as_str(&self) -> &str {
&self.0
if invalid {
return Err(WalletError::InvalidMnemonicUnknownWord);
}
if words_count != WORDS_LEN {
return Err(WalletError::InvalidMnemonicWordCount);
}

Ok(())
}
20 changes: 12 additions & 8 deletions rust/tw_hd_wallet/src/ton/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//
// Copyright © 2017 Trust Wallet.

use crate::ton::mnemonic::TonMnemonic;
use crate::bip39::normalize_mnemonic;
use crate::ton::mnemonic::validate_mnemonic_words;
use crate::{WalletError, WalletResult};
use tw_hash::hmac::hmac_sha512;
use tw_hash::pbkdf2::pbkdf2_hmac_sha512;
Expand All @@ -22,11 +23,11 @@ const TON_BASIC_SEED_ROUNDS: u32 = 390;
const TON_PASSPHRASE_SEED_SALT: &[u8] = b"TON fast seed version";
const TON_PASSPHRASE_SEED_ROUNDS: u32 = 1;

pub mod mnemonic;
mod mnemonic;

#[derive(ZeroizeOnDrop)]
#[derive(Debug, ZeroizeOnDrop)]
pub struct TonWallet {
mnemonic: TonMnemonic,
mnemonic: String,
passphrase: Option<String>,
entropy: H512,
seed: H512,
Expand All @@ -37,7 +38,10 @@ impl TonWallet {

/// Creates `TonWallet` while validating if there should or shouldn't be a passphrase.
/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/validate-mnemonic.ts#L20-L28
pub fn new(mnemonic: TonMnemonic, passphrase: Option<String>) -> WalletResult<TonWallet> {
pub fn new(mnemonic: &str, passphrase: Option<String>) -> WalletResult<TonWallet> {
let mnemonic = normalize_mnemonic(mnemonic);
validate_mnemonic_words(&mnemonic)?;

let entropy = Self::ton_mnemonic_to_entropy(&mnemonic, passphrase.as_deref());

match passphrase {
Expand Down Expand Up @@ -72,16 +76,16 @@ impl TonWallet {

/// Whether the mnemonic can be used with a passphrase only.
/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/is-password-needed.ts#L5-L11
fn is_password_needed(mnemonic: &TonMnemonic) -> bool {
fn is_password_needed(mnemonic: &str) -> bool {
// Password mnemonic (without password) should be password seed, but not basic seed.
let entropy = Self::ton_mnemonic_to_entropy(mnemonic, None);
is_password_seed(&entropy) && !is_basic_seed(&entropy)
}

/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L20-L23
fn ton_mnemonic_to_entropy(mnemonic: &TonMnemonic, passphrase: Option<&str>) -> H512 {
fn ton_mnemonic_to_entropy(mnemonic: &str, passphrase: Option<&str>) -> H512 {
let passphrase_bytes = passphrase.map(str::as_bytes).unwrap_or(&[]);
let entropy = hmac_sha512(mnemonic.as_str().as_bytes(), passphrase_bytes);
let entropy = hmac_sha512(mnemonic.as_bytes(), passphrase_bytes);
H512::try_from(entropy.as_slice()).expect("hmac_sha512 must return 64 bytes")
}
}
Expand Down
13 changes: 5 additions & 8 deletions rust/tw_hd_wallet/tests/ton_mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// Copyright © 2017 Trust Wallet.

use tw_encoding::hex::ToHex;
use tw_hd_wallet::ton::mnemonic::TonMnemonic;
use tw_hd_wallet::ton::TonWallet;
use tw_hd_wallet::WalletError;
use tw_keypair::traits::KeyPairTrait;
Expand All @@ -23,8 +22,7 @@ fn mnemonic_to_keypair_impl(input: MnemonicTest) {
Some(input.passphrase.to_string())
};

let mnemonic = TonMnemonic::new(input.mnemonic).unwrap();
let wallet = TonWallet::new(mnemonic, passphrase).unwrap();
let wallet = TonWallet::new(input.mnemonic, passphrase).unwrap();
let key_pair = wallet.to_key_pair();

assert_eq!(
Expand All @@ -51,9 +49,8 @@ fn mnemonic_to_keypair_error(input: MnemonicErrorTest) {
Some(input.passphrase.to_string())
};

let mnemonic = TonMnemonic::new(input.mnemonic).unwrap();
assert!(
TonWallet::new(mnemonic, passphrase).is_err(),
TonWallet::new(input.mnemonic, passphrase).is_err(),
"Expected an error"
);
}
Expand Down Expand Up @@ -147,13 +144,13 @@ fn test_mnemonic_to_keypair_error_expected_no_passphrase() {
#[test]
fn test_invalid_mnemonic() {
// 24 words mnemonic is supported only.
let error = TonMnemonic::new("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh").unwrap_err();
let error = TonWallet::new("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh", None).unwrap_err();
assert_eq!(error, WalletError::InvalidMnemonicWordCount);

let error = TonMnemonic::new("foo bar oooo edit wash faint patient cancel roof edit silly battle half engine reunion hotel joy fan unhappy oil alone sense empty mesh").unwrap_err();
let error = TonWallet::new("foo bar oooo edit wash faint patient cancel roof edit silly battle half engine reunion hotel joy fan unhappy oil alone sense empty mesh", None).unwrap_err();
assert_eq!(error, WalletError::InvalidMnemonicUnknownWord);

// Upper-case mnemonic is not allowed.
let error = TonMnemonic::new("TAIL SWING SUGGEST EDIT WASH FAINT PATIENT CANCEL ROOF EDIT SILLY BATTLE HALF ENGINE REUNION HOTEL JOY FAN UNHAPPY OIL ALONE SENSE EMPTY MESH").unwrap_err();
let error = TonWallet::new("TAIL SWING SUGGEST EDIT WASH FAINT PATIENT CANCEL ROOF EDIT SILLY BATTLE HALF ENGINE REUNION HOTEL JOY FAN UNHAPPY OIL ALONE SENSE EMPTY MESH", None).unwrap_err();
assert_eq!(error, WalletError::InvalidMnemonicUnknownWord);
}
3 changes: 0 additions & 3 deletions rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

#![allow(clippy::missing_safety_doc)]

use tw_hd_wallet::ton::mnemonic::TonMnemonic;
use tw_hd_wallet::ton::TonWallet;
use tw_keypair::ffi::privkey::TWPrivateKey;
use tw_keypair::ffi::pubkey::TWPublicKey;
Expand Down Expand Up @@ -32,7 +31,6 @@ pub unsafe extern "C" fn tw_ton_wallet_is_valid_mnemonic(
) -> bool {
let mnemonic = try_or_false!(TWString::from_ptr_as_ref(mnemonic));
let mnemonic = try_or_false!(mnemonic.as_str());
let mnemonic = try_or_false!(TonMnemonic::new(mnemonic));

let passphrase = TWString::from_ptr_as_ref(passphrase)
.and_then(TWString::as_str)
Expand All @@ -55,7 +53,6 @@ pub unsafe extern "C" fn tw_ton_wallet_create_with_mnemonic(
) -> *mut TWTONWallet {
let mnemonic = try_or_else!(TWString::from_ptr_as_ref(mnemonic), std::ptr::null_mut);
let mnemonic = try_or_else!(mnemonic.as_str(), std::ptr::null_mut);
let mnemonic = try_or_else!(TonMnemonic::new(mnemonic), std::ptr::null_mut);

let passphrase = TWString::from_ptr_as_ref(passphrase)
.and_then(TWString::as_str)
Expand Down

0 comments on commit d819e4d

Please sign in to comment.