Skip to content

Commit 0b16771

Browse files
[TON]: Add support for TON 24-words mnemonic (#3998)
* feat(ton): Add support for TON 24-words mnemonic in Rust * feat(ton): Add tw_ton_wallet FFIs * feat(ton): Add TWTONWallet FFI in C++ * feat(ton): Add tonMnemonic StoredKey type * feat(ton): Add StoredKey TON tests * feat(ton): Add TWStoredKey TON tests * feat(ton): Add TONWallet support in Swift * TODO add iOS tests * feat(ton): Add `KeyStore` iOS tests * feat(ton): Add TONWallet support in JavaScript * Add `KeyStore` TypeScript tests * feat(ton): Remove `TonMnemonic` structure, replace with a `validate_mnemonic_words` function * [CI] Trigger CI * feat(ton): Fix rustfmt * feat(ton): Fix C++ build * feat(ton): Fix C++ build * feat(ton): Fix C++ build * feat(ton): Fix C++ address analyzer * feat(ton): Fix C++ tests * feat(ton): Add Android tests * feat(ton): Bump `actions/upload-artifact` to v4 * Bump `dawidd6/action-download-artifact` to v6 * feat(eth): Fix PR comments
1 parent 5137601 commit 0b16771

File tree

61 files changed

+4041
-202
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+4041
-202
lines changed

android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

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

7+
import com.trustwallet.core.app.utils.toHex
78
import com.trustwallet.core.app.utils.toHexByteArray
89
import org.junit.Assert.assertEquals
910
import org.junit.Test
@@ -26,4 +27,20 @@ class TestTheOpenNetworkWallet {
2627
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="
2728
assertEquals(stateInit, expected)
2829
}
30+
31+
@Test
32+
fun TheOpenNetworkWalletIsValidMnemonic() {
33+
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"
34+
val noPassphrase = ""
35+
val invalidPassphrase = "Expected empty passphrase"
36+
assert(TONWallet.isValidMnemonic(validMnemonic, noPassphrase))
37+
assert(!TONWallet.isValidMnemonic(validMnemonic, invalidPassphrase))
38+
}
39+
40+
@Test
41+
fun TheOpenNetworkWalletGetKey() {
42+
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"
43+
val wallet = TONWallet(tonMnemonic, "")
44+
assertEquals(wallet.key.data().toHex(), "0xb471884e691a9f5bb641b14f33bb9e555f759c24e368c4c0d997db3a60704220")
45+
}
2946
}

android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.junit.Assert.*
44
import org.junit.Test
55
import wallet.core.jni.StoredKey
66
import wallet.core.jni.CoinType
7+
import wallet.core.jni.Derivation
78
import wallet.core.jni.StoredKeyEncryption
89

910
class TestKeyStore {
@@ -17,9 +18,12 @@ class TestKeyStore {
1718
val keyStore = StoredKey("Test Wallet", "password".toByteArray())
1819
val result = keyStore.decryptMnemonic("wrong".toByteArray())
1920
val result2 = keyStore.decryptMnemonic("password".toByteArray())
21+
val result3 = keyStore.decryptTONMnemonic("password".toByteArray())
2022

2123
assertNull(result)
2224
assertNotNull(result2)
25+
// StoredKey is an HD by default, so `decryptTONMnemonic` should return null.
26+
assertNull(result3)
2327
}
2428

2529
@Test
@@ -91,4 +95,48 @@ class TestKeyStore {
9195
val privateKey = newKeyStore.decryptPrivateKey("".toByteArray())
9296
assertNull(privateKey)
9397
}
98+
99+
@Test
100+
fun testImportTONWallet() {
101+
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"
102+
val password = "password".toByteArray()
103+
104+
val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON)
105+
106+
val decrypted1 = keyStore.decryptTONMnemonic("wrong".toByteArray())
107+
val decrypted2 = keyStore.decryptTONMnemonic("password".toByteArray())
108+
assertNull(decrypted1)
109+
assertNotNull(decrypted2)
110+
111+
assertEquals(keyStore.accountCount(), 1)
112+
113+
// `StoredKey.account(index)` is only allowed.
114+
// `StoredKey.accountForCoin(coin, wallet)` is not supported.
115+
val tonAccount = keyStore.account(0)
116+
assertEquals(tonAccount.address(), "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz")
117+
assertEquals(tonAccount.coin(), CoinType.TON)
118+
assertEquals(tonAccount.publicKey(), "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055")
119+
assertEquals(tonAccount.extendedPublicKey(), "")
120+
assertEquals(tonAccount.derivation(), Derivation.DEFAULT)
121+
assertEquals(tonAccount.derivationPath(), "")
122+
123+
val privateKey = keyStore.privateKey(CoinType.TON, password)
124+
assertEquals(privateKey.data().toHex(), "0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46")
125+
126+
// HD wallet is not supported for TON wallet
127+
val hdWallet = keyStore.wallet(password)
128+
assertNull(hdWallet)
129+
}
130+
131+
@Test
132+
fun testExportTONWallet() {
133+
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"
134+
val password = "password".toByteArray()
135+
136+
val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON)
137+
val json = keyStore.exportJSON()
138+
139+
val newKeyStore = StoredKey.importJSON(json)
140+
assertEquals(newKeyStore.decryptTONMnemonic(password), tonMnemonic)
141+
}
94142
}

