diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index 243b33df519..088079ae8fe 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -122,7 +122,7 @@ class CoinAddressDerivationTests { NATIVEEVMOS -> assertEquals("evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d", address) NERVOS -> assertEquals("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3", address) EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address) - TON -> assertEquals("EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9", address) + TON -> assertEquals("UQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUaT4", address) APTOS -> assertEquals("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) NEBL -> assertEquals("NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7", address) SUI -> assertEquals("0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2", address) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt index 66433c2e0a8..f0ae94056e6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt @@ -22,28 +22,44 @@ class TestTheOpenNetworkAddress { val publicKey = privateKey.getPublicKeyEd25519() val address = AnyAddress(publicKey, CoinType.TON) assertEquals(publicKey.data().toHex(), "0xf42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41") - assertEquals(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") } @Test fun testAddressFromPublicKey() { val publicKey = PublicKey("f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41".toHexByteArray(), PublicKeyType.ED25519) val address = AnyAddress(publicKey, CoinType.TON) - assertEquals(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") } @Test fun testAddressFromRawString() { val addressString = "0:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3" val address = AnyAddress(addressString, CoinType.TON) - assertEquals(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") } @Test - fun testAddressFromUserFriendlyString() { + fun testAddressFromBounceableString() { val addressString = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" val address = AnyAddress(addressString, CoinType.TON) - assertEquals(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressFromUserFriendlyString() { + val addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + val address = AnyAddress(addressString, CoinType.TON) + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressToBounceable() { + val addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + val bounceable = true + val testnet = false + val address = TONAddressConverter.toUserFriendly(addressString, bounceable, testnet) + assertEquals(address, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") } @Test diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index e378f7cdd42..51082a93898 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -295,6 +295,17 @@ TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key); TW_EXPORT_METHOD bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); +/// Re-derives address and public key for the specified chain. +/// It can be used to update the address if the default address format for the given chain has been changed. +/// This method needs the encryption password to re-write address. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \param coin Coin type for which to update the address and public key +/// \return `false` if the password is incorrect or there is no data for the specified chain, true otherwise. +TW_EXPORT_METHOD +bool TWStoredKeyUpdateAddress(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password, enum TWCoinType coin); + /// Retrieve stored key encoding parameters, as JSON string. /// /// \param key Non-null pointer to a stored key diff --git a/include/TrustWalletCore/TWTONAddressConverter.h b/include/TrustWalletCore/TWTONAddressConverter.h index 55dfcaff58a..142c98a4cb7 100644 --- a/include/TrustWalletCore/TWTONAddressConverter.h +++ b/include/TrustWalletCore/TWTONAddressConverter.h @@ -31,4 +31,12 @@ TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address); TW_EXPORT_STATIC_METHOD TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc); +/// Converts any TON address format to user friendly with the given parameters. +/// +/// \param address raw or user-friendly address to be converted. +/// \param bounceable whether the result address should be bounceable. +/// \param testnet whether the result address should be testnet. +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWTONAddressConverterToUserFriendly(TWString *_Nonnull address, bool bounceable, bool testnet); + TW_EXTERN_C_END diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt index 39f84b264cd..6a41399d554 100644 --- a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -115,7 +115,7 @@ class CoinAddressDerivationTests { NativeEvmos -> "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d" Nervos -> "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" Everscale -> "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04" - TON -> "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9" + TON -> "UQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUaT4" Aptos -> "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" Nebl -> "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7" Sui -> "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2" diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index fb0d8701f00..1d47a58feb7 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -155,6 +155,12 @@ Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet<>* wall return account; } +void StoredKey::updateAddressForAccount(const PrivateKey& privKey, Account& account) { + const auto pubKey = privKey.getPublicKey(TW::publicKeyType(account.coin)); + account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); + account.publicKey = hex(pubKey.bytes); +} + std::optional StoredKey::account(TWCoinType coin, const HDWallet<>* wallet) { const auto account = getDefaultAccountOrAny(coin, wallet); if (account.has_value()) { @@ -269,9 +275,7 @@ void StoredKey::fixAddresses(const Data& password) { } const auto& derivationPath = account.derivationPath; const auto key = wallet.getKey(account.coin, derivationPath); - const auto pubKey = key.getPublicKey(TW::publicKeyType(account.coin)); - account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); - account.publicKey = hex(pubKey.bytes); + updateAddressForAccount(key, account); } } break; @@ -282,14 +286,37 @@ void StoredKey::fixAddresses(const Data& password) { TW::validateAddress(account.coin, account.address)) { continue; } - const auto pubKey = key.getPublicKey(TW::publicKeyType(account.coin)); - account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); - account.publicKey = hex(pubKey.bytes); + updateAddressForAccount(key, account); } } break; } } +bool StoredKey::updateAddress(TWCoinType coin, const Data& password) { + auto account = std::find_if(accounts.begin(), accounts.end(), [coin](const auto &account) { + return account.coin == coin; + }); + if (account == accounts.end()) { + return false; + } + + switch (type) { + case StoredKeyType::mnemonicPhrase: { + const auto wallet = this->wallet(password); + const auto& derivationPath = account->derivationPath; + const auto key = wallet.getKey(account->coin, derivationPath); + updateAddressForAccount(key, *account); + } break; + + case StoredKeyType::privateKey: { + auto key = PrivateKey(payload.decrypt(password)); + updateAddressForAccount(key, *account); + } break; + } + + return true; +} + // ----------------- // Encoding/Decoding // ----------------- diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 6fae0da8a6d..f50afb25763 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -144,6 +144,12 @@ class StoredKey { /// the encryption password to re-derive addresses from private keys. void fixAddresses(const Data& password); + /// Re-derives address and public key for the specified chain. + /// + /// Use when address format for the given chain has been changed. This method needs + /// the encryption password to re-derive addresses from private keys. + bool updateAddress(TWCoinType coin, const Data& password); + private: /// Default constructor, private StoredKey() : type(StoredKeyType::mnemonicPhrase) {} @@ -169,6 +175,9 @@ class StoredKey { /// Re-derive account address if missing Account fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const; + + /// Re-derives public key and address for the specified account. + static void updateAddressForAccount(const PrivateKey& privKey, Account& account); }; } // namespace TW::Keystore diff --git a/src/TheOpenNetwork/Address.h b/src/TheOpenNetwork/Address.h index fe938a8f7b5..15b85bbd00a 100644 --- a/src/TheOpenNetwork/Address.h +++ b/src/TheOpenNetwork/Address.h @@ -48,7 +48,7 @@ class Address { /// Initializes an address with its parts explicit Address( int8_t workchainId, std::array hash, - bool userFriendly = true, bool bounceable = true, bool testOnly = false + bool userFriendly = true, bool bounceable = false, bool testOnly = false ) : addressData(workchainId, hash), isUserFriendly(userFriendly), isBounceable(bounceable), diff --git a/src/TheOpenNetwork/Entry.cpp b/src/TheOpenNetwork/Entry.cpp index b35c1c0e886..0cf95eb5ec6 100644 --- a/src/TheOpenNetwork/Entry.cpp +++ b/src/TheOpenNetwork/Entry.cpp @@ -17,7 +17,7 @@ bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, [[maybe_unused]] c } std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { - return Address(address).string(true, true, false); + return Address(address).string(true, false, false); } std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index b46ce5de1fa..a5f95bf46df 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -224,6 +224,15 @@ bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull } } +bool TWStoredKeyUpdateAddress(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password, enum TWCoinType coin) { + try { + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return key->impl.updateAddress(coin, passwordData); + } catch (...) { + return false; + } +} + TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key) { if (!key->impl.id) { return nullptr; diff --git a/src/interface/TWTONAddressConverter.cpp b/src/interface/TWTONAddressConverter.cpp index 8da3613f8b0..c9d2a26695f 100644 --- a/src/interface/TWTONAddressConverter.cpp +++ b/src/interface/TWTONAddressConverter.cpp @@ -11,13 +11,14 @@ using namespace TW; TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address) { auto& addressString = *reinterpret_cast(address); - if (!TheOpenNetwork::Address::isValid(addressString)) { + + try { + const TheOpenNetwork::Address addressTon(addressString); + auto bocEncoded = addressTon.toBoc(); + return TWStringCreateWithUTF8Bytes(bocEncoded.c_str()); + } catch (...) { return nullptr; } - - const TheOpenNetwork::Address addressTon(addressString); - auto bocEncoded = addressTon.toBoc(); - return TWStringCreateWithUTF8Bytes(bocEncoded.c_str()); } TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc) { @@ -38,3 +39,18 @@ TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc) { return nullptr; } } + +TWString *_Nullable TWTONAddressConverterToUserFriendly(TWString *_Nonnull address, bool bounceable, bool testnet) { + auto& addressString = *reinterpret_cast(address); + + try { + const TheOpenNetwork::Address addressTon(addressString); + + auto userFriendly = true; + const auto addressFormatted = addressTon.string(userFriendly, bounceable, testnet); + + return TWStringCreateWithUTF8Bytes(addressFormatted.c_str()); + } catch (...) { + return nullptr; + } +} diff --git a/swift/Tests/Blockchains/TheOpenNetworkTests.swift b/swift/Tests/Blockchains/TheOpenNetworkTests.swift index 2a64fc9204b..e8a5faf5dd6 100644 --- a/swift/Tests/Blockchains/TheOpenNetworkTests.swift +++ b/swift/Tests/Blockchains/TheOpenNetworkTests.swift @@ -11,26 +11,38 @@ class TheOpenNetworkTests: XCTestCase { let privateKey = PrivateKey(data: data!)! let publicKey = privateKey.getPublicKeyEd25519() let address = AnyAddress(publicKey: publicKey, coin: .ton) - XCTAssertEqual(address.description, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + XCTAssertEqual(address.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") } func testAddressFromPublicKey() { let data = Data(hexString: "f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41") let publicKey = PublicKey(data: data!, type: PublicKeyType.ed25519)! let address = AnyAddress(publicKey: publicKey, coin: .ton) - XCTAssertEqual(address.description, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + XCTAssertEqual(address.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") } func testAddressFromRawString() { let addressString = "0:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3" let address = AnyAddress(string: addressString, coin: .ton) - XCTAssertEqual(address!.description, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + XCTAssertEqual(address!.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") } - func testAddressFromUserFriendlyString() { + func testAddressFromBounceableString() { let addressString = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" let address = AnyAddress(string: addressString, coin: .ton) - XCTAssertEqual(address!.description, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + XCTAssertEqual(address!.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressFromUserFriendlyString() { + let addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + let address = AnyAddress(string: addressString, coin: .ton) + XCTAssertEqual(address!.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressToBounceable() { + let addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + let address = TONAddressConverter.toUserFriendly(address: addressString, bounceable: true, testnet: false) + XCTAssertEqual(address, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") } func testGenerateJettonAddress() { diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 7e17c1f3503..aeecba35076 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -308,7 +308,7 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ton: - let expectedResult = "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9"; + let expectedResult = "UQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUaT4"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .aptos: let expectedResult = "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; diff --git a/tests/chains/TheOpenNetwork/AddressTests.cpp b/tests/chains/TheOpenNetwork/AddressTests.cpp index a46d2d53fd6..a92e3a0e4cd 100644 --- a/tests/chains/TheOpenNetwork/AddressTests.cpp +++ b/tests/chains/TheOpenNetwork/AddressTests.cpp @@ -102,7 +102,7 @@ TEST(TheOpenNetworkAddress, FromPrivateKeyV4R2) { WalletV4R2 wallet(publicKey, WorkchainType::Basechain); const auto address = wallet.getAddress(); - ASSERT_EQ(address.string(), "EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl"); + ASSERT_EQ(address.string(), "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"); } TEST(TheOpenNetworkAddress, FromPublicKeyV4R2) { @@ -111,7 +111,7 @@ TEST(TheOpenNetworkAddress, FromPublicKeyV4R2) { WalletV4R2 wallet(publicKey, WorkchainType::Basechain); const auto address = wallet.getAddress(); - ASSERT_EQ(address.string(), "EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl"); + ASSERT_EQ(address.string(), "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"); } TEST(TheOpenNetworkAddress, GetJettonNotcoinAddress) { @@ -180,4 +180,58 @@ TEST(TheOpenNetworkAddress, FromBocError) { ASSERT_EQ(TWTONAddressConverterFromBoc(boc4.get()), nullptr); } +TEST(TheOpenNetworkAddress, ToUserFriendly) { + auto rawAddress = "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"; + auto bounceable = "EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl"; + auto nonBounceable = "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"; + auto bounceableTestnet = "kQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorvzv"; + auto nonBounceableTestnet = "0QCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorqEq"; + + // Raw to user friendly. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), true, false)), + bounceable + ); + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), false, false)), + nonBounceable + ); + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), true, true)), + bounceableTestnet + ); + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), false, true)), + nonBounceableTestnet + ); + + // Bounceable to non-bounceable. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(bounceable).get(), false, false)), + nonBounceable + ); + + // Non-bounceable to bounceable. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(nonBounceable).get(), true, false)), + bounceable + ); + + // Non-bounceable to non-bounceable. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(nonBounceable).get(), false, false)), + nonBounceable + ); +} + +TEST(TheOpenNetworkAddress, ToUserFriendlyError) { + // No "0:" prefix. + auto invalid1 = STRING("8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"); + ASSERT_EQ(TWTONAddressConverterToUserFriendly(invalid1.get(), true, false), nullptr); + + // Too short. + auto invalid2 = STRING("EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsor"); + ASSERT_EQ(TWTONAddressConverterToUserFriendly(invalid1.get(), false, false), nullptr); +} + } // namespace TW::TheOpenNetwork::tests diff --git a/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp b/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp index 003faec99a6..35f4dbd16fd 100644 --- a/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp +++ b/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp @@ -25,7 +25,7 @@ TEST(TWTheOpenNetwork, Address) { const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeTON)); const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressStr, "EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0"); + assertStringsEqual(addressStr, "UQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRRyQx"); } } // namespace TW::TheOpenNetwork::tests diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 96ceb19b711..3be107dee13 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -187,7 +187,7 @@ TEST(Coin, DeriveAddress) { EXPECT_EQ(address, "0:ef64d51f95ef17973b737277cfecbd2a8d551141be2f58f5fb362575fc3eb5b0"); break; case TWCoinTypeTON: - EXPECT_EQ(address, "EQAoYT8nMLfeNh6h0uIoK_wLm9JkvxiGxJDr6GRXJGu2ZhpY"); + EXPECT_EQ(address, "UQAoYT8nMLfeNh6h0uIoK_wLm9JkvxiGxJDr6GRXJGu2Zked"); break; case TWCoinTypeFIO: EXPECT_EQ(address, "FIO5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); diff --git a/tests/common/CoinAddressValidationTests.cpp b/tests/common/CoinAddressValidationTests.cpp index 06fea830573..b02fe64d635 100644 --- a/tests/common/CoinAddressValidationTests.cpp +++ b/tests/common/CoinAddressValidationTests.cpp @@ -419,7 +419,7 @@ TEST(Coin, ValidateAddressTheOpenNetwork) { EXPECT_TRUE(validateAddress(TWCoinTypeTON, "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae")); EXPECT_FALSE(validateAddress(TWCoinTypeTON, "8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae")); - ASSERT_EQ(normalizeAddress(TWCoinTypeTON, "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"), "EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl"); + ASSERT_EQ(normalizeAddress(TWCoinTypeTON, "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"), "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"); } } // namespace TW diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index af17983161a..dc2bd126f3f 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -278,6 +278,91 @@ TEST(TWStoredKey, fixAddresses) { EXPECT_TRUE(TWStoredKeyFixAddresses(key.get(), password.get())); } +// In this test, we add a TON account with an outdated bounceable (`EQ`) address to the key storage, +// and then check if `TWStoredKeyUpdateAddress` re-derives non-bounceable `UQ` instead. +TEST(TWStoredKey, UpdateAddressWithMnemonic) { + const auto keyName = STRING("key"); + const string passwordString = "password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + + // Create stored key with a dummy Bitcoin account. + auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + + const auto oldAddress = "EQDSRYDMMez8BdcOuPEiaR6aJZpO6EjlIwmOBFn14mMbnUtk"; + const auto newAddress = "UQDSRYDMMez8BdcOuPEiaR6aJZpO6EjlIwmOBFn14mMbnRah"; + const auto derivationPath = "m/44'/607'/0'"; + const auto extPubKey = ""; + const auto pubKey = "b191d35f81aa8b144aa91c90a6b887e0b165ad9c2933b1c5266eb5c4e8bea241"; + + // Add a TON account with an outdated address (bounceable). + TWStoredKeyAddAccount(key.get(), + STRING(oldAddress).get(), + TWCoinTypeTON, + STRING(derivationPath).get(), + STRING(pubKey).get(), + STRING(extPubKey).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); + + // Last step - update TON account address. + // Expect to have a non-bounceable address in the end. + ASSERT_TRUE(TWStoredKeyUpdateAddress(key.get(), password.get(), TWCoinTypeTON)); + const auto tonAccount = WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeTON, nullptr)); + assertStringsEqual(WRAPS(TWAccountAddress(tonAccount.get())), newAddress); +} + +// In this test, we add an Ethereum account with an outdated lowercase address to the key storage, +// and then check if `TWStoredKeyUpdateAddress` re-derives checksummed address instead. +TEST(TWStoredKey, UpdateAddressWithPrivateKey) { + const auto keyName = STRING("key"); + const auto privateKey = DATA("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + const string passwordString = "password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + + // Create stored key with a dummy Bitcoin account. + auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKey(privateKey.get(), keyName.get(), password.get(), TWCoinTypeBitcoin)); + + const auto oldAddress = "0xc2d7cf95645d33006175b78989035c7c9061d3f9"; + const auto newAddress = "0xC2D7CF95645D33006175B78989035C7c9061d3F9"; + const auto derivationPath = "m/44'/60'/0'"; + const auto extPubKey = ""; + const auto pubKey = "04efb99d9860f4dec4cb548a5722c27e9ef58e37fbab9719c5b33d55c216db49311221a01f638ce5f255875b194e0acaa58b19a89d2e56a864427298f826a7f887"; + + // Add an Ethereum account with an outdated address (lowercase). + TWStoredKeyAddAccount(key.get(), + STRING(oldAddress).get(), + TWCoinTypeEthereum, + STRING(derivationPath).get(), + STRING(pubKey).get(), + STRING(extPubKey).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); + + // Last step - update Ethereum account address. + // Expect to have a checksummed address in the end. + ASSERT_TRUE(TWStoredKeyUpdateAddress(key.get(), password.get(), TWCoinTypeEthereum)); + const auto ethAccount = WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeEthereum, nullptr)); + assertStringsEqual(WRAPS(TWAccountAddress(ethAccount.get())), newAddress); +} + +TEST(TWStoredKey, updateAddressInvalidPassword) { + const auto keyName = STRING("key"); + const string passwordString = "password"; + const string invalidPasswordString = "invalid password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + const auto invalidPassword = WRAPD(TWDataCreateWithBytes((const uint8_t*)invalidPasswordString.c_str(), invalidPasswordString.size())); + + auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + ASSERT_FALSE(TWStoredKeyUpdateAddress(key.get(), invalidPassword.get(), TWCoinTypeBitcoin)); +} + +TEST(TWStoredKey, updateAddressUnknownAccount) { + const auto keyName = STRING("key"); + const string passwordString = "password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + + auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + ASSERT_FALSE(TWStoredKeyUpdateAddress(key.get(), password.get(), TWCoinTypeEthereum)); +} + TEST(TWStoredKey, importInvalidKey) { auto bytes = TW::parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); diff --git a/wasm/tests/Blockchain/TheOpenNetwork.test.ts b/wasm/tests/Blockchain/TheOpenNetwork.test.ts index 6cd2eea2261..d7cda0c4a22 100644 --- a/wasm/tests/Blockchain/TheOpenNetwork.test.ts +++ b/wasm/tests/Blockchain/TheOpenNetwork.test.ts @@ -20,21 +20,21 @@ describe("TheOpenNetwork", () => { let address = AnyAddress.createWithPublicKey(publicKey, CoinType.ton) assert.equal(publicKey.description(), "f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41"); - assert.equal(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q"); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); }); it("test address from public key TheOpenNetwork", () => { const { PublicKey, PublicKeyType, HexCoding, AnyAddress, CoinType } = globalThis.core; let publicKey = PublicKey.createWithData(HexCoding.decode("f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41"), PublicKeyType.ed25519); let address = AnyAddress.createWithPublicKey(publicKey, CoinType.ton); - assert.equal(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q"); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); }); it("test address from raw string TheOpenNetwork", () => { const { AnyAddress, CoinType } = globalThis.core; let addressString = "0:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3"; let address = AnyAddress.createWithString(addressString, CoinType.ton); - assert.equal(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q"); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); }); it("test address invalid hex TheOpenNetwork", () => { @@ -55,7 +55,7 @@ describe("TheOpenNetwork", () => { const { AnyAddress, CoinType } = globalThis.core; let addressString = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q"; let address = AnyAddress.createWithString(addressString, CoinType.ton); - assert.equal(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q"); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); }); it("test address from user friendly invalid base64 decoding TheOpenNetwork", () => {