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

[TON]: Add support for TON 24-words mnemonic #3998

Merged
merged 27 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cbfd1a1
feat(ton): Add support for TON 24-words mnemonic in Rust
satoshiotomakan Aug 22, 2024
e762f64
feat(ton): Add tw_ton_wallet FFIs
satoshiotomakan Aug 23, 2024
a256fd9
feat(ton): Add TWTONWallet FFI in C++
satoshiotomakan Aug 23, 2024
79b4ca9
feat(ton): Add tonMnemonic StoredKey type
satoshiotomakan Sep 3, 2024
0da5ee4
feat(ton): Add StoredKey TON tests
satoshiotomakan Sep 4, 2024
db225c6
feat(ton): Add TWStoredKey TON tests
satoshiotomakan Sep 4, 2024
5aa0827
feat(ton): Add TONWallet support in Swift
satoshiotomakan Sep 6, 2024
f6494d3
feat(ton): Add `KeyStore` iOS tests
satoshiotomakan Sep 9, 2024
e22de4c
feat(ton): Add TONWallet support in JavaScript
satoshiotomakan Sep 9, 2024
7640110
Merge branch 'master' into s/ton-mnemonic
satoshiotomakan Sep 9, 2024
d819e4d
feat(ton): Remove `TonMnemonic` structure, replace with a `validate_m…
satoshiotomakan Sep 9, 2024
845ad51
[CI] Trigger CI
satoshiotomakan Sep 9, 2024
098d541
feat(ton): Fix rustfmt
satoshiotomakan Sep 9, 2024
5544ed2
feat(ton): Fix C++ build
satoshiotomakan Sep 9, 2024
d465c78
feat(ton): Fix C++ build
satoshiotomakan Sep 10, 2024
6025f72
feat(ton): Fix C++ build
satoshiotomakan Sep 10, 2024
c8f1261
Merge branch 'master' into s/ton-mnemonic
satoshiotomakan Sep 10, 2024
87695d4
feat(ton): Fix C++ address analyzer
satoshiotomakan Sep 10, 2024
93d661d
feat(ton): Fix C++ tests
satoshiotomakan Sep 10, 2024
efb6e22
feat(ton): Add Android tests
satoshiotomakan Sep 10, 2024
179b351
Merge branch 'master' into s/ton-mnemonic
satoshiotomakan Sep 11, 2024
6c60df2
feat(ton): Bump `actions/upload-artifact` to v4
satoshiotomakan Sep 11, 2024
7923ce5
feat(eth): Fix PR comments
satoshiotomakan Sep 12, 2024
026e367
Merge branch 'master' into s/ton-mnemonic
satoshiotomakan Sep 13, 2024
2164d3e
Merge branch 'master' into s/ton-mnemonic
satoshiotomakan Oct 4, 2024
9bfc1d1
Merge branch 'master' into s/ton-mnemonic
satoshiotomakan Oct 4, 2024
5f55b56
Merge branch 'dev' into s/ton-mnemonic
satoshiotomakan Oct 4, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package com.trustwallet.core.app.blockchains.theopennetwork

import com.trustwallet.core.app.utils.toHex
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert.assertEquals
import org.junit.Test
Expand All @@ -26,4 +27,20 @@ class TestTheOpenNetworkWallet {
val expected = "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg="
assertEquals(stateInit, expected)
}

@Test
fun TheOpenNetworkWalletIsValidMnemonic() {
val validMnemonic = "sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine"
val noPassphrase = ""
val invalidPassphrase = "Expected empty passphrase"
assert(TONWallet.isValidMnemonic(validMnemonic, noPassphrase))
assert(!TONWallet.isValidMnemonic(validMnemonic, invalidPassphrase))
}

@Test
fun TheOpenNetworkWalletGetKey() {
val tonMnemonic = "sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine"
val wallet = TONWallet(tonMnemonic, "")
assertEquals(wallet.key.data().toHex(), "0xb471884e691a9f5bb641b14f33bb9e555f759c24e368c4c0d997db3a60704220")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.junit.Assert.*
import org.junit.Test
import wallet.core.jni.StoredKey
import wallet.core.jni.CoinType
import wallet.core.jni.Derivation
import wallet.core.jni.StoredKeyEncryption

class TestKeyStore {
Expand All @@ -17,9 +18,12 @@ class TestKeyStore {
val keyStore = StoredKey("Test Wallet", "password".toByteArray())
val result = keyStore.decryptMnemonic("wrong".toByteArray())
val result2 = keyStore.decryptMnemonic("password".toByteArray())
val result3 = keyStore.decryptTONMnemonic("password".toByteArray())

assertNull(result)
assertNotNull(result2)
// StoredKey is an HD by default, so `decryptTONMnemonic` should return null.
assertNull(result3)
}

@Test
Expand Down Expand Up @@ -91,4 +95,48 @@ class TestKeyStore {
val privateKey = newKeyStore.decryptPrivateKey("".toByteArray())
assertNull(privateKey)
}

@Test
fun testImportTONWallet() {
val tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike"
val password = "password".toByteArray()

val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON)

val decrypted1 = keyStore.decryptTONMnemonic("wrong".toByteArray())
val decrypted2 = keyStore.decryptTONMnemonic("password".toByteArray())
assertNull(decrypted1)
assertNotNull(decrypted2)

assertEquals(keyStore.accountCount(), 1)

// `StoredKey.account(index)` is only allowed.
// `StoredKey.accountForCoin(coin, wallet)` is not supported.
val tonAccount = keyStore.account(0)
assertEquals(tonAccount.address(), "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz")
assertEquals(tonAccount.coin(), CoinType.TON)
assertEquals(tonAccount.publicKey(), "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055")
assertEquals(tonAccount.extendedPublicKey(), "")
assertEquals(tonAccount.derivation(), Derivation.DEFAULT)
assertEquals(tonAccount.derivationPath(), "")

val privateKey = keyStore.privateKey(CoinType.TON, password)
assertEquals(privateKey.data().toHex(), "0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46")

// HD wallet is not supported for TON wallet
val hdWallet = keyStore.wallet(password)
assertNull(hdWallet)
}

@Test
fun testExportTONWallet() {
val tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike"
val password = "password".toByteArray()

val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON)
val json = keyStore.exportJSON()

val newKeyStore = StoredKey.importJSON(json)
assertEquals(newKeyStore.decryptTONMnemonic(password), tonMnemonic)
}
}
38 changes: 38 additions & 0 deletions include/TrustWalletCore/TWStoredKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);

/// Imports a TON-specific wallet with a 24-words mnemonic.
///
/// \param tonMnemonic Non-null TON mnemonic
/// \param name The name of the stored key to import as a non-null string
/// \param password Non-null block of data, password of the stored key
/// \param coin the coin type
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return Nullptr if the key can't be imported, the stored key otherwise
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportTONWallet(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin);

/// Imports a TON-specific wallet with a 24-words mnemonic.
///
/// \param tonMnemonic Non-null TON mnemonic
/// \param name The name of the stored key to import as a non-null string
/// \param password Non-null block of data, password of the stored key
/// \param coin the coin type
/// \param encryption cipher encryption mode
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return Nullptr if the key can't be imported, the stored key otherwise
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportTONWalletWithEncryption(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);

/// Imports a key from JSON.
///
/// \param json Json stored key import format as a non-null block of data
Expand Down Expand Up @@ -152,6 +175,13 @@ TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key);
TW_EXPORT_PROPERTY
bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key);

/// Whether this key is a TON mnemonic phrase.
///
/// \param key Non-null pointer to a stored key
/// \return true if the given stored key is a TON mnemonic, false otherwise
TW_EXPORT_PROPERTY
bool TWStoredKeyIsTONMnemonic(struct TWStoredKey* _Nonnull key);

/// The number of accounts.
///
/// \param key Non-null pointer to a stored key
Expand Down Expand Up @@ -261,6 +291,14 @@ TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key,
TW_EXPORT_METHOD
TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);

/// Decrypts the TON mnemonic phrase.
///
/// \param key Non-null pointer to a stored key
/// \param password Non-null block of data, password of the stored key
/// \return TON decrypted mnemonic if success, null pointer otherwise
TW_EXPORT_METHOD
TWString* _Nullable TWStoredKeyDecryptTONMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);

/// Returns the private key for a specific coin. Returned object needs to be deleted.
///
/// \param key Non-null pointer to a stored key
Expand Down
36 changes: 35 additions & 1 deletion include/TrustWalletCore/TWTONWallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "TWBase.h"
#include "TWPrivateKey.h"
#include "TWPublicKey.h"
#include "TWString.h"

Expand All @@ -14,13 +15,46 @@ TW_EXTERN_C_BEGIN
TW_EXPORT_CLASS
struct TWTONWallet;

/// Determines whether the English mnemonic and passphrase are valid.
///
/// \param mnemonic Non-null english mnemonic
/// \param passphrase Nullable optional passphrase
/// \note passphrase can be null or empty string if no passphrase required
/// \return whether the mnemonic and passphrase are valid (valid checksum)
TW_EXPORT_STATIC_METHOD
bool TWTONWalletIsValidMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase);

/// Creates a \TONWallet from a valid TON mnemonic and passphrase.
///
/// \param mnemonic Non-null english mnemonic
/// \param passphrase Nullable optional passphrase
/// \note Null is returned on invalid mnemonic and passphrase
/// \note passphrase can be null or empty string if no passphrase required
/// \return Nullable TWTONWallet
TW_EXPORT_STATIC_METHOD
struct TWTONWallet* _Nullable TWTONWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase);

/// Delete the given \TONWallet
///
/// \param wallet Non-null pointer to private key
TW_EXPORT_METHOD
void TWTONWalletDelete(struct TWTONWallet* _Nonnull wallet);

/// Generates Ed25519 private key associated with the wallet.
///
/// \param wallet non-null TWTONWallet
/// \note Returned object needs to be deleted with \TWPrivateKeyDelete
/// \return The Ed25519 private key
TW_EXPORT_METHOD
struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wallet);

/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`.
///
/// \param publicKey wallet's public key.
/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0).
/// \param walletId wallet's ID allows to create multiple wallets for the same private key.
/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId);
TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey* _Nonnull publicKey, int32_t workchain, int32_t walletId);