include/TrustWalletCore/TWStoredKey.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,29 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo
7474
TW_EXPORT_STATIC_METHOD
7575
struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);
7676

77+
/// Imports a TON-specific wallet with a 24-words mnemonic.
78+
///
79+
/// \param tonMnemonic Non-null TON mnemonic
80+
/// \param name The name of the stored key to import as a non-null string
81+
/// \param password Non-null block of data, password of the stored key
82+
/// \param coin the coin type
83+
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
84+
/// \return Nullptr if the key can't be imported, the stored key otherwise
85+
TW_EXPORT_STATIC_METHOD
86+
struct TWStoredKey* _Nullable TWStoredKeyImportTONWallet(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin);
87+
88+
/// Imports a TON-specific wallet with a 24-words mnemonic.
89+
///
90+
/// \param tonMnemonic Non-null TON mnemonic
91+
/// \param name The name of the stored key to import as a non-null string
92+
/// \param password Non-null block of data, password of the stored key
93+
/// \param coin the coin type
94+
/// \param encryption cipher encryption mode
95+
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
96+
/// \return Nullptr if the key can't be imported, the stored key otherwise
97+
TW_EXPORT_STATIC_METHOD
98+
struct TWStoredKey* _Nullable TWStoredKeyImportTONWalletWithEncryption(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);
99+
77100
/// Imports a key from JSON.
78101
///
79102
/// \param json Json stored key import format as a non-null block of data
@@ -152,6 +175,13 @@ TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key);
152175
TW_EXPORT_PROPERTY
153176
bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key);
154177

178+
/// Whether this key is a TON mnemonic phrase.
179+
///
180+
/// \param key Non-null pointer to a stored key
181+
/// \return true if the given stored key is a TON mnemonic, false otherwise
182+
TW_EXPORT_PROPERTY
183+
bool TWStoredKeyIsTONMnemonic(struct TWStoredKey* _Nonnull key);
184+
155185
/// The number of accounts.
156186
///
157187
/// \param key Non-null pointer to a stored key
@@ -261,6 +291,14 @@ TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key,
261291
TW_EXPORT_METHOD
262292
TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);
263293

294+
/// Decrypts the TON mnemonic phrase.
295+
///
296+
/// \param key Non-null pointer to a stored key
297+
/// \param password Non-null block of data, password of the stored key
298+
/// \return TON decrypted mnemonic if success, null pointer otherwise
299+
TW_EXPORT_METHOD
300+
TWString* _Nullable TWStoredKeyDecryptTONMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);
301+
264302
/// Returns the private key for a specific coin. Returned object needs to be deleted.
265303
///
266304
/// \param key Non-null pointer to a stored key

include/TrustWalletCore/TWTONWallet.h

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include "TWBase.h"
8+
#include "TWPrivateKey.h"
89
#include "TWPublicKey.h"
910
#include "TWString.h"
1011

@@ -14,13 +15,46 @@ TW_EXTERN_C_BEGIN
1415
TW_EXPORT_CLASS
1516
struct TWTONWallet;
1617