TW_EXTERN_C_END
28 changes: 28 additions & 0 deletions rust/Cargo.lock

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

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ members = [
"tw_encoding",
"tw_evm",
"tw_hash",
"tw_hd_wallet",
"tw_keypair",
"tw_memory",
"tw_misc",
Expand Down
1 change: 1 addition & 0 deletions rust/chains/tw_ton/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ tw_number = { path = "../../tw_number" }
tw_misc = { path = "../../tw_misc" }
tw_proto = { path = "../../tw_proto" }
tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" }
zeroize = "1.8.1"
4 changes: 2 additions & 2 deletions rust/tw_any_coin/src/test_utils/address_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper;
use tw_keypair::tw::PublicKeyType;
use tw_memory::test_utils::tw_data_helper::TWDataHelper;
use tw_memory::test_utils::tw_string_helper::TWStringHelper;
use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor};
use tw_memory::test_utils::tw_wrapper::{TWAutoWrapper, WithDestructor};

pub type TWAnyAddressHelper = TWWrapper<TWAnyAddress>;
pub type TWAnyAddressHelper = TWAutoWrapper<TWAnyAddress>;

impl WithDestructor for TWAnyAddress {
fn destructor() -> unsafe extern "C" fn(*mut Self) {
Expand Down
1 change: 1 addition & 0 deletions rust/tw_hash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ blake2b-ref = "0.3.1"
digest = "0.10.6"
groestl = "0.10.1"
hmac = "0.12.1"
pbkdf2 = "0.12.1"
ripemd = "0.1.3"
serde = { version = "1.0", features = ["derive"], optional = true }
sha1 = "0.10.5"
Expand Down
2 changes: 2 additions & 0 deletions rust/tw_hash/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ pub enum CHashingCode {
Ok = 0,
InvalidHashLength = 1,
InvalidArgument = 2,
InvalidPassword = 3,
}

impl From<Error> for CHashingCode {
fn from(e: Error) -> Self {
match e {
Error::FromHexError(_) | Error::InvalidArgument => CHashingCode::InvalidArgument,
Error::InvalidHashLength => CHashingCode::InvalidHashLength,
Error::InvalidPassword => CHashingCode::InvalidPassword,
}
}
}
Expand Down
15 changes: 10 additions & 5 deletions rust/tw_hash/src/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
// Copyright © 2017 Trust Wallet.

use hmac::{Hmac, Mac};
use sha2::Sha256;
use sha2::{Sha256, Sha512};

type HmacSha256 = Hmac<Sha256>;
type HmacSha512 = Hmac<Sha512>;

pub fn hmac_sha256(key: &[u8], input: &[u8]) -> Vec<u8> {
let mut mac = HmacSha256::new_from_slice(key).unwrap();
let mut mac = HmacSha256::new_from_slice(key).expect("Hmac constructor should never fail");
mac.update(input);
let res = mac.finalize();
let code_bytes = res.into_bytes();
code_bytes.to_vec()
mac.finalize().into_bytes().to_vec()
}

pub fn hmac_sha512(key: &[u8], input: &[u8]) -> Vec<u8> {
let mut mac = HmacSha512::new_from_slice(key).expect("Hmac constructor should never fail");
mac.update(input);
mac.finalize().into_bytes().to_vec()
}
2 changes: 2 additions & 0 deletions rust/tw_hash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod ffi;
pub mod groestl;
pub mod hasher;
pub mod hmac;
pub mod pbkdf2;
pub mod ripemd;
pub mod sha1;
pub mod sha2;
Expand All @@ -28,6 +29,7 @@ pub enum Error {
FromHexError(FromHexError),
InvalidHashLength,
InvalidArgument,
InvalidPassword,
}

impl From<FromHexError> for Error {
Expand Down
10 changes: 10 additions & 0 deletions rust/tw_hash/src/pbkdf2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use crate::H512;
use sha2::Sha512;

pub fn pbkdf2_hmac_sha512(password: &[u8], salt: &[u8], rounds: u32) -> H512 {
pbkdf2::pbkdf2_hmac_array::<Sha512, { H512::LEN }>(password, salt, rounds).into()
}
Loading
Loading