18+
/// Determines whether the English mnemonic and passphrase are valid.
19+
///
20+
/// \param mnemonic Non-null english mnemonic
21+
/// \param passphrase Nullable optional passphrase
22+
/// \note passphrase can be null or empty string if no passphrase required
23+
/// \return whether the mnemonic and passphrase are valid (valid checksum)
24+
TW_EXPORT_STATIC_METHOD
25+
bool TWTONWalletIsValidMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase);
26+
27+
/// Creates a \TONWallet from a valid TON mnemonic and passphrase.
28+
///
29+
/// \param mnemonic Non-null english mnemonic
30+
/// \param passphrase Nullable optional passphrase
31+
/// \note Null is returned on invalid mnemonic and passphrase
32+
/// \note passphrase can be null or empty string if no passphrase required
33+
/// \return Nullable TWTONWallet
34+
TW_EXPORT_STATIC_METHOD
35+
struct TWTONWallet* _Nullable TWTONWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase);
36+
37+
/// Delete the given \TONWallet
38+
///
39+
/// \param wallet Non-null pointer to private key
40+
TW_EXPORT_METHOD
41+
void TWTONWalletDelete(struct TWTONWallet* _Nonnull wallet);
42+
43+
/// Generates Ed25519 private key associated with the wallet.
44+
///
45+
/// \param wallet non-null TWTONWallet
46+
/// \note Returned object needs to be deleted with \TWPrivateKeyDelete
47+
/// \return The Ed25519 private key
48+
TW_EXPORT_METHOD
49+
struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wallet);
50+
1751
/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`.
1852
///
1953
/// \param publicKey wallet's public key.
2054
/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0).
2155
/// \param walletId wallet's ID allows to create multiple wallets for the same private key.
2256
/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided.
2357
TW_EXPORT_STATIC_METHOD
24-
TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId);
58+
TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey* _Nonnull publicKey, int32_t workchain, int32_t walletId);
2559

2660
TW_EXTERN_C_END

rust/Cargo.lock

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

rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ members = [
2525
"tw_encoding",
2626
"tw_evm",
2727
"tw_hash",
28+
"tw_hd_wallet",
2829
"tw_keypair",
2930
"tw_memory",
3031
"tw_misc",

rust/chains/tw_ton/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ tw_number = { path = "../../tw_number" }
1414
tw_misc = { path = "../../tw_misc" }
1515
tw_proto = { path = "../../tw_proto" }
1616
tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" }
17+
zeroize = "1.8.1"

rust/tw_any_coin/src/test_utils/address_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper;
1919
use tw_keypair::tw::PublicKeyType;
2020
use tw_memory::test_utils::tw_data_helper::TWDataHelper;
2121
use tw_memory::test_utils::tw_string_helper::TWStringHelper;
22-
use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor};
22+
use tw_memory::test_utils::tw_wrapper::{TWAutoWrapper, WithDestructor};
2323

24-
pub type TWAnyAddressHelper = TWWrapper<TWAnyAddress>;
24+
pub type TWAnyAddressHelper = TWAutoWrapper<TWAnyAddress>;
2525

2626
impl WithDestructor for TWAnyAddress {
2727
fn destructor() -> unsafe extern "C" fn(*mut Self) {

rust/tw_hash/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ blake2b-ref = "0.3.1"
1313
digest = "0.10.6"
1414
groestl = "0.10.1"
1515
hmac = "0.12.1"
16+
pbkdf2 = "0.12.1"
1617
ripemd = "0.1.3"
1718
serde = { version = "1.0", features = ["derive"], optional = true }
1819
sha1 = "0.10.5"

rust/tw_hash/src/ffi.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ pub enum CHashingCode {
1414
Ok = 0,
1515
InvalidHashLength = 1,
1616
InvalidArgument = 2,
17+
InvalidPassword = 3,
1718
}
1819

1920
impl From<Error> for CHashingCode {
2021
fn from(e: Error) -> Self {
2122
match e {
2223
Error::FromHexError(_) | Error::InvalidArgument => CHashingCode::InvalidArgument,
2324
Error::InvalidHashLength => CHashingCode::InvalidHashLength,
25+
Error::InvalidPassword => CHashingCode::InvalidPassword,
2426
}
2527
}
2628
}

0 commit comments

Comments
 (0)