From 72c8b9e6513474521506ce79e5844a368f42a04c Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Mon, 30 Mar 2020 19:19:04 +0800 Subject: [PATCH 01/81] Update FIO block explorer url (#903) * :wrench: update FIO block explorer url * update coins.md --- coins.json | 18 ++++++++---------- docs/coins.md | 2 +- samples/android/app/build.gradle | 2 +- swift/Podfile.lock | 2 +- tests/FIO/TWCoinTypeTests.cpp | 8 ++++---- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/coins.json b/coins.json index 5fc87eb88cb..a5a3c759d8e 100644 --- a/coins.json +++ b/coins.json @@ -858,7 +858,6 @@ "curve": "ed25519Blake2bNano", "publicKeyType": "ed25519Blake2b", "url": "https://nano.org", - "rpcNodeInfo": "https://github.com/nanocurrency/nano-node", "explorer": { "url": "https://nanocrawler.cc", "txPath": "/explorer/block/", @@ -1073,18 +1072,17 @@ "derivationPath": "m/44'/235'/0'/0/0", "curve": "secp256k1", "publicKeyType": "secp256k1", - "url": "https://fio.foundation", - "rpcNodeInfo": "https://fio.foundation", + "url": "https://fioprotocol.io/", "explorer": { - "url": "https://fio.foundation", - "txPath": "/?", - "accountPath": "/?" + "url": "https://explorer.fioprotocol.io", + "txPath": "/transaction/", + "accountPath": "/account/" }, "info": { - "url": "https://fio.foundation", - "client": "https://fio.foundation", - "clientPublic": "", - "clientDocs": "https://fio.foundation" + "url": "https://fioprotocol.io", + "client": "https://github.com/fioprotocol/fio", + "clientPublic": "https://mainnet.fioprotocol.io", + "clientDocs": "https://developers.fioprotocol.io" } }, { diff --git a/docs/coins.md b/docs/coins.md index bd8b83ac4f5..2fae8caf2e8 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -27,7 +27,7 @@ This list is generated from [./coins.json](../coins.json) | 178 | POA Network | POA | | | | 194 | EOS | EOS | | | | 195 | Tron | TRX | | | -| 235 | FIO | FIO | | | +| 235 | FIO | FIO | | | | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | | 304 | IoTeX | IOTX | | | diff --git a/samples/android/app/build.gradle b/samples/android/app/build.gradle index 53d7c729e47..6dcd7757e4a 100644 --- a/samples/android/app/build.gradle +++ b/samples/android/app/build.gradle @@ -24,7 +24,7 @@ android { } project.ext { - walletcore_version = "2.0.3" + walletcore_version = "2.0.5" } dependencies { diff --git a/swift/Podfile.lock b/swift/Podfile.lock index 9250a5e40a0..46f957ed3b2 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -17,4 +17,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9af33e2495c8b16bfcea0f7779a0fcfe29e3ab60 -COCOAPODS: 1.9.0 +COCOAPODS: 1.9.1 diff --git a/tests/FIO/TWCoinTypeTests.cpp b/tests/FIO/TWCoinTypeTests.cpp index 60c5bf35440..b7bdaa4469a 100644 --- a/tests/FIO/TWCoinTypeTests.cpp +++ b/tests/FIO/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWFIOCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFIO)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFIO, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("f5axfpgffiqz"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFIO, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFIO)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFIO)); @@ -27,8 +27,8 @@ TEST(TWFIOCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFIO)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFIO)); assertStringsEqual(symbol, "FIO"); - assertStringsEqual(txUrl, "https://fio.foundation/?t123"); - assertStringsEqual(accUrl, "https://fio.foundation/?a12"); + assertStringsEqual(txUrl, "https://explorer.fioprotocol.io/transaction/930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3"); + assertStringsEqual(accUrl, "https://explorer.fioprotocol.io/account/f5axfpgffiqz"); assertStringsEqual(id, "fio"); assertStringsEqual(name, "FIO"); } From 72846ba7bc19f4f83cf88e764ae084fa76243146 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Sat, 4 Apr 2020 14:56:31 +0800 Subject: [PATCH 02/81] Update zcash/xlm block explorer (#915) * update zcash/xlm block explorer * Fix tests --- coins.json | 8 ++++---- tests/Stellar/TWCoinTypeTests.cpp | 8 ++++---- tests/Zcash/TWCoinTypeTests.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/coins.json b/coins.json index a5a3c759d8e..18047f66535 100644 --- a/coins.json +++ b/coins.json @@ -571,9 +571,9 @@ "xpub": "xpub", "xprv": "xprv", "explorer": { - "url": "https://sochain.com", - "txPath": "/tx/ZEC/", - "accountPath": "/address/ZEC/" + "url": "https://blockchair.com/zcash", + "txPath": "/transaction/", + "accountPath": "/address/" }, "info": { "url": "https://z.cash", @@ -682,7 +682,7 @@ "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { - "url": "https://stellarscan.io", + "url": "https://blockchair.com/stellar", "txPath": "/transaction/", "accountPath": "/account/" }, diff --git a/tests/Stellar/TWCoinTypeTests.cpp b/tests/Stellar/TWCoinTypeTests.cpp index a93bbfdbefa..52397d75d8a 100644 --- a/tests/Stellar/TWCoinTypeTests.cpp +++ b/tests/Stellar/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWStellarCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeStellar)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("d9aeabfa9d24df8c5755125f8af243b74cd3ff878656cfa72c566a8824bf6e84"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeStellar, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeStellar, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeStellar)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeStellar)); @@ -27,8 +27,8 @@ TEST(TWStellarCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeStellar)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeStellar)); assertStringsEqual(symbol, "XLM"); - assertStringsEqual(txUrl, "https://stellarscan.io/transaction/t123"); - assertStringsEqual(accUrl, "https://stellarscan.io/account/a12"); + assertStringsEqual(txUrl, "https://blockchair.com/stellar/transaction/d9aeabfa9d24df8c5755125f8af243b74cd3ff878656cfa72c566a8824bf6e84"); + assertStringsEqual(accUrl, "https://blockchair.com/stellar/account/GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52"); assertStringsEqual(id, "stellar"); assertStringsEqual(name, "Stellar"); } diff --git a/tests/Zcash/TWCoinTypeTests.cpp b/tests/Zcash/TWCoinTypeTests.cpp index fc887dd9891..da6af47b0d6 100644 --- a/tests/Zcash/TWCoinTypeTests.cpp +++ b/tests/Zcash/TWCoinTypeTests.cpp @@ -15,9 +15,9 @@ TEST(TWZcashCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZcash)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcash, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZcash, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZcash)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcash)); @@ -27,8 +27,8 @@ TEST(TWZcashCoinType, TWCoinType) { ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZcash)); ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZcash)); assertStringsEqual(symbol, "ZEC"); - assertStringsEqual(txUrl, "https://sochain.com/tx/ZEC/t123"); - assertStringsEqual(accUrl, "https://sochain.com/address/ZEC/a12"); + assertStringsEqual(txUrl, "https://blockchair.com/zcash/transaction/f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35"); + assertStringsEqual(accUrl, "https://blockchair.com/zcash/address/t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2"); assertStringsEqual(id, "zcash"); assertStringsEqual(name, "Zcash"); } From d3e86ea4007c271d763d7832fa4181b222c75329 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Mon, 6 Apr 2020 16:27:45 +0800 Subject: [PATCH 03/81] Change password type from string to data (#917) --- .../core/app/utils/TestKeyStore.kt | 44 ++++++++++-- include/TrustWalletCore/TWStoredKey.h | 42 ++++++------ src/Keystore/EncryptionParameters.cpp | 8 +-- src/Keystore/EncryptionParameters.h | 4 +- src/Keystore/StoredKey.cpp | 18 ++--- src/Keystore/StoredKey.h | 18 ++--- src/interface/TWStoredKey.cpp | 48 ++++++------- swift/Sources/KeyStore.swift | 26 +++---- swift/Sources/Wallet.swift | 6 +- swift/Tests/Keystore/AccountTests.swift | 9 +-- swift/Tests/Keystore/KeyStoreTests.swift | 12 ++-- swift/Tests/Keystore/KeystoreKeyTests.swift | 68 +++++++++++++++++-- swift/Tests/Keystore/WalletTests.swift | 2 +- .../Data/ethereum-wallet-address-no-0x.json | 26 ++++++- tests/Keystore/Data/myetherwallet.uu | 22 +++++- tests/Keystore/Data/web3j.json | 21 ++++++ tests/Keystore/StoredKeyTests.cpp | 24 +++++-- tests/interface/TWStoredKeyTests.cpp | 60 ++++++++++------ 18 files changed, 321 insertions(+), 137 deletions(-) create mode 100644 tests/Keystore/Data/web3j.json diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt index 3a19d9c2218..3832db95139 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt @@ -13,9 +13,9 @@ class TestKeyStore { @Test fun testDecryptMnemonic() { - val keyStore = StoredKey("Test Wallet", "password") - val result = keyStore.decryptMnemonic("wrong") - val result2 = keyStore.decryptMnemonic("password") + val keyStore = StoredKey("Test Wallet", "password".toByteArray()) + val result = keyStore.decryptMnemonic("wrong".toByteArray()) + val result2 = keyStore.decryptMnemonic("password".toByteArray()) assertNull(result) assertNotNull(result2) @@ -23,7 +23,7 @@ class TestKeyStore { @Test fun testRemoveCoins() { - val password = "password" + val password = "password".toByteArray() val keyStore = StoredKey("Test Wallet", password) val wallet = keyStore.wallet(password) @@ -37,15 +37,47 @@ class TestKeyStore { assertEquals(keyStore.account(0).coin(), CoinType.ETHEREUM) } + @Test + fun testLongHexPassword() { + val json = """ + { + "address": "34bae2218c254ed190c0f5b1dd4323aee8e7da09", + "id": "86066d8c-8dba-4d81-afd4-934e2a2b72a2", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "a4976ad73057007ad788d1f792db851d" + }, + "ciphertext": "5e4458d69964172c492616b751d6589b4ad7da4217dcfccecc3f4e515a934bb8", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "24c72d92bf88a4f7c7b3f5e3cb3620714d71fceabbb0bc6099f50c6d5d898e7c" + }, + "mac": "c15e3035ddcaca766dfc56648978d33e94d3c57d4a5e13fcf8b5f8dbb0902900" + } + } + """.trimIndent() + val password = "2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd" + val pass = password.toHexByteArray() + val keyStore = StoredKey.importJSON(json.toByteArray()) + val privateKey = keyStore.decryptPrivateKey(pass) + assertEquals(privateKey.toHex(), "0x043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b") + } + @Test fun testExportJSON() { - val password = "password" + val password = "password".toByteArray() val keyStore = StoredKey("Test Wallet", password) val json = keyStore.exportJSON() assertNotNull(json) val newKeyStore = StoredKey.importJSON(json) - val privateKey = newKeyStore.decryptPrivateKey("") + val privateKey = newKeyStore.decryptPrivateKey("".toByteArray()) assertNull(privateKey) } } diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 784339fd6e0..bb2048d236f 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -21,88 +21,88 @@ struct TWStoredKey; /// Loads a key from a file. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyLoad(TWString *_Nonnull path); +struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path); /// Imports a private key. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyImportPrivateKey(TWData *_Nonnull privateKey, TWString *_Nonnull name, TWString *_Nonnull password, enum TWCoinType coin); +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); /// Imports an HD wallet. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyImportHDWallet(TWString *_Nonnull mnemonic, TWString *_Nonnull name, TWString *_Nonnull password, enum TWCoinType coin); +struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); /// Imports a key from JSON. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nullable TWStoredKeyImportJSON(TWData *_Nonnull json); +struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json); /// Creates a new key. TW_EXPORT_STATIC_METHOD -struct TWStoredKey *_Nonnull TWStoredKeyCreate(TWString *_Nonnull name, TWString *_Nonnull password); +struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); TW_EXPORT_METHOD -void TWStoredKeyDelete(struct TWStoredKey *_Nonnull key); +void TWStoredKeyDelete(struct TWStoredKey* _Nonnull key); /// Stored key uniqie identifier. TW_EXPORT_PROPERTY -TWString *_Nullable TWStoredKeyIdentifier(struct TWStoredKey *_Nonnull key); +TWString* _Nullable TWStoredKeyIdentifier(struct TWStoredKey* _Nonnull key); /// Stored key namer. TW_EXPORT_PROPERTY -TWString *_Nonnull TWStoredKeyName(struct TWStoredKey *_Nonnull key); +TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key); /// Whether this key is a mnemonic phrase for a HD wallet. TW_EXPORT_PROPERTY -bool TWStoredKeyIsMnemonic(struct TWStoredKey *_Nonnull key); +bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key); /// The number of accounts. TW_EXPORT_PROPERTY -size_t TWStoredKeyAccountCount(struct TWStoredKey *_Nonnull key); +size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key); /// Returns the account at a given index. TW_EXPORT_METHOD -struct TWAccount *_Nullable TWStoredKeyAccount(struct TWStoredKey *_Nonnull key, size_t index); +struct TWAccount* _Nullable TWStoredKeyAccount(struct TWStoredKey* _Nonnull key, size_t index); /// Returns the account for a specific coin, creating it if necessary. TW_EXPORT_METHOD -struct TWAccount *_Nullable TWStoredKeyAccountForCoin(struct TWStoredKey *_Nonnull key, enum TWCoinType coin, struct TWHDWallet *_Nullable wallet); +struct TWAccount* _Nullable TWStoredKeyAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, struct TWHDWallet* _Nullable wallet); /// Remove the account for a specific coin TW_EXPORT_METHOD -void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey *_Nonnull key, enum TWCoinType coin); +void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin); /// Adds a new account. TW_EXPORT_METHOD -void TWStoredKeyAddAccount(struct TWStoredKey *_Nonnull key, TWString *_Nonnull address, TWString *_Nonnull derivationPath, TWString *_Nonnull extetndedPublicKey); +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey); /// Saves the key to a file. TW_EXPORT_METHOD -bool TWStoredKeyStore(struct TWStoredKey *_Nonnull key, TWString *_Nonnull path); +bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path); /// Decrypts the private key. TW_EXPORT_METHOD -TWData *_Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Decrypts the mnemonic phrase. TW_EXPORT_METHOD -TWString *_Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Returns the private key for a specific coin. TW_EXPORT_METHOD -struct TWPrivateKey *_Nullable TWStoredKeyPrivateKey(struct TWStoredKey *_Nonnull key, enum TWCoinType coin, TWString *_Nonnull password); +struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password); /// Dercrypts and returns the HD Wallet for mnemonic phrase keys. TW_EXPORT_METHOD -struct TWHDWallet *_Nullable TWStoredKeyWallet(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Exports the key as JSON TW_EXPORT_METHOD -TWData *_Nullable TWStoredKeyExportJSON(struct TWStoredKey *_Nonnull key); +TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key); /// Fills in empty and invalid addresses. /// /// This method needs the encryption password to re-derive addresses from private keys. /// @returns `false` if the password is incorrect. TW_EXPORT_METHOD -bool TWStoredKeyFixAddresses(struct TWStoredKey *_Nonnull key, TWString *_Nonnull password); +bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); TW_EXTERN_C_END diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 44f4af3f921..91540ad39f2 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -28,7 +28,7 @@ static Data computeMAC(Iter begin, Iter end, Data key) { return Hash::keccak256(data); } -EncryptionParameters::EncryptionParameters(const std::string& password, Data data) : mac() { +EncryptionParameters::EncryptionParameters(const Data& password, Data data) : mac() { auto scryptParams = boost::get(kdfParams); auto derivedKey = Data(scryptParams.desiredKeyLength); scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), @@ -50,21 +50,21 @@ EncryptionParameters::~EncryptionParameters() { std::fill(encrypted.begin(), encrypted.end(), 0); } -Data EncryptionParameters::decrypt(const std::string& password) const { +Data EncryptionParameters::decrypt(const Data& password) const { auto derivedKey = Data(); auto mac = Data(); if (kdfParams.which() == 0) { auto scryptParams = boost::get(kdfParams); derivedKey.resize(scryptParams.defaultDesiredKeyLength); - scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), + scrypt(password.data(), password.size(), scryptParams.salt.data(), scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), scryptParams.defaultDesiredKeyLength); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); } else if (kdfParams.which() == 1) { auto pbkdf2Params = boost::get(kdfParams); derivedKey.resize(pbkdf2Params.defaultDesiredKeyLength); - pbkdf2_hmac_sha256(reinterpret_cast(password.data()), password.size(), pbkdf2Params.salt.data(), + pbkdf2_hmac_sha256(password.data(), password.size(), pbkdf2Params.salt.data(), pbkdf2Params.salt.size(), pbkdf2Params.iterations, derivedKey.data(), pbkdf2Params.defaultDesiredKeyLength); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index db2e49cc1ba..eec3b44a9f3 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -54,13 +54,13 @@ struct EncryptionParameters { /// Initializes `EncryptionParameters` by encrypting data with a password /// using standard values. - EncryptionParameters(const std::string& password, Data data); + EncryptionParameters(const Data& password, Data data); /// Initializes `EncryptionParameters` with a JSON object. EncryptionParameters(const nlohmann::json& json); /// Decrypts the payload with the given password. - Data decrypt(const std::string& password) const; + Data decrypt(const Data& password) const; /// Saves `this` as a JSON object. nlohmann::json json() const; diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 476fa336d7e..b59f6de733b 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -27,7 +27,7 @@ using namespace TW; using namespace TW::Keystore; -StoredKey StoredKey::createWithMnemonic(const std::string& name, const std::string& password, const std::string& mnemonic) { +StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic) { if (!HDWallet::isValid(mnemonic)) { throw std::invalid_argument("Invalid mnemonic"); } @@ -37,7 +37,7 @@ StoredKey StoredKey::createWithMnemonic(const std::string& name, const std::stri return key; } -StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const std::string& password) { +StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password) { const auto wallet = TW::HDWallet(128, ""); const auto& mnemonic = wallet.mnemonic; assert(HDWallet::isValid(mnemonic)); @@ -46,7 +46,7 @@ StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const std return key; } -StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const std::string& password, const std::string& mnemonic, TWCoinType coin) { +StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin) { StoredKey key = createWithMnemonic(name, password, mnemonic); const auto wallet = HDWallet(mnemonic, ""); @@ -58,12 +58,12 @@ StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name return key; } -StoredKey StoredKey::createWithPrivateKey(const std::string& name, const std::string& password, const Data& privateKeyData) { +StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData) { StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKeyData); return key; } -StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const std::string& password, TWCoinType coin, const Data& privateKeyData) { +StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData) { const auto curve = TW::curve(coin); if (!PrivateKey::isValid(privateKeyData, curve)) { throw std::invalid_argument("Invalid private key data"); @@ -78,13 +78,13 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const std::string& password, Data data) +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, Data data) : type(type), id(), name(std::move(name)), payload(password, data), accounts() { boost::uuids::random_generator gen; id = boost::lexical_cast(gen()); } -const HDWallet StoredKey::wallet(const std::string& password) const { +const HDWallet StoredKey::wallet(const Data& password) const { if (type != StoredKeyType::mnemonicPhrase) { throw std::invalid_argument("Invalid account requested."); } @@ -139,7 +139,7 @@ void StoredKey::removeAccount(TWCoinType coin) { } -const PrivateKey StoredKey::privateKey(TWCoinType coin, const std::string& password) { +const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { switch (type) { case StoredKeyType::mnemonicPhrase: { const auto wallet = this->wallet(password); @@ -151,7 +151,7 @@ const PrivateKey StoredKey::privateKey(TWCoinType coin, const std::string& passw } } -void StoredKey::fixAddresses(const std::string& password) { +void StoredKey::fixAddresses(const Data& password) { switch (type) { case StoredKeyType::mnemonicPhrase: { const auto wallet = this->wallet(password); diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index c742ba5423a..00e29ab969d 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -43,23 +43,23 @@ class StoredKey { /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonic(const std::string& name, const std::string& password, const std::string& mnemonic); + static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic); /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicRandom(const std::string& name, const std::string& password); + static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password); /// Create a new StoredKey, with the given name, mnemonic and password, and also add the default address for the given coin.. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const std::string& password, const std::string& mnemonic, TWCoinType coin); + static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin); /// Create a new StoredKey, with the given name and private key. /// @throws std::invalid_argument if privateKeyData is not a vald private key - static StoredKey createWithPrivateKey(const std::string& name, const std::string& password, const Data& privateKeyData); + static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData); /// Create a new StoredKey, with the given name and private key, and also add the default address for the given coin.. /// @throws std::invalid_argument if privateKeyData is not a vald private key - static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const std::string& password, TWCoinType coin, const Data& privateKeyData); + static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData); /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -67,7 +67,7 @@ class StoredKey { /// Returns the HDWallet for this key. /// /// @throws std::invalid_argument if this key is of a type other than `mnemonicPhrase`. - const HDWallet wallet(const std::string& password) const; + const HDWallet wallet(const Data& password) const; /// Returns the account for a specific coin, creating it if necessary and /// the provided wallet is not `nullptr`. @@ -86,7 +86,7 @@ class StoredKey { /// /// @throws std::invalid_argument if this key is of a type other than /// `mnemonicPhrase` and a coin other than the default is requested. - const PrivateKey privateKey(TWCoinType coin, const std::string& password); + const PrivateKey privateKey(TWCoinType coin, const Data& password); /// Loads and decrypts a stored key from a file. /// @@ -110,7 +110,7 @@ class StoredKey { /// /// Use to fix legacy wallets with invalid address data. This method needs /// the encryption password to re-derive addresses from private keys. - void fixAddresses(const std::string& password); + void fixAddresses(const Data& password); private: /// Default constructor, private @@ -119,7 +119,7 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This contstructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const std::string& password, Data data); + StoredKey(StoredKeyType type, std::string name, const Data& password, Data data); }; } // namespace TW::Keystore diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index 795cfa38ce9..28dd3d992c6 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -25,29 +25,29 @@ struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path) { } } -struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWString* _Nonnull password) { +struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { const auto& nameString = *reinterpret_cast(name); - const auto& passwordString = *reinterpret_cast(password); - return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordString) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordData) }; } -struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWString* _Nonnull password, enum TWCoinType coin) { +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { try { const auto& privateKeyData = *reinterpret_cast(privateKey); const auto& nameString = *reinterpret_cast(name); - const auto& passwordString = *reinterpret_cast(password); - return new TWStoredKey{ StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordString, coin, privateKeyData) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData) }; } catch (...) { return nullptr; } } -struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWString* _Nonnull password, enum TWCoinType coin) { +struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { try { const auto& mnemonicString = *reinterpret_cast(mnemonic); const auto& nameString = *reinterpret_cast(name); - const auto& passwordString = *reinterpret_cast(password); - return new TWStoredKey{ StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordString, mnemonicString, coin) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin) }; } catch (...) { return nullptr; } @@ -124,20 +124,20 @@ bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) } } -TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - const auto data = key->impl.payload.decrypt(passwordString); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + const auto data = key->impl.payload.decrypt(passwordData); return TWDataCreateWithBytes(data.data(), data.size()); } catch (...) { return nullptr; } } -TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - const auto data = key->impl.payload.decrypt(passwordString); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + const auto data = key->impl.payload.decrypt(passwordData); const auto string = std::string(data.begin(), data.end()); return TWStringCreateWithUTF8Bytes(string.c_str()); } catch (...) { @@ -145,19 +145,19 @@ TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, } } -struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWString* _Nonnull password) { +struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - return new TWPrivateKey{ key->impl.privateKey(coin, passwordString) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWPrivateKey{ key->impl.privateKey(coin, passwordData) }; } catch (...) { return nullptr; } } -struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - return new TWHDWallet{ key->impl.wallet(passwordString) }; + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWHDWallet{ key->impl.wallet(passwordData) }; } catch (...) { return nullptr; } @@ -168,10 +168,10 @@ TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key) { return TWDataCreateWithBytes(reinterpret_cast(json.data()), json.size()); } -bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWString* _Nonnull password) { +bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { - const auto& passwordString = *reinterpret_cast(password); - key->impl.fixAddresses(passwordString); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + key->impl.fixAddresses(passwordData); return true; } catch (...) { return false; diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 35e46d9ab0d..d86ce0014c2 100755 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -75,7 +75,7 @@ public final class KeyStore { /// Creates a new wallet. HD default by default public func createWallet(name: String, password: String, coins: [CoinType]) throws -> Wallet { - let key = StoredKey(name: name, password: password) + let key = StoredKey(name: name, password: Data(password.utf8)) return try saveCreatedWallet(for: key, password: password, coins: coins) } @@ -101,7 +101,7 @@ public final class KeyStore { /// Remove accounts from a wallet. public func removeAccounts(wallet: Wallet, coins: [CoinType], password: String) throws -> Wallet { - guard wallet.key.decryptPrivateKey(password: password) != nil else { + guard wallet.key.decryptPrivateKey(password: Data(password.utf8)) != nil else { throw Error.invalidPassword } @@ -130,7 +130,7 @@ public final class KeyStore { guard let key = StoredKey.importJSON(json: json) else { throw Error.invalidKey } - guard let data = key.decryptPrivateKey(password: password) else { + guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } @@ -159,7 +159,7 @@ public final class KeyStore { /// - coin: coin to use for this wallet /// - Returns: new wallet public func `import`(privateKey: PrivateKey, name: String, password: String, coin: CoinType) throws -> Wallet { - guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: password, coin: coin) else { + guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin) else { throw Error.invalidKey } let url = makeAccountURL() @@ -180,7 +180,7 @@ public final class KeyStore { /// - coins: coins to add /// - Returns: new account public func `import`(mnemonic: String, name: String, encryptPassword: String, coins: [CoinType]) throws -> Wallet { - guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: encryptPassword, coin: coins.first ?? .ethereum) else { + guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coins.first ?? .ethereum) else { throw Error.invalidMnemonic } let url = makeAccountURL() @@ -211,12 +211,12 @@ public final class KeyStore { throw Error.accountNotFound } - if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWallet(mnemonic: mnemonic, name: "", password: newPassword, coin: coin) { + if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWallet(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } return json - } else if let newKey = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "", password: newPassword, coin: coin) { + } else if let newKey = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } @@ -233,7 +233,7 @@ public final class KeyStore { /// - password: account password /// - Returns: private key data for encrypted keys or menmonic phrase for HD wallets public func exportPrivateKey(wallet: Wallet, password: String) throws -> Data { - guard let key = wallet.key.decryptPrivateKey(password: password) else { + guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } return key @@ -247,7 +247,7 @@ public final class KeyStore { /// - Returns: mnemonic phrase /// - Throws: `EncryptError.invalidMnemonic` if the account is not an HD wallet. public func exportMnemonic(wallet: Wallet, password: String) throws -> String { - guard let mnemonic = wallet.key.decryptMnemonic(password: password) else { + guard let mnemonic = wallet.key.decryptMnemonic(password: Data(password.utf8)) else { throw Error.invalidPassword } return mnemonic @@ -278,7 +278,7 @@ public final class KeyStore { fatalError("Missing wallet") } - guard var privateKeyData = wallet.key.decryptPrivateKey(password: password) else { + guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } defer { @@ -291,10 +291,10 @@ public final class KeyStore { } if let mnemonic = checkMnemonic(privateKeyData), - let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: newPassword, coin: coins[0]) { + let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { wallets[index].key = key } else if let key = StoredKey.importPrivateKey( - privateKey: privateKeyData, name: newName, password: newPassword, coin: coins[0]) { + privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { wallets[index].key = key } else { throw Error.invalidKey @@ -310,7 +310,7 @@ public final class KeyStore { fatalError("Missing wallet") } - guard var privateKey = wallet.key.decryptPrivateKey(password: password) else { + guard var privateKey = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidKey } defer { diff --git a/swift/Sources/Wallet.swift b/swift/Sources/Wallet.swift index e702ea105cf..036d2a22d7e 100755 --- a/swift/Sources/Wallet.swift +++ b/swift/Sources/Wallet.swift @@ -36,7 +36,7 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the account /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func getAccount(password: String, coin: CoinType) throws -> Account { - let wallet = key.wallet(password: password) + let wallet = key.wallet(password: Data(password.utf8)) guard let account = key.accountForCoin(coin: coin, wallet: wallet) else { throw KeyStore.Error.invalidPassword } @@ -51,7 +51,7 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the added accounts /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func getAccounts(password: String, coins: [CoinType]) throws -> [Account] { - guard let wallet = key.wallet(password: password) else { + guard let wallet = key.wallet(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } return coins.compactMap({ key.accountForCoin(coin: $0, wallet: wallet) }) @@ -65,7 +65,7 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the private key /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func privateKey(password: String, coin: CoinType) throws -> PrivateKey { - guard let pk = key.privateKey(coin: coin, password: password) else { + guard let pk = key.privateKey(coin: coin, password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } return pk diff --git a/swift/Tests/Keystore/AccountTests.swift b/swift/Tests/Keystore/AccountTests.swift index 7d1d184173f..8fa934e9e11 100755 --- a/swift/Tests/Keystore/AccountTests.swift +++ b/swift/Tests/Keystore/AccountTests.swift @@ -9,7 +9,7 @@ import XCTest class AccountTests: XCTestCase { let words = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal" - let password = "password" + let password = Data("password".utf8) func testSignHash() throws { let privateKeyData = Data(hexString: "D30519BCAE8D180DBFCC94FE0B8383DC310185B0BE97B4365083EBCECCD75759")! @@ -42,9 +42,10 @@ class AccountTests: XCTestCase { func testExtendedPubkey() throws { let key = StoredKey.importHDWallet(mnemonic: words, name: "name", password: password, coin: .ethereum)! let wallet = Wallet(keyURL: URL(fileURLWithPath: "/"), key: key) - _ = try wallet.getAccount(password: password, coin: .bitcoin) - _ = try wallet.getAccount(password: password, coin: .bitcoinCash) - _ = try wallet.getAccount(password: password, coin: .ethereumClassic) + let stringPass = String(data: password, encoding: .utf8)! + _ = try wallet.getAccount(password: stringPass, coin: .bitcoin) + _ = try wallet.getAccount(password: stringPass, coin: .bitcoinCash) + _ = try wallet.getAccount(password: stringPass, coin: .ethereumClassic) XCTAssertEqual(wallet.accounts[0].extendedPublicKey, "") XCTAssertEqual(wallet.accounts[1].extendedPublicKey, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9") diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 928f5194345..f4e56822dd6 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -90,7 +90,7 @@ class KeyStoreTests: XCTestCase { let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) let savedWallet = savedKeyStore.wallets.first(where: { $0 == wallet })! - let data = savedWallet.key.decryptPrivateKey(password: "testpassword") + let data = savedWallet.key.decryptPrivateKey(password: Data("testpassword".utf8)) let mnemonic = String(data: data!, encoding: .ascii) XCTAssertEqual(savedWallet.accounts.count, coins.count) @@ -110,7 +110,7 @@ class KeyStoreTests: XCTestCase { let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) let savedWallet = savedKeyStore.wallets.first(where: { $0 == wallet })! - let data = savedWallet.key.decryptPrivateKey(password: "password") + let data = savedWallet.key.decryptPrivateKey(password: Data("password".utf8)) let mnemonic = String(data: data!, encoding: .ascii) XCTAssertEqual(savedWallet.accounts.count, coins.count) @@ -160,11 +160,11 @@ class KeyStoreTests: XCTestCase { func testImportKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let privateKeyData = Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")! - let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: "password", coin: .ethereum)! + let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: Data("password".utf8), coin: .ethereum)! let json = key.exportJSON()! let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) - let storedData = wallet.key.decryptPrivateKey(password: "newPassword") + let storedData = wallet.key.decryptPrivateKey(password: Data("newPassword".utf8)) XCTAssertNotNil(keyStore.keyWallet) XCTAssertNotNil(storedData) @@ -176,7 +176,7 @@ class KeyStoreTests: XCTestCase { let privateKey = PrivateKey(data: Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")!)! let wallet = try keyStore.import(privateKey: privateKey, name: "name", password: "password", coin: .ethereum) - let storedData = wallet.key.decryptPrivateKey(password: "password") + let storedData = wallet.key.decryptPrivateKey(password: Data("password".utf8)) XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) @@ -189,7 +189,7 @@ class KeyStoreTests: XCTestCase { func testImportWallet() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum]) - let storedData = wallet.key.decryptMnemonic(password: "newPassword") + let storedData = wallet.key.decryptMnemonic(password: Data("newPassword".utf8)) XCTAssertNotNil(storedData) XCTAssertEqual(wallet.accounts.count, 1) diff --git a/swift/Tests/Keystore/KeystoreKeyTests.swift b/swift/Tests/Keystore/KeystoreKeyTests.swift index 580a1f8dae1..cad779e3105 100755 --- a/swift/Tests/Keystore/KeystoreKeyTests.swift +++ b/swift/Tests/Keystore/KeystoreKeyTests.swift @@ -25,21 +25,21 @@ class KeystoreKeyTests: XCTestCase { let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! let key = StoredKey.load(path: url.path)! - XCTAssertNil(key.decryptPrivateKey(password: "password")) + XCTAssertNil(key.decryptPrivateKey(password: Data("password".utf8))) } func testDecrypt() { let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! let key = StoredKey.load(path: url.path)! - let privateKey = key.decryptPrivateKey(password: "testpassword")! + let privateKey = key.decryptPrivateKey(password: Data("testpassword".utf8))! XCTAssertEqual(privateKey.hexString, "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d") } func testCreateWallet() { let privateKeyData = Data(hexString: "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266")! - let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: "password", coin: .ethereum)! - let decrypted = key.decryptPrivateKey(password: "password")! + let key = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "name", password: Data("password".utf8), coin: .ethereum)! + let decrypted = key.decryptPrivateKey(password: Data("password".utf8))! XCTAssertEqual(decrypted.hexString, privateKeyData.hexString) } @@ -59,4 +59,64 @@ class KeystoreKeyTests: XCTestCase { XCTAssertEqual(account.address.description, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y") } + + func testLongHexPassword() { + let json = """ + { + "address": "34bae2218c254ed190c0f5b1dd4323aee8e7da09", + "id": "86066d8c-8dba-4d81-afd4-934e2a2b72a2", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "a4976ad73057007ad788d1f792db851d" + }, + "ciphertext": "5e4458d69964172c492616b751d6589b4ad7da4217dcfccecc3f4e515a934bb8", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "24c72d92bf88a4f7c7b3f5e3cb3620714d71fceabbb0bc6099f50c6d5d898e7c" + }, + "mac": "c15e3035ddcaca766dfc56648978d33e94d3c57d4a5e13fcf8b5f8dbb0902900" + } + } + """.data(using: .utf8)! + let keystore = StoredKey.importJSON(json: json)! + let password = Data(hexString: "2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd")! + let data = keystore.decryptPrivateKey(password: password) + XCTAssertEqual(data?.hexString, "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b") + } + + func testLongPassword() { + let json = """ + { + "address": "b92bcd5b14d2d70651d483c8f03bae79223b88ec", + "id": "480fc670-e5e6-4b39-ba33-61f54ce792f9", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "5d88369e4306bcc98e1fe70e7710f3a0" + }, + "ciphertext": "de9bac5d1ca94cdd067a51275a56f0766ed3631de108609ee240c42a0994f97e", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "3849479dd4880793cfd92ebfeb30c60dccf04f3f76a2778fe89bead4237ddad4" + }, + "mac": "361df97ff456009849ab1ffd4e71b61d7e66c9d2071c5a7563d1bbbdb0b2653b" + } + } + """.data(using: .utf8)! + let keystore = StoredKey.importJSON(json: json)! + let password = "2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd".data(using: .utf8)! + let data = keystore.decryptPrivateKey(password: password) + XCTAssertEqual(data?.hexString, "4357b2f9a6150ba969bc52f01c98cce5313595fe49f2d08303759c73e5c7a46c") + } } diff --git a/swift/Tests/Keystore/WalletTests.swift b/swift/Tests/Keystore/WalletTests.swift index 983797344cc..6b31424870e 100755 --- a/swift/Tests/Keystore/WalletTests.swift +++ b/swift/Tests/Keystore/WalletTests.swift @@ -21,7 +21,7 @@ class WalletTests: XCTestCase { func testIdentifier() throws { let url = URL(string: "UTC--2018-07-23T15-42-07.380692005-42000--6E199F01-FA96-4ADF-9A4B-36EE4B1E08C7")! - let key = StoredKey(name: "name", password: "password") + let key = StoredKey(name: "name", password: Data("password".utf8)) let wallet = Wallet(keyURL: url, key: key) XCTAssertEqual(wallet.identifier, "UTC--2018-07-23T15-42-07.380692005-42000--6E199F01-FA96-4ADF-9A4B-36EE4B1E08C7") } diff --git a/tests/Keystore/Data/ethereum-wallet-address-no-0x.json b/tests/Keystore/Data/ethereum-wallet-address-no-0x.json index 87f0f870863..43cc9c51a9a 100644 --- a/tests/Keystore/Data/ethereum-wallet-address-no-0x.json +++ b/tests/Keystore/Data/ethereum-wallet-address-no-0x.json @@ -1 +1,25 @@ -{"activeAccounts":[{"address":"Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309","derivationPath":"m/44'/60'/0'/0/0"}],"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"630d0a62bc8a6187c47fdb6c8b7bd38c"},"ciphertext":"4ec6d0157bde53900020d45c9db9235acdb366f727fe456b5a4536f20ea0848c","kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"4c3d38c0d114cb92b2cb4a44e92e27cf588f9ecd59840c8dea087e89d350d836"},"mac":"9748b96453a03bcdec2e714153d8d69f16cde7d398750932281f68844a6b2616"},"id":"48aa6b37-8276-44fe-aa4e-819145771183","type":"private-key","version":3} \ No newline at end of file +{ + "activeAccounts": [{ + "address": "Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", + "derivationPath": "m/44'/60'/0'/0/0" + }], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "630d0a62bc8a6187c47fdb6c8b7bd38c" + }, + "ciphertext": "4ec6d0157bde53900020d45c9db9235acdb366f727fe456b5a4536f20ea0848c", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "4c3d38c0d114cb92b2cb4a44e92e27cf588f9ecd59840c8dea087e89d350d836" + }, + "mac": "9748b96453a03bcdec2e714153d8d69f16cde7d398750932281f68844a6b2616" + }, + "id": "48aa6b37-8276-44fe-aa4e-819145771183", + "type": "private-key", + "version": 3 +} \ No newline at end of file diff --git a/tests/Keystore/Data/myetherwallet.uu b/tests/Keystore/Data/myetherwallet.uu index 10e2dab212d..751f16207e5 100755 --- a/tests/Keystore/Data/myetherwallet.uu +++ b/tests/Keystore/Data/myetherwallet.uu @@ -1 +1,21 @@ -{"version":3,"id":"beb60dc6-7553-4215-9aa7-4b26883da373","address":"8562fcccbae3019f5a716997609b301ac31fe04a","Crypto":{"ciphertext":"ed9de8b5568bac97c8199b82b197b61977440d360acec2ecf4f74f3da91308ab","cipherparams":{"iv":"cf6730826d91ce908aa15771952ab3e8"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"2904ecde7e2e5c6cb89291b2741bdd3dff3088f98a7013d63084c746efd7524f","n":1024,"r":8,"p":1},"mac":"cdb237916f98742d4e8f313638e16b04a4a780e69dd61c6407932be42d269c95"}} \ No newline at end of file +{ + "version": 3, + "id": "beb60dc6-7553-4215-9aa7-4b26883da373", + "address": "8562fcccbae3019f5a716997609b301ac31fe04a", + "Crypto": { + "ciphertext": "ed9de8b5568bac97c8199b82b197b61977440d360acec2ecf4f74f3da91308ab", + "cipherparams": { + "iv": "cf6730826d91ce908aa15771952ab3e8" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "2904ecde7e2e5c6cb89291b2741bdd3dff3088f98a7013d63084c746efd7524f", + "n": 1024, + "r": 8, + "p": 1 + }, + "mac": "cdb237916f98742d4e8f313638e16b04a4a780e69dd61c6407932be42d269c95" + } +} \ No newline at end of file diff --git a/tests/Keystore/Data/web3j.json b/tests/Keystore/Data/web3j.json new file mode 100644 index 00000000000..b47dc6a2d51 --- /dev/null +++ b/tests/Keystore/Data/web3j.json @@ -0,0 +1,21 @@ +{ + "address": "34bae2218c254ed190c0f5b1dd4323aee8e7da09", + "id": "86066d8c-8dba-4d81-afd4-934e2a2b72a2", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "a4976ad73057007ad788d1f792db851d" + }, + "ciphertext": "5e4458d69964172c492616b751d6589b4ad7da4217dcfccecc3f4e515a934bb8", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "24c72d92bf88a4f7c7b3f5e3cb3620714d71fceabbb0bc6099f50c6d5d898e7c" + }, + "mac": "c15e3035ddcaca766dfc56648978d33e94d3c57d4a5e13fcf8b5f8dbb0902900" + } +} \ No newline at end of file diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp index d5648c661e8..5e385323ac0 100644 --- a/tests/Keystore/StoredKeyTests.cpp +++ b/tests/Keystore/StoredKeyTests.cpp @@ -8,6 +8,7 @@ #include "Coin.h" #include "HexCoding.h" +#include "Data.h" #include "PrivateKey.h" #include @@ -19,7 +20,8 @@ namespace TW::Keystore { using namespace std; -const auto password = "password"; +const auto passwordString = "password"; +const auto password = TW::data(string(passwordString)); const auto mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; const TWCoinType coinTypeBc = TWCoinTypeBitcoin; @@ -176,14 +178,14 @@ TEST(StoredKey, LoadLegacyPrivateKey) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-private-key.json"); EXPECT_EQ(key.id, "3051ca7d-3d36-4a4a-acc2-09e9083732b0"); EXPECT_EQ(key.accounts[0].coin(), TWCoinTypeEthereum); - EXPECT_EQ(hex(key.payload.decrypt("testpassword")), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } TEST(StoredKey, LoadLivepeerKey) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/livepeer.json"); EXPECT_EQ(key.id, "70ea3601-ee21-4e94-a7e4-66255a987d22"); EXPECT_EQ(key.accounts[0].coin(), TWCoinTypeEthereum); - EXPECT_EQ(hex(key.payload.decrypt("Radchenko")), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); } TEST(StoredKey, LoadPBKDF2Key) { @@ -196,7 +198,7 @@ TEST(StoredKey, LoadPBKDF2Key) { EXPECT_EQ(boost::get(payload.kdfParams).iterations, 262144); EXPECT_EQ(hex(boost::get(payload.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); - EXPECT_EQ(hex(payload.decrypt("testpassword")), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } TEST(StoredKey, LoadLegacyMnemonic) { @@ -216,6 +218,14 @@ TEST(StoredKey, LoadLegacyMnemonic) { EXPECT_EQ(key.accounts[1].extendedPublicKey, "zpub6r97AegwVxVbJeuDAWP5KQgX5y4Q6KyFUrsFQRn8yzSXrnmpwg1ZKHSWwECR1Kiqgr4h93WN5kdS48KC6hVFniuZHqVFXjULZZkCwurqyPn"); } +TEST(StoredKey, LoadFromWeb3j) { + const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/web3j.json"); + EXPECT_EQ(key.id, "86066d8c-8dba-4d81-afd4-934e2a2b72a2"); + const auto password = parse_hex("2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd"); + const auto data = key.payload.decrypt(password); + EXPECT_EQ(hex(data), "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b"); +} + TEST(StoredKey, ReadWallet) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); @@ -250,12 +260,12 @@ TEST(StoredKey, InvalidPassword) { TEST(StoredKey, EmptyAccounts) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/empty-accounts.json"); - ASSERT_NO_THROW(key.payload.decrypt("testpassword")); + ASSERT_NO_THROW(key.payload.decrypt(TW::data("testpassword"))); } TEST(StoredKey, Decrypt) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - const auto privateKey = key.payload.decrypt("testpassword"); + const auto privateKey = key.payload.decrypt(TW::data("testpassword")); EXPECT_EQ(hex(privateKey), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } @@ -310,7 +320,7 @@ TEST(StoredKey, MissingAddress) { TEST(StoredKey, EtherWalletAddressNo0x) { auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/ethereum-wallet-address-no-0x.json"); - key.fixAddresses("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53"); + key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); } diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index b769752db9b..66704ee4c6f 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -10,7 +10,7 @@ #include #include #include - +#include #include "../src/HexCoding.h" #include @@ -24,17 +24,19 @@ using namespace std; /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct TWStoredKey *_Nonnull createAStoredKey(TWCoinType coin, const string& password) { +struct TWStoredKey *_Nonnull createAStoredKey(TWCoinType coin, TWData* password) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password2 = WRAPS(TWStringCreateWithUTF8Bytes(password.c_str())); - const auto key = TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password2.get(), coin); + const auto key = TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password, coin); return key; } /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. struct TWStoredKey *_Nonnull createDefaultStoredKey() { - return createAStoredKey(TWCoinTypeBitcoin, "password"); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + return createAStoredKey(TWCoinTypeBitcoin, password.get()); } TEST(TWStoredKey, loadPBKDF2Key) { @@ -52,7 +54,8 @@ TEST(TWStoredKey, loadNonexistent) { TEST(TWStoredKey, createWallet) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); const auto key = TWStoredKeyCreate(name.get(), password.get()); const auto name2 = WRAPS(TWStoredKeyName(key)); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); @@ -66,7 +69,8 @@ TEST(TWStoredKey, importPrivateKey) { const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; const auto privateKey = WRAPD(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes(privateKeyHex))); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); const auto coin = TWCoinTypeBitcoin; const auto key = TWStoredKeyImportPrivateKey(privateKey.get(), name.get(), password.get(), coin); const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key, password.get())); @@ -83,7 +87,8 @@ TEST(TWStoredKey, importPrivateKey) { TEST(TWStoredKey, importHDWallet) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); const auto coin = TWCoinTypeBitcoin; const auto key = TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password.get(), coin); EXPECT_TRUE(TWStoredKeyIsMnemonic(key)); @@ -96,11 +101,13 @@ TEST(TWStoredKey, importHDWallet) { } TEST(TWStoredKey, addressAddRemove) { - const auto password = "password"; + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; - const auto key = createAStoredKey(coin, password); + const auto key = createAStoredKey(coin, password.get()); - const auto wallet = TWStoredKeyWallet(key, WRAPS(TWStringCreateWithUTF8Bytes(password)).get()); + const auto wallet = TWStoredKeyWallet(key, password.get()); const auto accountCoin = TWStoredKeyAccountForCoin(key, coin, wallet); const auto accountAddress = WRAPS(TWAccountAddress(accountCoin)); EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); @@ -171,9 +178,11 @@ TEST(TWStoredKey, importJsonInvalid) { } TEST(TWStoredKey, fixAddresses) { - const auto password = "password"; - const auto key = createAStoredKey(TWCoinTypeBitcoin, password); - EXPECT_TRUE(TWStoredKeyFixAddresses(key, WRAPS(TWStringCreateWithUTF8Bytes(password)).get())); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + EXPECT_TRUE(TWStoredKeyFixAddresses(key, password.get())); TWStoredKeyDelete(key); } @@ -181,10 +190,11 @@ TEST(TWStoredKey, importInvalidKey) { auto bytes = TW::parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); auto name = WRAPS(TWStringCreateWithUTF8Bytes("test")); + auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(name.get())), TWStringSize(name.get()))); - auto eth = TWStoredKeyImportPrivateKey(data.get(), name.get(), name.get(), TWCoinTypeEthereum); - auto ont = TWStoredKeyImportPrivateKey(data.get(), name.get(), name.get(), TWCoinTypeOntology); - auto tezos = TWStoredKeyImportPrivateKey(data.get(), name.get(), name.get(), TWCoinTypeTezos); + auto eth = TWStoredKeyImportPrivateKey(data.get(), name.get(), password.get(), TWCoinTypeEthereum); + auto ont = TWStoredKeyImportPrivateKey(data.get(), name.get(), password.get(), TWCoinTypeOntology); + auto tezos = TWStoredKeyImportPrivateKey(data.get(), name.get(), password.get(), TWCoinTypeTezos); ASSERT_EQ(eth, nullptr); ASSERT_EQ(ont, nullptr); @@ -192,9 +202,11 @@ TEST(TWStoredKey, importInvalidKey) { } TEST(TWStoredKey, removeAccountForCoin) { - auto password = "password"; - auto key = TWStoredKeyCreate("Test KeyStore", password); - auto wallet = TWStoredKeyWallet(key, password); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + auto key = TWStoredKeyCreate("Test KeyStore", password.get()); + auto wallet = TWStoredKeyWallet(key, password.get()); ASSERT_NE(TWStoredKeyAccountForCoin(key, TWCoinTypeEthereum, wallet), nullptr); ASSERT_NE(TWStoredKeyAccountForCoin(key, TWCoinTypeBitcoin, wallet), nullptr); @@ -211,8 +223,12 @@ TEST(TWStoredKey, removeAccountForCoin) { TEST(TWStoredKey, getWalletPasswordInvalid) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto password = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto passwordInvalid = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); + const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); + auto key = TWStoredKeyCreate (name.get(), password.get()); ASSERT_NE(TWStoredKeyWallet(key, password.get()), nullptr); ASSERT_EQ(TWStoredKeyWallet(key, passwordInvalid.get()), nullptr); From 8f8a3962fa867d5c6ce63f4e32851712b968ecb9 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Thu, 9 Apr 2020 17:28:26 +0800 Subject: [PATCH 04/81] enable Filecoin json protobuf signing (#919) --- src/Filecoin/Entry.cpp | 4 ++++ src/Filecoin/Entry.h | 2 ++ src/Filecoin/Signer.cpp | 10 ++++++++++ src/Filecoin/Signer.h | 3 +++ tests/Filecoin/TWAnySignerTests.cpp | 13 +++++++++++++ 5 files changed, 32 insertions(+) diff --git a/src/Filecoin/Entry.cpp b/src/Filecoin/Entry.cpp index 8980a773fd8..ef98fcbdb87 100644 --- a/src/Filecoin/Entry.cpp +++ b/src/Filecoin/Entry.cpp @@ -25,3 +25,7 @@ string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byt void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return Signer::signJSON(json, key); +} diff --git a/src/Filecoin/Entry.h b/src/Filecoin/Entry.h index cb370ba5e3e..8259953005e 100644 --- a/src/Filecoin/Entry.h +++ b/src/Filecoin/Entry.h @@ -18,6 +18,8 @@ class Entry: public CoinEntry { virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + virtual bool supportsJSONSigning() const { return true; } + virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Filecoin diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index adadc8b9c29..ed5363ba63e 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -5,6 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #include "Signer.h" +#include "HexCoding.h" +#include using namespace TW; using namespace TW::Filecoin; @@ -38,3 +40,11 @@ Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce auto signature = privateKey.sign(toSign, TWCurveSECP256k1); return Data(signature.begin(), signature.end()); } + +std::string Signer::signJSON(const std::string& json, const Data& key) { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + auto output = Signer::sign(input); + return hex(output.encoded()); +} diff --git a/src/Filecoin/Signer.h b/src/Filecoin/Signer.h index 4509374f882..3d20617bcb4 100644 --- a/src/Filecoin/Signer.h +++ b/src/Filecoin/Signer.h @@ -23,6 +23,9 @@ class Signer { /// Signs a Proto::SigningInput transaction. static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + /// Signs a json Proto::SigningInput with private key + static std::string signJSON(const std::string& json, const Data& key); + /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; }; diff --git a/tests/Filecoin/TWAnySignerTests.cpp b/tests/Filecoin/TWAnySignerTests.cpp index 6df308c8c47..e1dd177277e 100644 --- a/tests/Filecoin/TWAnySignerTests.cpp +++ b/tests/Filecoin/TWAnySignerTests.cpp @@ -59,3 +59,16 @@ TEST(TWAnySignerFilecoin, Sign) { TWDataDelete(inputData); TWDataDelete(outputData); } + +TEST(TWAnySignerFilecoin, SignJSON) { + auto json = STRING(R"({"toAddress":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","nonce":"2","value":"IIasNRBSYAAA","gasPrice":"Ag==","gasLimit":"1000"})"); + auto key = DATA("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeFilecoin)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeFilecoin)); + assertStringsEqual(result, + "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c57" + "7fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac351052" + "600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60befa48" + "5903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); +} From 0d91994784f386f7373c66427131e107025ff791 Mon Sep 17 00:00:00 2001 From: lzxm160 Date: Fri, 10 Apr 2020 12:45:55 +0800 Subject: [PATCH 05/81] [IoTeX ] support IoTeX native staking (#912) * support IoTeX native staking --- .../app/blockchains/iotex/TestIotexSigning.kt | 227 +++++++++ src/IoTeX/Protobuf/.gitignore | 2 - src/IoTeX/Protobuf/action.proto | 40 -- src/IoTeX/Signer.cpp | 42 +- src/IoTeX/Signer.h | 3 +- src/IoTeX/Staking.cpp | 114 +++-- src/IoTeX/Staking.h | 33 +- src/proto/IoTeX.proto | 138 ++++-- swift/Tests/Blockchains/IoTeXTests.swift | 202 ++++++-- tests/IoTeX/StakingTests.cpp | 447 ++++++++++++------ tests/IoTeX/TWIoTeXStakingTests.cpp | 156 ------ tools/generate-files | 1 - 12 files changed, 885 insertions(+), 520 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt delete mode 100644 src/IoTeX/Protobuf/.gitignore delete mode 100644 src/IoTeX/Protobuf/action.proto delete mode 100644 tests/IoTeX/TWIoTeXStakingTests.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt new file mode 100644 index 00000000000..1871c6c0d76 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/iotex/TestIotexSigning.kt @@ -0,0 +1,227 @@ +package com.trustwallet.core.app.blockchains.IoTeX + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytes +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.IOTEX +import wallet.core.jni.proto.IoTeX +import wallet.core.jni.proto.IoTeX.SigningOutput + +class TestIotexSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testIotexSigningCreate() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Create + val create = IoTeX.Staking.Create.newBuilder().apply { + candidateName = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + stakedAmount = "100" + stakedDuration = 10000 + autoStake = true + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeCreate = create + } + + val sign = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytes = sign.encoded + assertEquals(signBytes.toByteArray().toHex(), "0x0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5aed8e2e026d46e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801") + } + fun testIotexSigningAddDeposit() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign AddDeposit + val adddeposit = IoTeX.Staking.AddDeposit.newBuilder().apply { + bucketIndex = 10 + amount = "10" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeAddDeposit = adddeposit + } + + var signAddDeposit = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + var signBytesAddDeposit = signAddDeposit.encoded + assertEquals(signBytesAddDeposit.toByteArray().toHex(), "0x0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb55e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01") + } + fun testIotexSigningUnstake() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Unstake + val unstake = IoTeX.Staking.Reclaim.newBuilder().apply { + bucketIndex = 10 + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeUnstake = unstake + } + + val signUnstake = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesUnstake = signUnstake.encoded + assertEquals(signBytesUnstake.toByteArray().toHex(), "0x0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601") + } + fun testIotexSigningWithdraw() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Withdraw + val withdraw = IoTeX.Staking.Reclaim.newBuilder().apply { + bucketIndex = 10 + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeWithdraw = withdraw + } + + val signWithdraw = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesWithdraw = signWithdraw.encoded + assertEquals(signBytesWithdraw.toByteArray().toHex(), "0x0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00") + } + fun testIotexSigningRestake() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign Restake + val restake = IoTeX.Staking.Restake.newBuilder().apply { + bucketIndex = 10 + stakedDuration = 1000 + autoStake = true + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeRestake = restake + } + + val signRestake = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesRestake = signRestake.encoded + assertEquals(signBytesRestake.toByteArray().toHex(), "0x0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418eedbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101") + } + fun testIotexSigningChangeCandidate() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign ChangeCandidate + val changecandidate = IoTeX.Staking.ChangeCandidate.newBuilder().apply { + bucketIndex = 10 + candidateName = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeChangeCandidate = changecandidate + } + + val signChangeCandidate = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesChangeCandidate = signChangeCandidate.encoded + assertEquals(signBytesChangeCandidate.toByteArray().toHex(), "0x0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f81895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801") + } + fun testIotexSigningTransferOwnership() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign TransferOwnership + val transfer = IoTeX.Staking.TransferOwnership.newBuilder().apply { + bucketIndex = 10 + voterAddress = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + + input.apply { + stakeTransferOwnership = transfer + } + + val signTransferOwnership = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesTransferOwnership = signTransferOwnership.encoded + assertEquals(signBytesTransferOwnership.toByteArray().toHex(), "0x0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01") + } + fun testIotexSigningCandidateBasicInfo() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("10") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign CandidateBasicInfo + val cbi = IoTeX.Staking.CandidateBasicInfo.newBuilder().apply { + name = "test" + operatorAddress = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng" + rewardAddress = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd" + }.build() + + input.apply { + candidateUpdate = cbi + } + + val signCandidateBasicInfo = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesCandidateBasicInfo = signCandidateBasicInfo.encoded + assertEquals(signBytesCandidateBasicInfo.toByteArray().toHex(), "0x0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c32657635646661393838716d677a673278346866617a6d7039766e326736366e671a29696f316a757678356730363365753474733833326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701") + } + fun testIotexSigningCandidateRegister() { + val input = IoTeX.SigningInput.newBuilder() + .setVersion(1) + .setNonce(0) + .setGasLimit(1000000) + .setGasPrice("1000") + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"))) + // test sign CandidateBasicInfo + val cbi = IoTeX.Staking.CandidateBasicInfo.newBuilder().apply { + name = "test" + operatorAddress = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he" + rewardAddress = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv" + }.build() + val cr = IoTeX.Staking.CandidateRegister.newBuilder().apply { + candidate = cbi + stakedAmount = "100" + stakedDuration = 10000 + autoStake = false + ownerAddress ="io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + payload = ByteString.copyFrom("payload".toByteArray()) + }.build() + input.apply { + candidateRegister = cr + } + + val signCandidateRegister = AnySigner.sign(input.build(), IOTEX, SigningOutput.parser()) + val signBytesCandidateRegister = signCandidateRegister.encoded + assertEquals(signBytesCandidateRegister.toByteArray().toHex(), "0x0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a32077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a9179b141ac68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400") + } +} diff --git a/src/IoTeX/Protobuf/.gitignore b/src/IoTeX/Protobuf/.gitignore deleted file mode 100644 index 687ffbb426e..00000000000 --- a/src/IoTeX/Protobuf/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.cc -*.h diff --git a/src/IoTeX/Protobuf/action.proto b/src/IoTeX/Protobuf/action.proto deleted file mode 100644 index 921d8c38807..00000000000 --- a/src/IoTeX/Protobuf/action.proto +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2018 IoTeX -// This is an alpha (internal) release and is not suitable for production. This -// source code is provided 'as is' and no warranties are given as to title or -// non-infringement, merchantability or fitness for purpose and, to the extent -// permitted by law, all liability for your use of the code is disclaimed. This -// source code is governed by Apache License 2.0 that can be found in the -// LICENSE file. - -syntax = "proto3"; -package iotextypes; - -message Transfer { - // used by state-based model - string amount = 1; - string recipient = 2; - bytes payload = 3; -} - -message Execution { - string amount = 1; - string contract = 2; - bytes data = 3; -} - -message ActionCore { - uint32 version = 1; - uint64 nonce = 2; - uint64 gasLimit = 3; - string gasPrice = 4; - oneof action { - Transfer transfer = 10; - Execution execution = 12; - } -} - -message Action { - ActionCore core = 1; - bytes senderPubKey = 2; - bytes signature = 3; -} diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index e7fb547d025..6b0e6d6ec1a 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -24,7 +24,7 @@ Data Signer::sign() const { } Proto::SigningOutput Signer::build() const { - auto signedAction = iotextypes::Action(); + auto signedAction = Proto::Action(); signedAction.mutable_core()->MergeFrom(action); auto key = PrivateKey(input.privatekey()); auto pk = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended).bytes; @@ -32,7 +32,7 @@ Proto::SigningOutput Signer::build() const { auto sig = key.sign(hash(), TWCurveSECP256k1); signedAction.set_signature(sig.data(), sig.size()); - auto output = IoTeX::Proto::SigningOutput(); + auto output = Proto::SigningOutput(); auto serialized = signedAction.SerializeAsString(); output.set_encoded(serialized); auto h = Hash::keccak256(serialized); @@ -44,41 +44,7 @@ Data Signer::hash() const { return Hash::keccak256(action.SerializeAsString()); } -static Data encodeStaking(const Proto::Staking& staking) { - Data encoded; - if (staking.has_stake()) { - auto& stake = staking.stake(); - stakingStake(TW::data(stake.candidate()), stake.duration(), stake.nondecay(), TW::data(stake.data()), encoded); - } else if (staking.has_unstake()) { - auto& unstake = staking.unstake(); - stakingUnstake(unstake.piggy_index(), TW::data(unstake.data()), encoded); - } else if (staking.has_withdraw()) { - auto& withdraw = staking.withdraw(); - stakingWithdraw(withdraw.piggy_index(), TW::data(withdraw.data()), encoded); - } else if (staking.has_movestake()) { - auto& move = staking.movestake(); - stakingMoveStake(move.piggy_index(), TW::data(move.candidate()), TW::data(move.data()), encoded); - } else if (staking.has_addstake()) { - auto& add = staking.addstake(); - stakingAddStake(add.piggy_index(), TW::data(add.data()), encoded); - } - return encoded; -} void Signer::toActionCore() { - if (input.has_staking()) { - action.set_version(input.version()); - action.set_nonce(input.nonce()); - action.set_gaslimit(input.gaslimit()); - action.set_gasprice(input.gasprice()); - auto& staking = input.staking(); - auto encoded = encodeStaking(staking); - auto& execution = *action.mutable_execution(); - execution.set_amount(staking.amount()); - execution.set_contract(staking.contract()); - execution.set_data(encoded.data(), encoded.size()); - } else { - // ActionCore is almost same as SigningInput, missing field privateKey = 5; - action.ParseFromString(input.SerializeAsString()); - action.DiscardUnknownFields(); - } + action.ParseFromString(input.SerializeAsString()); + action.DiscardUnknownFields(); } diff --git a/src/IoTeX/Signer.h b/src/IoTeX/Signer.h index d1e5d8caf33..31fc2c8a15f 100644 --- a/src/IoTeX/Signer.h +++ b/src/IoTeX/Signer.h @@ -9,7 +9,6 @@ #include "Data.h" #include "proto/IoTeX.pb.h" -#include "Protobuf/action.pb.h" namespace TW::IoTeX { @@ -20,7 +19,7 @@ class Signer { static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; public: Proto::SigningInput input; - iotextypes::ActionCore action; + Proto::ActionCore action; /// Initializes a transaction signer Signer(const Proto::SigningInput& input) : input(input) { toActionCore(); } diff --git a/src/IoTeX/Staking.cpp b/src/IoTeX/Staking.cpp index 938be61ed9d..493b3a86013 100644 --- a/src/IoTeX/Staking.cpp +++ b/src/IoTeX/Staking.cpp @@ -5,56 +5,106 @@ // file LICENSE at the root of the source code distribution tree. #include "Staking.h" - -#include "Ethereum/ABI/Function.h" #include "Data.h" -#include "uint256.h" +#include "HexCoding.h" +using namespace TW; namespace TW::IoTeX { -using namespace TW::Ethereum::ABI; +const char* FromData(const Data& data) { + auto s = new std::string(data.begin(), data.end()); + s->append(data.size(), '\0'); + auto ss = reinterpret_cast(s); + return ss->data(); +} -void stakingStake(const Data& candidate, uint64_t stakeDuration, bool nonDecay, const Data& dataIn, Data& dataOut) { - Function func("createPygg"); - func.addInParam(std::make_shared(12, candidate)); - func.addInParam(std::make_shared(uint256_t(stakeDuration))); - func.addInParam(std::make_shared(nonDecay)); - func.addInParam(std::make_shared(dataIn)); +Data dataFromString(const std::string& d) { + Data data; + std::copy(d.c_str(), d.c_str() + d.length(), back_inserter(data)); + return data; +} - func.encode(dataOut); +Data stakingCreate(const Data& candidate, const Data& amount, uint32_t duration, bool autoStake, + const Data& payload) { + auto action = IoTeX::Proto::Staking_Create(); + action.set_candidatename(FromData(candidate)); + action.set_stakedamount(FromData(amount)); + action.set_stakedduration(duration); + action.set_autostake(autoStake); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingUnstake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut) { - Function func("unstake"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(dataIn)); +Data stakingAddDeposit(uint64_t index, const Data& amount, const Data& payload) { + auto action = IoTeX::Proto::Staking_AddDeposit(); + action.set_bucketindex(index); + action.set_amount(FromData(amount)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); +} - func.encode(dataOut); +Data stakingUnstake(uint64_t index, const Data& payload) { + auto action = IoTeX::Proto::Staking_Reclaim(); + action.set_bucketindex(index); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingWithdraw(uint64_t pyggIndex, const Data& dataIn, Data& dataOut) { - Function func("withdraw"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(dataIn)); +Data stakingWithdraw(uint64_t index, const Data& payload) { + auto action = IoTeX::Proto::Staking_Reclaim(); + action.set_bucketindex(index); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); +} - func.encode(dataOut); +Data stakingRestake(uint64_t index, uint32_t duration, bool autoStake, const Data& payload) { + auto action = IoTeX::Proto::Staking_Restake(); + action.set_bucketindex(index); + action.set_stakedduration(duration); + action.set_autostake(autoStake); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingAddStake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut) { - Function func("storeToPygg"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(dataIn)); +Data stakingChangeCandidate(uint64_t index, const Data& candidate, const Data& payload) { + auto action = IoTeX::Proto::Staking_ChangeCandidate(); + action.set_bucketindex(index); + action.set_candidatename(FromData(candidate)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); +} - func.encode(dataOut); +Data stakingTransfer(uint64_t index, const Data& voterAddress, const Data& payload) { + auto action = IoTeX::Proto::Staking_TransferOwnership(); + action.set_bucketindex(index); + action.set_voteraddress(FromData(voterAddress)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } -void stakingMoveStake(uint64_t pyggIndex, const Data& candidate, const Data& dataIn, Data& dataOut) { - Function func("revote"); - func.addInParam(std::make_shared(uint256_t(pyggIndex))); - func.addInParam(std::make_shared(12, candidate)); - func.addInParam(std::make_shared(dataIn)); +Data candidateRegister(const Data& name, const Data& operatorAddress, const Data& rewardAddress, + const Data& amount, uint32_t duration, bool autoStake, + const Data& ownerAddress, const Data& payload) { + auto cbi = new IoTeX::Proto::Staking_CandidateBasicInfo(); + cbi->set_name(FromData(name)); + cbi->set_operatoraddress(FromData(operatorAddress)); + cbi->set_rewardaddress(FromData(rewardAddress)); - func.encode(dataOut); + auto action = IoTeX::Proto::Staking_CandidateRegister(); + action.set_allocated_candidate(cbi); + action.set_stakedamount(FromData(amount)); + action.set_stakedduration(duration); + action.set_autostake(autoStake); + action.set_owneraddress(FromData(ownerAddress)); + action.set_payload(FromData(payload)); + return dataFromString(action.SerializeAsString()); } +Data candidateUpdate(const Data& name, const Data& operatorAddress, const Data& rewardAddress) { + auto action = IoTeX::Proto::Staking_CandidateBasicInfo(); + action.set_name(FromData(name)); + action.set_operatoraddress(FromData(operatorAddress)); + action.set_rewardaddress(FromData(rewardAddress)); + return dataFromString(action.SerializeAsString()); +} } // namespace TW::IoTeX diff --git a/src/IoTeX/Staking.h b/src/IoTeX/Staking.h index 9d5a6846be1..73a5e737473 100644 --- a/src/IoTeX/Staking.h +++ b/src/IoTeX/Staking.h @@ -7,22 +7,37 @@ #pragma once #include "Data.h" - +#include "proto/IoTeX.pb.h" namespace TW::IoTeX { -/// Function to generate Stake message -void stakingStake(const Data& candidate, uint64_t stakeDuration, bool nonDecay, const Data& dataIn, Data& dataOut); +/// Function to generate Create message +Data stakingCreate(const Data& candidate, const Data& amount, uint32_t duration, bool autoStake, + const Data& payload); + +/// Function to generate AddDeposit message +Data stakingAddDeposit(uint64_t index, const Data& amount, const Data& payload); /// Function to generate Unstake message -void stakingUnstake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut); +Data stakingUnstake(uint64_t index, const Data& payload); /// Function to generate Withdraw message -void stakingWithdraw(uint64_t pyggIndex, const Data& dataIn, Data& dataOut); +Data stakingWithdraw(uint64_t index, const Data& payload); + +/// Function to generate Restake message +Data stakingRestake(uint64_t index, uint32_t duration, bool autoStake, const Data& payload); + +/// Function to generate ChangeCandidate message +Data stakingChangeCandidate(uint64_t index, const Data& candidate, const Data& payload); + +/// Function to generate Transfer message +Data stakingTransfer(uint64_t index, const Data& voterAddress, const Data& payload); -/// Function to generate AddStake message -void stakingAddStake(uint64_t pyggIndex, const Data& dataIn, Data& dataOut); +/// Function to generate candidate register message +Data candidateRegister(const Data& name, const Data& operatorAddress, const Data& rewardAddress, + const Data& amount, uint32_t duration, bool autoStake, + const Data& ownerAddress, const Data& payload); -/// Function to generate MoveStake message -void stakingMoveStake(uint64_t pyggIndex, const Data& candidate, const Data& dataIn, Data& dataOut); +/// Function to generate candidate update message +Data candidateUpdate(const Data& name, const Data& operatorAddress, const Data& rewardAddress); } // namespace TW::IoTeX diff --git a/src/proto/IoTeX.proto b/src/proto/IoTeX.proto index 669b049ecb3..1df337cf627 100644 --- a/src/proto/IoTeX.proto +++ b/src/proto/IoTeX.proto @@ -10,43 +10,73 @@ message Transfer { } message Staking { - message Stake { - string candidate = 1; - uint64 duration = 2; - bool nonDecay= 3; - bytes data = 4; - } - - message Unstake { - uint64 piggy_index = 1; - bytes data = 2; - } - - message Withdraw { - uint64 piggy_index = 1; - bytes data = 2; - } - - message AddStake { - uint64 piggy_index = 1; - bytes data = 2; - } - - message MoveStake { - uint64 piggy_index = 1; - string candidate = 2; - bytes data = 3; - } + // create stake + message Create { + string candidateName = 1; + string stakedAmount = 2; + uint32 stakedDuration = 3; + bool autoStake = 4; + bytes payload = 5; + } - string amount = 1; - string contract = 2; + // unstake or withdraw + message Reclaim { + uint64 bucketIndex = 1; + bytes payload = 2; + } + + // add the amount of bucket + message AddDeposit { + uint64 bucketIndex = 1; + string amount = 2; + bytes payload = 3; + } + + // restake the duration and autoStake flag of bucket + message Restake { + uint64 bucketIndex = 1; + uint32 stakedDuration = 2; + bool autoStake = 3; + bytes payload = 4; + } + + // move the bucket to vote for another candidate or transfer the ownership of bucket to another voters + message ChangeCandidate { + uint64 bucketIndex = 1; + string candidateName = 2; + bytes payload = 3; + } - oneof message { - Stake stake = 3; - Unstake unstake = 4; - Withdraw withdraw = 5; - AddStake addStake = 6; - MoveStake moveStake = 7; + message TransferOwnership { + uint64 bucketIndex = 1; + string voterAddress = 2; + bytes payload = 3; + } + + message CandidateBasicInfo { + string name = 1; + string operatorAddress = 2; + string rewardAddress = 3; + } + + message CandidateRegister { + CandidateBasicInfo candidate = 1; + string stakedAmount = 2; + uint32 stakedDuration = 3; + bool autoStake = 4; + string ownerAddress = 5; // if ownerAddress is absent, owner of candidate is the sender + bytes payload = 6; + } + oneof message { + Create stakeCreate = 1; + Reclaim stakeUnstake = 2; + Reclaim stakeWithdraw = 3; + AddDeposit stakeAddDeposit = 4; + Restake stakeRestake = 5; + ChangeCandidate stakeChangeCandidate = 6; + TransferOwnership stakeTransferOwnership = 7; + CandidateRegister candidateRegister = 8; + CandidateBasicInfo candidateUpdate = 9; } } @@ -65,8 +95,17 @@ message SigningInput { bytes privateKey = 5; oneof action { Transfer transfer = 10; - Staking staking = 11; ContractCall call = 12; + // Native staking + Staking.Create stakeCreate = 40; + Staking.Reclaim stakeUnstake = 41; + Staking.Reclaim stakeWithdraw = 42; + Staking.AddDeposit stakeAddDeposit = 43; + Staking.Restake stakeRestake = 44; + Staking.ChangeCandidate stakeChangeCandidate = 45; + Staking.TransferOwnership stakeTransferOwnership = 46; + Staking.CandidateRegister candidateRegister = 47; + Staking.CandidateBasicInfo candidateUpdate = 48; } } @@ -78,3 +117,30 @@ message SigningOutput { // Signed Action hash bytes hash = 2; } + +message ActionCore { + uint32 version = 1; + uint64 nonce = 2; + uint64 gasLimit = 3; + string gasPrice = 4; + oneof action { + Transfer transfer = 10; + ContractCall execution = 12; + // Native staking + Staking.Create stakeCreate = 40; + Staking.Reclaim stakeUnstake = 41; + Staking.Reclaim stakeWithdraw = 42; + Staking.AddDeposit stakeAddDeposit = 43; + Staking.Restake stakeRestake = 44; + Staking.ChangeCandidate stakeChangeCandidate = 45; + Staking.TransferOwnership stakeTransferOwnership = 46; + Staking.CandidateRegister candidateRegister = 47; + Staking.CandidateBasicInfo candidateUpdate = 48; + } +} + +message Action { + ActionCore core = 1; + bytes senderPubKey = 2; + bytes signature = 3; +} \ No newline at end of file diff --git a/swift/Tests/Blockchains/IoTeXTests.swift b/swift/Tests/Blockchains/IoTeXTests.swift index 1209db8d4e1..0e68e5ab83f 100644 --- a/swift/Tests/Blockchains/IoTeXTests.swift +++ b/swift/Tests/Blockchains/IoTeXTests.swift @@ -8,7 +8,6 @@ import XCTest import TrustWalletCore class IoTeXTests: XCTestCase { - func testSign() { let privateKey = PrivateKey(data: Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")!)! @@ -29,81 +28,182 @@ class IoTeXTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "0a39080110011801220131522e0a01311229696f3165326e7173797437666b707a733578377a6632756b306a6a3732746575356e36616b75337472124104fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5cf762f20459c9100eb9d4d7597f5817bf21e10b53a0120b9ec1ba5cddfdcb669b1a41ec9757ae6c9009315830faaab250b6db0e9535b00843277f596ae0b2b9efc0bd4e14138c056fc4cdfa285d13dd618052b3d1cb7a3f554722005a2941bfede96601") } - func stakingInput() -> IoTeXSigningInput { - let input = IoTeXSigningInput.with { + func testSignStakingCreate() { + var input = IoTeXSigningInput.with { $0.version = 1 - $0.nonce = 123 - $0.gasLimit = 888 - $0.gasPrice = "999" - $0.staking = IoTeXStaking.with { - $0.amount = "456" - $0.contract = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" - } - $0.privateKey = Data(hexString: "0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")! + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! } - return input + input.stakeCreate = IoTeXStaking.Create.with { + $0.candidateName = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + $0.stakedAmount = "100" + $0.stakedDuration = 10000 + $0.autoStake = true + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) + + XCTAssertEqual(output.encoded.hexString, "0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5aed8e2e026d46e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801") + XCTAssertEqual(output.hash.hexString, "f1785e47b4200c752bb6518bd18097a41e075438b8c18c9cb00e1ae2f38ce767") } - func testStake() { - // candidate name is a string - let candidate = "\u{01}\u{02}\u{03}\u{04}\u{05}\u{06}\u{07}\u{08}\t\n\u{0B}\u{0C}" - var input = stakingInput() - input.staking.stake = IoTeXStaking.Stake.with { - $0.candidate = candidate - $0.duration = 1001 - $0.nonDecay = true - $0.data = "this is a test".data(using: .utf8)! + func testSignStakingAddDeposit() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeAddDeposit = IoTeXStaking.AddDeposit.with { + $0.bucketIndex = 10 + $0.amount = "10" + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0a86020801107b18f806220339393962f7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611ac40107c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74686973206973206120746573740000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41a558bc9a4bfba920242ccd4d5c5da363ec534d4dd5eb67f88e9db7aaad5c50ad62dfe298c0e54e311ebba045f48cea1136e42a123a8e6b03d3e6ed82d4ec2b9401") - XCTAssertEqual(output.hash.hexString, "41b1f8be5f6b884c06556fba2611716e8e514b507f5a653fc02ac50ba13fbd6c") + XCTAssertEqual(output.encoded.hexString, "0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb55e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01") + XCTAssertEqual(output.hash.hexString, "ca8937d6f224a4e4bf93cb5605581de2d26fb0481e1dfc1eef384ee7ccf94b73") } + + func testSignStakingUnstake() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeUnstake = IoTeXStaking.Reclaim.with { + $0.bucketIndex = 10 + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - func testUnstake() { - var input = stakingInput() - input.staking.unstake = IoTeXStaking.Unstake.with { - $0.piggyIndex = 1001 + XCTAssertEqual(output.encoded.hexString, "0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601") + XCTAssertEqual(output.hash.hexString, "bed58b64a6c4e959eca60a86f0b2149ce0e1dd527ac5fd26aef725ebf7c22a7d") + } + + func testSignStakingWithdraw() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeWithdraw = IoTeXStaking.Reclaim.with { + $0.bucketIndex = 10 + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0aa5010801107b18f80622033939396296010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a64c8fd6ed000000000000000000000000000000000000000000000000000000000000003e9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41eeb7cb3fa7ec22a61156753d569b3f4da3c74c3c7e2f148b1a43e11d220cac5d164663ff6c785439679b088de9d7f2437545f007ca9cda4b2f5327d2c6eda5aa01") - XCTAssertEqual(output.hash.hexString, "b93a2874a72ce4eb8a41a20c209cf3fd188671ed8be8239a57960cbed887e962") + XCTAssertEqual(output.encoded.hexString, "0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00") + XCTAssertEqual(output.hash.hexString, "28049348cf34f1aa927caa250e7a1b08778c44efaf73b565b6fa9abe843871b4") } - func testWithdraw() { - var input = stakingInput() - input.staking.withdraw = IoTeXStaking.Withdraw.with { - $0.piggyIndex = 1001 + func testSignStakingRestake() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeRestake = IoTeXStaking.Restake.with { + $0.bucketIndex = 10 + $0.stakedDuration = 1000 + $0.autoStake = true + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0aa5010801107b18f80622033939396296010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a64030ba25d00000000000000000000000000000000000000000000000000000000000003e9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41903c79d042f6b1c05446ececb3d760ca154c539b5787e66135cd3db77638294d18dbbbbcb0de8b9a393cc7c0448cf246898e4343a2a51666e21e738ee6d8a6f700") - XCTAssertEqual(output.hash.hexString, "2b2657247a72cb262de214b4e793c7a01fa2139fd5d12a46d43c24f87f9e2396") + XCTAssertEqual(output.encoded.hexString, "0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418eedbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101") + XCTAssertEqual(output.hash.hexString, "8816e8f784a1fce40b54d1cd172bb6976fd9552f1570c73d1d9fcdc5635424a9") } - func testAddStake() { - var input = stakingInput() - input.staking.addStake = IoTeXStaking.AddStake.with { - $0.piggyIndex = 1001 + func testSignStakingChangeCandidate() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeChangeCandidate = IoTeXStaking.ChangeCandidate.with { + $0.bucketIndex = 10 + $0.candidateName = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) + + XCTAssertEqual(output.encoded.hexString, "0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f81895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801") + XCTAssertEqual(output.hash.hexString, "186526b5b9fe74e25beb52c83c41780a69108160bef2ddaf3bffb9f1f1e5e73a") + } + + func testSignStakingTransfer() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.stakeTransferOwnership = IoTeXStaking.TransferOwnership.with { + $0.bucketIndex = 10 + $0.voterAddress = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza" + $0.payload = "payload".data(using: .utf8)! + } + let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) + + XCTAssertEqual(output.encoded.hexString, "0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01") + XCTAssertEqual(output.hash.hexString, "74b2e1d6a09ba5d1298fa422d5850991ae516865077282196295a38f93c78b85") + } + + func testSignCandidateRegister() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "1000" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.candidateRegister = IoTeXStaking.CandidateRegister.with { + $0.candidate = IoTeXStaking.CandidateBasicInfo.with { + $0.name = "test" + $0.operatorAddress = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he" + $0.rewardAddress = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv" + } + $0.stakedAmount = "100" + $0.stakedDuration = 10000 + $0.autoStake = false + $0.ownerAddress = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj" + $0.payload = "payload".data(using: .utf8)! } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0aa5010801107b18f80622033939396296010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a646e7b301700000000000000000000000000000000000000000000000000000000000003e9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a410f0832bb7a48c9e468c0bcb2c4a35a202f9519a63c4d6474b48087ab9dc33aea18127940d5cda43710cd874cbdf7a7b26efc9c04236e14dfb4d9b6f7095b0b6c01") - XCTAssertEqual(output.hash.hexString, "c71058812a5febe5cdcdaf9499ba0b2c895f88d1acd3203e5097b307c2a5f1d1") + XCTAssertEqual(output.encoded.hexString, "0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b63786671383779786537666e7238727074683573686a32077061796c6f6164124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a9179b141ac68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400") + XCTAssertEqual(output.hash.hexString, "35f53a536e014b32b85df50483ef04849b80ad60635b3b1979c5ba1096b65237") } - func testMoveStake() { - // candidate name is a string - let candidate = "\u{01}\u{02}\u{03}\u{04}\u{05}\u{06}\u{07}\u{08}\t\n\u{0B}\u{0C}" - var input = stakingInput() - input.staking.moveStake = IoTeXStaking.MoveStake.with { - $0.piggyIndex = 1001 - $0.candidate = candidate + func testSignCandidateUpdate() { + var input = IoTeXSigningInput.with { + $0.version = 1 + $0.nonce = 0 + $0.gasLimit = 1000000 + $0.gasPrice = "10" + $0.privateKey = Data(hexString: "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1")! + } + input.candidateUpdate = IoTeXStaking.CandidateBasicInfo.with { + $0.name = "test" + $0.operatorAddress = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng" + $0.rewardAddress = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd" } let output: IoTeXSigningOutput = AnySigner.sign(input: input, coin: .ioTeX) - XCTAssertEqual(output.encoded.hexString, "0ac6010801107b18f806220339393962b7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611a8401d3e41fd200000000000000000000000000000000000000000000000000000000000003e90102030405060708090a0b0c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a4118b71944993d1cd5362379ff64e892024a75ce697b2755cc9bbfcac24482a19b1ec2951bec7461ef9eb0723f803987cb87e3c3afb340006cd5b413c1fac10d7c01") - XCTAssertEqual(output.hash.hexString, "33290ded342efaebf795855be73d34cbac149a2415ff9558de10303e6126f30d") + XCTAssertEqual(output.encoded.hexString, "0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c32657635646661393838716d677a673278346866617a6d7039766e326736366e671a29696f316a757678356730363365753474733833326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a1038ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701") + XCTAssertEqual(output.hash.hexString, "ca1a28f0e9a58ffc67037cc75066dbdd8e024aa2b2e416e4d6ce16c3d86282e5") } -} +} \ No newline at end of file diff --git a/tests/IoTeX/StakingTests.cpp b/tests/IoTeX/StakingTests.cpp index 96a79d3734f..0668a4a927d 100644 --- a/tests/IoTeX/StakingTests.cpp +++ b/tests/IoTeX/StakingTests.cpp @@ -4,193 +4,334 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "IoTeX/Staking.h" -#include "IoTeX/Signer.h" #include "Data.h" #include "HexCoding.h" +#include "IoTeX/Signer.h" +#include "IoTeX/Staking.h" +#include "PrivateKey.h" #include "proto/IoTeX.pb.h" #include "../interface/TWTestUtilities.h" - +#include #include using namespace TW; using namespace TW::IoTeX; -static const uint64_t pyggyIndex01 = 1001; -static std::string IOTEX_STAKING_CONTRACT = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; -static const Data IOTEX_STAKING_TEST = TW::data(std::string("this is a test")); -static const Data candidate12 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +TEST(TWIoTeXStaking, Create) { + std::string IOTEX_STAKING_CANDIDATE = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + std::string IOTEX_STAKING_AMOUNT = "100"; + Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); -TEST(IoTeXStaking, Stake) { - Data result; - stakingStake(candidate12, pyggyIndex01, true, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "07c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); + auto stake = stakingCreate(candidate, amount, 10000, true, payload); + ASSERT_EQ(hex(stake), "0a29696f313964307033616834673877773964376b63786671383779786537666e723872" + "7074683573686a120331303018904e20012a077061796c6f6164"); } -TEST(IoTeXStaking, Unstake) { - Data result; - stakingUnstake(pyggyIndex01, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "c8fd6ed000000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +TEST(TWIoTeXStaking, AddDeposit) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + std::string IOTEX_STAKING_AMOUNT = "10"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); + + auto stake = stakingAddDeposit(10, amount, payload); + + ASSERT_EQ(hex(stake), "080a120231301a077061796c6f6164"); } -TEST(IoTeXStaking, Withdraw) { - Data result; - stakingWithdraw(pyggyIndex01, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "030ba25d00000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +TEST(TWIoTeXStaking, Unstake) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingUnstake(10, payload); + + ASSERT_EQ(hex(stake), "080a12077061796c6f6164"); } -TEST(IoTeXStaking, AddStake) { - Data result; - stakingAddStake(pyggyIndex01, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "6e7b301700000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +TEST(TWIoTeXStaking, Withdraw) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingWithdraw(10, payload); + + ASSERT_EQ(hex(stake), "080a12077061796c6f6164"); } -TEST(IoTeXStaking, MoveStake) { - Data result; - stakingMoveStake(pyggyIndex01, candidate12, IOTEX_STAKING_TEST, result); - ASSERT_EQ(hex(result), "d3e41fd200000000000000000000000000000000000000000000000000000000000003e90102030405060708090a0b0c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000e7468697320697320612074657374000000000000000000000000000000000000"); +TEST(TWIoTeXStaking, Restake) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingRestake(10, 1000, true, payload); + + ASSERT_EQ(hex(stake), "080a10e807180122077061796c6f6164"); } -TEST(IoTeXStaking, SignStake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); +TEST(TWIoTeXStaking, ChangeCandidate) { + std::string IOTEX_STAKING_CANDIDATE = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingChangeCandidate(10, candidate, payload); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - // data = "this is a test" here, it could be null (user leaves data empty when signing the tx) - Data stake; - stakingStake(candidate12, pyggyIndex01, true, IOTEX_STAKING_TEST, stake); - staking->set_data(stake.data(), stake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "cc3c8f7a0129455d70457c4be42a8b31d8e1df59594c99041b6b6d091b295b32"); - // build() signs the tx - auto output = signer.build(); - // signed action's serialized bytes - auto encoded = output.encoded(); - ASSERT_EQ(hex(encoded.begin(), encoded.end()), "0a86020801107b18f806220339393962f7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611ac40107c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74686973206973206120746573740000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41a558bc9a4bfba920242ccd4d5c5da363ec534d4dd5eb67f88e9db7aaad5c50ad62dfe298c0e54e311ebba045f48cea1136e42a123a8e6b03d3e6ed82d4ec2b9401"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "41b1f8be5f6b884c06556fba2611716e8e514b507f5a653fc02ac50ba13fbd6c"); + ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c" + "64326e6b7079636333677a611a077061796c6f6164"); } -TEST(IoTeXStaking, SignUnstake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); +TEST(TWIoTeXStaking, Transfer) { + std::string IOTEX_STAKING_CANDIDATE = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingTransfer(10, candidate, payload); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data unstake; - stakingUnstake(pyggyIndex01, Data{}, unstake); - staking->set_data(unstake.data(), unstake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "784f3d71246dfe897c1cb02da94e8ef1ac2381ac7f25ecfee80eaa78237db95b"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "b93a2874a72ce4eb8a41a20c209cf3fd188671ed8be8239a57960cbed887e962"); + ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c6432" + "6e6b7079636333677a611a077061796c6f6164"); } -TEST(IoTeXStaking, SignWithdraw) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); +TEST(TWIoTeXStaking, CandidateRegister) { + std::string IOTEX_STAKING_NAME = "test"; + std::string IOTEX_STAKING_OPERATOR = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"; + std::string IOTEX_STAKING_REWARD = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"; + std::string IOTEX_STAKING_OWNER = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; + std::string IOTEX_STAKING_AMOUNT = "100"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data name(IOTEX_STAKING_NAME.begin(), IOTEX_STAKING_NAME.end()); + Data operatorAddress(IOTEX_STAKING_OPERATOR.begin(), IOTEX_STAKING_OPERATOR.end()); + Data reward(IOTEX_STAKING_REWARD.begin(), IOTEX_STAKING_REWARD.end()); + Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); + Data owner(IOTEX_STAKING_OWNER.begin(), IOTEX_STAKING_OWNER.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = + candidateRegister(name, operatorAddress, reward, amount, 10000, false, owner, payload); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data withdraw; - stakingWithdraw(pyggyIndex01, Data{}, withdraw); - staking->set_data(withdraw.data(), withdraw.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "ff55882624b2a1d6ae2d9fdec5f8a0f13b2f23c8b28c8ba91773b63f49b97fcc"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "2b2657247a72cb262de214b4e793c7a01fa2139fd5d12a46d43c24f87f9e2396"); + ASSERT_EQ(hex(stake), + "0a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a3539" + "7937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a64" + "7472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b637866" + "71383779786537666e7238727074683573686a32077061796c6f6164"); } -TEST(IoTeXStaking, SignAddStake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); +TEST(TWIoTeXStaking, CandidateUpdate) { + std::string IOTEX_STAKING_NAME = "test"; + std::string IOTEX_STAKING_OPERATOR = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng"; + std::string IOTEX_STAKING_REWARD = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd"; + Data name(IOTEX_STAKING_NAME.begin(), IOTEX_STAKING_NAME.end()); + Data operatorAddress(IOTEX_STAKING_OPERATOR.begin(), IOTEX_STAKING_OPERATOR.end()); + Data reward(IOTEX_STAKING_REWARD.begin(), IOTEX_STAKING_REWARD.end()); + + auto stake = candidateUpdate(name, operatorAddress, reward); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data addStake; - stakingAddStake(pyggyIndex01, Data{}, addStake); - staking->set_data(addStake.data(), addStake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "7581e7f779429aa502879581fdc29f87917acfe638069255b6f033c45d7f24fe"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "c71058812a5febe5cdcdaf9499ba0b2c895f88d1acd3203e5097b307c2a5f1d1"); + ASSERT_EQ(hex(stake), "0a04746573741229696f31636c36726c32657635646661393838716d677a6732783468" + "66617a6d7039766e326736366e671a29696f316a757678356730363365753474733833" + "326e756b7034766763776b32676e6335637539617964"); } -TEST(IoTeXStaking, SignMoveStake) { +TEST(TWIoTeXStaking, SignAll) { auto input = Proto::SigningInput(); input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_nonce(0); + input.set_gaslimit(1000000); + input.set_gasprice("10"); + auto keyhex = parse_hex("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"); input.set_privatekey(keyhex.data(), keyhex.size()); + Proto::SigningOutput output; + + { // sign stakecreate + auto action = input.mutable_stakecreate(); + action->set_candidatename("io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"); + action->set_stakedamount("100"); + action->set_stakedduration(10000); + action->set_autostake(true); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ(hex(output.encoded()), + "0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671" + "3837797865" + "37666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d890" + "3f6b3793bd" + "db4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c" + "0bc76ef30d" + "d6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5ae" + "d8e2e026d4" + "6e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "f1785e47b4200c752bb6518bd18097a41e075438b8c18c9cb00e1ae2f38ce767"); + input.release_stakecreate(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakeadddeposit + auto action = input.mutable_stakeadddeposit(); + action->set_bucketindex(10); + action->set_amount("10"); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793" + "bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0b" + "c76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb5" + "5e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "ca8937d6f224a4e4bf93cb5605581de2d26fb0481e1dfc1eef384ee7ccf94b73"); + input.release_stakeadddeposit(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakeunstake + auto action = input.mutable_stakeunstake(); + action->set_bucketindex(10); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5" + "d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30d" + "d6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a" + "184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "bed58b64a6c4e959eca60a86f0b2149ce0e1dd527ac5fd26aef725ebf7c22a7d"); + input.release_stakeunstake(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakewithdraw + auto action = input.mutable_stakewithdraw(); + action->set_bucketindex(10); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5" + "d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30d" + "d6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75" + "340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "28049348cf34f1aa927caa250e7a1b08778c44efaf73b565b6fa9abe843871b4"); + input.release_stakewithdraw(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakerestake + auto action = input.mutable_stakerestake(); + action->set_bucketindex(10); + action->set_stakedduration(1000); + action->set_autostake(true); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b37" + "93bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c" + "0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418ee" + "dbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "8816e8f784a1fce40b54d1cd172bb6976fd9552f1570c73d1d9fcdc5635424a9"); + input.release_stakerestake(); + output.release_encoded(); + output.release_hash(); + } + { // sign stakechangecandidate + auto action = input.mutable_stakechangecandidate(); + action->set_bucketindex(10); + action->set_candidatename("io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a7263636739793568" + "6e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4e" + "a5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef3" + "0dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f8" + "1895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "186526b5b9fe74e25beb52c83c41780a69108160bef2ddaf3bffb9f1f1e5e73a"); + input.release_stakechangecandidate(); + output.release_encoded(); + output.release_hash(); + } + { // sign staketransfer + auto action = input.mutable_staketransferownership(); + action->set_bucketindex(10); + action->set_voteraddress("io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a7263636739793568" + "6e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4e" + "a5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef3" + "0dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d" + "00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "74b2e1d6a09ba5d1298fa422d5850991ae516865077282196295a38f93c78b85"); + input.release_staketransferownership(); + output.release_encoded(); + output.release_hash(); + } + { // sign candidateupdate + auto action = input.mutable_candidateupdate(); + action->set_name("test"); + action->set_operatoraddress("io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng"); + action->set_rewardaddress("io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c3265763564666139383871" + "6d677a673278346866617a6d7039766e326736366e671a29696f316a7576783567303633657534747338" + "33326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d" + "637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a103" + "8ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11" + "ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "ca1a28f0e9a58ffc67037cc75066dbdd8e024aa2b2e416e4d6ce16c3d86282e5"); + input.release_candidateupdate(); + output.release_encoded(); + output.release_hash(); + } + { // sign candidateregister + input.set_gasprice("1000"); + auto cbi = input.mutable_candidateregister()->mutable_candidate(); + cbi->set_name("test"); + cbi->set_operatoraddress("io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"); + cbi->set_rewardaddress("io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"); - // staking is implemented using the Execution message - auto staking = input.mutable_call(); - staking->set_amount("456"); - staking->set_contract(IOTEX_STAKING_CONTRACT); - - // call staking API to generate calldata - Data moveStake; - stakingMoveStake(pyggyIndex01, candidate12, Data{}, moveStake); - staking->set_data(moveStake.data(), moveStake.size()); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "818637b9708ec9e075c7a17f23757cb6895eae6dd3331f7e44129efae6ca9a21"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "33290ded342efaebf795855be73d34cbac149a2415ff9558de10303e6126f30d"); + auto action = input.mutable_candidateregister(); + action->set_stakedamount("100"); + action->set_stakedduration(10000); + action->set_autostake(false); + action->set_owneraddress("io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"); + action->set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ(hex(output.encoded()), + "0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a" + "7672743467" + "757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e" + "3235796d68" + "65756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f3139643070" + "3361683467" + "3877773964376b63786671383779786537666e7238727074683573686a32077061796c6f61641241" + "04755ce6d8" + "903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99" + "a5c1335b58" + "3c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a" + "9179b141ac" + "68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "35f53a536e014b32b85df50483ef04849b80ad60635b3b1979c5ba1096b65237"); + } } diff --git a/tests/IoTeX/TWIoTeXStakingTests.cpp b/tests/IoTeX/TWIoTeXStakingTests.cpp deleted file mode 100644 index 502e5dc62f9..00000000000 --- a/tests/IoTeX/TWIoTeXStakingTests.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright © 2017-2020 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. - -#include "Data.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "IoTeX/Signer.h" -#include "proto/IoTeX.pb.h" -#include "../interface/TWTestUtilities.h" - -#include - -using namespace TW; -using namespace TW::IoTeX; - -static const char *_Nonnull IOTEX_STAKING_CONTRACT = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; -static const char * IOTEX_STAKING_TEST = "this is a test"; - -TEST(TWIoTeXStaking, SignStake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); - - auto& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto name = Data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - auto candidate = std::string(name.begin(), name.end()); - auto& stake = *staking.mutable_stake(); - stake.set_candidate(candidate); - stake.set_duration(1001); - stake.set_nondecay(true); - stake.set_data(IOTEX_STAKING_TEST); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "cc3c8f7a0129455d70457c4be42a8b31d8e1df59594c99041b6b6d091b295b32"); - // build() signs the tx - auto output = signer.build(); - // signed action's serialized bytes - auto encoded = output.encoded(); - ASSERT_EQ(hex(encoded.begin(), encoded.end()), "0a86020801107b18f806220339393962f7010a033435361229696f3178707136326177383575717a72636367397935686e727976386c64326e6b7079636333677a611ac40107c35fc00102030405060708090a0b0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e74686973206973206120746573740000000000000000000000000000000000001241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41a558bc9a4bfba920242ccd4d5c5da363ec534d4dd5eb67f88e9db7aaad5c50ad62dfe298c0e54e311ebba045f48cea1136e42a123a8e6b03d3e6ed82d4ec2b9401"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "41b1f8be5f6b884c06556fba2611716e8e514b507f5a653fc02ac50ba13fbd6c"); -} - -TEST(TWIoTeXStaking, SignUnstake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); - - auto& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto& unstake = *staking.mutable_unstake(); - unstake.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "784f3d71246dfe897c1cb02da94e8ef1ac2381ac7f25ecfee80eaa78237db95b"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "b93a2874a72ce4eb8a41a20c209cf3fd188671ed8be8239a57960cbed887e962"); -} - -TEST(TWIoTeXStaking, SignWithdraw) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); - - auto& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto& withdraw = *staking.mutable_withdraw(); - withdraw.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "ff55882624b2a1d6ae2d9fdec5f8a0f13b2f23c8b28c8ba91773b63f49b97fcc"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "2b2657247a72cb262de214b4e793c7a01fa2139fd5d12a46d43c24f87f9e2396"); -} - -TEST(TWIoTeXStaking, SignAddStake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); - - auto& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto& add = *staking.mutable_addstake(); - add.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "7581e7f779429aa502879581fdc29f87917acfe638069255b6f033c45d7f24fe"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "c71058812a5febe5cdcdaf9499ba0b2c895f88d1acd3203e5097b307c2a5f1d1"); -} - -TEST(TWIoTeXStaking, SignMoveStake) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); - - auto& staking = *input.mutable_staking(); - staking.set_amount("456"); - staking.set_contract(IOTEX_STAKING_CONTRACT); - - auto name = Data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - auto candidate = std::string(name.begin(), name.end()); - auto& move = *staking.mutable_movestake(); - move.set_candidate(candidate); - move.set_piggy_index(1001); - - auto signer = IoTeX::Signer(std::move(input)); - // raw action's hash - ASSERT_EQ(hex(signer.hash()), "818637b9708ec9e075c7a17f23757cb6895eae6dd3331f7e44129efae6ca9a21"); - // build() signs the tx - auto output = signer.build(); - // signed action's hash - ASSERT_EQ(hex(output.hash()), "33290ded342efaebf795855be73d34cbac149a2415ff9558de10303e6126f30d"); -} diff --git a/tools/generate-files b/tools/generate-files index 20079f3c417..523835e157c 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -59,7 +59,6 @@ fi # Generate internal message protocol Protobuf files -- not every time "$PROTOC" -I=$PREFIX/include -I=src/Tron/Protobuf --cpp_out=src/Tron/Protobuf src/Tron/Protobuf/*.proto "$PROTOC" -I=$PREFIX/include -I=src/Zilliqa/Protobuf --cpp_out=src/Zilliqa/Protobuf src/Zilliqa/Protobuf/*.proto -"$PROTOC" -I=$PREFIX/include -I=src/IoTeX/Protobuf --cpp_out=src/IoTeX/Protobuf src/IoTeX/Protobuf/*.proto # Generate Proto interface file "$PROTOC" -I=$PREFIX/include -I=src/proto --plugin=$PREFIX/bin/protoc-gen-c-typedef --c-typedef_out include/TrustWalletCore src/proto/*.proto From 4fd327aa3346265c2b88bbb4d7b89860505c421d Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 14 Apr 2020 11:43:51 +0000 Subject: [PATCH 06/81] Fix possible memory leak in walletconsole/Util.cpp (#921) Add missing call to `delete` in function `fileR`. --- walletconsole/lib/Util.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/walletconsole/lib/Util.cpp b/walletconsole/lib/Util.cpp index 5a54509998e..01bcba85e9f 100644 --- a/walletconsole/lib/Util.cpp +++ b/walletconsole/lib/Util.cpp @@ -79,6 +79,7 @@ bool Util::fileR(const string& fileName, string& res) { char* buffer = new char[length]; if (!infile.read(buffer, length)) { _out << "Could not read file '" << fileName << "'" << endl; + delete[] buffer; return false; } int red = infile.gcount(); From e8ff9c710673e327229b2cc0d91d3e054fe72fcf Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Wed, 15 Apr 2020 04:11:26 +0200 Subject: [PATCH 07/81] Build sample apps in CI (#909) * Script for building sample app (C++). * Include go sample app in build script. * Add echo's. * Sample build: include OSX and Android build. * Minor FIo test extension. * Samples build: split in 3, by 3 build platforms. * Revert "Samples build: split in 3, by 3 build platforms." This reverts commit a262043b626b096b2be2cf32b57208ee0391ebac. * Options for different platforms. * Sample-build: Rearranged with functions per sample app Co-authored-by: Catenocrypt --- samples/go/main.go | 4 +-- tests/FIO/AddressTests.cpp | 7 ++-- tools/samples-build | 73 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) create mode 100755 tools/samples-build diff --git a/samples/go/main.go b/samples/go/main.go index 3f56b5241a9..38507a3a341 100644 --- a/samples/go/main.go +++ b/samples/go/main.go @@ -1,7 +1,7 @@ package main -// #cgo CFLAGS: -I/wallet-core/include -// #cgo LDFLAGS: -L/wallet-core/build -L/wallet-core/build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #cgo CFLAGS: -I../../include +// #cgo LDFLAGS: -L../../build -L../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm // #include // #include // #include diff --git a/tests/FIO/AddressTests.cpp b/tests/FIO/AddressTests.cpp index 62d117e99d9..f78e3ae25f2 100644 --- a/tests/FIO/AddressTests.cpp +++ b/tests/FIO/AddressTests.cpp @@ -35,10 +35,9 @@ TEST(FIOAddress, ValidateData) { } TEST(FIOAddress, FromString) { - ASSERT_EQ( - Address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o").string(), - "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o" - ); + Address addr("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + ASSERT_EQ(addr.string(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + ASSERT_EQ(hex(addr.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f343fc54e"); } TEST(FIOAddress, FromStringInvalid) { diff --git a/tools/samples-build b/tools/samples-build new file mode 100755 index 00000000000..c956e7bc4c8 --- /dev/null +++ b/tools/samples-build @@ -0,0 +1,73 @@ +#!/bin/bash +# +# This script builds the sample applications. + +set -e + +if [ -z $1 ] +then + echo "No platform argument given, use all|linux|ios|android" + set $1 'linux' +fi + +if [ ! "$1" = "linux" ] && [ ! "$1" = "ios" ] && [ ! "$1" = "android" ] && [ ! "$1" = "all" ] +then + echo "Wrong platform argument given, use all|linux|ios|android" + set $1 'linux' +fi + +echo "Platform argument: "$1 + +build_cpp() { + pushd samples/cpp + echo "Building CPP sample app:"`pwd` + cmake . -DWALLET_CORE=../../ + make + echo "Building CPP sample app: done" + popd +} + +build_ios() { + pushd samples/osx/cocoapods + echo "Building OSX sample app:"`pwd` + pod repo update + pod install + echo "Building OSX sample app: done" + popd +} + +build_android() { + pushd samples/android + echo "Building Android sample app:"`pwd` + ./gradlew assembleDebug + echo "Building Android sample app: done" + popd +} + +build_go() { + pushd samples/go + echo "Building GO sample app:"`pwd` + go build -o main + echo "Building GO sample app: done" + popd +} + +if [ "$1" = "linux" ]; then + build_cpp + build_go +fi + +if [ "$1" = "ios" ]; then + build_ios +fi + +if [ "$1" = "android" ]; then + build_android +fi + +if [ "$1" = "all" ]; then + build_cpp + build_go + build_ios + build_android +fi From 87c4f1fd8dbf5a8d2d46b389ef98369c4060b8c6 Mon Sep 17 00:00:00 2001 From: Viktor Radchenko <1641795+vikmeup@users.noreply.github.com> Date: Wed, 22 Apr 2020 21:14:03 -0700 Subject: [PATCH 08/81] Create auto_assign.yml (#928) --- .github/auto_assign.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/auto_assign.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 00000000000..8dbdbff7c77 --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,18 @@ +# Set to true to add reviewers to pull requests +addReviewers: true + +# Set to true to add assignees to pull requests +addAssignees: false + +# A list of reviewers to be added to pull requests (GitHub user name) +reviewers: + - hewigovens + - catenocrypt + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +skipKeywords: + - wip + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +numberOfReviewers: 0 From 15fb8f3d31778a6786896de33d9dd088f4200731 Mon Sep 17 00:00:00 2001 From: Viktor Radchenko <1641795+vikmeup@users.noreply.github.com> Date: Thu, 23 Apr 2020 16:28:53 -0700 Subject: [PATCH 09/81] Add CODEOWNERS (#930) * Create CODEOWNERS --- .github/CODEOWNERS | 6 ++++++ .github/auto_assign.yml | 18 ------------------ 2 files changed, 6 insertions(+), 18 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 .github/auto_assign.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..33536958aad --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. + +* @hewigovens @catenocrypt diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml deleted file mode 100644 index 8dbdbff7c77..00000000000 --- a/.github/auto_assign.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Set to true to add reviewers to pull requests -addReviewers: true - -# Set to true to add assignees to pull requests -addAssignees: false - -# A list of reviewers to be added to pull requests (GitHub user name) -reviewers: - - hewigovens - - catenocrypt - -# A list of keywords to be skipped the process that add reviewers if pull requests include it -skipKeywords: - - wip - -# A number of reviewers added to the pull request -# Set 0 to add all the reviewers (default: 0) -numberOfReviewers: 0 From e0e21bc2c201b81d2d742d012cce9e1c530672cf Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Tue, 28 Apr 2020 03:05:40 +0800 Subject: [PATCH 10/81] Migrate CI to GitHub Actions (#937) * Create android-ci.yml * Add ios-ci.yml * Add linux-ci.yml * Add sudo * try set boost version * install libc++ * Fix linux coverage * move internal src to local/src * Fix protobuf symbolic link --- .github/workflows/android-ci.yml | 37 ++++++++++++++++++++++++ .github/workflows/ios-ci.yml | 32 +++++++++++++++++++++ .github/workflows/linux-ci.yml | 43 ++++++++++++++++++++++++++++ README.md | 6 ++-- TrustWalletCore.podspec | 2 +- android/build.gradle | 2 +- android/trustwalletcore/build.gradle | 2 +- cmake/Protobuf.cmake | 4 +-- samples/android/build.gradle | 2 +- swift/cpp.xcconfig.in | 2 +- swift/protobuf | 2 +- tests/CMakeLists.txt | 2 +- tests/interface/TWTestUtilities.cpp | 1 - tools/coverage | 18 +++++++++++- tools/install-dependencies | 12 ++++---- tools/ios-test | 2 +- 16 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/android-ci.yml create mode 100644 .github/workflows/ios-ci.yml create mode 100644 .github/workflows/linux-ci.yml diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml new file mode 100644 index 00000000000..c38c0dfdf7f --- /dev/null +++ b/.github/workflows/android-ci.yml @@ -0,0 +1,37 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: [macos-10.15] + + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: brew install boost ninja + - name: Install Android Dependencies + run: | + $ANDROID_HOME/tools/bin/sdkmanager --verbose "cmake;3.10.2.4988404" "ndk-bundle" + $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-26;google_apis;x86" + - name: Accept Licenses + run: echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/tools/bin/sdkmanager --licenses + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Run test + run: | + tools/generate-files + tools/android-test diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml new file mode 100644 index 00000000000..e3ad8eb88a2 --- /dev/null +++ b/.github/workflows/ios-ci.yml @@ -0,0 +1,32 @@ +name: iOS CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: [macos-10.15] + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: | + brew install boost ninja xcodegen + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Run codegen tests + run: tools/codegen-test + - name: Run iOS tests + run: | + tools/generate-files + tools/ios-test diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml new file mode 100644 index 00000000000..666c0343f1c --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,43 @@ +name: Linux CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: [ubuntu-18.04] + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: | + # build-essential libboost-all-dev clang-9 ruby-full cmake + sudo apt-get install libc++-dev libc++abi-dev ninja-build lcov llvm + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Build and test + run: | + tools/generate-files + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON -DBOOST_ROOT=${BOOST_ROOT_1_72_0} + make -Cbuild + build/tests/tests tests --gtest_output=xml + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + - name: Gather code coverage + run: | + sudo rm -rf coverage.info + tools/coverage diff --git a/README.md b/README.md index a42187be546..827eca33c04 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Trust Wallet Core is a cross-platform library that implements low-level cryptographic wallet functionality for all supported blockchains. Most of the code is C++ with a set of strict exported C interfaces. The library provides idiomatic interfaces for all supported languages (currently Swift for iOS and Java for Android). -[![iOS status](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_apis/build/status/Wallet%20Core%20iOS)](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_build/latest?definitionId=13) -[![Android status](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_apis/build/status/Wallet%20Core%20Android)](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_build/latest?definitionId=11) -[![Linux status](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_apis/build/status/Wallet%20Core%20Linux)](https://dev.azure.com/TrustWallet/Trust%20Wallet%20Core/_build/latest?definitionId=24) +![iOS CI](https://github.com/trustwallet/wallet-core/workflows/iOS%20CI/badge.svg) +![Android CI](https://github.com/trustwallet/wallet-core/workflows/Android%20CI/badge.svg) +![Linux CI](https://github.com/trustwallet/wallet-core/workflows/Linux%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/82e76f6ea4ba4f0d9029e8846c04c093)](https://www.codacy.com/app/hewigovens/wallet-core?utm_source=github.com&utm_medium=referral&utm_content=TrustWallet/wallet-core&utm_campaign=Badge_Grade) ![Codecov](https://codecov.io/gh/TrustWallet/wallet-core/branch/master/graph/badge.svg) diff --git a/TrustWalletCore.podspec b/TrustWalletCore.podspec index 028cb115716..3182e93d445 100644 --- a/TrustWalletCore.podspec +++ b/TrustWalletCore.podspec @@ -28,7 +28,7 @@ Pod::Spec.new do |s| end s.subspec 'Core' do |ss| - protobuf_source_dir = 'build/protobuf/staging/protobuf-3.9.0' + protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.9.0' include_dir = 'build/local/include' ss.source_files = 'src/**/*.{c,cc,cpp,h}', diff --git a/android/build.gradle b/android/build.gradle index c3b350a38ce..a710c7f55ed 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:3.6.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' diff --git a/android/trustwalletcore/build.gradle b/android/trustwalletcore/build.gradle index 83bc1d15148..145624ca9a6 100644 --- a/android/trustwalletcore/build.gradle +++ b/android/trustwalletcore/build.gradle @@ -4,7 +4,7 @@ group='com.github.trustwallet' android { compileSdkVersion 28 - + ndkVersion '21.1.6352462' defaultConfig { minSdkVersion 23 targetSdkVersion 28 diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake index 72f271d70d8..7f27286911e 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -1,5 +1,5 @@ -set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/protobuf/staging/protobuf-3.9.0) -set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/protobuf/staging/protobuf-3.9.0) +set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.9.0) +set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.9.0) # Updated from https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotopuf.cmake diff --git a/samples/android/build.gradle b/samples/android/build.gradle index a706027ba80..b908ed10fd6 100644 --- a/samples/android/build.gradle +++ b/samples/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:3.6.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/swift/cpp.xcconfig.in b/swift/cpp.xcconfig.in index 9d351112b5b..9e2fd5ef152 100644 --- a/swift/cpp.xcconfig.in +++ b/swift/cpp.xcconfig.in @@ -5,4 +5,4 @@ // file LICENSE at the root of the source code distribution tree. HEADER_SEARCH_PATHS = $(SRCROOT)/../src @Boost_INCLUDE_DIRS@ -SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/protobuf/staging/protobuf-3.9.0/src +SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.9.0/src diff --git a/swift/protobuf b/swift/protobuf index 42dacc8e080..b0c567bc5a1 120000 --- a/swift/protobuf +++ b/swift/protobuf @@ -1 +1 @@ -../build/protobuf/staging/protobuf-3.9.0/src \ No newline at end of file +../build/local/src/protobuf/protobuf-3.9.0/src \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be1569b89cd..f6acfc476be 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,7 +6,7 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Add googletest directly to our build. This defines # the gtest and gtest_main targets. -add_subdirectory(${CMAKE_SOURCE_DIR}/build/gtest/staging/googletest-release-1.8.1 +add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.8.1 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) diff --git a/tests/interface/TWTestUtilities.cpp b/tests/interface/TWTestUtilities.cpp index 14d256a89bc..2c7412330c8 100644 --- a/tests/interface/TWTestUtilities.cpp +++ b/tests/interface/TWTestUtilities.cpp @@ -7,7 +7,6 @@ #include "TWTestUtilities.h" #include -#include using namespace std; diff --git a/tools/coverage b/tools/coverage index aea89ceac39..a60dd5266d7 100755 --- a/tools/coverage +++ b/tools/coverage @@ -4,7 +4,23 @@ set -e -lcov --capture --directory . --output-file coverage.info +function install_llvm_gcov() { + cat << EOF > /tmp/llvm-gcov.sh +#!/bin/bash +exec /usr/bin/llvm-cov gcov "\$@" +EOF + sudo chmod 755 /tmp/llvm-gcov.sh +} + +if [[ `uname` == "Darwin" ]]; then + echo "gcov is llvm-cov on macOS" + lcov --capture --directory . --output-file coverage.info +else + echo "call llvm-cov on Linux" + install_llvm_gcov + lcov --gcov-tool /tmp/llvm-gcov.sh --capture --directory . --output-file coverage.info +fi + lcov --remove coverage.info '/usr/*' --output-file coverage.info lcov --remove coverage.info '/Applications/*' --output-file coverage.info lcov --remove coverage.info '*/build/*' --output-file coverage.info diff --git a/tools/install-dependencies b/tools/install-dependencies index 53536a5afd5..80919b45cdd 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -16,7 +16,7 @@ export LD_RUN_PATH="$PREFIX/lib" # Download Google Test export GTEST_VERSION=1.8.1 -GTEST_DIR="$ROOT/build/gtest/staging" +GTEST_DIR="$ROOT/build/local/src/gtest" mkdir -p "$GTEST_DIR" cd "$GTEST_DIR" if [ ! -f release-$GTEST_VERSION.tar.gz ]; then @@ -32,7 +32,7 @@ make install # Download Check export CHECK_VERSION=0.12.0 -CHECK_DIR="$ROOT/build/check/staging" +CHECK_DIR="$ROOT/build/local/src/check" mkdir -p "$CHECK_DIR" cd "$CHECK_DIR" if [ ! -f check-$CHECK_VERSION.tar.gz ]; then @@ -48,7 +48,7 @@ make install # Download Nlohmann JSON export JSON_VERSION=3.5.0 -JSON_DIR="$ROOT/build/json/staging" +JSON_DIR="$ROOT/build/local/json" mkdir -p "$JSON_DIR" cd "$JSON_DIR" if [ ! -f include.zip ]; then @@ -58,7 +58,7 @@ unzip -d "$PREFIX" -o include.zip # Download Protobuf sources export PROTOBUF_VERSION=3.9.0 -PROTOBUF_DIR="$ROOT/build/protobuf/staging" +PROTOBUF_DIR="$ROOT/build/local/src/protobuf" mkdir -p "$PROTOBUF_DIR" cd "$PROTOBUF_DIR" if [ ! -f protobuf-java-$PROTOBUF_VERSION.tar.gz ]; then @@ -75,10 +75,10 @@ make install make clean "$PREFIX/bin/protoc" --version -if [ -x "$(command -v swift)" ]; then +if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then # Download Swift Protobuf sources export SWIFT_PROTOBUF_VERSION=1.7.0 - SWIFT_PROTOBUF_DIR="$ROOT/build/swift-protobuf/staging" + SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" if [ ! -f $SWIFT_PROTOBUF_VERSION.tar.gz ]; then diff --git a/tools/ios-test b/tools/ios-test index 1206d3c57a5..8c68a4b70f9 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -7,5 +7,5 @@ set -e pushd swift xcodegen pod install -xcrun xcodebuild -workspace TrustWalletCore.xcworkspace -scheme TrustWalletCore -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11,OS=13.0' test +xcrun xcodebuild -workspace TrustWalletCore.xcworkspace -scheme TrustWalletCore -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11' test popd From 7dde1a51d0ce2b2a459af3d9dfe2ea4f7cc422f2 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:45:11 +0200 Subject: [PATCH 11/81] Bitcoin Script minor fixes and new tests. (#933) * Bitcoin Script minor fixes and new tests. * Extra tests for the missing 3 lines. * Fix isPayToWitnessScriptHash (was typo), new isPayToWitnessPublicKeyHash(). * A rename (Pubkey -> PublicKey). * Extra test for TWBitcoinScript coverage. --- include/TrustWalletCore/TWBitcoinScript.h | 4 + src/Bitcoin/Script.cpp | 44 ++- src/Bitcoin/Script.h | 24 +- src/Bitcoin/TransactionSigner.cpp | 4 +- src/Decred/Signer.cpp | 4 +- src/interface/TWBitcoinScript.cpp | 10 +- tests/Bitcoin/BitcoinScriptTests.cpp | 285 ++++++++++++++++++ tests/Bitcoin/TWBitcoinScriptTests.cpp | 131 +++++++- tests/Bitcoin/TWBitcoinSigningTests.cpp | 2 +- .../Groestlcoin/TWGroestlcoinSigningTests.cpp | 2 +- 10 files changed, 465 insertions(+), 45 deletions(-) create mode 100644 tests/Bitcoin/BitcoinScriptTests.cpp diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index a50499fa4db..a382328c45d 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -49,6 +49,10 @@ bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript *_Nonnull scr TW_EXPORT_PROPERTY bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *_Nonnull script); +/// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. +TW_EXPORT_PROPERTY +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *_Nonnull script); + /// Determines whether this is a witness programm script. TW_EXPORT_PROPERTY bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *_Nonnull script); diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index bf1ce32aad7..358e030b3e6 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -42,6 +42,11 @@ bool Script::isPayToScriptHash() const { bool Script::isPayToWitnessScriptHash() const { // Extra-fast test for pay-to-witness-script-hash + return bytes.size() == 34 && bytes[0] == OP_0 && bytes[1] == 0x20; +} + +bool Script::isPayToWitnessPublicKeyHash() const { + // Extra-fast test for pay-to-witness-public-key-hash return bytes.size() == 22 && bytes[0] == OP_0 && bytes[1] == 0x14; } @@ -55,7 +60,7 @@ bool Script::isWitnessProgram() const { return bytes[1] + 2 == bytes.size(); } -bool Script::matchPayToPubkey(Data& result) const { +bool Script::matchPayToPublicKey(Data& result) const { if (bytes.size() == PublicKey::secp256k1ExtendedSize + 2 && bytes[0] == PublicKey::secp256k1ExtendedSize && bytes.back() == OP_CHECKSIG) { result.clear(); @@ -73,7 +78,7 @@ bool Script::matchPayToPubkey(Data& result) const { return false; } -bool Script::matchPayToPubkeyHash(Data& result) const { +bool Script::matchPayToPublicKeyHash(Data& result) const { if (bytes.size() == 25 && bytes[0] == OP_DUP && bytes[1] == OP_HASH160 && bytes[2] == 20 && bytes[23] == OP_EQUALVERIFY && bytes[24] == OP_CHECKSIG) { result.clear(); @@ -93,30 +98,21 @@ bool Script::matchPayToScriptHash(Data& result) const { } bool Script::matchPayToWitnessPublicKeyHash(Data& result) const { - if (bytes.size() == 22 && bytes[0] == OP_0 && bytes[1] == 0x14) { - result.clear(); - std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); - return true; + if (!isPayToWitnessPublicKeyHash()) { + return false; } - return false; + result.clear(); + std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); + return true; } bool Script::matchPayToWitnessScriptHash(Data& result) const { - if (bytes.size() == 34 && bytes[0] == OP_0 && bytes[1] == 0x20) { - result.clear(); - std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); - return true; - } - return false; -} - -/// Decodes a small integer -static inline int decodeNumber(uint8_t opcode) { - if (opcode == OP_0) { - return 0; + if (!isPayToWitnessScriptHash()) { + return false; } - assert(opcode >= OP_1 && opcode <= OP_16); - return static_cast(opcode) - static_cast(OP_1 - 1); + result.clear(); + std::copy(std::begin(bytes) + 2, std::end(bytes), std::back_inserter(result)); + return true; } bool Script::matchMultisig(std::vector& keys, int& required) const { @@ -185,7 +181,7 @@ bool Script::getScriptOp(size_t& index, uint8_t& opcode, Data& operand) const { if (bytes.size() - index < 1) { return false; } - size = index; + size = bytes[index]; index += 1; } else if (opcode == OP_PUSHDATA2) { if (bytes.size() - index < 2) { @@ -240,11 +236,13 @@ Script Script::buildPayToWitnessProgram(const Data& program) { return script; } -Script Script::buildPayToWitnessPubkeyHash(const Data& hash) { +Script Script::buildPayToWitnessPublicKeyHash(const Data& hash) { + assert(hash.size() == 20); return Script::buildPayToWitnessProgram(hash); } Script Script::buildPayToWitnessScriptHash(const Data& scriptHash) { + assert(scriptHash.size() == 32); return Script::buildPayToWitnessProgram(scriptHash); } diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index f6b5de13427..08dfe6bfe28 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -43,14 +43,17 @@ class Script { /// Determines whether this is a pay-to-witness-script-hash (P2WSH) script. bool isPayToWitnessScriptHash() const; + /// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. + bool isPayToWitnessPublicKeyHash() const; + /// Determines whether this is a witness programm script. bool isWitnessProgram() const; /// Matches the script to a pay-to-public-key (P2PK) script. - bool matchPayToPubkey(Data& publicKey) const; + bool matchPayToPublicKey(Data& publicKey) const; /// Matches the script to a pay-to-public-key-hash (P2PKH). - bool matchPayToPubkeyHash(Data& keyHash) const; + bool matchPayToPublicKeyHash(Data& keyHash) const; /// Matches the script to a pay-to-script-hash (P2SH). bool matchPayToScriptHash(Data& scriptHash) const; @@ -58,7 +61,7 @@ class Script { /// Matches the script to a pay-to-witness-public-key-hash (P2WPKH). bool matchPayToWitnessPublicKeyHash(Data& keyHash) const; - /// Matches the script to a pay-to-witness-script-hash (P2WSH). + /// Matches the script to a pay-to-witness-script-hash (P2WSH). Returns the script hash, a SHA256 of the witness script. bool matchPayToWitnessScriptHash(Data& scriptHash) const; /// Matches the script to a multisig script. @@ -75,7 +78,7 @@ class Script { /// Builds a pay-to-witness-public-key-hash (P2WPKH) script from a public /// key hash. - static Script buildPayToWitnessPubkeyHash(const Data& hash); + static Script buildPayToWitnessPublicKeyHash(const Data& hash); /// Builds a pay-to-witness-script-hash (P2WSH) script from a script hash. static Script buildPayToWitnessScriptHash(const Data& scriptHash); @@ -88,7 +91,7 @@ class Script { void encode(Data& data) const; /// Encodes a small integer - static uint8_t encodeNumber(int n) { + static inline uint8_t encodeNumber(int n) { assert(n >= 0 && n <= 16); if (n == 0) { return OP_0; @@ -96,8 +99,17 @@ class Script { return OP_1 + uint8_t(n - 1); } - private: + /// Decodes a small integer + static inline int decodeNumber(uint8_t opcode) { + if (opcode == OP_0) { + return 0; + } + assert(opcode >= OP_1 && opcode <= OP_16); + return static_cast(opcode) - static_cast(OP_1 - 1); + } + /// Extracts a single opcode at the given index including its operand. + /// Public for testability. /// /// \param index [in/out] index where the operation starts, on return the /// index of the next operation. \param opcode [out] the opcode. \param diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 6260242854d..acf191cf11d 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -169,7 +169,7 @@ Result> TransactionSigner::si } results.resize(required + 1); return Result>::success(std::move(results)); - } else if (script.matchPayToPubkey(data)) { + } else if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(data)); auto key = keyForPublicKeyHash(keyHash); if (key.empty()) { @@ -183,7 +183,7 @@ Result> TransactionSigner::si return Result>::failure("Failed to sign."); } return Result>::success({signature}); - } else if (script.matchPayToPubkeyHash(data)) { + } else if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); if (key.empty()) { // Error: Missing keyxs diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index 2a7b99ca0a3..09ddf587313 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -108,7 +108,7 @@ Result> Signer::signStep(Bitcoin::Script script, size_t index) std::vector keys; int required; - if (script.matchPayToPubkey(data)) { + if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(data)); auto key = keyForPublicKeyHash(keyHash); if (key.empty()) { @@ -121,7 +121,7 @@ Result> Signer::signStep(Bitcoin::Script script, size_t index) return Result>::failure("Failed to sign."); } return Result>::success({signature}); - } else if (script.matchPayToPubkeyHash(data)) { + } else if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); if (key.empty()) { // Error: Missing keyxs diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 1fa6575c560..b316b1d0fe6 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -59,6 +59,10 @@ bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *scrip return script->impl.isPayToWitnessScriptHash(); } +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *script) { + return script->impl.isPayToWitnessPublicKeyHash(); +} + bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *script) { return script->impl.isWitnessProgram(); } @@ -69,7 +73,7 @@ bool TWBitcoinScriptEqual(const struct TWBitcoinScript *_Nonnull lhs, const stru TWData *TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *script) { std::vector data; - if (script->impl.matchPayToPubkey(data)) { + if (script->impl.matchPayToPublicKey(data)) { return TWDataCreateWithBytes(data.data(), data.size()); } return nullptr; @@ -77,7 +81,7 @@ TWData *TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *script) { TWData *TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript *script) { std::vector data; - if (script->impl.matchPayToPubkeyHash(data)) { + if (script->impl.matchPayToPublicKeyHash(data)) { return TWDataCreateWithBytes(data.data(), data.size()); } return nullptr; @@ -127,7 +131,7 @@ struct TWBitcoinScript *TWBitcoinScriptBuildPayToScriptHash(TWData *scriptHash) struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *hash) { auto v = reinterpret_cast*>(hash); - auto script = Script::buildPayToWitnessPubkeyHash(*v); + auto script = Script::buildPayToWitnessPublicKeyHash(*v); return new TWBitcoinScript{ .impl = script }; } diff --git a/tests/Bitcoin/BitcoinScriptTests.cpp b/tests/Bitcoin/BitcoinScriptTests.cpp new file mode 100644 index 00000000000..c54505e9894 --- /dev/null +++ b/tests/Bitcoin/BitcoinScriptTests.cpp @@ -0,0 +1,285 @@ +// Copyright © 2017-2020 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. + +#include "Bitcoin/Script.h" +#include "../interface/TWTestUtilities.h" +#include "HexCoding.h" + +#include + +using namespace TW; +using namespace TW::Bitcoin; + +const Script PayToScriptHash(parse_hex("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87")); +const Script PayToWitnessScriptHash(parse_hex("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); +const Script PayToWitnessPublicKeyHash(parse_hex("0014" "79091972186c449eb1ded22b78e40d009bdf0089")); +const Script PayToPublicKeySecp256k1(parse_hex("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac")); +const Script PayToPublicKeySecp256k1Extended(parse_hex("41" "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "66b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91ac")); +const Script PayToPublicKeyHash(parse_hex("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac")); + +TEST(BitcoinScript, PayToPublicKey) { + Data res; + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + EXPECT_EQ(PayToScriptHash.matchPayToPublicKey(res), false); + EXPECT_EQ(PayToWitnessScriptHash.matchPayToPublicKey(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToPublicKey(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToPublicKey(res), false); +} + +TEST(BitcoinScript, PayToPublicKeyHash) { + Data res; + EXPECT_EQ(PayToPublicKeyHash.matchPayToPublicKeyHash(res), true); + EXPECT_EQ(hex(res), "79091972186c449eb1ded22b78e40d009bdf0089"); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToPublicKey(res), true); + EXPECT_EQ(hex(res), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + EXPECT_EQ(PayToScriptHash.matchPayToPublicKeyHash(res), false); + EXPECT_EQ(PayToWitnessScriptHash.matchPayToPublicKeyHash(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToPublicKeyHash(res), false); +} + +TEST(BitcoinScript, PayToScriptHash) { + EXPECT_EQ(PayToScriptHash.isPayToScriptHash(), true); + EXPECT_EQ(PayToScriptHash.bytes.size(), 23); + + EXPECT_EQ(PayToWitnessScriptHash.isPayToScriptHash(), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isPayToScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToScriptHash(), false); + EXPECT_EQ(PayToPublicKeyHash.isPayToScriptHash(), false); + + Data res; + EXPECT_EQ(PayToScriptHash.matchPayToScriptHash(res), true); + EXPECT_EQ(hex(res), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + + EXPECT_EQ(PayToWitnessScriptHash.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToScriptHash(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToScriptHash(res), false); +} + +TEST(BitcoinScript, PayToWitnessScriptHash) { + EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessScriptHash(), true); + EXPECT_EQ(PayToWitnessScriptHash.bytes.size(), 34); + + EXPECT_EQ(PayToScriptHash.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToWitnessScriptHash(), false); + EXPECT_EQ(PayToPublicKeyHash.isPayToWitnessScriptHash(), false); + + Data res; + EXPECT_EQ(PayToWitnessScriptHash.matchPayToWitnessScriptHash(res), true); + EXPECT_EQ(hex(res), "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + + EXPECT_EQ(PayToScriptHash.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToWitnessScriptHash(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToWitnessScriptHash(res), false); +} + +TEST(BitcoinScript, PayToWitnessPublicKeyHash) { + EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessPublicKeyHash(), true); + EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22); + + EXPECT_EQ(PayToScriptHash.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isPayToWitnessPublicKeyHash(), false); + EXPECT_EQ(PayToPublicKeyHash.isPayToWitnessPublicKeyHash(), false); + + Data res; + EXPECT_EQ(PayToWitnessPublicKeyHash.matchPayToWitnessPublicKeyHash(res), true); + EXPECT_EQ(hex(res), "79091972186c449eb1ded22b78e40d009bdf0089"); + + EXPECT_EQ(PayToScriptHash.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToWitnessScriptHash.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.matchPayToWitnessPublicKeyHash(res), false); + EXPECT_EQ(PayToPublicKeyHash.matchPayToWitnessPublicKeyHash(res), false); +} + +TEST(BitcoinScript, WitnessProgram) { + EXPECT_EQ(PayToWitnessScriptHash.isWitnessProgram(), true); + EXPECT_EQ(PayToWitnessPublicKeyHash.isWitnessProgram(), true); + + EXPECT_EQ(PayToScriptHash.isWitnessProgram(), false); + EXPECT_EQ(PayToPublicKeySecp256k1.isWitnessProgram(), false); + EXPECT_EQ(PayToPublicKeySecp256k1Extended.isWitnessProgram(), false); + EXPECT_EQ(PayToPublicKeyHash.isWitnessProgram(), false); +} + +TEST(BitcoinScript, EncodeNumber) { + EXPECT_EQ(Script::encodeNumber(0), OP_0); + EXPECT_EQ(Script::encodeNumber(1), OP_1); + EXPECT_EQ(Script::encodeNumber(3), OP_3); + EXPECT_EQ(Script::encodeNumber(9), OP_9); +} + +TEST(BitcoinScript, DecodeNumber) { + EXPECT_EQ(Script::decodeNumber(OP_0), 0); + EXPECT_EQ(Script::decodeNumber(OP_1), 1); + EXPECT_EQ(Script::decodeNumber(OP_3), 3); + EXPECT_EQ(Script::decodeNumber(OP_9), 9); +} + +TEST(BitcoinScript, GetScriptOp) { + { + size_t index = 5; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("")).getScriptOp(index, opcode, operand), false); + } + { + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4f")).getScriptOp(index, opcode, operand), true); + EXPECT_EQ(index, 1); + EXPECT_EQ(opcode, 0x4f); + EXPECT_EQ(hex(operand), ""); + } + { + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("05" "0102030405")).getScriptOp(index, opcode, operand), true); + EXPECT_EQ(index, 6); + EXPECT_EQ(opcode, 0x05); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA1 + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4c" "05" "0102030405")).getScriptOp(index, opcode, operand), true); + EXPECT_EQ(index, 7); + EXPECT_EQ(opcode, 0x4c); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA1 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4c")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA1 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4c" "05" "010203")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA2 + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4d" "0500" "0102030405")).getScriptOp(index, opcode, operand), true); + EXPECT_EQ(index, 8); + EXPECT_EQ(opcode, 0x4d); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA2 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4d" "05")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA2 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4d" "0500" "010203")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA4 + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4e" "05000000" "0102030405")).getScriptOp(index, opcode, operand), true); + EXPECT_EQ(index, 10); + EXPECT_EQ(opcode, 0x4e); + EXPECT_EQ(hex(operand), "0102030405"); + } + { // OP_PUSHDATA4 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4e" "0500")).getScriptOp(index, opcode, operand), false); + } + { // OP_PUSHDATA4 too short + size_t index = 0; uint8_t opcode; Data operand; + EXPECT_EQ(Script(parse_hex("4e" "05000000" "010203")).getScriptOp(index, opcode, operand), false); + } +} + +TEST(BitcoinScript, MatchMultiSig) { + std::vector keys; + int required; + EXPECT_EQ(Script(parse_hex("")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("20")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("00ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("4fae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("20ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514cae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c05ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "05" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "00ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "52ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51aeae")).matchMultisig(keys, required), false); + + // valid one key + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 1); + ASSERT_EQ(keys.size(), 1); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("52" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); + + // valid two keys + EXPECT_EQ(Script(parse_hex("52" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "52" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 2); + ASSERT_EQ(keys.size(), 2); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + // OP_PUSHDATA1 + EXPECT_EQ(Script(parse_hex("514cae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c" "05" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c" "05" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514c" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + + // valid one key, OP_PUSHDATA1 + EXPECT_EQ(Script(parse_hex("514c" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 1); + ASSERT_EQ(keys.size(), 1); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + // OP_PUSHDATA2 + EXPECT_EQ(Script(parse_hex("514dae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514d" "0500" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514d" "0500" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514d" "2100" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + + // valid one key, OP_PUSHDATA2 + EXPECT_EQ(Script(parse_hex("514d" "2100" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 1); + ASSERT_EQ(keys.size(), 1); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + // OP_PUSHDATA4 + EXPECT_EQ(Script(parse_hex("514eae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "0500" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "05000000" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "05000000" "0102030405" "ae")).matchMultisig(keys, required), false); + EXPECT_EQ(Script(parse_hex("514e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ae")).matchMultisig(keys, required), false); + + // valid one key, OP_PUSHDATA2 + EXPECT_EQ(Script(parse_hex("514e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 1); + ASSERT_EQ(keys.size(), 1); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + // valid three keys, mixed + EXPECT_EQ(Script(parse_hex("53" + "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" + "4d" "2100" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" + "4e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" + "53" "ae")).matchMultisig(keys, required), true); + EXPECT_EQ(required, 3); + ASSERT_EQ(keys.size(), 3); + EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); +} diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/Bitcoin/TWBitcoinScriptTests.cpp index 6e5c716fcc5..5337008e9a7 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/Bitcoin/TWBitcoinScriptTests.cpp @@ -11,7 +11,124 @@ #include -TEST(BitcoinScript, ScriptHash) { +const auto PayToScriptHash = TWBitcoinScriptCreateWithData(DATA("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87").get()); +const auto PayToWitnessScriptHash = TWBitcoinScriptCreateWithData(DATA("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db").get()); +const auto PayToWitnessPublicKeyHash = TWBitcoinScriptCreateWithData(DATA("0014" "79091972186c449eb1ded22b78e40d009bdf0089").get()); +const auto PayToPublicKeySecp256k1 = TWBitcoinScriptCreateWithData(DATA("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac").get()); +const auto PayToPublicKeyHash = TWBitcoinScriptCreateWithData(DATA("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac").get()); + +TEST(TWBitcoinScript, Create) { + auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); + { + auto script = TWBitcoinScriptCreateWithData(data.get()); + ASSERT_TRUE(script != nullptr); + ASSERT_EQ(TWBitcoinScriptSize(script), 23); + } + { + auto script = TWBitcoinScriptCreateWithBytes(TWDataBytes(data.get()), TWDataSize(data.get())); + ASSERT_TRUE(script != nullptr); + ASSERT_EQ(TWBitcoinScriptSize(script), 23); + + auto scriptCopy = TWBitcoinScriptCreateCopy(script); + ASSERT_TRUE(scriptCopy != nullptr); + ASSERT_EQ(TWBitcoinScriptSize(scriptCopy), 23); + } +} + +TEST(TWBitcoinScript, Equals) { + auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); + auto script = TWBitcoinScriptCreateWithBytes(TWDataBytes(data.get()), TWDataSize(data.get())); + auto scriptCopy = TWBitcoinScriptCreateCopy(script); + ASSERT_TRUE(TWBitcoinScriptEqual(script, scriptCopy)); +} + +TEST(TWBitcoinScript, IsPayToScriptHash) { + ASSERT_TRUE(TWBitcoinScriptIsPayToScriptHash(PayToScriptHash)); +} + +TEST(TWBitcoinScript, IsPayToWitnessScriptHash) { + ASSERT_TRUE(TWBitcoinScriptIsPayToWitnessScriptHash(PayToWitnessScriptHash)); +} + +TEST(TWBitcoinScript, IsPayToWitnessPublicKeyHash) { + ASSERT_TRUE(TWBitcoinScriptIsPayToWitnessPublicKeyHash(PayToWitnessPublicKeyHash)); +} + +TEST(TWBitcoinScript, IsWitnessProgram) { + ASSERT_TRUE(TWBitcoinScriptIsWitnessProgram(PayToWitnessScriptHash)); + ASSERT_TRUE(TWBitcoinScriptIsWitnessProgram(PayToWitnessPublicKeyHash)); + ASSERT_FALSE(TWBitcoinScriptIsWitnessProgram(PayToScriptHash)); +} + +TEST(TWBitcoinScript, MatchPayToPubkey) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToPubkey(PayToPublicKeySecp256k1)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToPubkey(PayToScriptHash), nullptr); +} + +TEST(TWBitcoinScript, TWBitcoinScriptMatchPayToPubkeyHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToPubkeyHash(PayToPublicKeyHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "79091972186c449eb1ded22b78e40d009bdf0089"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToPubkeyHash(PayToScriptHash), nullptr); +} + +TEST(TWBitcoinScript, MatchPayToScriptHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToScriptHash(PayToScriptHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToScriptHash(PayToPublicKeySecp256k1), nullptr); +} + +TEST(TWBitcoinScript, MatchPayToWitnessPublicKeyHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToWitnessPublicKeyHash(PayToWitnessPublicKeyHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "79091972186c449eb1ded22b78e40d009bdf0089"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToWitnessPublicKeyHash(PayToPublicKeySecp256k1), nullptr); +} + +TEST(TWBitcoinScript, MatchPayToWitnessScriptHash) { + const auto res = WRAPD(TWBitcoinScriptMatchPayToWitnessScriptHash(PayToWitnessScriptHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + + ASSERT_EQ(TWBitcoinScriptMatchPayToWitnessScriptHash(PayToPublicKeySecp256k1), nullptr); +} + +TEST(TWBitcoinScript, Encode) { + const auto res = WRAPD(TWBitcoinScriptEncode(PayToScriptHash)); + ASSERT_TRUE(res.get() != nullptr); + const auto hexRes = WRAPS(TWStringCreateWithHexData(res.get())); + ASSERT_STREQ(TWStringUTF8Bytes(hexRes.get()), "17a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); +} + +TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { + const auto hash = DATA("79091972186c449eb1ded22b78e40d009bdf0089"); + const auto script = TWBitcoinScriptBuildPayToWitnessPubkeyHash(hash.get()); + ASSERT_TRUE(script != nullptr); + const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script)).get())); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0014" "79091972186c449eb1ded22b78e40d009bdf0089"); +} + +TEST(TWBitcoinScript, BuildPayToWitnessScriptHash) { + const auto hash = DATA("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + const auto script = TWBitcoinScriptBuildPayToWitnessScriptHash(hash.get()); + ASSERT_TRUE(script != nullptr); + const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script)).get())); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); +} + +TEST(TWBitcoinScript, ScriptHash) { auto pkData = DATA("cf5007e19af3641199f21f3fa54dff2fa2627471"); auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(pkData.get())); @@ -26,7 +143,7 @@ TEST(BitcoinScript, ScriptHash) { ASSERT_STREQ(TWStringUTF8Bytes(hexData.get()), "c470d22e69a2a967f2cec0cd5a5aebb955cdd395"); } -TEST(BitcoinScript, RedeemScript) { +TEST(TWBitcoinScript, RedeemScript) { auto pkData = DATA("cf5007e19af3641199f21f3fa54dff2fa2627471"); auto embeddedScript = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKeyHash(pkData.get())); @@ -40,7 +157,7 @@ TEST(BitcoinScript, RedeemScript) { ASSERT_STREQ(TWStringUTF8Bytes(hexData.get()), "a914c470d22e69a2a967f2cec0cd5a5aebb955cdd39587"); } -TEST(BitcoinScript, LockScriptForP2PKHAddress) { +TEST(TWBitcoinScript, LockScriptForP2PKHAddress) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac"); @@ -50,7 +167,7 @@ TEST(BitcoinScript, LockScriptForP2PKHAddress) { assertHexEqual(scriptPub2Data, "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac"); } -TEST(BitcoinScript, LockScriptForP2SHAddress) { +TEST(TWBitcoinScript, LockScriptForP2SHAddress) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("37rHiL4DN2wkt8pgCAUfYJRxhir98ZGN1y").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9144391adbec172cad6a9fc3eebca36aeec6640abda87"); @@ -60,7 +177,7 @@ TEST(BitcoinScript, LockScriptForP2SHAddress) { assertHexEqual(scriptPub2Data, "a914ad40768af6419a20bdb94d83c06b6c8c94721dc087"); } -TEST(BitcoinScript, LockScriptForP2WPKHAddress) { +TEST(TWBitcoinScript, LockScriptForP2WPKHAddress) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1q6hppaw7uld68amnnu5vpp5dd5u7k92c2vtdtkq").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014d5c21ebbdcfb747eee73e51810d1ada73d62ab0a"); @@ -70,13 +187,13 @@ TEST(BitcoinScript, LockScriptForP2WPKHAddress) { assertHexEqual(scriptPub2Data, "0014039f2ffd2b28703f0e9c73ccf3ce564adebbb5e8"); } -TEST(BitcoinScript, LockScriptForP2WSHAddress) { +TEST(TWBitcoinScript, LockScriptForP2WSHAddress) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1qcuqamesrt803xld4l2j2vxx8rxmrx7aq82mkw7xwxh643wzqjlnqutkcv2").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0020c701dde60359df137db5faa4a618c719b6337ba03ab76778ce35f558b84097e6"); } -TEST(BitcoinScript, LockScriptForCashAddress) { +TEST(TWBitcoinScript, LockScriptForCashAddress) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a914b1fb7e043152fd1eed7bfaf66679ad3b6c9068f387"); diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 931e8dfe46f..45137ad48ef 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -249,7 +249,7 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { ASSERT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "79091972186c449eb1ded22b78e40d009bdf0089"); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - auto redeemScript = Script::buildPayToWitnessPubkeyHash(utxoPubkeyHash); + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); ASSERT_EQ(hex(scriptHash.begin(), scriptHash.end()), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); diff --git a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp index 28d75b48d4c..c8a098a7aff 100644 --- a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -91,7 +91,7 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { EXPECT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "2fc7d70acef142d1f7b5ef2f20b1a9b759797674"); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - auto redeemScript = Script::buildPayToWitnessPubkeyHash(utxoPubkeyHash); + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); ASSERT_EQ(hex(scriptHash.begin(), scriptHash.end()), "0055b0c94df477ee6b9f75185dfc9aa8ce2e52e4"); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); From 4dfeeeb1e85132cf04cda9b5f23f5879d9cf5bb7 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Wed, 29 Apr 2020 09:03:15 +0200 Subject: [PATCH 12/81] Some low-impact cppcheck finding fixes. (#938) * Some low-impact cppcheck finding fixes. * Some reverts to address diff coverage metric. * Add scripts for cppcheck to tools. * Some minor reference type style linting. Co-authored-by: Catenocrypt --- src/Aeternity/Address.cpp | 8 ++++---- src/Aeternity/Address.h | 8 ++++---- src/Aeternity/Signer.cpp | 8 ++++---- src/Aeternity/Signer.h | 8 ++++---- src/Aeternity/Transaction.cpp | 4 ++-- src/Aeternity/Transaction.h | 8 ++++---- src/Aion/Transaction.h | 2 +- src/Algorand/BinaryCoding.h | 6 +++--- src/Algorand/Transaction.cpp | 2 +- src/Algorand/Transaction.h | 4 ++-- src/Bech32Address.h | 2 +- src/Binance/Address.h | 4 ++-- src/Cardano/AddressV3.h | 2 +- src/Cbor.cpp | 26 +++++++++++++------------- src/Cbor.h | 2 +- src/Coin.cpp | 2 +- src/Cosmos/Address.h | 2 +- src/DerivationPath.cpp | 2 +- src/DerivationPath.h | 4 ++-- src/EOS/Asset.cpp | 8 ++++---- src/EOS/Transaction.cpp | 2 +- src/EOS/Transaction.h | 4 ++-- src/Ethereum/ABI/Bytes.h | 2 +- src/Ethereum/ABI/Function.h | 2 +- src/Ethereum/Signer.cpp | 4 ++-- src/Ethereum/Signer.h | 4 ++-- src/Ethereum/Transaction.h | 2 +- src/FIO/Action.h | 2 +- src/HDWallet.cpp | 4 ++-- src/HDWallet.h | 4 ++-- src/Harmony/Address.h | 4 ++-- src/Harmony/Signer.cpp | 8 ++++---- src/Harmony/Signer.h | 8 ++++---- src/Harmony/Transaction.h | 2 +- src/IoTeX/Address.h | 4 ++-- src/Keystore/EncryptionParameters.cpp | 4 ++-- src/Keystore/EncryptionParameters.h | 4 ++-- src/Keystore/PBKDF2Parameters.h | 2 +- src/Keystore/ScryptParameters.h | 2 +- src/Keystore/StoredKey.cpp | 2 +- src/Keystore/StoredKey.h | 2 +- src/NEAR/Serialization.cpp | 2 +- src/NEO/Address.h | 8 ++++---- src/NEO/CoinReference.h | 2 +- src/NEO/ISerializable.h | 2 +- src/NEO/MinerTransaction.h | 2 +- src/NEO/ReadData.cpp | 10 +++++----- src/NEO/ReadData.h | 12 ++++++------ src/NEO/Script.cpp | 4 ++-- src/NEO/Script.h | 4 ++-- src/NEO/Serializable.h | 7 ++----- src/NEO/Transaction.cpp | 4 ++-- src/NEO/Transaction.h | 6 +++--- src/NEO/TransactionAttribute.h | 2 +- src/NEO/TransactionOutput.h | 2 +- src/NEO/Witness.h | 2 +- src/Nebulas/Address.cpp | 6 +++--- src/Solana/Address.cpp | 4 ++-- src/Solana/Address.h | 6 +++--- src/Solana/Transaction.h | 2 +- src/TON/Cell.cpp | 2 +- src/Tezos/Signer.cpp | 2 +- src/Tezos/Signer.h | 2 +- src/Theta/Transaction.h | 2 +- src/VeChain/Clause.h | 2 +- src/Waves/Address.cpp | 8 ++++---- src/Waves/Address.h | 15 ++++----------- src/Waves/Transaction.cpp | 14 +++++++------- src/Waves/Transaction.h | 2 +- src/XXHash64.h | 1 + src/Zilliqa/Address.h | 4 ++-- src/interface/TWAnyAddress.cpp | 4 ++-- src/interface/TWBase58.cpp | 2 +- src/interface/TWBitcoinAddress.cpp | 2 +- src/interface/TWData.cpp | 3 +-- src/interface/TWPrivateKey.cpp | 4 ++-- src/interface/TWPublicKey.cpp | 8 ++++---- tools/lint-cppcheck-all | 7 +++++++ tools/lint-cppcheck-diff | 15 +++++++++++++++ 79 files changed, 191 insertions(+), 179 deletions(-) create mode 100755 tools/lint-cppcheck-all create mode 100755 tools/lint-cppcheck-diff diff --git a/src/Aeternity/Address.cpp b/src/Aeternity/Address.cpp index ff256597044..101ba527854 100644 --- a/src/Aeternity/Address.cpp +++ b/src/Aeternity/Address.cpp @@ -13,7 +13,7 @@ using namespace TW::Aeternity; /// Determines whether a string makes a valid address. -bool Address::isValid(const std::string &string) { +bool Address::isValid(const std::string& string) { if (string.empty()) { return false; } @@ -34,7 +34,7 @@ Address::Address(const PublicKey &publicKey) { } /// Initializes an address from a string representation. -Address::Address(const std::string &string) { +Address::Address(const std::string& string) { if (!isValid(string)) { throw std::invalid_argument("Invalid address"); } @@ -48,11 +48,11 @@ std::string Address::string() const { return Identifiers::prefixAccountPubkey + Base58::bitcoin.encodeCheck(bytes); } -bool Address::checkType(const std::string &type) { +bool Address::checkType(const std::string& type) { return type == Identifiers::prefixAccountPubkey; } -bool Address::checkPayload(const std::string &payload) { +bool Address::checkPayload(const std::string& payload) { unsigned long base58 = Base58::bitcoin.decodeCheck(payload).size(); return base58 == size; } diff --git a/src/Aeternity/Address.h b/src/Aeternity/Address.h index 0f8b4e24724..07e9b732939 100644 --- a/src/Aeternity/Address.h +++ b/src/Aeternity/Address.h @@ -15,10 +15,10 @@ class Address { Data bytes; /// Determines whether a string makes a valid address. - static bool isValid(const std::string &string); + static bool isValid(const std::string& string); /// Initializes an address from a string representation. - explicit Address(const std::string &string); + explicit Address(const std::string& string); /// Initializes an address from a public key. explicit Address(const PublicKey &publicKey); @@ -28,8 +28,8 @@ class Address { private: - static bool checkType(const std::string &type); - static bool checkPayload(const std::string &payload); + static bool checkType(const std::string& type); + static bool checkPayload(const std::string& payload); }; inline bool operator==(const Address& lhs, const Address& rhs) { diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index 02a6c0412a2..7c9adf1fafa 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -48,7 +48,7 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey &privateKey, Transaction return createProtoOutput(signature, signedEncodedTx); } -Data Signer::buildRlpTxRaw(Data &txRaw, Data &sigRaw) { +Data Signer::buildRlpTxRaw(Data& txRaw, Data& sigRaw) { auto rlpTxRaw = Data(); auto signaturesList = Data(); append(signaturesList, Ethereum::RLP::encode(sigRaw)); @@ -61,7 +61,7 @@ Data Signer::buildRlpTxRaw(Data &txRaw, Data &sigRaw) { return Ethereum::RLP::encodeList(rlpTxRaw); } -Data Signer::buildMessageToSign(Data &txRaw) { +Data Signer::buildMessageToSign(Data& txRaw) { auto data = Data(); Data bytes(Identifiers::networkId.begin(), Identifiers::networkId.end()); append(data, bytes); @@ -69,7 +69,7 @@ Data Signer::buildMessageToSign(Data &txRaw) { return data; } -Proto::SigningOutput Signer::createProtoOutput(std::string &signature, const std::string &signedTx) { +Proto::SigningOutput Signer::createProtoOutput(std::string& signature, const std::string& signedTx) { auto output = Proto::SigningOutput(); output.set_signature(signature); @@ -77,7 +77,7 @@ Proto::SigningOutput Signer::createProtoOutput(std::string &signature, const std return output; } -std::string Signer::encodeBase64WithChecksum(const std::string &prefix, const TW::Data &rawTx) { +std::string Signer::encodeBase64WithChecksum(const std::string& prefix, const TW::Data& rawTx) { auto checksum = Hash::sha256(Hash::sha256(rawTx)); std::vector checksumPart(checksum.begin(), checksum.begin() + checkSumSize); diff --git a/src/Aeternity/Signer.h b/src/Aeternity/Signer.h index eae11aa4bc4..dd289e5487d 100644 --- a/src/Aeternity/Signer.h +++ b/src/Aeternity/Signer.h @@ -21,14 +21,14 @@ class Signer { private: static const uint8_t checkSumSize = 4; - static Data buildRlpTxRaw(Data &txRaw, Data &sigRaw); + static Data buildRlpTxRaw(Data& txRaw, Data& sigRaw); - static Data buildMessageToSign(Data &txRaw); + static Data buildMessageToSign(Data& txRaw); - static Proto::SigningOutput createProtoOutput(std::string &signature, const std::string &signedTx); + static Proto::SigningOutput createProtoOutput(std::string& signature, const std::string& signedTx); /// Encode a byte array into base64 with prefix and a checksum - static std::string encodeBase64WithChecksum(const std::string &prefix, const TW::Data &rawTx); + static std::string encodeBase64WithChecksum(const std::string& prefix, const TW::Data& rawTx); }; } // namespace TW::Aeternity diff --git a/src/Aeternity/Transaction.cpp b/src/Aeternity/Transaction.cpp index 198df6c3af0..416531544b5 100644 --- a/src/Aeternity/Transaction.cpp +++ b/src/Aeternity/Transaction.cpp @@ -26,11 +26,11 @@ Data Transaction::encode() { append(encoded, encodeSafeZero(nonce)); append(encoded, Ethereum::RLP::encode(payload)); - const Data &raw = Ethereum::RLP::encodeList(encoded); + const Data& raw = Ethereum::RLP::encodeList(encoded); return raw; } -TW::Data Transaction::buildTag(const std::string &address) { +TW::Data Transaction::buildTag(const std::string& address) { auto payload = address.substr(Identifiers::prefixTransaction.size(), address.size()); auto data = Data(); diff --git a/src/Aeternity/Transaction.h b/src/Aeternity/Transaction.h index 979d9b8312d..6bace4629b8 100644 --- a/src/Aeternity/Transaction.h +++ b/src/Aeternity/Transaction.h @@ -31,11 +31,11 @@ class Transaction { uint64_t nonce; Transaction( - std::string &sender_id, - std::string &recipientId, + std::string& sender_id, + std::string& recipientId, uint256_t amount, uint256_t fee, - std::string &payload, + std::string& payload, uint64_t ttl, uint64_t nonce ) @@ -51,7 +51,7 @@ class Transaction { //// buildIDTag assemble an id() object //// see https://github.com/aeternity/protocol/blob/epoch-v0.22.0/serializations.md#the-id-type - static Data buildTag(const std::string &address); + static Data buildTag(const std::string& address); /// Awternity network does not accept zero int values as rlp param, /// instead empty byte array should be encoded diff --git a/src/Aion/Transaction.h b/src/Aion/Transaction.h index e1c8013e5bf..c87e5bc0256 100644 --- a/src/Aion/Transaction.h +++ b/src/Aion/Transaction.h @@ -28,7 +28,7 @@ class Transaction { std::vector signature; Transaction(uint128_t nonce, uint128_t gasPrice, uint128_t gasLimit, Address to, - uint128_t amount, Data payload) + uint128_t amount, const Data& payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) , gasLimit(std::move(gasLimit)) diff --git a/src/Algorand/BinaryCoding.h b/src/Algorand/BinaryCoding.h index 87895a97daa..3a169cf3e00 100644 --- a/src/Algorand/BinaryCoding.h +++ b/src/Algorand/BinaryCoding.h @@ -11,7 +11,7 @@ namespace TW::Algorand { -static inline void encodeString(std::string string, Data &data) { +static inline void encodeString(std::string string, Data& data) { // encode string header auto bytes = Data(string.begin(), string.end()); if (bytes.size() < 0x20) { @@ -36,7 +36,7 @@ static inline void encodeString(std::string string, Data &data) { append(data, bytes); } -static inline void encodeNumber(uint64_t number, Data &data) { +static inline void encodeNumber(uint64_t number, Data& data) { if (number < 0x80) { // positive fixint data.push_back(static_cast(number)); @@ -59,7 +59,7 @@ static inline void encodeNumber(uint64_t number, Data &data) { } } -static inline void encodeBytes(const Data &bytes, Data &data) { +static inline void encodeBytes(const Data& bytes, Data& data) { auto size = bytes.size(); if (size < 0x100) { // bin 8 diff --git a/src/Algorand/Transaction.cpp b/src/Algorand/Transaction.cpp index a2f6cdd7820..ac608af5b3e 100644 --- a/src/Algorand/Transaction.cpp +++ b/src/Algorand/Transaction.cpp @@ -71,7 +71,7 @@ Data Transaction::serialize() const { return data; } -Data Transaction::serialize(Data &signature) const { +Data Transaction::serialize(Data& signature) const { /* Algorand transaction and signature are encoded with msgpack: { "sig": diff --git a/src/Algorand/Transaction.h b/src/Algorand/Transaction.h index a545d65d48f..f7b9c3dd694 100644 --- a/src/Algorand/Transaction.h +++ b/src/Algorand/Transaction.h @@ -27,7 +27,7 @@ class Transaction { Data genesisHash; Transaction(Address &from, Address &to, uint64_t fee, uint64_t amount, uint64_t firstRound, - uint64_t lastRound, Data ¬e, std::string type, std::string &genesisIdg, Data &genesisHash) + uint64_t lastRound, Data& note, std::string type, std::string& genesisIdg, Data& genesisHash) : from(from) , to(to) , fee(fee), amount(amount) , firstRound(firstRound), lastRound(lastRound) @@ -36,7 +36,7 @@ class Transaction { public: Data serialize() const; - Data serialize(Data &signature) const; + Data serialize(Data& signature) const; }; } // namespace TW::Algorand diff --git a/src/Bech32Address.h b/src/Bech32Address.h index a5aea665805..23b4c06a640 100644 --- a/src/Bech32Address.h +++ b/src/Bech32Address.h @@ -48,7 +48,7 @@ class Bech32Address { Bech32Address(const std::string& hrp, HasherType hasher, const PublicKey& publicKey); void setHrp(const std::string& hrp_in) { hrp = std::move(hrp_in); } - void setKey(Data keyHash_in) { keyHash = std::move(keyHash_in); } + void setKey(const Data& keyHash_in) { keyHash = std::move(keyHash_in); } inline const std::string& getHrp() const { return hrp; } diff --git a/src/Binance/Address.h b/src/Binance/Address.h index 412fac043c0..198540e0962 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -17,12 +17,12 @@ class Address: public Bech32Address { public: static const std::string hrp; // HRP_BINANCE - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) {} + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2_RIPEMD, publicKey) {} diff --git a/src/Cardano/AddressV3.h b/src/Cardano/AddressV3.h index 2c9655041be..960750ccee3 100644 --- a/src/Cardano/AddressV3.h +++ b/src/Cardano/AddressV3.h @@ -86,7 +86,7 @@ class AddressV3 { Data data() const; private: - AddressV3() : legacyAddressV2(nullptr) {} + AddressV3() : legacyAddressV2(nullptr), discrimination(Discrim_Production), kind(Kind_Single) {} }; inline bool operator==(const AddressV3& lhs, const AddressV3& rhs) { diff --git a/src/Cbor.cpp b/src/Cbor.cpp index ae2fccf1ab8..51304779b21 100644 --- a/src/Cbor.cpp +++ b/src/Cbor.cpp @@ -162,8 +162,8 @@ Decode Decode::skipClone(uint32_t offset) const { Decode::TypeDesc Decode::getTypeDesc() const { TypeDesc typeDesc; typeDesc.isIndefiniteValue = false; - typeDesc.majorType = (MajorType)(byte(0) >> 5); - auto minorType = (TW::byte)((uint8_t)byte(0) & 0x1F); + typeDesc.majorType = (MajorType)(getByte(0) >> 5); + auto minorType = (TW::byte)((uint8_t)getByte(0) & 0x1F); if (minorType < 24) { // direct value typeDesc.byteCount = 1; @@ -172,31 +172,31 @@ Decode::TypeDesc Decode::getTypeDesc() const { } if (minorType == 24) { typeDesc.byteCount = 1 + 1; - typeDesc.value = byte(1); + typeDesc.value = getByte(1); return typeDesc; } if (minorType == 25) { typeDesc.byteCount = 1 + 2; - typeDesc.value = (uint16_t)(((uint16_t)byte(1) << 8) + (uint16_t)byte(2)); + typeDesc.value = (uint16_t)(((uint16_t)getByte(1) << 8) + (uint16_t)getByte(2)); return typeDesc; } if (minorType == 26) { typeDesc.byteCount = 1 + 4; - typeDesc.value = (uint32_t)(((uint32_t)byte(1) << 24) + ((uint32_t)byte(2) << 16) + ((uint32_t)byte(3) << 8) + (uint32_t)byte(4)); + typeDesc.value = (uint32_t)(((uint32_t)getByte(1) << 24) + ((uint32_t)getByte(2) << 16) + ((uint32_t)getByte(3) << 8) + (uint32_t)getByte(4)); return typeDesc; } if (minorType == 27) { typeDesc.byteCount = 1 + 8; typeDesc.value = (uint64_t)( - ((uint64_t)byte(1) << 56) + - ((uint64_t)byte(2) << 48) + - ((uint64_t)byte(3) << 40) + - ((uint64_t)byte(4) << 32) + - ((uint64_t)byte(5) << 24) + - ((uint64_t)byte(6) << 16) + - ((uint64_t)byte(7) << 8) + - ((uint64_t)byte(8))); + ((uint64_t)getByte(1) << 56) + + ((uint64_t)getByte(2) << 48) + + ((uint64_t)getByte(3) << 40) + + ((uint64_t)getByte(4) << 32) + + ((uint64_t)getByte(5) << 24) + + ((uint64_t)getByte(6) << 16) + + ((uint64_t)getByte(7) << 8) + + ((uint64_t)getByte(8))); return typeDesc; } if (minorType >= 28 && minorType <= 30) { diff --git a/src/Cbor.h b/src/Cbor.h index 653bd23bfb9..71de1adb2e0 100644 --- a/src/Cbor.h +++ b/src/Cbor.h @@ -118,7 +118,7 @@ class Decode { /// Skip ahead: form other Decode data with offset Decode skipClone(uint32_t offset) const; /// Get the Nth byte - inline TW::byte byte(uint32_t idx) const { + inline TW::byte getByte(uint32_t idx) const { if (subStart + idx >= data->origData.size()) { throw std::invalid_argument("CBOR data too short"); } return data->origData[subStart + idx]; } diff --git a/src/Coin.cpp b/src/Coin.cpp index c6135ddbec0..8f96291d962 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -147,7 +147,7 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { return dispatcher->validateAddress(coin, string, p2pkh, p2sh, hrp); } -std::string TW::normalizeAddress(TWCoinType coin, const std::string &address) { +std::string TW::normalizeAddress(TWCoinType coin, const std::string& address) { if (!TW::validateAddress(coin, address)) { // invalid address, not normalizing return ""; diff --git a/src/Cosmos/Address.h b/src/Cosmos/Address.h index 67d91040c07..49bf640d5cb 100644 --- a/src/Cosmos/Address.h +++ b/src/Cosmos/Address.h @@ -20,7 +20,7 @@ class Address: public Bech32Address { Address() : Bech32Address("") {} /// Initializes an address with a key hash. - Address(const std::string& hrp, Data keyHash) : Bech32Address(hrp, keyHash) {} + Address(const std::string& hrp, const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. Address(const std::string& hrp, const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2_RIPEMD, publicKey) {} diff --git a/src/DerivationPath.cpp b/src/DerivationPath.cpp index 269a6a5c06a..e0f97ed8c71 100644 --- a/src/DerivationPath.cpp +++ b/src/DerivationPath.cpp @@ -24,7 +24,7 @@ DerivationPath::DerivationPath(const std::string& string) { while (it != end) { uint32_t value; - if (std::sscanf(it, "%d", &value) != 1) { + if (std::sscanf(it, "%ud", &value) != 1) { throw std::invalid_argument("Invalid component"); } while (it != end && isdigit(*it)) { diff --git a/src/DerivationPath.h b/src/DerivationPath.h index 9a254da1d0e..0955d24cc37 100644 --- a/src/DerivationPath.h +++ b/src/DerivationPath.h @@ -101,8 +101,8 @@ struct DerivationPath { /// Creates a `DerivationPath` by BIP44 components. DerivationPath(TWPurpose purpose, TWCoinType coin, uint32_t account, uint32_t change, - uint32_t address) { - indices = std::vector(5); + uint32_t address) + : indices(std::vector(5)) { setPurpose(purpose); setCoin(coin); setAccount(account); diff --git a/src/EOS/Asset.cpp b/src/EOS/Asset.cpp index 80d7e159665..17d03be7976 100644 --- a/src/EOS/Asset.cpp +++ b/src/EOS/Asset.cpp @@ -12,11 +12,11 @@ using namespace TW::EOS; -static const int64_t precision = 1000; -static const uint8_t maxDecimals = 18; +static const int64_t Precision = 1000; +static const uint8_t MaxDecimals = 18; Asset::Asset(int64_t amount, uint8_t decimals, const std::string& symbol) { - if (decimals > maxDecimals) { + if (decimals > MaxDecimals) { throw std::invalid_argument("Too many decimals!"); } this->symbol |= decimals; @@ -112,7 +112,7 @@ std::string Asset::string() const { int charsWritten = snprintf(buffer, maxBufferSize, "%.*f %s", decimals, - static_cast(amount) / precision, + static_cast(amount) / Precision, getSymbol().c_str()); if (charsWritten < 0 || charsWritten > maxBufferSize) { diff --git a/src/EOS/Transaction.cpp b/src/EOS/Transaction.cpp index 57138378b2d..e7ae58700aa 100644 --- a/src/EOS/Transaction.cpp +++ b/src/EOS/Transaction.cpp @@ -18,7 +18,7 @@ using namespace TW; using namespace TW::EOS; using json = nlohmann::json; -Signature::Signature(Data sig, Type type) : data(sig), type(type) { +Signature::Signature(const Data& sig, Type type) : data(sig), type(type) { if (sig.size() != DataSize) { throw std::invalid_argument("Invalid signature size!"); } diff --git a/src/EOS/Transaction.h b/src/EOS/Transaction.h index d2413e8aee1..5d6660c462a 100644 --- a/src/EOS/Transaction.h +++ b/src/EOS/Transaction.h @@ -25,7 +25,7 @@ class Signature { static const size_t DataSize = 65; static const size_t ChecksumSize = 4; - Signature(Data sig, Type type); + Signature(const Data& sig, Type type); virtual ~Signature() { } void serialize(Data& os) const noexcept; std::string string() const noexcept; @@ -36,7 +36,7 @@ class Extension { uint16_t type; Data buffer; - Extension(uint16_t type, Data buffer) : type(type), buffer(buffer) { } + Extension(uint16_t type, const Data& buffer) : type(type), buffer(buffer) { } virtual ~Extension() { } void serialize(Data& os) const noexcept; nlohmann::json serialize() const noexcept; diff --git a/src/Ethereum/ABI/Bytes.h b/src/Ethereum/ABI/Bytes.h index c2b85e2e5b1..69eb5aaede8 100644 --- a/src/Ethereum/ABI/Bytes.h +++ b/src/Ethereum/ABI/Bytes.h @@ -64,7 +64,7 @@ class ParamString: public ParamCollection public: ParamString() = default; ParamString(std::string val): ParamCollection() { setVal(val); } - void setVal(std::string& val) { _str = val; } + void setVal(const std::string& val) { _str = val; } const std::string& getVal() const { return _str; } virtual std::string getType() const { return "string"; }; virtual size_t getSize() const { return 32 + ValueEncoder::paddedTo32(_str.size()); } diff --git a/src/Ethereum/ABI/Function.h b/src/Ethereum/ABI/Function.h index cf3585096bd..11d020c8ab7 100644 --- a/src/Ethereum/ABI/Function.h +++ b/src/Ethereum/ABI/Function.h @@ -26,7 +26,7 @@ class Function { ParamSet _outParams; Function(std::string name) : name(std::move(name)) {} - Function(std::string name, std::vector> inParams) + Function(std::string name, const std::vector>& inParams) : name(std::move(name)), _inParams(ParamSet(inParams)) {} virtual ~Function() {} /// Add an input parameter. Returns the index of the parameter. diff --git a/src/Ethereum/Signer.cpp b/src/Ethereum/Signer.cpp index 2dd09f11b15..bbeeb7c8157 100644 --- a/src/Ethereum/Signer.cpp +++ b/src/Ethereum/Signer.cpp @@ -44,7 +44,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { } std::tuple Signer::values(const uint256_t &chainID, - const Data &signature) noexcept { + const Data& signature) noexcept { boost::multiprecision::uint256_t r, s, v; import_bits(r, signature.begin(), signature.begin() + 32); import_bits(s, signature.begin() + 32, signature.begin() + 64); @@ -62,7 +62,7 @@ std::tuple Signer::values(const uint256_t &chai } std::tuple -Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept { +Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept { auto signature = privateKey.sign(hash, TWCurveSECP256k1); return values(chainID, signature); } diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h index 50b279f5f9c..b041b64417f 100644 --- a/src/Ethereum/Signer.h +++ b/src/Ethereum/Signer.h @@ -46,13 +46,13 @@ class Signer { /// /// @returns the r, s, and v values of the transaction signature static std::tuple - sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept; + sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept; /// R, S, and V values for the given chain identifier and signature. /// /// @returns the r, s, and v values of the transaction signature static std::tuple values(const uint256_t &chainID, - const Data &signature) noexcept; + const Data& signature) noexcept; protected: /// Computes the transaction hash. diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h index e16d5d8f17c..d5af92b3ed8 100644 --- a/src/Ethereum/Transaction.h +++ b/src/Ethereum/Transaction.h @@ -26,7 +26,7 @@ class Transaction { uint256_t r = uint256_t(); uint256_t s = uint256_t(); - Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, Data to, uint256_t amount, + Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, uint256_t amount, Data payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) diff --git a/src/FIO/Action.h b/src/FIO/Action.h index b88931a71f6..c79ef65a500 100644 --- a/src/FIO/Action.h +++ b/src/FIO/Action.h @@ -92,7 +92,7 @@ class AddPubAddressData { std::string tpid; std::string actor; - AddPubAddressData(const std::string& fioAddress, std::vector addresses, + AddPubAddressData(const std::string& fioAddress, const std::vector& addresses, uint64_t fee, const std::string& tpid, const std::string& actor) : fioAddress(fioAddress), addresses(addresses), fee(fee), tpid(tpid), actor(actor) {} diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 9d13c0af0d6..52d2deab333 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -139,7 +139,7 @@ std::string HDWallet::getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, T return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); } -std::optional HDWallet::getPublicKeyFromExtended(const std::string &extended, const DerivationPath& path) { +std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, const DerivationPath& path) { const auto coin = path.coin(); const auto curve = TW::curve(coin); const auto hasher = TW::base58Hasher(coin); @@ -173,7 +173,7 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string &e } } -std::optional HDWallet::getPrivateKeyFromExtended(const std::string &extended, const DerivationPath& path) { +std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, const DerivationPath& path) { const auto coin = path.coin(); const auto curve = TW::curve(coin); const auto hasher = TW::base58Hasher(coin); diff --git a/src/HDWallet.h b/src/HDWallet.h index fb2dd62e4ee..784dcbd0c34 100644 --- a/src/HDWallet.h +++ b/src/HDWallet.h @@ -83,10 +83,10 @@ class HDWallet { std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const; /// Computes the public key from an exteded public key representation. - static std::optional getPublicKeyFromExtended(const std::string &extended, const DerivationPath& path); + static std::optional getPublicKeyFromExtended(const std::string& extended, const DerivationPath& path); /// Computes the private key from an exteded private key representation. - static std::optional getPrivateKeyFromExtended(const std::string &extended, const DerivationPath& path); + static std::optional getPrivateKeyFromExtended(const std::string& extended, const DerivationPath& path); public: // Private key type (later could be moved out of HDWallet) diff --git a/src/Harmony/Address.h b/src/Harmony/Address.h index 2c65defc17e..a36d8507ec1 100644 --- a/src/Harmony/Address.h +++ b/src/Harmony/Address.h @@ -18,12 +18,12 @@ class Address: public Bech32Address { static const std::string hrp; // HRP_HARMONY - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) { + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) { if (getKeyHash().size() != Address::size) { throw std::invalid_argument("invalid address data"); } diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index a75d8110f75..0543facad72 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -13,7 +13,7 @@ using namespace TW; using namespace TW::Harmony; std::tuple Signer::values(const uint256_t &chainID, - const Data &signature) noexcept { + const Data& signature) noexcept { auto r = load(Data(signature.begin(), signature.begin() + 32)); auto s = load(Data(signature.begin() + 32, signature.begin() + 64)); auto v = load(Data(signature.begin() + 64, signature.begin() + 65)); @@ -22,13 +22,13 @@ std::tuple Signer::values(const uint256_t &chai } std::tuple -Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept { +Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept { auto signature = privateKey.sign(hash, TWCurveSECP256k1); return values(chainID, signature); } template -Proto::SigningOutput Signer::prepareOutput(const Data &encoded, const T &transaction) noexcept { +Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T &transaction) noexcept { auto protoOutput = Proto::SigningOutput(); auto v = store(transaction.v); @@ -322,7 +322,7 @@ Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput &input } template -void Signer::sign(const PrivateKey &privateKey, const Data &hash, T &transaction) const noexcept { +void Signer::sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept { auto tuple = sign(chainID, privateKey, hash); transaction.r = std::get<0>(tuple); transaction.s = std::get<1>(tuple); diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index a7aa10d5cbc..70d048838b9 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -52,23 +52,23 @@ class Signer { explicit Signer(uint256_t chainID) : chainID(std::move(chainID)) {} template - static Proto::SigningOutput prepareOutput(const Data &encoded, const T &transaction) noexcept; + static Proto::SigningOutput prepareOutput(const Data& encoded, const T &transaction) noexcept; /// Signs the given transaction. template - void sign(const PrivateKey &privateKey, const Data &hash, T &transaction) const noexcept; + void sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept; /// Signs a hash with the given private key for the given chain identifier. /// /// @returns the r, s, and v values of the transaction signature static std::tuple - sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept; + sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept; /// R, S, and V values for the given chain identifier and signature. /// /// @returns the r, s, and v values of the transaction signature static std::tuple values(const uint256_t &chainID, - const Data &signature) noexcept; + const Data& signature) noexcept; std::string txnAsRLPHex(Transaction &transaction) const noexcept; diff --git a/src/Harmony/Transaction.h b/src/Harmony/Transaction.h index c06e4359513..ac43aa253f0 100644 --- a/src/Harmony/Transaction.h +++ b/src/Harmony/Transaction.h @@ -32,7 +32,7 @@ class Transaction { uint256_t s = uint256_t(); Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t fromShardID, - uint256_t toShardID, Address to, uint256_t amount, Data payload) + uint256_t toShardID, Address to, uint256_t amount, const Data& payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) , gasLimit(std::move(gasLimit)) diff --git a/src/IoTeX/Address.h b/src/IoTeX/Address.h index f34c353e704..a76743a4250 100644 --- a/src/IoTeX/Address.h +++ b/src/IoTeX/Address.h @@ -18,12 +18,12 @@ class Address: public Bech32Address { static const std::string hrp; // HRP_IOTEX - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) { + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) { if (getKeyHash().size() != Address::size) { throw std::invalid_argument("invalid address data"); } diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 91540ad39f2..279cc76761c 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -20,7 +20,7 @@ using namespace TW; using namespace TW::Keystore; template -static Data computeMAC(Iter begin, Iter end, Data key) { +static Data computeMAC(Iter begin, Iter end, const Data& key) { auto data = Data(); data.reserve((end - begin) + key.size()); data.insert(data.end(), begin, end); @@ -28,7 +28,7 @@ static Data computeMAC(Iter begin, Iter end, Data key) { return Hash::keccak256(data); } -EncryptionParameters::EncryptionParameters(const Data& password, Data data) : mac() { +EncryptionParameters::EncryptionParameters(const Data& password, const Data& data) : mac() { auto scryptParams = boost::get(kdfParams); auto derivedKey = Data(scryptParams.desiredKeyLength); scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index eec3b44a9f3..e1e68933977 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -46,7 +46,7 @@ struct EncryptionParameters { EncryptionParameters() = default; /// Initializes `EncryptionParameters` with standard values. - EncryptionParameters(Data encrypted, AESParameters cipherParams, boost::variant kdfParams, Data mac) + EncryptionParameters(const Data& encrypted, AESParameters cipherParams, boost::variant kdfParams, const Data& mac) : encrypted(std::move(encrypted)) , cipherParams(std::move(cipherParams)) , kdfParams(std::move(kdfParams)) @@ -54,7 +54,7 @@ struct EncryptionParameters { /// Initializes `EncryptionParameters` by encrypting data with a password /// using standard values. - EncryptionParameters(const Data& password, Data data); + EncryptionParameters(const Data& password, const Data& data); /// Initializes `EncryptionParameters` with a JSON object. EncryptionParameters(const nlohmann::json& json); diff --git a/src/Keystore/PBKDF2Parameters.h b/src/Keystore/PBKDF2Parameters.h index b99ab79d786..0d473b5615b 100644 --- a/src/Keystore/PBKDF2Parameters.h +++ b/src/Keystore/PBKDF2Parameters.h @@ -35,7 +35,7 @@ struct PBKDF2Parameters { PBKDF2Parameters(); /// Initializes `PBKDF2Parameters` with all values. - PBKDF2Parameters(Data salt, uint32_t iterations, std::size_t desiredKeyLength) + PBKDF2Parameters(const Data& salt, uint32_t iterations, std::size_t desiredKeyLength) : salt(std::move(salt)), desiredKeyLength(desiredKeyLength), iterations(iterations) {} /// Initializes `PBKDF2Parameters` with a JSON object. diff --git a/src/Keystore/ScryptParameters.h b/src/Keystore/ScryptParameters.h index 460dc4b8e64..10e7c019bd6 100644 --- a/src/Keystore/ScryptParameters.h +++ b/src/Keystore/ScryptParameters.h @@ -66,7 +66,7 @@ struct ScryptParameters { /// Initializes `ScryptParameters` with all values. /// /// @throws ScryptValidationError if the parameters are invalid. - ScryptParameters(Data salt, uint32_t n, uint32_t r, uint32_t p, std::size_t desiredKeyLength) + ScryptParameters(const Data& salt, uint32_t n, uint32_t r, uint32_t p, std::size_t desiredKeyLength) : salt(std::move(salt)), desiredKeyLength(desiredKeyLength), n(n), p(p), r(r) { auto error = validate(); if (error) { diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index b59f6de733b..bba73ee4907 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -78,7 +78,7 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, Data data) +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data) : type(type), id(), name(std::move(name)), payload(password, data), accounts() { boost::uuids::random_generator gen; id = boost::lexical_cast(gen()); diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 00e29ab969d..475271e9283 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -119,7 +119,7 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This contstructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, Data data); + StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data); }; } // namespace TW::Keystore diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index 9e69a1951cf..4b6e24826de 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -30,7 +30,7 @@ static void writeU128(Data& data, const std::string& numberData) { data.insert(std::end(data), std::begin(numberData), std::end(numberData)); } -template static void writeRawBuffer(Data &data, const T& buf) { +template static void writeRawBuffer(Data& data, const T& buf) { data.insert(std::end(data), std::begin(buf), std::end(buf)); } diff --git a/src/NEO/Address.h b/src/NEO/Address.h index bd2afc2a44a..d3d78ab7088 100644 --- a/src/NEO/Address.h +++ b/src/NEO/Address.h @@ -23,13 +23,13 @@ class Address : public TW::Base58Address { static const byte version = 0x17; /// Determines whether a string makes a valid NEO address. - static bool isValid(const std::string &string); + static bool isValid(const std::string& string); /// Initializes a NEO address with a string representation. - explicit Address(const std::string &string) : TW::Base58Address(string) {} + explicit Address(const std::string& string) : TW::Base58Address(string) {} /// Initializes a NEO address with a collection of bytes. - explicit Address(const Data &data) : TW::Base58Address(data) {} + explicit Address(const Data& data) : TW::Base58Address(data) {} /// Initializes an address with a collection of public key. explicit Address(uint8_t m, const std::vector& publicKeys); @@ -40,7 +40,7 @@ class Address : public TW::Base58Address { /// Initializes a NEO address without a public key. explicit Address(); - Data toScriptHash(const Data &data) const; + Data toScriptHash(const Data& data) const; Data toScriptHash() const; }; diff --git a/src/NEO/CoinReference.h b/src/NEO/CoinReference.h index 2b040923ede..d21b8865630 100644 --- a/src/NEO/CoinReference.h +++ b/src/NEO/CoinReference.h @@ -29,7 +29,7 @@ class CoinReference : public Serializable { return Hash::sha256Size + prevIndexSize; } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { prevHash = load(readBytes(data, Hash::sha256Size, initial_pos)); prevIndex = decode16LE(data.data() + initial_pos + Hash::sha256Size); } diff --git a/src/NEO/ISerializable.h b/src/NEO/ISerializable.h index 3106328cdc3..64e30ee0754 100644 --- a/src/NEO/ISerializable.h +++ b/src/NEO/ISerializable.h @@ -17,7 +17,7 @@ class ISerializable { virtual ~ISerializable() {} virtual int64_t size() const = 0; virtual Data serialize() const = 0; - virtual void deserialize(const Data &data, int initial_pos = 0) = 0; + virtual void deserialize(const Data& data, int initial_pos = 0) = 0; }; } // namespace TW::NEO diff --git a/src/NEO/MinerTransaction.h b/src/NEO/MinerTransaction.h index 01e3235fb06..ca73b306958 100644 --- a/src/NEO/MinerTransaction.h +++ b/src/NEO/MinerTransaction.h @@ -15,7 +15,7 @@ class MinerTransaction : public Transaction { public: uint32_t nonce; - virtual int deserializeExclusiveData(const Data &data, int initial_pos = 0) { + virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { nonce = decode32LE(data.data() + initial_pos); return initial_pos + 4; } diff --git a/src/NEO/ReadData.cpp b/src/NEO/ReadData.cpp index e419f944319..f7444e6af3c 100644 --- a/src/NEO/ReadData.cpp +++ b/src/NEO/ReadData.cpp @@ -8,14 +8,14 @@ #include "ReadData.h" -TW::Data TW::readBytes(const TW::Data &from, int max, int initial_pos) { +TW::Data TW::readBytes(const TW::Data& from, int max, int initial_pos) { if (from.size() - initial_pos < max) { throw std::invalid_argument("Data::Cannot read enough bytes!"); } return TW::Data(from.begin() + initial_pos, from.begin() + initial_pos + max); } -TW::Data TW::readVarBytes(const Data &from, int initial_pos, uint32_t* dataRead) { +TW::Data TW::readVarBytes(const Data& from, int initial_pos, uint32_t* dataRead) { uint64_t size = readVar(from, initial_pos); auto shift = varIntSize(size); if (dataRead) { @@ -24,7 +24,7 @@ TW::Data TW::readVarBytes(const Data &from, int initial_pos, uint32_t* dataRead) return readBytes(from, int(size), initial_pos + int(shift)); } -template<> uint64_t TW::readVar(const TW::Data &from, int initial_pos, const uint64_t &max) { +template<> uint64_t TW::readVar(const TW::Data& from, int initial_pos, const uint64_t &max) { byte fb = from[initial_pos]; uint64_t value; if (fb == 0xFD) { @@ -43,11 +43,11 @@ template<> uint64_t TW::readVar(const TW::Data &from, int initial_pos, const uin return value; } -template<> int64_t TW::readVar(const TW::Data &from, int initial_pos, const int64_t &max) { +template<> int64_t TW::readVar(const TW::Data& from, int initial_pos, const int64_t &max) { return (int64_t) readVar(from, initial_pos, uint64_t(max)); } -TW::Data TW::writeVarBytes(const Data &from, int initial_pos) { +TW::Data TW::writeVarBytes(const Data& from, int initial_pos) { Data resp; encodeVarInt(uint64_t(from.size() - initial_pos), resp); resp.insert(resp.end(), from.begin() + initial_pos, from.end()); diff --git a/src/NEO/ReadData.h b/src/NEO/ReadData.h index 80b86693f02..7f7e0a0b4c7 100644 --- a/src/NEO/ReadData.h +++ b/src/NEO/ReadData.h @@ -13,14 +13,14 @@ namespace TW { -Data readBytes(const Data &from, int max, int initial_pos = 0); -Data readVarBytes(const Data &from, int initial_pos = 0, uint32_t* dataRead = nullptr); +Data readBytes(const Data& from, int max, int initial_pos = 0); +Data readVarBytes(const Data& from, int initial_pos = 0, uint32_t* dataRead = nullptr); -template T readVar(const TW::Data &from, int initial_pos = 0, const T &max = INT_MAX); -template<> int64_t readVar(const TW::Data &from, int initial_pos, const int64_t &max); -template<> uint64_t readVar(const TW::Data &from, int initial_pos, const uint64_t &max); +template T readVar(const TW::Data& from, int initial_pos = 0, const T &max = INT_MAX); +template<> int64_t readVar(const TW::Data& from, int initial_pos, const int64_t &max); +template<> uint64_t readVar(const TW::Data& from, int initial_pos, const uint64_t &max); -Data writeVarBytes(const Data &from, int initial_pos = 0); +Data writeVarBytes(const Data& from, int initial_pos = 0); template static std::vector concat(const std::vector& v1, const std::vector& v2) { diff --git a/src/NEO/Script.cpp b/src/NEO/Script.cpp index 6e89610335b..a168dfe74bb 100644 --- a/src/NEO/Script.cpp +++ b/src/NEO/Script.cpp @@ -8,7 +8,7 @@ namespace TW::NEO { -Data Script::CreateSignatureRedeemScript(Data publicKey) { +Data Script::CreateSignatureRedeemScript(const Data& publicKey) { Data result; result.push_back((byte)PUSHBYTES21); result.insert(result.end(), publicKey.begin(), publicKey.end()); @@ -16,7 +16,7 @@ Data Script::CreateSignatureRedeemScript(Data publicKey) { return result; } -Data Script::CreateInvocationScript(Data signature) { +Data Script::CreateInvocationScript(const Data& signature) { Data result; result.push_back((byte)PUSHBYTES40); result.insert(result.end(), signature.begin(), signature.end()); diff --git a/src/NEO/Script.h b/src/NEO/Script.h index 5cbd4a59d82..64d47982754 100644 --- a/src/NEO/Script.h +++ b/src/NEO/Script.h @@ -11,8 +11,8 @@ namespace TW::NEO { class Script { public: - static Data CreateSignatureRedeemScript(Data publicKey); - static Data CreateInvocationScript(Data signature); + static Data CreateSignatureRedeemScript(const Data& publicKey); + static Data CreateInvocationScript(const Data& signature); }; } // namespace TW::NEO diff --git a/src/NEO/Serializable.h b/src/NEO/Serializable.h index 423253b861b..51ec20edf73 100644 --- a/src/NEO/Serializable.h +++ b/src/NEO/Serializable.h @@ -44,12 +44,9 @@ class Serializable : public ISerializable { } template - static inline int deserialize(std::vector &resp, const Data &data, int initial_pos = 0) { + static inline int deserialize(std::vector &resp, const Data& data, int initial_pos = 0) { uint64_t size = readVar(data, initial_pos, INT_MAX); - if (size < 0) { - throw std::invalid_argument("Serializable::deserialize ArgumentOutOfRangeException"); - } - + // assert(size >= 0); initial_pos += varIntSize(size); for (uint64_t i = 0; i < size; ++i) { T value; diff --git a/src/NEO/Transaction.cpp b/src/NEO/Transaction.cpp index 897ec967714..6c352b36894 100644 --- a/src/NEO/Transaction.cpp +++ b/src/NEO/Transaction.cpp @@ -21,7 +21,7 @@ int64_t Transaction::size() const { return serialize().size(); } -void Transaction::deserialize(const Data &data, int initial_pos) { +void Transaction::deserialize(const Data& data, int initial_pos) { type = (TransactionType) data[initial_pos++]; version = data[initial_pos++]; initial_pos = deserializeExclusiveData(data, initial_pos); @@ -33,7 +33,7 @@ void Transaction::deserialize(const Data &data, int initial_pos) { Serializable::deserialize(outputs, data, initial_pos); } -Transaction * Transaction::deserializeFrom(const Data &data, int initial_pos) { +Transaction * Transaction::deserializeFrom(const Data& data, int initial_pos) { Transaction * resp = nullptr; switch ((TransactionType) data[initial_pos]) { case TransactionType::TT_MinerTransaction: diff --git a/src/NEO/Transaction.h b/src/NEO/Transaction.h index 5fb387f5f47..50a311e3d9a 100644 --- a/src/NEO/Transaction.h +++ b/src/NEO/Transaction.h @@ -28,18 +28,18 @@ class Transaction : public Serializable { virtual ~Transaction() {} int64_t size() const override; - void deserialize(const Data &data, int initial_pos = 0) override; + void deserialize(const Data& data, int initial_pos = 0) override; Data serialize() const override; bool operator==(const Transaction &other) const; - virtual int deserializeExclusiveData(const Data &data, int initial_pos = 0) { return initial_pos; } + virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { return initial_pos; } virtual Data serializeExclusiveData() const { return Data(); } Data getHash() const; uint256_t getHashUInt256() const; - static Transaction * deserializeFrom(const Data &data, int initial_pos = 0); + static Transaction * deserializeFrom(const Data& data, int initial_pos = 0); }; } // namespace TW::NEO diff --git a/src/NEO/TransactionAttribute.h b/src/NEO/TransactionAttribute.h index 3978484a614..7a5187e65c0 100644 --- a/src/NEO/TransactionAttribute.h +++ b/src/NEO/TransactionAttribute.h @@ -24,7 +24,7 @@ class TransactionAttribute : public Serializable { return 1 + data.size(); } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { if (data.size() < initial_pos + 1) { throw std::invalid_argument("Invalid data for deserialization"); } diff --git a/src/NEO/TransactionOutput.h b/src/NEO/TransactionOutput.h index 85d05d7d297..1c27d3f3781 100644 --- a/src/NEO/TransactionOutput.h +++ b/src/NEO/TransactionOutput.h @@ -31,7 +31,7 @@ class TransactionOutput : public Serializable { return store(assetId).size() + valueSize + store(scriptHash).size(); } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { assetId = load(readBytes(data, assetIdSize, initial_pos)); value = decode64LE(data.data() + initial_pos + assetIdSize); scriptHash = load(readBytes(data, scriptHashSize, initial_pos + assetIdSize + valueSize)); diff --git a/src/NEO/Witness.h b/src/NEO/Witness.h index 808037b7dcf..6f0e7c9d139 100644 --- a/src/NEO/Witness.h +++ b/src/NEO/Witness.h @@ -23,7 +23,7 @@ class Witness : public Serializable { return invocationScript.size() + verificationScript.size(); } - void deserialize(const Data &data, int initial_pos = 0) override { + void deserialize(const Data& data, int initial_pos = 0) override { uint32_t size; invocationScript = readVarBytes(data, initial_pos, &size); verificationScript = readVarBytes(data, initial_pos + size); diff --git a/src/Nebulas/Address.cpp b/src/Nebulas/Address.cpp index acdc595d17d..bbc81447f43 100644 --- a/src/Nebulas/Address.cpp +++ b/src/Nebulas/Address.cpp @@ -11,7 +11,7 @@ using namespace TW::Nebulas; -bool Address::isValid(const std::string &string) { +bool Address::isValid(const std::string& string) { auto data = Base58::bitcoin.decode(string); if (data.size() != (size_t)Address::size) { return false; @@ -30,7 +30,7 @@ bool Address::isValid(const std::string &string) { return ::memcmp(dataSha3.data(), checksum.data(), 4) == 0; } -Address::Address(const std::string &string) { +Address::Address(const std::string& string) { if (!isValid(string)) { throw std::invalid_argument("Invalid address string"); } @@ -39,7 +39,7 @@ Address::Address(const std::string &string) { std::copy(data.begin(), data.end(), bytes.begin()); } -Address::Address(const Data &data) { +Address::Address(const Data& data) { if (!Base58Address::isValid(data)) { throw std::invalid_argument("Invalid address data"); } diff --git a/src/Solana/Address.cpp b/src/Solana/Address.cpp index a45bcc3ba94..93460cdf9ea 100644 --- a/src/Solana/Address.cpp +++ b/src/Solana/Address.cpp @@ -41,8 +41,8 @@ Data Address::vector() const { return vec; } -Address addressFromValidatorSeed(Address& fromAddress, Address& validatorAddress, - Address& programId) { +Address addressFromValidatorSeed(const Address& fromAddress, const Address& validatorAddress, + const Address& programId) { Data extended = fromAddress.vector(); std::string seed = validatorAddress.string(); Data vecSeed(seed.begin(), seed.end()); diff --git a/src/Solana/Address.h b/src/Solana/Address.h index fda8c909415..8bdc71ec85e 100644 --- a/src/Solana/Address.h +++ b/src/Solana/Address.h @@ -40,6 +40,6 @@ class Address : public Base58Address<32> { } // namespace TW::Solana -TW::Solana::Address addressFromValidatorSeed(TW::Solana::Address& fromAddress, - TW::Solana::Address& validatorAddress, - TW::Solana::Address& programId); +TW::Solana::Address addressFromValidatorSeed(const TW::Solana::Address& fromAddress, + const TW::Solana::Address& validatorAddress, + const TW::Solana::Address& programId); diff --git a/src/Solana/Transaction.h b/src/Solana/Transaction.h index 12ffdbe9c6f..b64cce5335d 100644 --- a/src/Solana/Transaction.h +++ b/src/Solana/Transaction.h @@ -54,7 +54,7 @@ struct CompiledInstruction { // The program input data Data data; - CompiledInstruction(uint8_t programIdIndex, Data accounts, Data data) + CompiledInstruction(uint8_t programIdIndex, const Data& accounts, const Data& data) : programIdIndex(programIdIndex), accounts(accounts), data(data) {} // This constructor creates a default System Transfer instruction diff --git a/src/TON/Cell.cpp b/src/TON/Cell.cpp index af70dbfb3c2..be52dc56130 100644 --- a/src/TON/Cell.cpp +++ b/src/TON/Cell.cpp @@ -41,7 +41,7 @@ Slice Slice::createFromHex(std::string const& dataStr) { } Slice Slice::createFromBits(const Data& data, size_t sizeBits) { - if (sizeBits <= 0) { + if (sizeBits == 0) { throw std::runtime_error("empty data"); } Slice s; diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index 0c4aae8ba1f..b0732fd78c9 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -45,7 +45,7 @@ Data Signer::signOperationList(const PrivateKey& privateKey, const OperationList return signData(privateKey, forged); } -Data Signer::signData(const PrivateKey& privateKey, Data data) { +Data Signer::signData(const PrivateKey& privateKey, const Data& data) { Data watermarkedData = Data(); watermarkedData.push_back(0x03); append(watermarkedData, data); diff --git a/src/Tezos/Signer.h b/src/Tezos/Signer.h index d6b88fcc6b5..798d703ab21 100644 --- a/src/Tezos/Signer.h +++ b/src/Tezos/Signer.h @@ -25,7 +25,7 @@ class Signer { public: /// Signs the given transaction. Data signOperationList(const PrivateKey& privateKey, const OperationList& operationList); - Data signData(const PrivateKey& privateKey, Data data); + Data signData(const PrivateKey& privateKey, const Data& data); }; } // namespace TW::Tezos diff --git a/src/Theta/Transaction.h b/src/Theta/Transaction.h index fccc7e5439d..735c21ab1a8 100644 --- a/src/Theta/Transaction.h +++ b/src/Theta/Transaction.h @@ -24,7 +24,7 @@ class TxInput { TxInput(Ethereum::Address address, Coins coins, uint64_t sequence) : address(std::move(address)), coins(std::move(coins)), sequence(sequence) {} - TxInput(Ethereum::Address address, Coins coins, uint64_t sequence, Data signature) + TxInput(Ethereum::Address address, Coins coins, uint64_t sequence, const Data& signature) : address(std::move(address)), coins(std::move(coins)), sequence(sequence), signature(std::move(signature)) {} }; diff --git a/src/VeChain/Clause.h b/src/VeChain/Clause.h index eb8d1e5dd06..9bbb1bc6435 100644 --- a/src/VeChain/Clause.h +++ b/src/VeChain/Clause.h @@ -19,7 +19,7 @@ class Clause { uint256_t value; Data data; - Clause(Ethereum::Address to, uint256_t value, Data data = {}) + Clause(Ethereum::Address to, uint256_t value, const Data& data = {}) : to(std::move(to)), value(std::move(value)), data(std::move(data)) {} /// Decodes from a proto representation. diff --git a/src/Waves/Address.cpp b/src/Waves/Address.cpp index 7b42ff51dc0..be51732e937 100644 --- a/src/Waves/Address.cpp +++ b/src/Waves/Address.cpp @@ -22,7 +22,7 @@ Data Address::secureHash(const T &data) { return Hash::keccak256(Hash::blake2b(data, 32)); } -bool Address::isValid(const Data &decoded) { +bool Address::isValid(const Data& decoded) { if (decoded.size() != Address::size) { return false; } @@ -44,12 +44,12 @@ bool Address::isValid(const Data &decoded) { return std::memcmp(data_checksum.data(), calculated_checksum.data(), 4) == 0; } -bool Address::isValid(const std::string &string) { +bool Address::isValid(const std::string& string) { const auto decoded = Base58::bitcoin.decode(string); return isValid(decoded); } -Address::Address(const std::string &string) { +Address::Address(const std::string& string) { const auto decoded = Base58::bitcoin.decode(string); if (!isValid(string)) { throw std::invalid_argument("Invalid address key data"); @@ -57,7 +57,7 @@ Address::Address(const std::string &string) { std::copy(decoded.begin(), decoded.end(), bytes.begin()); } -Address::Address(const Data &data) { +Address::Address(const Data& data) { if (!isValid(data)) { throw std::invalid_argument("Invalid address data"); } diff --git a/src/Waves/Address.h b/src/Waves/Address.h index 81950ee6b93..df518415334 100644 --- a/src/Waves/Address.h +++ b/src/Waves/Address.h @@ -17,9 +17,6 @@ namespace TW::Waves { class Address : public Base58Address<26> { public: - /// Number of bytes in an address. - static const size_t size = 26; - /// Address version. static const signed char v1 = 0x01; @@ -29,20 +26,16 @@ class Address : public Base58Address<26> { template static Data secureHash(const T &data); - /// Address data consisting of a version and network bytes followed by the public key - /// hash and the checksum. - std::array bytes; - /// Determines whether a string makes a valid address. - static bool isValid(const std::string &string); + static bool isValid(const std::string& string); - static bool isValid(const Data &data); + static bool isValid(const Data& data); /// Initializes a address with a string representation. - explicit Address(const std::string &string); + explicit Address(const std::string& string); /// Initializes a address with a collection of bytes. - explicit Address(const Data &data); + explicit Address(const Data& data); /// Initializes a address with a public key and a prefix. explicit Address(const PublicKey &publicKey); diff --git a/src/Waves/Transaction.cpp b/src/Waves/Transaction.cpp index 277c1540bc3..ee9d2a5af44 100644 --- a/src/Waves/Transaction.cpp +++ b/src/Waves/Transaction.cpp @@ -17,7 +17,7 @@ using json = nlohmann::json; const std::string Transaction::WAVES = "WAVES"; -Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::string fee_asset, Address to, Data attachment, int64_t timestamp, Data pub_key) { +Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::string fee_asset, Address to, const Data& attachment, int64_t timestamp, const Data& pub_key) { auto data = Data(); if (asset.empty()) { asset = Transaction::WAVES; @@ -50,7 +50,7 @@ Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::stri return data; } -Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, Data pub_key) { +Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, const Data& pub_key) { auto data = Data(); data.resize(2); data[0] = static_cast(TransactionType::lease); @@ -65,7 +65,7 @@ Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, return data; } -Data serializeCancelLease(Data leaseId, int64_t fee, int64_t timestamp, Data pub_key) { +Data serializeCancelLease(const Data& leaseId, int64_t fee, int64_t timestamp, const Data& pub_key) { auto data = Data(); data.resize(2); data[0] = static_cast(TransactionType::cancelLease); @@ -79,7 +79,7 @@ Data serializeCancelLease(Data leaseId, int64_t fee, int64_t timestamp, Data pub return data; } -json jsonTransfer(Data signature, int64_t amount, const std::string &asset, int64_t fee, const std::string &fee_asset, Address to, Data attachment, int64_t timestamp, Data pub_key) { +json jsonTransfer(const Data& signature, int64_t amount, const std::string& asset, int64_t fee, const std::string& fee_asset, Address to, const Data& attachment, int64_t timestamp, const Data& pub_key) { json jsonTx; jsonTx["type"] = TransactionType::transfer; @@ -101,7 +101,7 @@ json jsonTransfer(Data signature, int64_t amount, const std::string &asset, int6 return jsonTx; } -json jsonLease(Data signature, int64_t amount, int64_t fee, Address to, int64_t timestamp, Data pub_key) { +json jsonLease(const Data& signature, int64_t amount, int64_t fee, Address to, int64_t timestamp, const Data& pub_key) { json jsonTx; jsonTx["type"] = TransactionType::lease; @@ -116,7 +116,7 @@ json jsonLease(Data signature, int64_t amount, int64_t fee, Address to, int64_t return jsonTx; } -json jsonCancelLease(Data signature, Data leaseId, int64_t fee, int64_t timestamp, Data pub_key) { +json jsonCancelLease(const Data& signature, const Data& leaseId, int64_t fee, int64_t timestamp, const Data& pub_key) { json jsonTx; jsonTx["type"] = TransactionType::cancelLease; @@ -162,7 +162,7 @@ Data Transaction::serializeToSign() const { -json Transaction::buildJson(Data signature) const { +json Transaction::buildJson(const Data& signature) const { if (input.has_transfer_message()) { auto message = input.transfer_message(); auto attachment = Data(message.attachment().begin(), message.attachment().end()); diff --git a/src/Waves/Transaction.h b/src/Waves/Transaction.h index 130f44ba095..b22fd54a030 100644 --- a/src/Waves/Transaction.h +++ b/src/Waves/Transaction.h @@ -29,7 +29,7 @@ class Transaction { public: Data serializeToSign() const; - nlohmann::json buildJson(Data signature) const; + nlohmann::json buildJson(const Data& signature) const; }; } // namespace TW::Waves diff --git a/src/XXHash64.h b/src/XXHash64.h index 34122ebc85e..6147ff42759 100644 --- a/src/XXHash64.h +++ b/src/XXHash64.h @@ -33,6 +33,7 @@ class XXHash64 state[1] = seed + Prime2; state[2] = seed; state[3] = seed - Prime1; + buffer[0] = 0; bufferSize = 0; totalLength = 0; } diff --git a/src/Zilliqa/Address.h b/src/Zilliqa/Address.h index 9aa09804f59..be7a7ef234f 100644 --- a/src/Zilliqa/Address.h +++ b/src/Zilliqa/Address.h @@ -18,12 +18,12 @@ class Address : public Bech32Address { public: static const std::string hrp; // HRP_ZILLIQA - static bool isValid(const std::string addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } Address() : Bech32Address(hrp) {} /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) {} + Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} /// Initializes an address with a public key. Address(const PublicKey& publicKey) : Bech32Address(hrp, HASHER_SHA2, publicKey) {} diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index fcd2587ddf2..d2c028ed584 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -36,13 +36,13 @@ bool TWAnyAddressEqual(struct TWAnyAddress* _Nonnull lhs, struct TWAnyAddress* _ } bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { - auto& address = *reinterpret_cast(string); + const auto& address = *reinterpret_cast(string); return TW::validateAddress(coin, address); } struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, enum TWCoinType coin) { - auto& address = *reinterpret_cast(string); + const auto& address = *reinterpret_cast(string); auto normalized = TW::normalizeAddress(coin, address); if (normalized.empty()) { return nullptr; } return new TWAnyAddress{TWStringCreateWithUTF8Bytes(normalized.c_str()), coin}; diff --git a/src/interface/TWBase58.cpp b/src/interface/TWBase58.cpp index ee38331260e..923b8a33bf0 100644 --- a/src/interface/TWBase58.cpp +++ b/src/interface/TWBase58.cpp @@ -13,7 +13,7 @@ using namespace TW; TWString *_Nonnull TWBase58Encode(TWData *_Nonnull data) { - auto& d = *reinterpret_cast(data); + const auto& d = *reinterpret_cast(data); const auto str = Base58::bitcoin.encodeCheck(d); return TWStringCreateWithUTF8Bytes(str.c_str()); } diff --git a/src/interface/TWBitcoinAddress.cpp b/src/interface/TWBitcoinAddress.cpp index b18e840364d..8df8de2a37f 100644 --- a/src/interface/TWBitcoinAddress.cpp +++ b/src/interface/TWBitcoinAddress.cpp @@ -38,7 +38,7 @@ struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_N } struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnull data) { - auto& d = *reinterpret_cast(data); + const auto& d = *reinterpret_cast(data); try { return new TWBitcoinAddress{ Address(d) }; } catch (...) { diff --git a/src/interface/TWData.cpp b/src/interface/TWData.cpp index 5a0ae87d6b2..ed290bfccf2 100644 --- a/src/interface/TWData.cpp +++ b/src/interface/TWData.cpp @@ -70,8 +70,7 @@ void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte) { void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append) { auto v = const_cast*>(reinterpret_cast*>(data)); auto av = reinterpret_cast*>(append); - for (auto& b : *av) - v->push_back(b); + std::copy(av->begin(), av->end(), std::back_inserter(*v)); } void TWDataReverse(TWData *_Nonnull data) { diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index 9ae603f8ffd..ab6b2b27ccb 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -90,7 +90,7 @@ struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivate } TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { - auto& d = *reinterpret_cast(digest); + const auto& d = *reinterpret_cast(digest); auto result = pk->impl.sign(d, curve); if (result.empty()) { return nullptr; @@ -110,7 +110,7 @@ TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull } TWData *TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message, enum TWCurve curve) { - auto& msg = *reinterpret_cast(message); + const auto& msg = *reinterpret_cast(message); auto result = pk->impl.signSchnorr(msg, curve); if (result.empty()) { diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 9ada3d666d0..83f2c9c4789 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -49,14 +49,14 @@ struct TWPublicKey *_Nonnull TWPublicKeyUncompressed(struct TWPublicKey *_Nonnul } bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *signature, TWData *message) { - auto& s = *reinterpret_cast(signature); - auto& m = *reinterpret_cast(message); + const auto& s = *reinterpret_cast(signature); + const auto& m = *reinterpret_cast(message); return pk->impl.verify(s, m); } bool TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { - auto& s = *reinterpret_cast(signature); - auto& m = *reinterpret_cast(message); + const auto& s = *reinterpret_cast(signature); + const auto& m = *reinterpret_cast(message); return pk->impl.verifySchnorr(s, m); } diff --git a/tools/lint-cppcheck-all b/tools/lint-cppcheck-all new file mode 100755 index 00000000000..09ee00310bf --- /dev/null +++ b/tools/lint-cppcheck-all @@ -0,0 +1,7 @@ +#!/bin/bash +# +# This script lints all of the C++ code with cppcheck. + +set -e + +find src \( -name "*.cpp" -o -name "*.h" \) -not -path "./src/proto/*" -not -path "./src/Tron/Protobuf/*" -exec cppcheck --force --quiet --enable=all '{}' \; diff --git a/tools/lint-cppcheck-diff b/tools/lint-cppcheck-diff new file mode 100755 index 00000000000..2b568769fea --- /dev/null +++ b/tools/lint-cppcheck-diff @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# +# This script lints the files modified in the current branch with cppcheck. + +merge_base = `git merge-base HEAD origin/master`.strip +changed_files = `git diff --name-only HEAD #{merge_base}`.strip.split(/\s/) +files = changed_files.select { |f| File.extname(f) == '.cpp' || File.extname(f) == '.h' } +if files.empty? + puts 'No files to lint' + exit 0 +end + +puts "Linting #{files.count} files" +system "cppcheck --quiet --force --enable=all #{files.join(' ')}" +exit $?.exitstatus From 02eab8ab3518b70fb9c6209b7f2633b2f2c8e9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 29 Apr 2020 13:10:33 +0300 Subject: [PATCH 13/81] Add Elrond blockchain (#929) * Registers new coin. * Define protobuf models * Implement entry, address and add tests * Fix AnyAddress, add further address tests * Implement serialization and signing, add tests * Add key derivation tests * Add tests for Android * Add tests for iOS * Fix derivation path and tests * Fix after review (including build issues) --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../blockchains/elrond/TestElrondAddress.kt | 49 ++++++++++ .../blockchains/elrond/TestElrondSigner.kt | 57 ++++++++++++ coins.json | 22 +++++ docs/coins.md | 1 + include/TrustWalletCore/TWBlockchain.h | 1 + include/TrustWalletCore/TWCoinType.h | 1 + src/Coin.cpp | 2 + src/Elrond/Address.cpp | 17 ++++ src/Elrond/Address.h | 38 ++++++++ src/Elrond/Entry.cpp | 31 +++++++ src/Elrond/Entry.h | 25 ++++++ src/Elrond/Serialization.cpp | 67 ++++++++++++++ src/Elrond/Serialization.h | 21 +++++ src/Elrond/Signer.cpp | 38 ++++++++ src/Elrond/Signer.h | 28 ++++++ src/interface/TWAnyAddress.cpp | 11 +++ src/proto/Elrond.proto | 36 ++++++++ swift/Tests/Blockchains/ElrondTests.swift | 51 +++++++++++ swift/Tests/CoinAddressDerivationTests.swift | 3 + tests/Bech32AddressTests.cpp | 8 ++ tests/CoinAddressDerivationTests.cpp | 1 + tests/CoinAddressValidationTests.cpp | 6 ++ tests/Elrond/AddressTests.cpp | 79 ++++++++++++++++ tests/Elrond/SerializationTests.cpp | 54 +++++++++++ tests/Elrond/SignerTests.cpp | 89 +++++++++++++++++++ tests/Elrond/TWAnySignerTests.cpp | 55 ++++++++++++ tests/Elrond/TWCoinTypeTests.cpp | 34 +++++++ tests/Elrond/TestAccounts.h | 17 ++++ tests/interface/TWAnyAddressTests.cpp | 7 ++ tests/interface/TWHDWalletTests.cpp | 10 +++ tests/interface/TWHRPTests.cpp | 3 + 32 files changed, 863 insertions(+) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt create mode 100644 src/Elrond/Address.cpp create mode 100644 src/Elrond/Address.h create mode 100644 src/Elrond/Entry.cpp create mode 100644 src/Elrond/Entry.h create mode 100644 src/Elrond/Serialization.cpp create mode 100644 src/Elrond/Serialization.h create mode 100644 src/Elrond/Signer.cpp create mode 100644 src/Elrond/Signer.h create mode 100644 src/proto/Elrond.proto create mode 100644 swift/Tests/Blockchains/ElrondTests.swift create mode 100644 tests/Elrond/AddressTests.cpp create mode 100644 tests/Elrond/SerializationTests.cpp create mode 100644 tests/Elrond/SignerTests.cpp create mode 100644 tests/Elrond/TWAnySignerTests.cpp create mode 100644 tests/Elrond/TWCoinTypeTests.cpp create mode 100644 tests/Elrond/TestAccounts.h 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 8bfe4dd5268..18ab0a98320 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 @@ -83,5 +83,6 @@ class CoinAddressDerivationTests { CARDANO -> assertEquals("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", address) NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) + ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt new file mode 100644 index 00000000000..05c17837ddc --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt @@ -0,0 +1,49 @@ +// Copyright © 2017-2020 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. + +package com.trustwallet.core.app.blockchains.elrond + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestElrondAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + private val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" + private var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" + private var alicePubKeyHex = "0xfd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" + + @Test + fun testAddressFromPrivateKey() { + val key = PrivateKey(aliceSeedHex.toHexByteArray()) + val pubKey = key.publicKeyEd25519 + val address = AnyAddress(pubKey, CoinType.ELROND) + + assertEquals(alicePubKeyHex, pubKey.data().toHex()) + assertEquals(aliceBech32, address.description()) + } + + @Test + fun testAddressFromPublicKey() { + val pubKey = PublicKey(alicePubKeyHex.toHexByteArray(), PublicKeyType.ED25519) + val address = AnyAddress(pubKey, CoinType.ELROND) + + assertEquals(aliceBech32, address.description()) + } + + @Test + fun testAddressFromString() { + val address = AnyAddress(aliceBech32, CoinType.ELROND) + + assertEquals(aliceBech32, address.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt new file mode 100644 index 00000000000..160dd9d7765 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt @@ -0,0 +1,57 @@ +// Copyright © 2017-2020 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. + +package com.trustwallet.core.app.blockchains.elrond + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import junit.framework.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Elrond + +class TestElrondSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + val aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" + var aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" + var alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" + + val bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" + var bobSeedHex = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e" + var bobPubKeyHex = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36" + + @Test + fun signTransaction() { + val transaction = Elrond.TransactionMessage.newBuilder() + .setNonce(0) + .setValue("0") + .setSender(aliceBech32) + .setReceiver(bobBech32) + .setGasPrice(200000000000000) + .setGasLimit(500000000) + .setData("foo") + .build() + + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val signingInput = Elrond.SigningInput.newBuilder() + .setPrivateKey(privateKey) + .setTransaction(transaction) + .build() + + val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) + val expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":0,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"$expectedSignature"}""", output.encoded) + } +} diff --git a/coins.json b/coins.json index 18047f66535..175ef086de3 100644 --- a/coins.json +++ b/coins.json @@ -1330,5 +1330,27 @@ "clientPublic": "", "clientDocs": "https://docs.lotu.sh" } + }, + { + "id": "elrond", + "name": "Elrond", + "symbol": "ERD", + "decimals": 18, + "blockchain": "ElrondNetwork", + "derivationPath": "m/44'/508'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "hrp": "erd", + "explorer": { + "url": "https://explorer.elrond.com", + "txPath": "/transactions/", + "accountPath": "/address/" + }, + "info": { + "url": "https://elrond.com/", + "client": "https://github.com/ElrondNetwork/elrond-go", + "clientPublic": "https://api.elrond.com", + "clientDocs": "https://docs.elrond.com" + } } ] diff --git a/docs/coins.md b/docs/coins.md index 2fae8caf2e8..c99e9d92157 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -43,6 +43,7 @@ This list is generated from [./coins.json](../coins.json) | 461 | Filecoin | FIL | | | | 500 | Theta | THETA | | | | 501 | Solana | SOL | | | +| 508 | Elrond | ERD | | | | 714 | Binance | BNB | | | | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 8b1b82d6028..6a174d8af7c 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -45,6 +45,7 @@ enum TWBlockchain { TWBlockchainCardano = 30, TWBlockchainNEO = 31, TWBlockchainFilecoin = 32, + TWBlockchainElrondNetwork = 33, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 0c2a152cec0..bf70c377ee9 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -79,6 +79,7 @@ enum TWCoinType { TWCoinTypeKusama = 434, TWCoinTypePolkadot = 354, TWCoinTypeFilecoin = 461, + TWCoinTypeElrond = 508, }; /// Returns the blockchain for a coin type. diff --git a/src/Coin.cpp b/src/Coin.cpp index 8f96291d962..9ea55ffbfb9 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -51,6 +51,7 @@ #include "Waves/Entry.h" #include "Zcash/Entry.h" #include "Zilliqa/Entry.h" +#include "Elrond/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -100,6 +101,7 @@ void setupDispatchers() { new Waves::Entry(), new Zcash::Entry(), new Zilliqa::Entry(), + new Elrond::Entry(), }; // end_of_coin_entries_marker_do_not_modify dispatchMap.clear(); diff --git a/src/Elrond/Address.cpp b/src/Elrond/Address.cpp new file mode 100644 index 00000000000..1af84ace200 --- /dev/null +++ b/src/Elrond/Address.cpp @@ -0,0 +1,17 @@ +// Copyright © 2017-2020 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. + +#include + +#include "Address.h" + +using namespace TW::Elrond; + +const std::string Address::hrp = HRP_ELROND; + +bool Address::isValid(const std::string& string) { + return Bech32Address::isValid(string, hrp); +} diff --git a/src/Elrond/Address.h b/src/Elrond/Address.h new file mode 100644 index 00000000000..1f96edc6700 --- /dev/null +++ b/src/Elrond/Address.h @@ -0,0 +1,38 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "../Data.h" +#include "../PublicKey.h" +#include "../Bech32Address.h" + +#include + +namespace TW::Elrond { + +class Address : public Bech32Address { + public: + // The human-readable part of the address, as defined in "coins.json" + static const std::string hrp; // HRP_ELROND + + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string); + + Address() : Bech32Address(hrp) {} + + /// Initializes an address with a key hash. + Address(Data keyHash) : Bech32Address(hrp, keyHash) {} + + /// Initializes an address with a public key. + Address(const PublicKey& publicKey) : Bech32Address(hrp, publicKey.bytes) {} + + static bool decode(const std::string& addr, Address& obj_out) { + return Bech32Address::decode(addr, obj_out, hrp); + } +}; + +} // namespace TW::Elrond diff --git a/src/Elrond/Entry.cpp b/src/Elrond/Entry.cpp new file mode 100644 index 00000000000..76b806ff128 --- /dev/null +++ b/src/Elrond/Entry.cpp @@ -0,0 +1,31 @@ +// Copyright © 2017-2020 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. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +using namespace TW::Elrond; +using namespace std; + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { + return Address::isValid(address); +} + +string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { + return Address(publicKey).string(); +} + +void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return Signer::signJSON(json, key); +} diff --git a/src/Elrond/Entry.h b/src/Elrond/Entry.h new file mode 100644 index 00000000000..78a5f60a39f --- /dev/null +++ b/src/Elrond/Entry.h @@ -0,0 +1,25 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Elrond { + +/// Entry point for implementation of Elrond coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry: public CoinEntry { +public: + virtual std::vector coinTypes() const { return {TWCoinTypeElrond}; } + virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; + virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + virtual bool supportsJSONSigning() const { return true; } + virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +}; + +} // namespace TW::Elrond diff --git a/src/Elrond/Serialization.cpp b/src/Elrond/Serialization.cpp new file mode 100644 index 00000000000..193daf5d210 --- /dev/null +++ b/src/Elrond/Serialization.cpp @@ -0,0 +1,67 @@ +// Copyright © 2017-2020 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. + +#include "Serialization.h" + +#include "../Elrond/Address.h" +#include "../proto/Elrond.pb.h" +#include "Base64.h" +#include "PrivateKey.h" + +using namespace TW; + +std::map fields_order { + {"nonce", 1}, + {"value", 2}, + {"receiver", 3}, + {"sender", 4}, + {"gasPrice", 5}, + {"gasLimit", 6}, + {"data", 7}, + {"signature", 8} +}; + +struct FieldsSorter { + bool operator() (const string& lhs, const string& rhs) const { + return fields_order[lhs] < fields_order[rhs]; + } +}; + +template +using sorted_map = std::map; +using sorted_json = nlohmann::basic_json; + +string Elrond::serializeTransaction(const Proto::TransactionMessage& message) { + sorted_json payload { + {"nonce", json(message.nonce())}, + {"value", json(message.value())}, + {"receiver", json(message.receiver())}, + {"sender", json(message.sender())}, + {"gasPrice", json(message.gas_price())}, + {"gasLimit", json(message.gas_limit())}, + }; + + if (!message.data().empty()) { + payload["data"] = json(TW::Base64::encode(TW::data(message.data()))); + } + + return payload.dump(); +} + +string Elrond::serializeSignedTransaction(const Proto::TransactionMessage& message, string signature) { + sorted_json payload { + {"nonce", json(message.nonce())}, + {"value", json(message.value())}, + {"receiver", json(message.receiver())}, + {"sender", json(message.sender())}, + {"gasPrice", json(message.gas_price())}, + {"gasLimit", json(message.gas_limit())}, + {"data", json(message.data())}, + {"signature", json(signature)}, + }; + + return payload.dump(); +} diff --git a/src/Elrond/Serialization.h b/src/Elrond/Serialization.h new file mode 100644 index 00000000000..e56d8a5bf87 --- /dev/null +++ b/src/Elrond/Serialization.h @@ -0,0 +1,21 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "../proto/Elrond.pb.h" +#include "Data.h" +#include + +using string = std::string; +using json = nlohmann::json; + +namespace TW::Elrond { + +string serializeTransaction(const Proto::TransactionMessage& message); +string serializeSignedTransaction(const Proto::TransactionMessage& message, string encodedSignature); + +} // namespace diff --git a/src/Elrond/Signer.cpp b/src/Elrond/Signer.cpp new file mode 100644 index 00000000000..20519c8b80a --- /dev/null +++ b/src/Elrond/Signer.cpp @@ -0,0 +1,38 @@ +// Copyright © 2017-2020 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. + +#include "Signer.h" +#include "Address.h" +#include "Serialization.h" +#include "../PublicKey.h" +#include "HexCoding.h" + +#include + +using namespace TW; +using namespace TW::Elrond; + +Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { + auto privateKey = PrivateKey(input.private_key()); + auto signableAsString = serializeTransaction(input.transaction()); + auto signableAsData = TW::data(signableAsString); + auto signature = privateKey.sign(signableAsData, TWCurveED25519); + auto encodedSignature = hex(signature); + auto encoded = serializeSignedTransaction(input.transaction(), encodedSignature); + + auto protoOutput = Proto::SigningOutput(); + protoOutput.set_signature(encodedSignature); + protoOutput.set_encoded(encoded); + return protoOutput; +} + +std::string Signer::signJSON(const std::string& json, const Data& key) { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + auto output = sign(input); + return output.encoded(); +} diff --git a/src/Elrond/Signer.h b/src/Elrond/Signer.h new file mode 100644 index 00000000000..046f5e52d67 --- /dev/null +++ b/src/Elrond/Signer.h @@ -0,0 +1,28 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "../Data.h" +#include "../PrivateKey.h" +#include "../proto/Elrond.pb.h" + +namespace TW::Elrond { + +/// Helper class that performs Elrond transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + + /// Signs a json Proto::SigningInput with private key + static std::string signJSON(const std::string& json, const Data& key); +}; + +} // namespace TW::Elrond diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index d2c028ed584..dd6ee615f4c 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -20,6 +20,7 @@ #include "../Cardano/AddressV3.h" #include "../NEO/Address.h" #include "../Nano/Address.h" +#include "../Elrond/Address.h" #include "../Coin.h" #include "../HexCoding.h" @@ -184,6 +185,16 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { data = Data(addr.bytes.begin(), addr.bytes.end()); break; } + + case TWCoinTypeElrond: { + Elrond::Address addr; + if (Elrond::Address::decode(string, addr)) { + data = addr.getKeyHash(); + } + + break; + } + default: break; } return TWDataCreateWithBytes(data.data(), data.size()); diff --git a/src/proto/Elrond.proto b/src/proto/Elrond.proto new file mode 100644 index 00000000000..41b4a0efc06 --- /dev/null +++ b/src/proto/Elrond.proto @@ -0,0 +1,36 @@ +// Copyright © 2017-2020 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. + +syntax = "proto3"; + +package TW.Elrond.Proto; +option java_package = "wallet.core.jni.proto"; + +// A transaction, typical balance transfer +message TransactionMessage { + uint64 nonce = 1; + string value = 2; + string receiver = 3; + string sender = 4; + uint64 gas_price = 5; + uint64 gas_limit = 6; + string data = 7; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + + oneof message_oneof { + TransactionMessage transaction = 2; + } +} + +// Transaction signing output. +message SigningOutput { + string encoded = 1; + string signature = 2; +} diff --git a/swift/Tests/Blockchains/ElrondTests.swift b/swift/Tests/Blockchains/ElrondTests.swift new file mode 100644 index 00000000000..c3e3e5b4b8c --- /dev/null +++ b/swift/Tests/Blockchains/ElrondTests.swift @@ -0,0 +1,51 @@ +// Copyright © 2017-2020 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. + +import TrustWalletCore +import XCTest + +class ElrondTests: XCTestCase { + + let aliceBech32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" + let aliceSeedHex = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf" + let alicePubKeyHex = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" + let bobBech32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" + + func testAddress() { + let key = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let pubkey = key.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .elrond) + let addressFromString = AnyAddress(string: aliceBech32, coin: .elrond)! + + XCTAssertEqual(pubkey.data.hexString, alicePubKeyHex) + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = ElrondSigningInput.with { + $0.transaction = ElrondTransactionMessage.with { + $0.nonce = 0 + $0.value = "0" + $0.sender = aliceBech32 + $0.receiver = bobBech32 + $0.gasPrice = 200000000000000 + $0.gasLimit = 500000000 + $0.data = "foo" + } + + $0.privateKey = privateKey.data + } + + let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) + let expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308" + let expectedEncoded = #"{"nonce":0,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 81b55b548ed..2a09ac50169 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -190,6 +190,9 @@ class CoinAddressDerivationTests: XCTestCase { case .filecoin: let expectedResult = "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + case .elrond: + let expectedResult = "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" + AssetCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } diff --git a/tests/Bech32AddressTests.cpp b/tests/Bech32AddressTests.cpp index ff425cb7c4f..4b6024ecd5e 100644 --- a/tests/Bech32AddressTests.cpp +++ b/tests/Bech32AddressTests.cpp @@ -27,6 +27,10 @@ TEST(Bech32Address, Valid) { ASSERT_TRUE(Bech32Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", "io")); ASSERT_TRUE(Bech32Address::isValid("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7", "zil")); + + ASSERT_TRUE(Bech32Address::isValid("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", "erd")); + ASSERT_TRUE(Bech32Address::isValid("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", "erd")); + ASSERT_TRUE(Bech32Address::isValid("erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", "erd")); } TEST(Bech32Address, Invalid) { @@ -48,6 +52,10 @@ TEST(Bech32Address, Invalid) { ASSERT_FALSE(Bech32Address::isValid("")); ASSERT_FALSE(Bech32Address::isValid("0x")); ASSERT_FALSE(Bech32Address::isValid("91cddcebe846ce4d47712287eee53cf17c2cfb7")); + + ASSERT_FALSE(Bech32Address::isValid("", "erd")); + ASSERT_FALSE(Bech32Address::isValid("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35!", "erd")); + ASSERT_FALSE(Bech32Address::isValid("xerd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", "erd")); } TEST(Bech32Address, InvalidWrongPrefix) { diff --git a/tests/CoinAddressDerivationTests.cpp b/tests/CoinAddressDerivationTests.cpp index 16ac09114df..1bf597c6d88 100644 --- a/tests/CoinAddressDerivationTests.cpp +++ b/tests/CoinAddressDerivationTests.cpp @@ -71,6 +71,7 @@ TEST(Coin, DeriveAddress) { EXPECT_EQ(TW::deriveAddress(TWCoinTypeFilecoin, privateKey), "f1qsx7qwiojh5duxbxhbqgnlyx5hmpcf7mcz5oxsy"); EXPECT_EQ(TW::deriveAddress(TWCoinTypeNEAR, privateKey), "NEAR2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5HYrdGj"); EXPECT_EQ(TW::deriveAddress(TWCoinTypeSolana, privateKey), "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeElrond, privateKey), "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); } } // namespace TW diff --git a/tests/CoinAddressValidationTests.cpp b/tests/CoinAddressValidationTests.cpp index 7b08cf9f61b..86a0d7f309a 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/CoinAddressValidationTests.cpp @@ -373,4 +373,10 @@ TEST(Coin, ValidateAddressVeChain) { EXPECT_EQ(normalizeAddress(TWCoinTypeVeChain, "0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f"), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); } +TEST(Coin, ValidateAddressElrond) { + EXPECT_TRUE(validateAddress(TWCoinTypeElrond, "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); + EXPECT_FALSE(validateAddress(TWCoinTypeElrond, "xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); +} + + } // namespace TW diff --git a/tests/Elrond/AddressTests.cpp b/tests/Elrond/AddressTests.cpp new file mode 100644 index 00000000000..3dd1fb6f166 --- /dev/null +++ b/tests/Elrond/AddressTests.cpp @@ -0,0 +1,79 @@ +// Copyright © 2017-2020 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. + +#include +#include + +#include "HexCoding.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include "Elrond/Address.h" +#include "TestAccounts.h" + +using namespace TW; +using namespace TW::Elrond; + + +TEST(ElrondAddress, Valid) { + ASSERT_TRUE(Address::isValid(ALICE_BECH32)); + ASSERT_TRUE(Address::isValid(BOB_BECH32)); + ASSERT_TRUE(Address::isValid(CAROL_BECH32)); +} + +TEST(ElrondAddress, Invalid) { + ASSERT_FALSE(Address::isValid("")); + ASSERT_FALSE(Address::isValid("foo")); + ASSERT_FALSE(Address::isValid("10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); + ASSERT_FALSE(Address::isValid("xerd10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); + ASSERT_FALSE(Address::isValid("foo10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); + ASSERT_FALSE(Address::isValid(ALICE_PUBKEY_HEX)); +} + +TEST(ElrondAddress, FromString) { + Address alice, bob, carol; + ASSERT_TRUE(Address::decode(ALICE_BECH32, alice)); + ASSERT_TRUE(Address::decode(BOB_BECH32, bob)); + ASSERT_TRUE(Address::decode(CAROL_BECH32, carol)); + + ASSERT_EQ(ALICE_PUBKEY_HEX, hex(alice.getKeyHash())); + ASSERT_EQ(BOB_PUBKEY_HEX, hex(bob.getKeyHash())); + ASSERT_EQ(CAROL_PUBKEY_HEX, hex(carol.getKeyHash())); +} + +TEST(ElrondAddress, FromData) { + const auto alice = Address(parse_hex(ALICE_PUBKEY_HEX)); + const auto bob = Address(parse_hex(BOB_PUBKEY_HEX)); + const auto carol = Address(parse_hex(CAROL_PUBKEY_HEX)); + + ASSERT_EQ(ALICE_BECH32, alice.string()); + ASSERT_EQ(BOB_BECH32, bob.string()); + ASSERT_EQ(CAROL_BECH32, carol.string()); +} + +TEST(ElrondAddress, FromPrivateKey) { + auto aliceKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto alice = Address(aliceKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(ALICE_BECH32, alice.string()); + + auto bobKey = PrivateKey(parse_hex(BOB_SEED_HEX)); + auto bob = Address(bobKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(BOB_BECH32, bob.string()); + + auto carolKey = PrivateKey(parse_hex(CAROL_SEED_HEX)); + auto carol = Address(carolKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(CAROL_BECH32, carol.string()); +} + +TEST(ElrondAddress, FromPublicKey) { + auto alice = PublicKey(parse_hex(ALICE_PUBKEY_HEX), TWPublicKeyTypeED25519); + ASSERT_EQ(ALICE_BECH32, Address(alice).string()); + + auto bob = PublicKey(parse_hex(BOB_PUBKEY_HEX), TWPublicKeyTypeED25519); + ASSERT_EQ(BOB_BECH32, Address(bob).string()); + + auto carol = PublicKey(parse_hex(CAROL_PUBKEY_HEX), TWPublicKeyTypeED25519); + ASSERT_EQ(CAROL_BECH32, Address(carol).string()); +} diff --git a/tests/Elrond/SerializationTests.cpp b/tests/Elrond/SerializationTests.cpp new file mode 100644 index 00000000000..80c048bb9da --- /dev/null +++ b/tests/Elrond/SerializationTests.cpp @@ -0,0 +1,54 @@ +// Copyright © 2017-2020 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. + +#include +#include +#include "boost/format.hpp" + +#include "HexCoding.h" +#include "Elrond/Serialization.h" +#include "TestAccounts.h" + +using namespace TW; +using namespace TW::Elrond; + +TEST(ElrondSerialization, SignableString) { + Proto::TransactionMessage message; + message.set_nonce(42); + message.set_value("43"); + message.set_sender("alice"); + message.set_receiver("bob"); + message.set_data("foobar"); + + string jsonString = serializeTransaction(message); + ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"bob","sender":"alice","gasPrice":0,"gasLimit":0,"data":"Zm9vYmFy"})", jsonString); +} + +TEST(ElrondSerialization, SignableStringWithRealData) { + Proto::TransactionMessage message; + message.set_nonce(15); + message.set_value("100"); + message.set_sender(ALICE_BECH32); + message.set_receiver(BOB_BECH32); + message.set_gas_price(200000000000000); + message.set_gas_limit(500000000); + message.set_data("for dinner"); + + string expected = (boost::format(R"({"nonce":15,"value":"100","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"Zm9yIGRpbm5lcg=="})") % BOB_BECH32 % ALICE_BECH32).str(); + string actual = serializeTransaction(message); + ASSERT_EQ(expected, actual); +} + +TEST(ElrondSerialization, SignableStringWithoutData) { + Proto::TransactionMessage message; + message.set_nonce(42); + message.set_value("43"); + message.set_sender("feed"); + message.set_receiver("abba"); + + string jsonString = serializeTransaction(message); + ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"abba","sender":"feed","gasPrice":0,"gasLimit":0})", jsonString); +} diff --git a/tests/Elrond/SignerTests.cpp b/tests/Elrond/SignerTests.cpp new file mode 100644 index 00000000000..adda6c83a4d --- /dev/null +++ b/tests/Elrond/SignerTests.cpp @@ -0,0 +1,89 @@ +// Copyright © 2017-2020 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. + +#include +#include "boost/format.hpp" + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Elrond/Signer.h" +#include "Elrond/Address.h" +#include "TestAccounts.h" + +using namespace TW; +using namespace TW::Elrond; + + +TEST(ElrondSigner, Sign) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_transaction()->set_nonce(0); + input.mutable_transaction()->set_value("0"); + input.mutable_transaction()->set_sender(ALICE_BECH32); + input.mutable_transaction()->set_receiver(BOB_BECH32); + input.mutable_transaction()->set_gas_price(200000000000000); + input.mutable_transaction()->set_gas_limit(500000000); + input.mutable_transaction()->set_data("foo"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignJSON) { + // Shuffle some fields, assume arbitrary order in the input + auto input = (boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000}})") % BOB_BECH32 % ALICE_BECH32).str(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + + auto encoded = Signer::signJSON(input, privateKey.bytes); + auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignWithoutData) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_transaction()->set_nonce(0); + input.mutable_transaction()->set_value("0"); + input.mutable_transaction()->set_sender(ALICE_BECH32); + input.mutable_transaction()->set_receiver(BOB_BECH32); + input.mutable_transaction()->set_gas_price(200000000000000); + input.mutable_transaction()->set_gas_limit(500000000); + input.mutable_transaction()->set_data(""); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "39ab0e18bfce04bf53c9610faa3b9e7cecfca919510a7631e529e9086279b70a4832df32a5d1b8fdceb4e9082f2995da97f9195532c8d611ee749bc312cbf90c"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(ElrondSigner, SignJSONWithoutData) { + // Shuffle some fields, assume arbitrary order in the input + auto input = (boost::format(R"({"transaction" : {"value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000}})") % BOB_BECH32 % ALICE_BECH32).str(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + + auto encoded = Signer::signJSON(input, privateKey.bytes); + auto expectedSignature = "39ab0e18bfce04bf53c9610faa3b9e7cecfca919510a7631e529e9086279b70a4832df32a5d1b8fdceb4e9082f2995da97f9195532c8d611ee749bc312cbf90c"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedEncoded, encoded); +} diff --git a/tests/Elrond/TWAnySignerTests.cpp b/tests/Elrond/TWAnySignerTests.cpp new file mode 100644 index 00000000000..f9366ab52ba --- /dev/null +++ b/tests/Elrond/TWAnySignerTests.cpp @@ -0,0 +1,55 @@ +// Copyright © 2017-2020 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. + +#include +#include "boost/format.hpp" + +#include +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" +#include "Elrond/Signer.h" +#include "TestAccounts.h" + +using namespace TW; +using namespace TW::Elrond; + + +TEST(TWAnySignerElrond, Sign) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_transaction()->set_nonce(0); + input.mutable_transaction()->set_value("0"); + input.mutable_transaction()->set_sender(ALICE_BECH32); + input.mutable_transaction()->set_receiver(BOB_BECH32); + input.mutable_transaction()->set_gas_price(200000000000000); + input.mutable_transaction()->set_gas_limit(500000000); + input.mutable_transaction()->set_data("foo"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeElrond); + + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_EQ(expectedSignature, signature); + ASSERT_EQ(expectedEncoded, encoded); +} + +TEST(TWAnySignerElrond, SignJSON) { + // Shuffle some fields, assume arbitrary order in the input + auto input = STRING((boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000}})") % BOB_BECH32 % ALICE_BECH32).str().c_str()); + auto privateKey = DATA(ALICE_SEED_HEX); + auto encoded = WRAPS(TWAnySignerSignJSON(input.get(), privateKey.get(), TWCoinTypeElrond)); + auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeElrond)); + assertStringsEqual(encoded, expectedEncoded.c_str()); +} diff --git a/tests/Elrond/TWCoinTypeTests.cpp b/tests/Elrond/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..9e73e615721 --- /dev/null +++ b/tests/Elrond/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 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. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "../interface/TWTestUtilities.h" +#include +#include + + +TEST(TWElrondCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeElrond)); + auto txId = TWStringCreateWithUTF8Bytes("1fc9785cb8bea0129a16cf7bddc97630c176a556ea566f0e72923c882b5cb3c8"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeElrond, txId)); + auto accId = TWStringCreateWithUTF8Bytes("erd12yne790km8ezwetkz7m3hmqy9utdc6vdkgsunfzpwguec6v04p2qtk9uqj"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeElrond, accId)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeElrond)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeElrond)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeElrond), 18); + ASSERT_EQ(TWBlockchainElrondNetwork, TWCoinTypeBlockchain(TWCoinTypeElrond)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeElrond)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeElrond)); + assertStringsEqual(symbol, "ERD"); + assertStringsEqual(txUrl, "https://explorer.elrond.com/transactions/1fc9785cb8bea0129a16cf7bddc97630c176a556ea566f0e72923c882b5cb3c8"); + assertStringsEqual(accUrl, "https://explorer.elrond.com/address/erd12yne790km8ezwetkz7m3hmqy9utdc6vdkgsunfzpwguec6v04p2qtk9uqj"); + assertStringsEqual(id, "elrond"); + assertStringsEqual(name, "Elrond"); +} diff --git a/tests/Elrond/TestAccounts.h b/tests/Elrond/TestAccounts.h new file mode 100644 index 00000000000..55060002abd --- /dev/null +++ b/tests/Elrond/TestAccounts.h @@ -0,0 +1,17 @@ +// Copyright © 2017-2020 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. + +#pragma once + +const auto ALICE_BECH32 = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"; +const auto ALICE_SEED_HEX = "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"; +const auto ALICE_PUBKEY_HEX = "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"; +const auto BOB_BECH32 = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"; +const auto BOB_SEED_HEX = "e3a3a3d1ac40d42d8fd4c569a9749b65a1250dd3d10b6f4e438297662ea4850e"; +const auto BOB_PUBKEY_HEX = "c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36"; +const auto CAROL_BECH32 = "erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0"; +const auto CAROL_SEED_HEX = "a926316cc3daf8ff25ba3e417797e6dfd51f62ae735ab07148234732f7314052"; +const auto CAROL_PUBKEY_HEX = "2cf945faf0162ceede93fe3addda83fe2ebe2041f8a70b2521767044638fa29f"; diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index c35b6935a79..dbc77477d5d 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -119,4 +119,11 @@ TEST(AnyAddress, Data) { auto keyHash = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(keyHash, "172bdf43265c0adfe105a1a8c45b3f406a38362f24"); } + // elrond + { + auto string = STRING("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeElrond)); + auto pubkey = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(pubkey, "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"); + } } diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 62f67695f0e..6b286eee3cb 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -231,6 +231,16 @@ TEST(HDWallet, DeriveAlgorand) { assertHexEqual(privateKeyData, "ce0b7ac644e2b7d9d14d3928b11643f43e48c33d3e328d059fef8add7f070e82"); } +TEST(HDWallet, DeriveElrond) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeElrond)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeElrond, privateKey.get())); + + assertHexEqual(privateKeyData, "0eb593141de897d60a0883320793eb49e63d556ccdf783a87ec014f150d50453"); + assertStringsEqual(address, "erd1a6l7q9cfvrgr80xuzm37tapdr4zm3mwrtl6vt8f45df45x7eadfs8ds5vv"); +} + TEST(HDWallet, ExtendedKeys) { auto words = STRING("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"); auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 11af1a19e50..3b04ad4f8c3 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -28,6 +28,7 @@ TEST(TWHRP, StringForHRP) { ASSERT_STREQ(stringForHRP(TWHRPMonacoin), "mona"); ASSERT_STREQ(stringForHRP(TWHRPKava), "kava"); ASSERT_STREQ(stringForHRP(TWHRPCardano), "addr"); + ASSERT_STREQ(stringForHRP(TWHRPElrond), "erd"); } TEST(TWHRP, HRPForString) { @@ -47,6 +48,7 @@ TEST(TWHRP, HRPForString) { ASSERT_EQ(hrpForString("mona"), TWHRPMonacoin); ASSERT_EQ(hrpForString("kava"), TWHRPKava); ASSERT_EQ(hrpForString("addr"), TWHRPCardano); + ASSERT_EQ(hrpForString("erd"), TWHRPElrond); } TEST(TWHPR, HPRByCoinType) { @@ -65,6 +67,7 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPMonacoin, TWCoinTypeHRP(TWCoinTypeMonacoin)); ASSERT_EQ(TWHRPKava, TWCoinTypeHRP(TWCoinTypeKava)); ASSERT_EQ(TWHRPCardano, TWCoinTypeHRP(TWCoinTypeCardano)); + ASSERT_EQ(TWHRPElrond, TWCoinTypeHRP(TWCoinTypeElrond)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeAion)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeCallisto)); From be696466158cbabab3fee4e56fa973bf6ddd963c Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Thu, 30 Apr 2020 16:00:47 +0800 Subject: [PATCH 14/81] Add TWFIOAccount (#940) * Add TWFIOAccount * Move regex into Actor.cpp, add android test --- android/app/build.gradle | 2 +- .../app/blockchains/fio/TestFIOAddress.kt | 20 +++++++ android/build.gradle | 2 +- include/TrustWalletCore/TWFIOAccount.h | 28 +++++++++ src/Cardano/AddressV3.h | 2 +- src/FIO/Actor.cpp | 60 +++++++++++++++++++ src/FIO/Actor.h | 27 +++++++++ src/FIO/Signer.cpp | 43 +------------ src/FIO/Signer.h | 12 ---- src/FIO/TransactionBuilder.cpp | 1 + src/interface/TWFIOAccount.cpp | 39 ++++++++++++ swift/Tests/Blockchains/FIOTests.swift | 16 +++++ tests/FIO/SignerTests.cpp | 1 + tests/FIO/TWFIOAccountTests.cpp | 46 ++++++++++++++ 14 files changed, 242 insertions(+), 57 deletions(-) create mode 100644 include/TrustWalletCore/TWFIOAccount.h create mode 100644 src/FIO/Actor.cpp create mode 100644 src/FIO/Actor.h create mode 100644 src/interface/TWFIOAccount.cpp create mode 100644 tests/FIO/TWFIOAccountTests.cpp diff --git a/android/app/build.gradle b/android/app/build.gradle index 609fbaf8b18..03b4d26e54b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 28 - buildToolsVersion '28.0.3' + ndkVersion '21.1.6352462' defaultConfig { applicationId "com.trustwallet.core.app" minSdkVersion 23 diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt index cce536ff660..6b6461e78d1 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt @@ -9,6 +9,7 @@ package com.trustwallet.core.app.blockchains.fio import com.trustwallet.core.app.utils.toHex import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test import wallet.core.jni.* @@ -28,4 +29,23 @@ class TestFIOAddress { val addressFromKey = AnyAddress(pubkey, CoinType.FIO) assertEquals(addressFromKey.description(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o") } + + @Test + fun testAccount() { + assertEquals(FIOAccount("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE").description(), "hhq2g4qgycfb") + assertEquals(FIOAccount("hhq2g4qgycfb").description(), "hhq2g4qgycfb") + assertEquals(FIOAccount("rewards@wallet").description(), "rewards@wallet") + + var account: FIOAccount? = null + var account2: FIOAccount? = null + try { + account = FIOAccount("asdf19s") + account2 = FIOAccount("0x320196ef1b137786be51aa391e78e0a2c756f46b") + } catch (ex: Exception) { + print(ex) + } + + assertNull(account) + assertNull(account2) + } } diff --git a/android/build.gradle b/android/build.gradle index a710c7f55ed..b10f80b42be 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:3.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' diff --git a/include/TrustWalletCore/TWFIOAccount.h b/include/TrustWalletCore/TWFIOAccount.h new file mode 100644 index 00000000000..8012cbf0faf --- /dev/null +++ b/include/TrustWalletCore/TWFIOAccount.h @@ -0,0 +1,28 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "TWBase.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a FIO Account name +TW_EXPORT_CLASS +struct TWFIOAccount; + +TW_EXPORT_STATIC_METHOD +struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string); + +TW_EXPORT_METHOD +void TWFIOAccountDelete(struct TWFIOAccount *_Nonnull account); + +/// Returns the short account string representation. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWFIOAccountDescription(struct TWFIOAccount *_Nonnull account); + +TW_EXTERN_C_END diff --git a/src/Cardano/AddressV3.h b/src/Cardano/AddressV3.h index 960750ccee3..2097396523d 100644 --- a/src/Cardano/AddressV3.h +++ b/src/Cardano/AddressV3.h @@ -86,7 +86,7 @@ class AddressV3 { Data data() const; private: - AddressV3() : legacyAddressV2(nullptr), discrimination(Discrim_Production), kind(Kind_Single) {} + AddressV3() : discrimination(Discrim_Production), kind(Kind_Single), legacyAddressV2(nullptr) {} }; inline bool operator==(const AddressV3& lhs, const AddressV3& rhs) { diff --git a/src/FIO/Actor.cpp b/src/FIO/Actor.cpp new file mode 100644 index 00000000000..7502f1605b8 --- /dev/null +++ b/src/FIO/Actor.cpp @@ -0,0 +1,60 @@ +// Copyright © 2017-2020 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. + +#include "Actor.h" + +#include + +using namespace TW::FIO; +using namespace std; + +string Actor::actor(const Address& addr) +{ + uint64_t shortenedKey = shortenKey(addr.bytes); + string name13 = name(shortenedKey); + // trim to 12 chracters + return name13.substr(0, 12); +} + +bool Actor::validate(const std::string& addr) { + regex pattern(R"(\b([a-z1-5]{3,})[.@]?\b)"); + smatch match; + return regex_search(addr, match, pattern); +} + +uint64_t Actor::shortenKey(const array& addrKey) +{ + uint64_t res = 0; + int i = 1; // Ignore key head + int len = 0; + while (len <= 12) { + assert(i < 33); // Means the key has > 20 bytes with trailing zeroes... + + auto trimmed_char = uint64_t(addrKey[i] & (len == 12 ? 0x0f : 0x1f)); + if (trimmed_char == 0) { i++; continue; } // Skip a zero and move to next + + auto shuffle = len == 12 ? 0 : 5 * (12 - len) - 1; + res |= trimmed_char << shuffle; + + len++; i++; + } + return res; +} + +string Actor::name(uint64_t shortKey) { + static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; + + string str(13,'.'); //We are forcing the string to be 13 characters + + uint64_t tmp = shortKey; + for(uint32_t i = 0; i <= 12; i++ ) { + char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)]; + str[12 - i] = c; + tmp >>= (i == 0 ? 4 : 5); + } + + return str; +} diff --git a/src/FIO/Actor.h b/src/FIO/Actor.h new file mode 100644 index 00000000000..2024d756c05 --- /dev/null +++ b/src/FIO/Actor.h @@ -0,0 +1,27 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "Address.h" +#include + +namespace TW::FIO { +/// Helper class for Actor name generation from address +class Actor { + public: + /// Generate the actor name of the address + static std::string actor(const Address& addr); + + /// Check if the address is valid + static bool validate(const std::string& addr); + + /// Used internally, derive shortened uint64 key from adddr bytes + static uint64_t shortenKey(const std::array& addrKey); + /// Used internally, derive name from uint64 shortened key + static std::string name(uint64_t shortKey); +}; +} // namespace TW::FIO diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index 3e09738bb4e..43abd7df8d3 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -6,6 +6,7 @@ #include "Signer.h" #include "Address.h" +#include "Actor.h" #include "../Base58.h" #include "../Hash.h" @@ -61,46 +62,4 @@ int Signer::isCanonical(uint8_t by, uint8_t sig[64]) { && !(sig[32] == 0 && !(sig[33] & 0x80)); } -string Actor::actor(const Address& addr) -{ - uint64_t shortenedKey = shortenKey(addr.bytes); - string name13 = name(shortenedKey); - // trim to 12 chracters - return name13.substr(0, 12); -} - -uint64_t Actor::shortenKey(const array& addrKey) -{ - uint64_t res = 0; - int i = 1; // Ignore key head - int len = 0; - while (len <= 12) { - assert(i < 33); // Means the key has > 20 bytes with trailing zeroes... - - auto trimmed_char = uint64_t(addrKey[i] & (len == 12 ? 0x0f : 0x1f)); - if (trimmed_char == 0) { i++; continue; } // Skip a zero and move to next - - auto shuffle = len == 12 ? 0 : 5 * (12 - len) - 1; - res |= trimmed_char << shuffle; - - len++; i++; - } - return res; -} - -string Actor::name(uint64_t shortKey) { - static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; - - string str(13,'.'); //We are forcing the string to be 13 characters - - uint64_t tmp = shortKey; - for(uint32_t i = 0; i <= 12; i++ ) { - char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)]; - str[12 - i] = c; - tmp >>= (i == 0 ? 4 : 5); - } - - return str; -} - } // namespace TW::FIO diff --git a/src/FIO/Signer.h b/src/FIO/Signer.h index b7595e7ae8e..7604ca4f953 100644 --- a/src/FIO/Signer.h +++ b/src/FIO/Signer.h @@ -38,16 +38,4 @@ class Signer { static int isCanonical(uint8_t by, uint8_t sig[64]); }; -/// Helper class for Actor name generation from address -class Actor { - public: - /// Generate the actor name of the address - static std::string actor(const Address& addr); - - /// Used internally, derive shortened uint64 key from adddr bytes - static uint64_t shortenKey(const std::array& addrKey); - /// Used internally, derive name from uint64 shortened key - static std::string name(uint64_t shortKey); -}; - } // namespace TW::FIO diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index c7ec086af47..71eaa4764ea 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -6,6 +6,7 @@ #include "TransactionBuilder.h" +#include "Actor.h" #include "Encryption.h" #include "NewFundsRequest.h" #include "Signer.h" diff --git a/src/interface/TWFIOAccount.cpp b/src/interface/TWFIOAccount.cpp new file mode 100644 index 00000000000..b600d82b493 --- /dev/null +++ b/src/interface/TWFIOAccount.cpp @@ -0,0 +1,39 @@ +// Copyright © 2017-2020 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. + +#include + +#include "FIO/Actor.h" +#include "FIO/Address.h" + +#include + +using namespace TW; +using namespace TW::FIO; + +struct TWFIOAccount { + std::string description; +}; + +struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string) { + const auto& account = *reinterpret_cast(string); + if (Address::isValid(account)) { + const auto addr = Address(account); + return new TWFIOAccount{Actor::actor(addr)}; + } + if (Actor::validate(account)) { + return new TWFIOAccount{account}; + } + return nullptr; +} + +void TWFIOAccountDelete(struct TWFIOAccount *_Nonnull account) { + delete account; +} + +TWString *_Nonnull TWFIOAccountDescription(struct TWFIOAccount *_Nonnull account) { + return TWStringCreateWithUTF8Bytes(account->description.c_str()); +} diff --git a/swift/Tests/Blockchains/FIOTests.swift b/swift/Tests/Blockchains/FIOTests.swift index 06444088bdb..eb38aebef65 100644 --- a/swift/Tests/Blockchains/FIOTests.swift +++ b/swift/Tests/Blockchains/FIOTests.swift @@ -139,4 +139,20 @@ class FIOTests: XCTestCase { XCTAssertEqual(out.json, expectedJson) } + + func testAccountNames() { + XCTAssertEqual( + FIOAccount(string: "FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE")!.description, "hhq2g4qgycfb" + ) + XCTAssertEqual( + FIOAccount(string: "hhq2g4qgycfb")!.description, "hhq2g4qgycfb" + ) + + XCTAssertEqual( + FIOAccount(string: "rewards@wallet")!.description, "rewards@wallet" + ) + + XCTAssertNil(FIOAccount(string: "asdf19s")) + XCTAssertNil(FIOAccount(string: "0x320196ef1b137786be51aa391e78e0a2c756f46b")) + } } diff --git a/tests/FIO/SignerTests.cpp b/tests/FIO/SignerTests.cpp index ab452f6a544..cdf1277a11b 100644 --- a/tests/FIO/SignerTests.cpp +++ b/tests/FIO/SignerTests.cpp @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "FIO/Actor.h" #include "FIO/Signer.h" #include "HexCoding.h" diff --git a/tests/FIO/TWFIOAccountTests.cpp b/tests/FIO/TWFIOAccountTests.cpp new file mode 100644 index 00000000000..3c4764d4801 --- /dev/null +++ b/tests/FIO/TWFIOAccountTests.cpp @@ -0,0 +1,46 @@ +// Copyright © 2017-2020 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. + +#include "../interface/TWTestUtilities.h" +#include + +#include + +TEST(TWFIO, Account) { + { + auto string = STRING("FIO6XKiobBGrmPZDaHne47TF25CamjFc3cRe8hs7oAiCN59TbJzes"); + auto account = TWFIOAccountCreateWithString(string.get()); + auto description = WRAPS(TWFIOAccountDescription(account)); + assertStringsEqual(description, "rpxtnpsr3gxd"); + TWFIOAccountDelete(account); + } + + { + auto account = TWFIOAccountCreateWithString(STRING("lhp1ytjibtea").get()); + auto account2 = TWFIOAccountCreateWithString(STRING("wrcjejslfplp").get()); + auto account3 = TWFIOAccountCreateWithString(STRING("rewards@wallet").get()); + auto description = WRAPS(TWFIOAccountDescription(account)); + auto description2 = WRAPS(TWFIOAccountDescription(account2)); + auto description3 = WRAPS(TWFIOAccountDescription(account3)); + assertStringsEqual(description, "lhp1ytjibtea"); + assertStringsEqual(description2, "wrcjejslfplp"); + assertStringsEqual(description3, "rewards@wallet"); + + TWFIOAccountDelete(account); + TWFIOAccountDelete(account2); + TWFIOAccountDelete(account3); + } + + { + auto string = STRING("asdf19s"); + auto string2 = STRING("0x320196ef1b137786be51aa391e78e0a2c756f46b"); + auto account = TWFIOAccountCreateWithString(string.get()); + auto account2 = TWFIOAccountCreateWithString(string2.get()); + + ASSERT_EQ(account, nullptr); + ASSERT_EQ(account2, nullptr); + } +} From cb6e57f52abb713f544335ac10160b79293e93a4 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Sat, 2 May 2020 23:22:52 +0200 Subject: [PATCH 15/81] Bitcoin Signing coverage (#939) * Bitcoin Signing coverage: minor SigHashType refactor. * SigHashType, part 2. * Extra tests for Bitcoin signing coverage. * Cleanup. * One extra test for the last uncovered Transaction line :) Co-authored-by: Catenocrypt --- src/Bitcoin/SigHashType.h | 21 +++ src/Bitcoin/SignatureVersion.h | 2 - src/Bitcoin/Transaction.cpp | 15 +- src/Bitcoin/Transaction.h | 1 + src/Bitcoin/TransactionSigner.cpp | 4 +- src/Decred/Signer.cpp | 4 +- src/Decred/Transaction.cpp | 13 +- src/Decred/Transaction.h | 1 + src/Zcash/Transaction.cpp | 7 +- src/interface/TWBitcoin.cpp | 5 +- tests/Bitcoin/TWBitcoinSigningTests.cpp | 239 ++++++++++++++++++++++-- 11 files changed, 269 insertions(+), 43 deletions(-) create mode 100644 src/Bitcoin/SigHashType.h diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h new file mode 100644 index 00000000000..690cfb2ed0b --- /dev/null +++ b/src/Bitcoin/SigHashType.h @@ -0,0 +1,21 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include + +namespace TW::Bitcoin { + +// Defines the number of bits of the hash type which is used to identify which +// outputs are signed. +static const uint8_t SigHashMask = 0x1f; + +inline bool hashTypeIsSingle(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeSingle; } + +inline bool hashTypeIsNone(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeNone; } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SignatureVersion.h b/src/Bitcoin/SignatureVersion.h index 87669836277..fa3363eec3f 100644 --- a/src/Bitcoin/SignatureVersion.h +++ b/src/Bitcoin/SignatureVersion.h @@ -6,8 +6,6 @@ #pragma once -#include - namespace TW::Bitcoin { enum SignatureVersion { BASE, diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index 47d1071a313..a7af607385b 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -6,6 +6,7 @@ #include "SegwitAddress.h" #include "Transaction.h" +#include "SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -34,7 +35,7 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i // Input nSequence (none/all, depending on flags) if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && - !TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + !hashTypeIsSingle(hashType) && !hashTypeIsNone(hashType)) { auto hashSequence = getSequenceHash(); std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); } else { @@ -51,10 +52,10 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i encode32LE(inputs[index].sequence, data); // Outputs (none/one/all, depending on flags) - if (!TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + if (!hashTypeIsSingle(hashType) && !hashTypeIsNone(hashType)) { auto hashOutputs = getOutputsHash(); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); - } else if (TWBitcoinSigHashTypeIsSingle(hashType) && index < outputs.size()) { + } else if (hashTypeIsSingle(hashType) && index < outputs.size()) { auto outputData = std::vector{}; outputs[index].encode(outputData); auto hashOutputs = TW::Hash::hash(hasher, outputData); @@ -164,8 +165,8 @@ std::vector Transaction::getSignatureHashBase(const Script& scriptCode, serializeInput(subindex, scriptCode, index, hashType, data); } - auto hashNone = (hashType & 0x1f) == TWBitcoinSigHashTypeNone; - auto hashSingle = (hashType & 0x1f) == TWBitcoinSigHashTypeSingle; + auto hashNone = hashTypeIsNone(hashType); + auto hashSingle = hashTypeIsSingle(hashType); auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); encodeVarInt(serializedOutputCount, data); for (auto subindex = 0; subindex < serializedOutputCount; subindex += 1) { @@ -205,8 +206,8 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size } // Serialize the nSequence - auto hashNone = (hashType & 0x1f) == TWBitcoinSigHashTypeNone; - auto hashSingle = (hashType & 0x1f) == TWBitcoinSigHashTypeSingle; + auto hashNone = hashTypeIsNone(hashType); + auto hashSingle = hashTypeIsSingle(hashType); if (subindex != index && (hashSingle || hashNone)) { encode32LE(0, data); } else { diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index 19d27b93ddd..0814138b306 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/Transaction.h @@ -6,6 +6,7 @@ #pragma once +#include #include "Script.h" #include "TransactionInput.h" #include "TransactionOutput.h" diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index acf191cf11d..ed96ae1b575 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -9,6 +9,7 @@ #include "TransactionInput.h" #include "TransactionOutput.h" #include "UnspentSelector.h" +#include "SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -25,8 +26,7 @@ Result TransactionSigner::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); - const bool hashSingle = - ((input.hash_type() & ~TWBitcoinSigHashTypeAnyoneCanPay) == TWBitcoinSigHashTypeSingle); + const auto hashSingle = hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < plan.utxos.size(); i += 1) { auto& utxo = plan.utxos[i]; diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index 09ddf587313..ec089551258 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -8,6 +8,7 @@ #include "TransactionInput.h" #include "TransactionOutput.h" +#include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -49,8 +50,7 @@ Result Signer::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); - const bool hashSingle = - ((input.hash_type() & ~TWBitcoinSigHashTypeAnyoneCanPay) == TWBitcoinSigHashTypeSingle); + const auto hashSingle = Bitcoin::hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < txPlan.utxos.size(); i += 1) { auto& utxo = txPlan.utxos[i]; diff --git a/src/Decred/Transaction.cpp b/src/Decred/Transaction.cpp index f1b746f474f..9be21fbe88f 100644 --- a/src/Decred/Transaction.cpp +++ b/src/Decred/Transaction.cpp @@ -6,6 +6,7 @@ #include "Transaction.h" +#include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -23,10 +24,6 @@ static const uint32_t sigHashSerializePrefix = 1; // Indicates the serialization only contains witness data. static const uint32_t sigHashSerializeWitness = 3; -// Defines the number of bits of the hash type which is used to identify which -// outputs are signed. -static const byte sigHashMask = 0x1f; - std::size_t sigHashWitnessSize(const std::vector& inputs, const Bitcoin::Script& signScript); } // namespace @@ -35,7 +32,7 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz enum TWBitcoinSigHashType hashType) const { assert(index < inputs.size()); - if (TWBitcoinSigHashTypeIsSingle(hashType) && index >= outputs.size()) { + if (Bitcoin::hashTypeIsSingle(hashType) && index >= outputs.size()) { throw std::invalid_argument("attempt to sign single input at index " "larger than the number of outputs"); } @@ -48,7 +45,7 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz } auto outputsToSign = outputs; - switch (hashType & sigHashMask) { + switch (hashType & Bitcoin::SigHashMask) { case TWBitcoinSigHashTypeNone: outputsToSign = {}; break; @@ -93,7 +90,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT input.previousOutput.encode(preimage); auto sequence = input.sequence; - if ((TWBitcoinSigHashTypeIsNone(hashType) || TWBitcoinSigHashTypeIsSingle(hashType)) && + if ((Bitcoin::hashTypeIsNone(hashType) || Bitcoin::hashTypeIsSingle(hashType)) && i != signIndex) { sequence = 0; } @@ -106,7 +103,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT auto& output = outputsToSign[i]; auto value = output.value; auto pkScript = output.script; - if (TWBitcoinSigHashTypeIsSingle(hashType) && i != index) { + if (Bitcoin::hashTypeIsSingle(hashType) && i != index) { value = -1; pkScript = {}; } diff --git a/src/Decred/Transaction.h b/src/Decred/Transaction.h index 69742710246..57aa1650ca0 100644 --- a/src/Decred/Transaction.h +++ b/src/Decred/Transaction.h @@ -6,6 +6,7 @@ #pragma once +#include #include "TransactionInput.h" #include "TransactionOutput.h" #include "Bitcoin/Script.h" diff --git a/src/Zcash/Transaction.cpp b/src/Zcash/Transaction.cpp index 29e4b0b153d..f872270d2dc 100644 --- a/src/Zcash/Transaction.cpp +++ b/src/Zcash/Transaction.cpp @@ -6,6 +6,7 @@ #include "Transaction.h" +#include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" #include "../Hash.h" #include "../HexCoding.h" @@ -50,7 +51,7 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e // Input nSequence (none/all, depending on flags) if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && - !TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + !Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { auto hashSequence = getSequenceHash(); std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); } else { @@ -58,10 +59,10 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e } // Outputs (none/one/all, depending on flags) - if (!TWBitcoinSigHashTypeIsSingle(hashType) && !TWBitcoinSigHashTypeIsNone(hashType)) { + if (!Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { auto hashOutputs = getOutputsHash(); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); - } else if (TWBitcoinSigHashTypeIsSingle(hashType) && index < outputs.size()) { + } else if (Bitcoin::hashTypeIsSingle(hashType) && index < outputs.size()) { auto outputData = Data{}; outputs[index].encode(outputData); auto hashOutputs = diff --git a/src/interface/TWBitcoin.cpp b/src/interface/TWBitcoin.cpp index 54359ae3b19..d6cf837ce7e 100644 --- a/src/interface/TWBitcoin.cpp +++ b/src/interface/TWBitcoin.cpp @@ -5,11 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include +#include "../Bitcoin/SigHashType.h" bool TWBitcoinSigHashTypeIsSingle(enum TWBitcoinSigHashType type) { - return (type & 0x1f) == TWBitcoinSigHashTypeSingle; + return TW::Bitcoin::hashTypeIsSingle(type); } bool TWBitcoinSigHashTypeIsNone(enum TWBitcoinSigHashType type) { - return (type & 0x1f) == TWBitcoinSigHashTypeNone; + return TW::Bitcoin::hashTypeIsNone(type); } diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 45137ad48ef..f7b5a08d1b6 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -57,22 +57,8 @@ TEST(BitcoinSigning, EncodeP2WPKH) { } TEST(BitcoinSigning, SignP2WPKH) { - // Build transaction - auto unsignedTx = Transaction(1, 0x11); - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto outpoint0 = OutPoint(hash0, 0); - unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xffffffee); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - auto outpoint1 = OutPoint(hash1, 1); - unsignedTx.inputs.emplace_back(outpoint1, Script(), UINT32_MAX); - - auto outScript0 = Script(parse_hex("76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac")); - unsignedTx.outputs.emplace_back(112'340'000, outScript0); - - auto outScript1 = Script(parse_hex("76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac")); - unsignedTx.outputs.emplace_back(223'450'000, outScript1); // Setup input Proto::SigningInput input; @@ -137,6 +123,136 @@ TEST(BitcoinSigning, SignP2WPKH) { ); } +TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeSingle); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(210'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_amount(210'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "02" + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "49483045022100c723312dccfcc1f3716ae1fc8b045dda97a6f381cadad99a11289b73d7ce89390220261e7a75b8ccef3cddc16ab2f5a3cd7c47626a0c3a6e35f4bcf1b31235c9e73403" "00000000" + "02" + "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "000000000000" + ); +} + +TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(210'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_amount(210'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "02" + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "4847304402206ed3e388d440cb845eef2fce0740b83bdd77764ad0e7dd815a20760718291a5302203f78d743350d80aa2508e90d5a984636c5503d02c1e8656442f0f0275db95baa80" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "4847304402202cb0d71911596b9527b68829689fe600fdd237fa890826f2fbaf61a43d2a945f022038102d595d27e60a75c396388cb5d8c1d0bd341e0040dba56fbad413c3395bf080" "00000000" + "02" + "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "000000000000" + ); +} + TEST(BitcoinSigning, EncodeP2WSH) { auto unsignedTx = Transaction(1, 0); @@ -157,10 +273,9 @@ TEST(BitcoinSigning, EncodeP2WSH) { "00000000"); } -TEST(BitcoinSigning, SignP2WSH) { - // Setup input +Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType) { Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashType); input.set_amount(1000); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); @@ -185,6 +300,12 @@ TEST(BitcoinSigning, SignP2WSH) { utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + return input; +} + +TEST(BitcoinSigning, SignP2WSH) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -210,6 +331,90 @@ TEST(BitcoinSigning, SignP2WSH) { ); } +TEST(BitcoinSigning, SignP2WSH_HashNone) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeNone); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" + // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), "01000000" + "0001" + "01" + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" + "01" + "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "02" + "483045022100caa585732cfc50226a90834a306d23d5d2ab1e94af2c66136a637e3d9bad3688022069028750908e53a663bb1f434fd655bcc0cf8d394c6fa1fd5a4983790135722e02232103596d3451025c" + "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" + ); +} + +TEST(BitcoinSigning, SignP2WSH_HashSingle) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeSingle); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" + // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), "01000000" + "0001" + "01" + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" + "01" + "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "02" + "47304402201ba80b2c48fe82915297dc9782ae2141e40263001fafd21b02c04a092503f01e0220666d6c63475c6c52abd09371c200ac319bcf4a7c72eb3782e95790f5c847f0b903232103596d3451025c" + "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" + ); +} + +TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAnyoneCanPay); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" + // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), "01000000" + "0001" + "01" + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" + "01" + "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "02" + "47304402206fc6f499c9b0080dd444b410ca0599b59321e7891fc8e59ab215f6d2995b2e5f0220182466b434e91d14c9d247d3726d3c7f22a2a1cbf6c172314e1155b307f467b080232103596d3451025c" + "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" + ); +} + TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { auto unsignedTx = Transaction(1, 0x492); From b76214dea46c3fbc68619dfe129329e77e187bf3 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Wed, 6 May 2020 01:20:27 +0800 Subject: [PATCH 16/81] Replace parse_hex with boost::unhex (#944) * replace parse_hex with boost::unhex * Add android test --- .github/workflows/linux-ci.yml | 2 +- .../ethereum/TestEthereumAddress.kt | 23 +++++++++++++ src/HexCoding.cpp | 21 ------------ src/HexCoding.h | 33 +++++-------------- swift/Tests/Blockchains/EthereumTests.swift | 5 +++ tests/HexCodingTests.cpp | 22 +++++++++++++ walletconsole/lib/CMakeLists.txt | 2 +- 7 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt delete mode 100644 src/HexCoding.cpp create mode 100644 tests/HexCodingTests.cpp diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 666c0343f1c..e911669998c 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -32,7 +32,7 @@ jobs: run: | tools/generate-files cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON -DBOOST_ROOT=${BOOST_ROOT_1_72_0} - make -Cbuild + make -Cbuild -j12 build/tests/tests tests --gtest_output=xml env: CC: /usr/bin/clang diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt new file mode 100644 index 00000000000..f4b29c5db5d --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt @@ -0,0 +1,23 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import org.junit.Assert.assertFalse + +class TestEthereumAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumAddresses() { + val any = AnyAddress("0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1", CoinType.ETHEREUM) + assertEquals(any.coin(), CoinType.ETHEREUM) + assertEquals(any.description(), "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1") + + assertFalse(AnyAddress.isValid("0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4", CoinType.ETHEREUM)) + } +} diff --git a/src/HexCoding.cpp b/src/HexCoding.cpp deleted file mode 100644 index 03f43d298b3..00000000000 --- a/src/HexCoding.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2020 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. - -#include "HexCoding.h" - -#include - -std::tuple TW::value(uint8_t c) { - if (c >= '0' && c <= '9') - return std::make_tuple(c - '0', true); - if (c >= 'a' && c <= 'z') - return std::make_tuple(c - 'a' + 10, true); - if (c >= 'A' && c <= 'Z') - return std::make_tuple(c - 'A' + 10, true); - - // Invalid digit - return std::make_tuple(0, false); -} diff --git a/src/HexCoding.h b/src/HexCoding.h index 33f9aecd93d..9f6b0561a4c 100644 --- a/src/HexCoding.h +++ b/src/HexCoding.h @@ -8,6 +8,8 @@ #include "Data.h" +#include + #include #include #include @@ -61,32 +63,13 @@ inline Data parse_hex(const Iter begin, const Iter end) { if (end - begin >= 2 && *begin == '0' && *(begin + 1) == 'x') { it += 2; } - - Data result; - result.reserve(((end - begin) + 1) / 2); - - while (it != end) { - auto high = value(*it); - if (!std::get<1>(high)) { - return {}; - } - it += 1; - - if (it == end) { - result.push_back(std::get<0>(high)); - break; - } - - auto low = value(*it); - if (!std::get<1>(low)) { - return {}; - } - it += 1; - - result.push_back(static_cast((std::get<0>(high) << 4) | std::get<0>(low))); + try { + std::string temp; + boost::algorithm::unhex(it, end, std::back_inserter(temp)); + return Data(temp.begin(), temp.end()); + } catch (...) { + return {}; } - - return result; } /// Parses a string of hexadecimal values. diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index 391315b8b6c..9db6e3b3f23 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -14,6 +14,11 @@ class EthereumTests: XCTestCase { XCTAssertEqual(anyAddress?.description, "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1") XCTAssertEqual(anyAddress?.coin, .ethereum) + + let invalid = "0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4" + XCTAssertNil(Data(hexString: invalid)) + XCTAssertNil(AnyAddress(string: invalid, coin: .ethereum)) + XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .ethereum)) } func testSigner() { diff --git a/tests/HexCodingTests.cpp b/tests/HexCodingTests.cpp new file mode 100644 index 00000000000..c902afc9c7c --- /dev/null +++ b/tests/HexCodingTests.cpp @@ -0,0 +1,22 @@ +// Copyright © 2017-2020 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. + +#include "HexCoding.h" +#include "Data.h" +#include + +namespace TW { + +TEST(HexCoding, validation) { + const std::string valid = "0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1"; + const std::string invalid = "0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4"; + const auto bytes = parse_hex(invalid); + const auto bytes2 = parse_hex(valid); + + ASSERT_TRUE(bytes.empty()); + ASSERT_EQ("0x" + hex(bytes2), valid); +} +} diff --git a/walletconsole/lib/CMakeLists.txt b/walletconsole/lib/CMakeLists.txt index 8e740a6bb4f..b1ac8a1afac 100644 --- a/walletconsole/lib/CMakeLists.txt +++ b/walletconsole/lib/CMakeLists.txt @@ -2,7 +2,7 @@ file(GLOB_RECURSE walletconsolelib_sources *.cpp) add_library(walletconsolelib ${walletconsolelib_sources}) #target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore protobuf Boost::boost) -#target_link_libraries(walletconsolelib TrezorCrypto TrustWalletCore protobuf Boost::boost) +target_link_libraries(walletconsolelib TrezorCrypto TrustWalletCore protobuf Boost::boost) target_include_directories(walletconsolelib PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/src) target_compile_options(walletconsolelib PRIVATE "-Wall") From 39070cf3369ef495165d7364f9f4c5e064311c25 Mon Sep 17 00:00:00 2001 From: h4x3rotab Date: Wed, 6 May 2020 05:08:25 +0800 Subject: [PATCH 17/81] Add Bitcoin Gold implementation (#926) * Add BTG coin definition * Add c++ implementation and tests * Add proto models definition * Remove unused comments * Add TWCoin type BitcoinGold into entry * Remove unused import * add bitcoingold address test in swift test case * add bitcoingold address test in android test case * Fix typo in swift test * revert unknow coin handler in swift * replace magic number 38 with p2pkhPrefix in bitcoingold address test * Remove unnecessary implementation in Bitcoin Gold * Add lockscript&zpub test and generated a tx which already mined by mainnet * Remove unnecessary protobuf file * Add BTG hashType definition into enum TWBitcoinSigHashType * Update tests/BitcoinGold/TWSignerTests.cpp Add mainnet transaction url Co-Authored-By: hewig <360470+hewigovens@users.noreply.github.com> Co-authored-by: Wenfeng Wang Co-authored-by: hewig <360470+hewigovens@users.noreply.github.com> --- .../blockchains/CoinAddressDerivationTests.kt | 1 + coins.json | 28 ++++ docs/coins.md | 1 + .../TrustWalletCore/TWBitcoinSigHashType.h | 1 + include/TrustWalletCore/TWCoinType.h | 1 + src/Bitcoin/Entry.cpp | 2 + src/Bitcoin/Entry.h | 1 + swift/Tests/CoinAddressDerivationTests.swift | 3 + tests/BitcoinGold/TWAddressTests.cpp | 35 +++++ tests/BitcoinGold/TWBitcoinGoldTests.cpp | 125 ++++++++++++++++++ tests/BitcoinGold/TWCoinTypeTests.cpp | 34 +++++ tests/BitcoinGold/TWSegwitAddressTests.cpp | 49 +++++++ tests/BitcoinGold/TWSignerTests.cpp | 85 ++++++++++++ 13 files changed, 366 insertions(+) create mode 100644 tests/BitcoinGold/TWAddressTests.cpp create mode 100644 tests/BitcoinGold/TWBitcoinGoldTests.cpp create mode 100644 tests/BitcoinGold/TWCoinTypeTests.cpp create mode 100644 tests/BitcoinGold/TWSegwitAddressTests.cpp create mode 100644 tests/BitcoinGold/TWSignerTests.cpp 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 18ab0a98320..63fbfbbe7c7 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 @@ -29,6 +29,7 @@ class CoinAddressDerivationTests { BINANCE -> assertEquals("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", address) BITCOIN -> assertEquals("bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d", address) BITCOINCASH -> assertEquals("bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70", address) + BITCOINGOLD -> assertEquals("btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg", address) CALLISTO -> assertEquals("0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04", address) DASH -> assertEquals("XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT", address) DIGIBYTE -> assertEquals("dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu", address) diff --git a/coins.json b/coins.json index 175ef086de3..4c1e7e99ab6 100644 --- a/coins.json +++ b/coins.json @@ -100,6 +100,34 @@ "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, + { + "id": "bitcoingold", + "name": "Bitcoin Gold", + "symbol": "BTG", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/156'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 38, + "p2shPrefix": 23, + "hrp": "btg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://explorer.bitcoingold.org/insight", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://bitcoingold.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, { "id": "callisto", "name": "Callisto", diff --git a/docs/coins.md b/docs/coins.md index c99e9d92157..7457ad39952 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -22,6 +22,7 @@ This list is generated from [./coins.json](../coins.json) | 144 | XRP | XRP | | | | 145 | Bitcoin Cash | BCH | | | | 148 | Stellar | XLM | | | +| 156 | Bitcoin Gold | BTG | | | | 165 | Nano | NANO | | | | 175 | Ravencoin | RVN | | | | 178 | POA Network | POA | | | diff --git a/include/TrustWalletCore/TWBitcoinSigHashType.h b/include/TrustWalletCore/TWBitcoinSigHashType.h index 1ed7d7c0e4c..33fba1992d2 100644 --- a/include/TrustWalletCore/TWBitcoinSigHashType.h +++ b/include/TrustWalletCore/TWBitcoinSigHashType.h @@ -16,6 +16,7 @@ enum TWBitcoinSigHashType { TWBitcoinSigHashTypeNone = 0x02, TWBitcoinSigHashTypeSingle = 0x03, TWBitcoinSigHashTypeFork = 0x40, + TWBitcoinSigHashTypeForkBTG = 0x4f40, TWBitcoinSigHashTypeAnyoneCanPay = 0x80 }; diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index bf70c377ee9..98095785c80 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -27,6 +27,7 @@ enum TWCoinType { TWCoinTypeBinance = 714, TWCoinTypeBitcoin = 0, TWCoinTypeBitcoinCash = 145, + TWCoinTypeBitcoinGold = 156, TWCoinTypeCallisto = 820, TWCoinTypeCardano = 1815, // Note: Cardano Shelley testnet uses purpose 1852 (not 44) 1852/1815 TWCoinTypeCosmos = 118, diff --git a/src/Bitcoin/Entry.cpp b/src/Bitcoin/Entry.cpp index a64de869007..42c39c99cd1 100644 --- a/src/Bitcoin/Entry.cpp +++ b/src/Bitcoin/Entry.cpp @@ -22,6 +22,7 @@ bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2p case TWCoinTypeMonacoin: case TWCoinTypeQtum: case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: return SegwitAddress::isValid(address, hrp) || Address::isValid(address, {{p2pkh}, {p2sh}}); @@ -60,6 +61,7 @@ string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byt case TWCoinTypeDigiByte: case TWCoinTypeLitecoin: case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: return SegwitAddress(publicKey, 0, hrp).string(); case TWCoinTypeBitcoinCash: diff --git a/src/Bitcoin/Entry.h b/src/Bitcoin/Entry.h index 51e0de06ad8..09d5ff27d81 100644 --- a/src/Bitcoin/Entry.h +++ b/src/Bitcoin/Entry.h @@ -18,6 +18,7 @@ class Entry: public CoinEntry { return { TWCoinTypeBitcoin, TWCoinTypeBitcoinCash, + TWCoinTypeBitcoinGold, TWCoinTypeDash, TWCoinTypeDigiByte, TWCoinTypeDogecoin, diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 2a09ac50169..6535122f94f 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -34,6 +34,9 @@ class CoinAddressDerivationTests: XCTestCase { case .bitcoinCash: let expectedResult = "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bitcoinGold: + let expectedResult = "btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg" + AssetCoinDerivation(coin, expectedResult, derivedAddress, address) case .callisto: let expectedResult = "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) diff --git a/tests/BitcoinGold/TWAddressTests.cpp b/tests/BitcoinGold/TWAddressTests.cpp new file mode 100644 index 00000000000..77c192461bb --- /dev/null +++ b/tests/BitcoinGold/TWAddressTests.cpp @@ -0,0 +1,35 @@ +// Copyright © 2017-2020 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. +// + +#include "../interface/TWTestUtilities.h" +#include "Bitcoin/Address.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" +#include "Coin.h" +#include +#include + +using namespace TW; + +const char *PRIVATE_KEY = "CA6D1402199530A5D610A01A53505B6A344CF61B0CCB2902D5AEFBEA63C274BB"; +const char *ADDRESS = "GSGUyooxtCUVBonYV8AANp7FvKy3WTvpMR"; +const char *FAKEADDRESS = "GSGUyooxtCUVBonYV9AANp7FvKy3WTvpMR"; + +TEST(TWBitcoinGoldAddress, Valid) { + ASSERT_TRUE(Bitcoin::Address::isValid(std::string(ADDRESS))); + ASSERT_FALSE(Bitcoin::Address::isValid(std::string(FAKEADDRESS))); +} + +TEST(TWBitcoinGoldAddress, PubkeyToAddress) { + const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + /// construct with public key + auto address = Bitcoin::Address(PublicKey(publicKey), p2pkhPrefix(TWCoinTypeBitcoinGold)); + ASSERT_EQ(address.string(), ADDRESS); +} diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp new file mode 100644 index 00000000000..1d5c2bf84f9 --- /dev/null +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -0,0 +1,125 @@ +// Copyright © 2017-2020 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. + +#include +#include +#include +#include +#include +#include + +#include "Bitcoin/SegwitAddress.h" +#include "proto/Bitcoin.pb.h" +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWBitcoinGoldScript, LockScriptTest) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("btg1q6572ulr0kmywle8a30lvagm9xsg9k9n5cmzfdj").get(), TWCoinTypeBitcoinGold)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07").get(), TWCoinTypeBitcoinGold)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "0014ebae10950c8a35a506e0e265b928305233e802ab"); +} + +TEST(BitcoinGoldKey, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("shoot island position soft burden budget tooth cruel issue economy destroy above").get(), + STRING("TREZOR").get() + )); + + // .bip84 + auto zprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoinGold, TWHDVersionZPRV)); + auto zpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoinGold, TWHDVersionZPUB)); + + assertStringsEqual(zprv, "zprvAdB7dYnT955ubXEkdBWhDqFSyeDfVpKQmVJPbRXGiAg4mnGT7dCBsZZFeik1mNt6bS4zkdZSNtZm8dqu1Lrp1brQ16NgYgeEoiz6ftUfVAW"); + assertStringsEqual(zpub, "zpub6rAU34KLySeCp1KDjD3hayCBXg49uH3G8iDzPovtGWD3eabbfAWSRMsjVyfuRfCCquiKTD6YV42nHUBtwh2TbVPvWqxrGuyEvHN17c3XUXw"); +} + +TEST(BitcoinGoldKey, DeriveFromZPub) { + auto zpub = STRING("zpub6rAU34KLySeCp1KDjD3hayCBXg49uH3G8iDzPovtGWD3eabbfAWSRMsjVyfuRfCCquiKTD6YV42nHUBtwh2TbVPvWqxrGuyEvHN17c3XUXw"); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/156'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/156'/0'/0/9").get()); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2, TWCoinTypeBitcoinGold)); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + + auto address9 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey9, TWCoinTypeBitcoinGold)); + auto address9String = WRAPS(TWAnyAddressDescription(address9.get())); + + assertStringsEqual(address2String, "btg1qkdgxykht6nww9l9rn0xhslf78nl605gwka9zak"); + assertStringsEqual(address9String, "btg1qzw2ptuyaw023gm7te2r5e3xkufn9wrm3kzrg8t"); +} + +TEST(TWBitcoinGoldTxGeneration, TxGeneration) { + const int64_t amount = 5000; + + // Setup input + Proto::SigningInput input; + input.set_coin_type(TWCoinTypeBitcoinGold); + input.set_hash_type(TWBitcoinSigHashTypeAll | TWBitcoinSigHashTypeForkBTG); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("btg1qmd6x5awe4t5fjhgntv0pngzdwajjg250wxdcs0"); + input.set_change_address("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07"); + + auto utxoKey0 = parse_hex("cbe13a79b82ec7f8871b336a64fd8d531f598e7c9022e29c67e824cfd54af57f"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + + auto scriptPub1 = Script(parse_hex("0014db746a75d9aae8995d135b1e19a04d7765242a8f")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(10000); + + auto hash0 = parse_hex("5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(0xfffffffd); + + // Sign + auto txSinger = TransactionSigner(std::move(input)); + txSinger.transaction.lockTime = 0x00098971; + auto result = txSinger.sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "01" + "5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f" "00000000" "00" "fdffffff" + "02" + "8813000000000000" "160014db746a75d9aae8995d135b1e19a04d7765242a8f" + "a612000000000000" "160014ebae10950c8a35a506e0e265b928305233e802ab" + "02" + "483045022100bf1dcc37c2d3794e216b0b1cfcb04c7f49ef360ae941e46dc9b168f54f5447fe02205a0912bf3a3c0ac0e490c665bcde5239f553c013b2447a6fb5df6387ac029c8c41" + "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" + "71890900" + ); +} + diff --git a/tests/BitcoinGold/TWCoinTypeTests.cpp b/tests/BitcoinGold/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ddbad39b6ec --- /dev/null +++ b/tests/BitcoinGold/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 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. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "../interface/TWTestUtilities.h" +#include +#include + + +TEST(TWBitcoinGoldCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinGold)); + auto txId = TWStringCreateWithUTF8Bytes("2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinGold, txId)); + auto accId = TWStringCreateWithUTF8Bytes("GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinGold, accId)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinGold)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinGold)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinGold), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoinGold)); + ASSERT_EQ(23, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinGold)); + ASSERT_EQ(0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinGold)); + assertStringsEqual(symbol, "BTG"); + assertStringsEqual(txUrl, "https://explorer.bitcoingold.org/insight/tx/2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23"); + assertStringsEqual(accUrl, "https://explorer.bitcoingold.org/insight/address/GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U"); + assertStringsEqual(id, "bitcoingold"); + assertStringsEqual(name, "Bitcoin Gold"); +} diff --git a/tests/BitcoinGold/TWSegwitAddressTests.cpp b/tests/BitcoinGold/TWSegwitAddressTests.cpp new file mode 100644 index 00000000000..9d2f4965807 --- /dev/null +++ b/tests/BitcoinGold/TWSegwitAddressTests.cpp @@ -0,0 +1,49 @@ +// Copyright © 2017-2020 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. +// + +#include "../interface/TWTestUtilities.h" +#include "Bitcoin/SegwitAddress.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" +#include +#include + +using namespace TW; + +TEST(TWBitcoinGoldSegwitAddress, Valid) { + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk")); + ASSERT_FALSE(Bitcoin::SegwitAddress::isValid("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sl")); +} + +/// Initializes a Bech32 address with a human-readable part, a witness +/// version, and a witness program. +TEST(TWBitcoinGoldSegwitAddress, WitnessProgramToAddress) { + auto address = Bitcoin::SegwitAddress("btg", 0, parse_hex("5e6132a9ad21f7423081441ab4ae229501f6c8a8")); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); +} + +/// Initializes a Bech32 address with a public key and a HRP prefix. +TEST(TWBitcoinGoldSegwitAddress, PubkeyToAddress) { + const auto publicKey = PublicKey(parse_hex("02f74712b5d765a73b52a14c1e113f2ef3f9502d09d5987ee40f53828cfe68b9a6"), TWPublicKeyTypeSECP256k1); + + /// construct with public key + auto address = Bitcoin::SegwitAddress(publicKey, 0, "btg"); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); +} + +/// Decodes a SegWit address. +TEST(TWBitcoinGoldSegwitAddress, Decode) { + std::pair result = Bitcoin::SegwitAddress::decode("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); + + ASSERT_TRUE(result.second); + ASSERT_EQ(result.first.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); +} diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/BitcoinGold/TWSignerTests.cpp new file mode 100644 index 00000000000..5e91212b883 --- /dev/null +++ b/tests/BitcoinGold/TWSignerTests.cpp @@ -0,0 +1,85 @@ +// Copyright © 2017-2020 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. + +#include +#include +#include +#include + +#include "Bitcoin/SegwitAddress.h" +#include "proto/Bitcoin.pb.h" +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWBitcoinGoldSigner, SignTransaction) { + const int64_t amount = 10000; + + // Setup input + Proto::SigningInput input; + input.set_coin_type(TWCoinTypeBitcoinGold); + input.set_hash_type(TWBitcoinSigHashTypeAll | TWBitcoinSigHashTypeForkBTG); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("btg1qmd6x5awe4t5fjhgntv0pngzdwajjg250wxdcs0"); + input.set_change_address("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07"); + + auto utxoKey0 = parse_hex("cbe13a79b82ec7f8871b336a64fd8d531f598e7c9022e29c67e824cfd54af57f"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + + auto scriptPub1 = Script(parse_hex("0014db746a75d9aae8995d135b1e19a04d7765242a8f")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(99000); + + auto hash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(0xfffffffd); + + // Sign + auto txSinger = TransactionSigner(std::move(input)); + txSinger.transaction.lockTime = 0x00098971; + auto result = txSinger.sign(); + + ASSERT_TRUE(result) << result.error();; + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + // BitcoinGold Mainnet: https://btg2.trezor.io/tx/3e818ad25d73123b6c1c8099ed462aa5413a4ef57d66d9d260306c012753ba43 + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "01" + "1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03" "01000000" "00" "fdffffff" + "02" + "1027000000000000" "160014db746a75d9aae8995d135b1e19a04d7765242a8f" + "c65a010000000000" "160014ebae10950c8a35a506e0e265b928305233e802ab" + "02" + "473044022029b81a6b8f57f76aaf510d8a222ca835bd806936e329aead433f120007d6847002203afa611ff7823ec2a6770359901b0cacf56527cbf947b226ed86b61811545e2b41" + "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" + "71890900" + ); +} + From b61a11453d3476e17634f62d645cc2603fd153df Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Thu, 7 May 2020 16:00:04 +0800 Subject: [PATCH 18/81] Speed up bootstrap.sh, pretty iOS test (#948) * speed up bootstrap.sh, pretty iOS test * run TrezorCryptoTests and add -j12 to other make command --- .github/workflows/linux-ci.yml | 5 ++++- bootstrap.sh | 2 +- docker/wallet-core/Dockerfile | 2 +- tools/install-dependencies | 2 +- tools/ios-test | 2 +- tools/tests | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index e911669998c..d199fd14617 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -32,11 +32,14 @@ jobs: run: | tools/generate-files cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON -DBOOST_ROOT=${BOOST_ROOT_1_72_0} - make -Cbuild -j12 + make -Cbuild -j12 tests TrezorCryptoTests + + build/trezor-crypto/tests/TrezorCryptoTests build/tests/tests tests --gtest_output=xml env: CC: /usr/bin/clang CXX: /usr/bin/clang++ + CK_TIMEOUT_MULTIPLIER: 4 - name: Gather code coverage run: | sudo rm -rf coverage.info diff --git a/bootstrap.sh b/bootstrap.sh index e5ddf642a52..f8898e319c4 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -11,7 +11,7 @@ tools/generate-files echo "#### Building... ####" cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -make -Cbuild tests TrezorCryptoTests +make -Cbuild -j12 tests TrezorCryptoTests if [ -x "$(command -v clang-tidy)" ]; then echo "#### Linting... ####" diff --git a/docker/wallet-core/Dockerfile b/docker/wallet-core/Dockerfile index 84112998ae2..61fef366e8e 100644 --- a/docker/wallet-core/Dockerfile +++ b/docker/wallet-core/Dockerfile @@ -80,6 +80,6 @@ RUN ruby --version \ RUN cd /wallet-core \ && ./tools/generate-files \ && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ - && make -Cbuild + && make -Cbuild -j12 CMD ["/bin/bash"] diff --git a/tools/install-dependencies b/tools/install-dependencies index 80919b45cdd..9fe3d19c611 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -96,7 +96,7 @@ fi # Protobuf plugins cd "$ROOT/protobuf-plugin" cmake -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX -make -Cbuild +make -Cbuild -j12 make -Cbuild install cd "$ROOT" diff --git a/tools/ios-test b/tools/ios-test index 8c68a4b70f9..861623c0267 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -7,5 +7,5 @@ set -e pushd swift xcodegen pod install -xcrun xcodebuild -workspace TrustWalletCore.xcworkspace -scheme TrustWalletCore -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11' test +xcrun xcodebuild -workspace TrustWalletCore.xcworkspace -scheme TrustWalletCore -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11' test | xcpretty popd diff --git a/tools/tests b/tools/tests index 1662d1c3e47..cff83bf35fb 100755 --- a/tools/tests +++ b/tools/tests @@ -5,7 +5,7 @@ set -e cmake -H. -Bbuild -make -Cbuild tests TrezorCryptoTests +make -Cbuild -j12 tests TrezorCryptoTests export CK_TIMEOUT_MULTIPLIER=4 build/trezor-crypto/tests/TrezorCryptoTests From 870a192cc85a53d39eca0820b4bbffebefad02a6 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Fri, 8 May 2020 22:17:14 +0200 Subject: [PATCH 19/81] Extra Decred signer tests and fixes (#949) * Extra P2SH signer test. * Revert "Extra P2SH signer test." * Extra P2SH signer test. * Minor fixes. * Bitcoin test fix. * Extra Decred tests, with no plan, with no UTXOs. * Extra Bitcoin signing test, with no UTXOs. * Formatting. * Extra Bitcoin signing test, P2PKH. * Minor Bitcoin signing test fix. * Coverage for Bitcoin pushAll, merge with Decred. Co-authored-by: Catenocrypt --- src/Bitcoin/TransactionSigner.cpp | 3 + src/Bitcoin/TransactionSigner.h | 4 +- src/Decred/Signer.cpp | 30 +-- src/Decred/Signer.h | 1 - src/Decred/TransactionBuilder.h | 1 - tests/Aion/TWAnySignerTests.cpp | 2 +- tests/Bitcoin/BitcoinScriptTests.cpp | 47 +++++ tests/Bitcoin/TWBitcoinSigningTests.cpp | 135 ++++++++++-- tests/BitcoinGold/TWBitcoinGoldTests.cpp | 2 +- tests/BitcoinGold/TWSignerTests.cpp | 2 +- tests/Decred/SignerTests.cpp | 258 ++++++++++++++++++++++- 11 files changed, 430 insertions(+), 55 deletions(-) diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index ed96ae1b575..b1489d616fd 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -26,6 +26,9 @@ Result TransactionSigner::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); + if (plan.utxos.size() == 0) { + return Result::failure("Plan without UTXOs"); + } const auto hashSingle = hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < plan.utxos.size(); i += 1) { auto& utxo = plan.utxos[i]; diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index 972bca1eb46..f6f3f292eb3 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -61,13 +61,15 @@ class TransactionSigner { /// \returns the signed transaction or an error. Result sign(); + // internal, public for testability and Decred + static Data pushAll(const std::vector& results); + private: Result sign(Script script, size_t index, const Proto::UnspentTransaction& utxo); Result> signStep(Script script, size_t index, const Proto::UnspentTransaction& utxo, uint32_t version); Data createSignature(const Transaction& transaction, const Script& script, const Data& key, size_t index, Amount amount, uint32_t version); - Data pushAll(const std::vector& results); /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index ec089551258..366bf43844f 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -9,6 +9,7 @@ #include "TransactionInput.h" #include "TransactionOutput.h" #include "../Bitcoin/SigHashType.h" +#include "../Bitcoin/TransactionSigner.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -50,6 +51,9 @@ Result Signer::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); + if (txPlan.utxos.size() == 0) { + return Result::failure("Plan without UTXOs"); + } const auto hashSingle = Bitcoin::hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < txPlan.utxos.size(); i += 1) { auto& utxo = txPlan.utxos[i]; @@ -96,7 +100,7 @@ Result Signer::sign(Bitcoin::Script script, size_t index) { results.push_back(redeemScript.bytes); } - return Result::success(Bitcoin::Script(pushAll(results))); + return Result::success(Bitcoin::Script(Bitcoin::TransactionSigner::pushAll(results))); } Result> Signer::signStep(Bitcoin::Script script, size_t index) { @@ -182,30 +186,6 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri return signature; } -Data Signer::pushAll(const std::vector& results) { - auto data = Data{}; - for (auto& result : results) { - if (result.empty()) { - data.push_back(OP_0); - } else if (result.size() == 1 && result[0] >= 1 && result[0] <= 16) { - data.push_back(Bitcoin::Script::encodeNumber(result[0])); - } else if (result.size() < OP_PUSHDATA1) { - data.push_back(static_cast(result.size())); - } else if (result.size() <= 0xff) { - data.push_back(OP_PUSHDATA1); - data.push_back(static_cast(result.size())); - } else if (result.size() <= 0xffff) { - data.push_back(OP_PUSHDATA2); - encode16LE(static_cast(result.size()), data); - } else { - data.push_back(OP_PUSHDATA4); - encode32LE(static_cast(result.size()), data); - } - std::copy(begin(result), end(result), back_inserter(data)); - } - return data; -} - Data Signer::keyForPublicKeyHash(const Data& hash) const { for (auto& key : input.private_key()) { auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); diff --git a/src/Decred/Signer.h b/src/Decred/Signer.h index 4d01bf512c7..a6ee7da31b1 100644 --- a/src/Decred/Signer.h +++ b/src/Decred/Signer.h @@ -76,7 +76,6 @@ class Signer { Result> signStep(Bitcoin::Script script, size_t index); Data createSignature(const Transaction& transaction, const Bitcoin::Script& script, const Data& key, size_t index); - Data pushAll(const std::vector& results); /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index 3804d380049..e294177e4ab 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -48,7 +48,6 @@ struct TransactionBuilder { auto input = TransactionInput(); input.previousOutput = utxo.out_point(); input.sequence = utxo.out_point().sequence(); - input.sequence = utxo.out_point().sequence(); tx.inputs.push_back(std::move(input)); } diff --git a/tests/Aion/TWAnySignerTests.cpp b/tests/Aion/TWAnySignerTests.cpp index 9ba8cc58fac..5abc8d729cd 100644 --- a/tests/Aion/TWAnySignerTests.cpp +++ b/tests/Aion/TWAnySignerTests.cpp @@ -25,7 +25,7 @@ TEST(TWAnySignerAion, Sign) { auto gasPrice = store(uint256_t(20000000000)); input.set_gas_price(gasPrice.data(), gasPrice.size()); auto gasLimit = store(uint256_t(21000)); - input.set_gas_limit(gasLimit.data(), gasLimit.size());; + input.set_gas_limit(gasLimit.data(), gasLimit.size()); auto nonce = store(uint256_t(9)); input.set_nonce(nonce.data(), nonce.size()); input.set_private_key(privateKey.data(), privateKey.size()); diff --git a/tests/Bitcoin/BitcoinScriptTests.cpp b/tests/Bitcoin/BitcoinScriptTests.cpp index c54505e9894..4942cc948ff 100644 --- a/tests/Bitcoin/BitcoinScriptTests.cpp +++ b/tests/Bitcoin/BitcoinScriptTests.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Bitcoin/Script.h" +#include "Bitcoin/TransactionSigner.h" #include "../interface/TWTestUtilities.h" #include "HexCoding.h" @@ -283,3 +284,49 @@ TEST(BitcoinScript, MatchMultiSig) { EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); } + +TEST(BitcoinTransactionSigner, PushAllEmpty) { + { + std::vector input = {}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), ""); + } + { + std::vector input = {parse_hex("")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "00"); + } + { + std::vector input = {parse_hex("09")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "59" "09"); + } + { + std::vector input = {parse_hex("00010203040506070809")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "0a" "00010203040506070809"); + } + { + std::vector input = {parse_hex("0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "4c50" "0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809"); + } + { + // 2-byte len + Data in1 = Data(256 + 10); + Data expected = parse_hex("4d" "0a01"); + TW::append(expected, in1); + std::vector input = {in1}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), hex(expected)); + } + { + // 4-byte len + Data in1 = Data(65536 + 256 + 10); + Data expected = parse_hex("4e" "0a010100"); + TW::append(expected, in1); + std::vector input = {in1}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), hex(expected)); + } +} \ No newline at end of file diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index f7b5a08d1b6..b3d7e447c32 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -25,8 +25,68 @@ using namespace TW; using namespace TW::Bitcoin; +TEST(BitcoinSigning, SignP2PKH) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + ASSERT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash); + Data scriptHash; + utxo0Script.matchPayToPublicKeyHash(scriptHash); + ASSERT_EQ(hex(scriptHash), "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + + auto utxo0 = input.add_utxo(); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(625'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); + utxo1->set_amount(600'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "01" + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "6a47304402202819d70d4bec472113a1392cadc0860a7a1b34ea0869abb4bdce3290c3aba086022023eff75f410ad19cdbe6c6a017362bd554ce5fb906c13534ddc306be117ad30a012103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ffffffff" + "02" + "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "0000000000" + ); +} + TEST(BitcoinSigning, EncodeP2WPKH) { - auto emptyScript = WRAP(TWBitcoinScript, TWBitcoinScriptCreate()); auto unsignedTx = Transaction(1, 0x11); auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); @@ -94,16 +154,16 @@ TEST(BitcoinSigning, SignP2WPKH) { auto utxo1 = input.add_utxo(); auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(600'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "03b30d55430f08365d19a62d3bd32e459ab50984fbcf22921ecc85f1e09dc6ed" @@ -161,16 +221,16 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { auto utxo1 = input.add_utxo(); auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(210'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; @@ -180,11 +240,13 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { "0001" "02" "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "49483045022100c723312dccfcc1f3716ae1fc8b045dda97a6f381cadad99a11289b73d7ce89390220261e7a75b8ccef3cddc16ab2f5a3cd7c47626a0c3a6e35f4bcf1b31235c9e73403" "00000000" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "0100000000" "ffffffff" "02" "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "000000000000" + "0002" + "47304402206b91d2c69022a54652731b4302eabe59c87949cf62f4c5674c7d4c0d1fbf898102200cee8eeb6ef9542426788c06ed51004799b730083ae3d4daf3c3d5fdc2275d1d0321025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" ); } @@ -226,16 +288,16 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { auto utxo1 = input.add_utxo(); auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(210'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; @@ -245,11 +307,13 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { "0001" "02" "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "4847304402206ed3e388d440cb845eef2fce0740b83bdd77764ad0e7dd815a20760718291a5302203f78d743350d80aa2508e90d5a984636c5503d02c1e8656442f0f0275db95baa80" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "4847304402202cb0d71911596b9527b68829689fe600fdd237fa890826f2fbaf61a43d2a945f022038102d595d27e60a75c396388cb5d8c1d0bd341e0040dba56fbad413c3395bf080" "00000000" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "ffffffff" "02" "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "000000000000" + "0002" + "483045022100a5eedab7da09317141e35730256ef9b76da0c2442995a1c2b5458ee7d8834ba302201dc10b47cd4e2e53c7253770cd6907c94c828317d217e3065db009345acf41ac8021025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" ); } @@ -296,8 +360,8 @@ Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType) { auto p2wsh = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); utxo0->set_script(p2wsh.bytes.data(), p2wsh.bytes.size()); utxo0->set_amount(1226); - auto hash0 = DATA("0001000000000000000000000000000000000000000000000000000000000000"); - utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + auto hash0 = parse_hex("0001000000000000000000000000000000000000000000000000000000000000"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); return input; @@ -310,7 +374,7 @@ TEST(BitcoinSigning, SignP2WSH) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -338,7 +402,7 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -366,7 +430,7 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -394,7 +458,7 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -569,7 +633,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { signer.transaction = unsignedTx; signer.plan.utxos = {*utxo}; auto result = signer.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); auto expected = "" @@ -602,3 +666,32 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { signedTx.encode(true, serialized); ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); } + +TEST(BitcoinSigning, Sign_NegativeNoUtxos) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + // Fails as there are 0 utxos + ASSERT_FALSE(result) << result.error(); +} diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp index 1d5c2bf84f9..0c209135b7a 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -103,7 +103,7 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { txSinger.transaction.lockTime = 0x00098971; auto result = txSinger.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/BitcoinGold/TWSignerTests.cpp index 5e91212b883..9a5c587c66c 100644 --- a/tests/BitcoinGold/TWSignerTests.cpp +++ b/tests/BitcoinGold/TWSignerTests.cpp @@ -62,7 +62,7 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { txSinger.transaction.lockTime = 0x00098971; auto result = txSinger.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; diff --git a/tests/Decred/SignerTests.cpp b/tests/Decred/SignerTests.cpp index f2889377aab..a49067965ac 100644 --- a/tests/Decred/SignerTests.cpp +++ b/tests/Decred/SignerTests.cpp @@ -6,7 +6,6 @@ #include "Decred/Address.h" #include "Decred/Signer.h" -#include "proto/Bitcoin.pb.h" #include "proto/Decred.pb.h" #include "Hash.h" @@ -19,7 +18,7 @@ using namespace TW; using namespace TW::Decred; -TEST(DecredSigner, Sign) { +TEST(DecredSigner, SignP2PKH) { const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -62,7 +61,7 @@ TEST(DecredSigner, Sign) { auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); utxo0->set_amount(100'000'000); - utxo0->mutable_out_point()->set_hash(originTx.hash().data(), 32); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); utxo0->mutable_out_point()->set_index(0); @@ -114,3 +113,256 @@ TEST(DecredSigner, Sign) { result.payload().encode(encoded); EXPECT_EQ(hex(encoded), expectedEncoded); } + +TEST(DecredSigner, SignP2SH) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToScriptHash(scriptHash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(100'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + + // Sign + auto signer = Signer(std::move(input)); + signer.transaction = redeemTx; + signer.txPlan.utxos.push_back(*utxo0); + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "9e47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + +TEST(DecredSigner, SignNegativeNoUtxo) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + // Sign + auto signer = Signer(std::move(input)); + signer.transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + // Fails as there are 0 utxos + ASSERT_FALSE(result); +} + +TEST(DecredSigner, SignP2PKH_Noplan) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 150'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(150'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + + // Sign + auto signer = Signer(std::move(input)); + signer.transaction = redeemTx; + //signer.txPlan.utxos.push_back(*utxo0); + //signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + std::cerr << "result.payload().inputs " << result.payload().inputs.size() << "\n"; + ASSERT_TRUE(result.payload().inputs.size() >= 1); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" + "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "6a47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} From 6bf65b4c7a6b7b77fae2ce77990a201a62cdb516 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Thu, 14 May 2020 00:53:14 +0200 Subject: [PATCH 20/81] Bitcoin&Decred signing: Fix for assert bug for invalid address. (#952) * Bitcoin&Decred signing: Fix for assert bug for invalid address. * An extra test for covering case of error in signStep(). * Revert one extra check that is not really needed. * Revert one extra check that is not really needed. * Add comment to test, to re-trigger build. Co-authored-by: Catenocrypt --- src/Bitcoin/TransactionSigner.cpp | 14 ++++- src/Decred/Signer.cpp | 6 ++ tests/Bitcoin/TWBitcoinSigningTests.cpp | 81 ++++++++++++++++++++----- tests/Decred/SignerTests.cpp | 50 +++++++++++++++ 4 files changed, 134 insertions(+), 17 deletions(-) diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index b1489d616fd..02f9541c6c5 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -22,6 +22,10 @@ using namespace TW::Bitcoin; template Result TransactionSigner::sign() { + if (transaction.inputs.size() == 0 || plan.utxos.size() == 0) { + return Result::failure("Missing inputs or UTXOs"); + } + signedInputs.clear(); std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); @@ -38,9 +42,11 @@ Result TransactionSigner::sign() { continue; } auto script = Script(utxo.script().begin(), utxo.script().end()); - auto result = sign(script, i, utxo); - if (!result) { - return Result::failure(result.error()); + if (i < transaction.inputs.size()) { + auto result = sign(script, i, utxo); + if (!result) { + return Result::failure(result.error()); + } } } @@ -53,6 +59,8 @@ Result TransactionSigner::sign() { template Result TransactionSigner::sign(Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo) { + assert(index < transaction.inputs.size()); + Script redeemScript; std::vector results; std::vector witnessStack; diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index 366bf43844f..0eb4e9b2a2e 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -47,6 +47,10 @@ Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noe } Result Signer::sign() { + if (txPlan.utxos.size() == 0 || transaction.inputs.size() == 0) { + return Result::failure("Missing inputs or UTXOs"); + } + signedInputs.clear(); std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); @@ -77,6 +81,8 @@ Result Signer::sign() { } Result Signer::sign(Bitcoin::Script script, size_t index) { + assert(index < transaction.inputs.size()); + Bitcoin::Script redeemScript; std::vector results; diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index b3d7e447c32..f718c0d626b 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -337,7 +337,7 @@ TEST(BitcoinSigning, EncodeP2WSH) { "00000000"); } -Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType) { +Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false) { Proto::SigningInput input; input.set_hash_type(hashType); input.set_amount(1000); @@ -351,10 +351,12 @@ Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType) { auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); input.add_private_key(utxoKey1.data(), utxoKey1.size()); - auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); - auto scriptHash = "593128f9f90e38b706c18623151e37d2da05c229"; - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHash] = scriptString; + if (!omitScript) { + auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); + auto scriptHash = "593128f9f90e38b706c18623151e37d2da05c229"; + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHash] = scriptString; + } auto utxo0 = input.add_utxo(); auto p2wsh = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); @@ -405,9 +407,6 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" - // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" - Data serialized; signedTx.encode(true, serialized); ASSERT_EQ(hex(serialized), "01000000" @@ -433,9 +432,6 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" - // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" - Data serialized; signedTx.encode(true, serialized); ASSERT_EQ(hex(serialized), "01000000" @@ -461,9 +457,6 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" - // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" - Data serialized; signedTx.encode(true, serialized); ASSERT_EQ(hex(serialized), "01000000" @@ -479,6 +472,16 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { ); } +TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { + // Setup input, with omitted script + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, true); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_FALSE(result) << result.error(); +} + TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { auto unsignedTx = Transaction(1, 0x492); @@ -695,3 +698,53 @@ TEST(BitcoinSigning, Sign_NegativeNoUtxos) { // Fails as there are 0 utxos ASSERT_FALSE(result) << result.error(); } + +TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("THIS-IS-NOT-A-BITCOIN-ADDRESS"); + input.set_change_address("THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"); + + auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(625'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); + utxo1->set_amount(600'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_FALSE(result) << result.error(); +} diff --git a/tests/Decred/SignerTests.cpp b/tests/Decred/SignerTests.cpp index a49067965ac..2614662dd5e 100644 --- a/tests/Decred/SignerTests.cpp +++ b/tests/Decred/SignerTests.cpp @@ -366,3 +366,53 @@ TEST(DecredSigner, SignP2PKH_Noplan) { result.payload().encode(encoded); EXPECT_EQ(hex(encoded), expectedEncoded); } + +TEST(DecredSigning, SignP2WPKH_NegativeAddressWrongType) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto scriptPub1 = Bitcoin::Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(625'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); + utxo1->set_amount(600'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = Signer(std::move(input)).sign(); + + ASSERT_FALSE(result) << result.error(); +} From 25467e1ef8e2f7f5899bd0705b5788fb3bfaa5f9 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Thu, 14 May 2020 16:41:33 +0800 Subject: [PATCH 21/81] Update Android Gradle to 6.4.0 (#956) * update gradle to 6.4.0 --- android/build.gradle | 4 +- android/gradle.properties | 10 +- android/gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 58910 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- android/gradlew | 53 +++-- android/gradlew.bat | 188 ++++++++++-------- 6 files changed, 146 insertions(+), 113 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b10f80b42be..4f90d08d99d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,8 +10,8 @@ buildscript { classpath 'com.android.tools.build:gradle:3.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.9.10' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.15.2' } } diff --git a/android/gradle.properties b/android/gradle.properties index 01f57a6814d..53c570a2de0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -7,6 +7,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m +org.gradle.caching=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -19,17 +20,16 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -android.enableR8 = false VERSION_NAME=0.12.1 VERSION_CODE=1 GROUP=com.trustwallet.walletcore POM_DESCRIPTION=Cross-platform, cross-blockchain wallet library. -POM_URL=https://github.com/TrustWallet/wallet-core -POM_SCM_URL=https://github.com/TrustWallet/wallet-core -POM_SCM_CONNECTION=scm:git@github.com:TrustWallet/wallet-core.git -POM_SCM_DEV_CONNECTION=scm:git@github.com:TrustWallet/wallet-core.git +POM_URL=https://github.com/trustwallet/wallet-core +POM_SCM_URL=https://github.com/trustwallet/wallet-core +POM_SCM_CONNECTION=scm:git@github.com:trustwallet/wallet-core.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:trustwallet/wallet-core.git POM_LICENCE_NAME=MIT POM_LICENCE_URL=https://opensource.org/licenses/MIT POM_LICENCE_DIST=repo diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index f6b961fd5a86aa5fbfe90f707c3138408be7c718..62d4c053550b91381bbd28b1afc82d634bf73a8a 100644 GIT binary patch literal 58910 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 literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3521434ef95..4f930cba13a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 07 08:02:53 CST 2020 +#Thu May 14 14:21:48 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip diff --git a/android/gradlew b/android/gradlew index cccdd3d517f..fbd7c515832 100755 --- a/android/gradlew +++ b/android/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat index f9553162f12..a9f778a7a96 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -1,84 +1,104 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 8c9ddbeee193466ffed6524a3a00347e374b1fd5 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 15 May 2020 16:34:31 +0800 Subject: [PATCH 22/81] [Swift] Return nil if hex string length is odd (#959) * return nil if hex string length is odd --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .gitignore | 1 + swift/Sources/Extensions/Data+Hex.swift | 5 ++ swift/Tests/Blockchains/HarmonyTests.swift | 80 +++++++++++----------- swift/Tests/DataTests.swift | 6 ++ tests/HexCodingTests.cpp | 8 +++ 6 files changed, 61 insertions(+), 41 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a0460d715e..88420d082c5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,4 +23,4 @@ - [ ] Prefix PR title with `[WIP]` if necessary. - [ ] Add tests to cover changes as needed. - [ ] Update documentation as needed. -- [ ] If there is a related Issue, mention it in the description (e.g. 'Fixes #444 ). +- [ ] If there is a related Issue, mention it in the description (e.g. Fixes # ). diff --git a/.gitignore b/.gitignore index 7a5561d7def..adb11327ba8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ lib/protobuf .idea/ .vscode/ .project +.history/ # Generated files jni/cpp/generated diff --git a/swift/Sources/Extensions/Data+Hex.swift b/swift/Sources/Extensions/Data+Hex.swift index 817a2a9605e..3c4642eaa76 100644 --- a/swift/Sources/Extensions/Data+Hex.swift +++ b/swift/Sources/Extensions/Data+Hex.swift @@ -16,6 +16,11 @@ extension Data { string = hexString } + // Check odd length hex string + if string.count % 2 != 0 { + return nil + } + // Convert the string to bytes for better performance guard let stringData = string.data(using: .ascii, allowLossyConversion: true) else { return nil diff --git a/swift/Tests/Blockchains/HarmonyTests.swift b/swift/Tests/Blockchains/HarmonyTests.swift index 172641f323b..c5ac36ebd11 100644 --- a/swift/Tests/Blockchains/HarmonyTests.swift +++ b/swift/Tests/Blockchains/HarmonyTests.swift @@ -12,12 +12,12 @@ class HarmonyTests: XCTestCase { func testSigner() { let transaction = HarmonyTransactionMessage.with { - $0.nonce = Data(hexString: "0x9")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x09")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x5208")! - $0.fromShardID = Data(hexString: "0x1")! - $0.toShardID = Data(hexString: "0x0")! - $0.payload = Data(hexString: "0x")! + $0.fromShardID = Data(hexString: "0x01")! + $0.toShardID = Data() + $0.payload = Data() $0.toAddress = "one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc" $0.amount = Data(hexString: "0x4c53ecdc18a60000")! } @@ -49,16 +49,16 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let rate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let maxRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x9")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x09")! + $0.precision = Data(hexString: "0x01")! } let maxChangeRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x5")! - $0.precision = Data(hexString: "0x2")! + $0.value = Data(hexString: "0x05")! + $0.precision = Data(hexString: "0x02")! } let commission = HarmonyCommissionRate.with { $0.rate = rate @@ -71,7 +71,7 @@ class HarmonyTests: XCTestCase { $0.validatorAddress = oneAddress $0.description_p = description $0.commissionRates = commission - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotPubKeys = [pubKey] $0.slotKeySigs = [blsSig] @@ -79,8 +79,8 @@ class HarmonyTests: XCTestCase { } let staking = HarmonyStakingMessage.with { $0.createValidatorMessage = createValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x0")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data(hexString: "0x00")! $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -107,24 +107,24 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let commissionRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let editValidator = HarmonyDirectiveEditValidator.with { $0.validatorAddress = oneAddress $0.description_p = desc $0.commissionRate = commissionRate - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotKeyToRemove = pubKeyData $0.slotKeyToAdd = pubKeyData $0.slotKeyToAddSig = blsSigData - $0.active = Data(hexString: "0x1")! + $0.active = Data(hexString: "0x01")! } let staking = HarmonyStakingMessage.with { $0.editValidatorMessage = editValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -151,24 +151,24 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let commissionRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let editValidator = HarmonyDirectiveEditValidator.with { $0.validatorAddress = oneAddress $0.description_p = desc $0.commissionRate = commissionRate - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotKeyToRemove = pubKeyData $0.slotKeyToAdd = pubKeyData $0.slotKeyToAddSig = blsSigData - $0.active = Data(hexString: "0x2")! + $0.active = Data(hexString: "0x02")! } let staking = HarmonyStakingMessage.with { $0.editValidatorMessage = editValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -194,24 +194,24 @@ class HarmonyTests: XCTestCase { $0.details = "Don't mess with me!!!" } let commissionRate = HarmonyDecimal.with { - $0.value = Data(hexString: "0x1")! - $0.precision = Data(hexString: "0x1")! + $0.value = Data(hexString: "0x01")! + $0.precision = Data(hexString: "0x01")! } let editValidator = HarmonyDirectiveEditValidator.with { $0.validatorAddress = oneAddress $0.description_p = desc $0.commissionRate = commissionRate - $0.minSelfDelegation = Data(hexString: "0xa")! + $0.minSelfDelegation = Data(hexString: "0x0a")! $0.maxTotalDelegation = Data(hexString: "0x0bb8")! $0.slotKeyToRemove = pubKeyData $0.slotKeyToAdd = pubKeyData $0.slotKeyToAddSig = blsSigData - $0.active = Data(hexString: "0x0")! + $0.active = Data() } let staking = HarmonyStakingMessage.with { $0.editValidatorMessage = editValidator - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -233,12 +233,12 @@ class HarmonyTests: XCTestCase { let delegate = HarmonyDirectiveDelegate.with { $0.delegatorAddress = oneAddress $0.validatorAddress = oneAddress - $0.amount = Data(hexString: "0xa")! + $0.amount = Data(hexString: "0x0a")! } let staking = HarmonyStakingMessage.with { $0.delegateMessage = delegate - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -262,12 +262,12 @@ class HarmonyTests: XCTestCase { let undelegate = HarmonyDirectiveUndelegate.with { $0.delegatorAddress = oneAddress $0.validatorAddress = oneAddress - $0.amount = Data(hexString: "0xa")! + $0.amount = Data(hexString: "0x0a")! } let staking = HarmonyStakingMessage.with { $0.undelegateMessage = undelegate - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { @@ -293,8 +293,8 @@ class HarmonyTests: XCTestCase { } let staking = HarmonyStakingMessage.with { $0.collectRewards = collectRewards - $0.nonce = Data(hexString: "0x2")! - $0.gasPrice = Data(hexString: "0x")! + $0.nonce = Data(hexString: "0x02")! + $0.gasPrice = Data() $0.gasLimit = Data(hexString: "0x64")! } let input = HarmonySigningInput.with { diff --git a/swift/Tests/DataTests.swift b/swift/Tests/DataTests.swift index 09566f45a4a..c3c1a198c3e 100644 --- a/swift/Tests/DataTests.swift +++ b/swift/Tests/DataTests.swift @@ -15,4 +15,10 @@ class DataTests: XCTestCase { XCTAssertEqual(Array(data), bytes) } + + func testOddLength() { + XCTAssertNil(Data(hexString: "0x0")) + XCTAssertNil(Data(hexString: "0x28fa6ae00")) + XCTAssertNil(Data(hexString: "28fa6ae00")) + } } diff --git a/tests/HexCodingTests.cpp b/tests/HexCodingTests.cpp index c902afc9c7c..3c06029227e 100644 --- a/tests/HexCodingTests.cpp +++ b/tests/HexCodingTests.cpp @@ -19,4 +19,12 @@ TEST(HexCoding, validation) { ASSERT_TRUE(bytes.empty()); ASSERT_EQ("0x" + hex(bytes2), valid); } + +TEST(HexCoding, OddLength) { + const std::string invalid = "28fa6ae00"; + const auto bytes = parse_hex(invalid); + + ASSERT_TRUE(bytes.empty()); +} + } From f1411a9e2de6e1473c2b086ff34e1f9c56418b29 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Mon, 18 May 2020 18:33:10 +0200 Subject: [PATCH 23/81] (BTC) Extra tests for UnspentSelector UTXO selection (#960) * (BTC) Extra tests for UnspentSelector UTXO selection. * Add Dust test cases. * More test with more UTXO inputs. * Revert "More test with more UTXO inputs." This reverts commit 747b4891163994a90784a72e1b3e5c1dc00d3708. * More test with more UTXO inputs. Co-authored-by: Catenocrypt --- tests/Bitcoin/UnspentSelectorTests.cpp | 260 ++++++++++++++++++++----- 1 file changed, 209 insertions(+), 51 deletions(-) diff --git a/tests/Bitcoin/UnspentSelectorTests.cpp b/tests/Bitcoin/UnspentSelectorTests.cpp index c818e26d682..cbc07018be3 100644 --- a/tests/Bitcoin/UnspentSelectorTests.cpp +++ b/tests/Bitcoin/UnspentSelectorTests.cpp @@ -26,61 +26,69 @@ inline auto sum(const std::vector& utxos) { return s; } -inline auto buildUTXO(const OutPoint& outPoint, int64_t amount) { +inline auto buildUTXO(int64_t amount) { Proto::UnspentTransaction utxo; utxo.set_amount(amount); + const auto& outPoint = transactionOutPoint; utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); utxo.mutable_out_point()->set_index(outPoint.index); return utxo; } +void buildUTXOs(std::vector& utxos, const std::vector& amounts) { + for (auto it = amounts.begin(); it != amounts.end(); it++) { + utxos.push_back(buildUTXO(*it)); + } +} + +bool verifySelected(const std::vector& selected, const std::vector& expectedAmounts) { + if (selected.size() != expectedAmounts.size()) { + std::cout << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; + return false; + } + auto n = expectedAmounts.size(); + for (auto i = 0; i < n; ++i) { + if (expectedAmounts[i] != selected[i].amount()) { + std::cout << "Wrong UTXOs amount, pos " << i << " amount " << selected[i].amount() << " expected " << expectedAmounts[i] << std::endl; + return false; + } + } + return true; +} + TEST(UnspentSelector, SelectUnpsents1) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 6000)); - utxos.push_back(buildUTXO(transactionOutPoint, 1000)); - utxos.push_back(buildUTXO(transactionOutPoint, 11000)); - utxos.push_back(buildUTXO(transactionOutPoint, 12000)); + buildUTXOs(utxos, {4000, 2000, 6000, 1000, 11000, 12000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 5000, 1); - ASSERT_EQ(sum(selected), 11000); + ASSERT_TRUE(verifySelected(selected, {11000})); } TEST(UnspentSelector, SelectUnpsents2) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 6000)); - utxos.push_back(buildUTXO(transactionOutPoint, 1000)); - utxos.push_back(buildUTXO(transactionOutPoint, 50000)); - utxos.push_back(buildUTXO(transactionOutPoint, 120000)); + buildUTXOs(utxos, {4000, 2000, 6000, 1000, 50000, 120000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 10000, 1); - ASSERT_EQ(sum(selected), 50000); + ASSERT_TRUE(verifySelected(selected, {50000})); } TEST(UnspentSelector, SelectUnpsents3) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 5000)); + buildUTXOs(utxos, {4000, 2000, 5000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 6000, 1); - ASSERT_EQ(sum(selected), 9000); + ASSERT_TRUE(verifySelected(selected, {4000, 5000})); } TEST(UnspentSelector, SelectUnpsents4) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 40000)); - utxos.push_back(buildUTXO(transactionOutPoint, 30000)); - utxos.push_back(buildUTXO(transactionOutPoint, 30000)); + buildUTXOs(utxos, {40000, 30000, 30000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 50000, 1); @@ -90,15 +98,7 @@ TEST(UnspentSelector, SelectUnpsents4) { TEST(UnspentSelector, SelectUnpsents5) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 1000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2000)); - utxos.push_back(buildUTXO(transactionOutPoint, 3000)); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 5000)); - utxos.push_back(buildUTXO(transactionOutPoint, 6000)); - utxos.push_back(buildUTXO(transactionOutPoint, 7000)); - utxos.push_back(buildUTXO(transactionOutPoint, 8000)); - utxos.push_back(buildUTXO(transactionOutPoint, 9000)); + buildUTXOs(utxos, {1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 28000, 1); @@ -108,9 +108,7 @@ TEST(UnspentSelector, SelectUnpsents5) { TEST(UnspentSelector, SelectUnpsentsInsufficient) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); - utxos.push_back(buildUTXO(transactionOutPoint, 4000)); + buildUTXOs(utxos, {4000, 4000, 4000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 15000, 1); @@ -120,8 +118,7 @@ TEST(UnspentSelector, SelectUnpsentsInsufficient) { TEST(UnspentSelector, SelectCustomCase) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 794121)); - utxos.push_back(buildUTXO(transactionOutPoint, 2289357)); + buildUTXOs(utxos, {794121, 2289357}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 2287189, 61); @@ -129,22 +126,187 @@ TEST(UnspentSelector, SelectCustomCase) { ASSERT_EQ(sum(selected), 3083478); } +TEST(UnspentSelector, SelectNegativeNoUtxo) { + auto utxos = std::vector(); + buildUTXOs(utxos, {}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 100000, 1); + + ASSERT_TRUE(verifySelected(selected, {})); +} + +TEST(UnspentSelector, SelectNegativeTarget0) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 0, 1); + + ASSERT_TRUE(verifySelected(selected, {})); +} + +TEST(UnspentSelector, SelectOneTypical) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 50'000, 1); + + ASSERT_TRUE(verifySelected(selected, {100'000})); +} + +TEST(UnspentSelector, SelectOneInsufficient) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 200'000, 1); + + ASSERT_TRUE(verifySelected(selected, {})); +} + +TEST(UnspentSelector, SelectOneInsufficientEqual) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 100'000, 1); + + ASSERT_TRUE(verifySelected(selected, {})); +} + +TEST(UnspentSelector, SelectOneInsufficientHigher) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 99'900, 1); + + ASSERT_TRUE(verifySelected(selected, {})); +} + +TEST(UnspentSelector, SelectOneFitsExactly) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 100'000 - 192, 1); // shuold be 226 + + ASSERT_TRUE(verifySelected(selected, {100'000})); + + ASSERT_EQ(selector.calculator.calculate(1, 2, 1), 226); + ASSERT_EQ(selector.calculator.calculate(1, 1, 1), 192); + + // 1 sat more and does not fit any more + selected = selector.select(utxos, 100'000 - 192 + 1, 1); + + ASSERT_TRUE(verifySelected(selected, {})); +} + +TEST(UnspentSelector, SelectThreeNoDust) { + auto utxos = std::vector(); + buildUTXOs(utxos, {100'000, 70'000, 75'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 100'000 - 226 - 10, 1); + + // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust + ASSERT_TRUE(verifySelected(selected, {75'000, 100'000})); + + // Now 100'000 fits with no dust; 546 is the dust limit + selected = selector.select(utxos, 100'000 - 226 - 546, 1); + ASSERT_TRUE(verifySelected(selected, {100'000})); + + // One more and we are over dust limit + selected = selector.select(utxos, 100'000 - 226 - 546 + 1, 1); + ASSERT_TRUE(verifySelected(selected, {75'000, 100'000})); +} + +TEST(UnspentSelector, SelectTwoFirstEnough) { + auto utxos = std::vector(); + buildUTXOs(utxos, {20'000, 80'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 15'000, 1); + + ASSERT_TRUE(verifySelected(selected, {20'000})); +} + +TEST(UnspentSelector, SelectTwoSecondEnough) { + auto utxos = std::vector(); + buildUTXOs(utxos, {20'000, 80'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 70'000, 1); + + ASSERT_TRUE(verifySelected(selected, {80'000})); +} + +TEST(UnspentSelector, SelectTwoBoth) { + auto utxos = std::vector(); + buildUTXOs(utxos, {20'000, 80'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 90'000, 1); + + ASSERT_TRUE(verifySelected(selected, {20'000, 80'000})); +} + +TEST(UnspentSelector, SelectTwoFirstEnoughButSecond) { + auto utxos = std::vector(); + buildUTXOs(utxos, {20'000, 22'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 18'000, 1); + + ASSERT_TRUE(verifySelected(selected, {22'000})); +} + +TEST(UnspentSelector, SelectTenThree) { + auto utxos = std::vector(); + buildUTXOs(utxos, {1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 300'000, 1); + + ASSERT_TRUE(verifySelected(selected, {100'000, 125'000, 150'000})); +} + +TEST(UnspentSelector, SelectTenThreeExact) { + auto utxos = std::vector(); + buildUTXOs(utxos, {1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); + + auto selector = UnspentSelector(); + auto selected = selector.select(utxos, 375'000 - 522 - 546, 1); + + ASSERT_TRUE(verifySelected(selected, {100'000, 125'000, 150'000})); + + auto fee = selector.calculator.calculate(3, 2, 1); + ASSERT_EQ(fee, 522); + + // one more, and it's too much + selected = selector.select(utxos, 375'000 - 522 - 546 + 1, 1); + + ASSERT_TRUE(verifySelected(selected, {7'000, 100'000, 125'000, 150'000})); +} + TEST(UnspentSelector, SelectMaxCase) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 10189534)); + buildUTXOs(utxos, {10189534}); auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 10189342, 1); + auto selected = selector.select(utxos, 10189534 - 226, 1); - ASSERT_EQ(sum(selected), 10189534); - ASSERT_TRUE(selected.size() > 0); + ASSERT_TRUE(verifySelected(selected, {10189534})); + + auto fee = selector.calculator.calculate(1, 2, 1); + ASSERT_EQ(fee, 226); } TEST(UnspentSelector, SelectZcashUnpsents) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 100000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2592)); - utxos.push_back(buildUTXO(transactionOutPoint, 73774)); + buildUTXOs(utxos, {100000, 2592, 73774}); auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); auto selector = UnspentSelector(calculator); @@ -156,11 +318,11 @@ TEST(UnspentSelector, SelectZcashUnpsents) { TEST(UnspentSelector, SelectGroestlUnpsents) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 499971976)); + buildUTXOs(utxos, {499971976}); auto calculator = UnspentCalculator::getCalculator(TWCoinTypeGroestlcoin); auto selector = UnspentSelector(calculator); - auto selected = selector.select(utxos, 499951976, 1, 2); + auto selected = selector.select(utxos, 499951976, 1, 1); ASSERT_EQ(sum(selected), 499971976); ASSERT_TRUE(selected.size() > 0); @@ -168,9 +330,7 @@ TEST(UnspentSelector, SelectGroestlUnpsents) { TEST(UnspentSelector, SelectZcashMaxUnpsents) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 100000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2592)); - utxos.push_back(buildUTXO(transactionOutPoint, 73774)); + buildUTXOs(utxos, {100000, 2592, 73774}); auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); auto selector = UnspentSelector(calculator); @@ -182,9 +342,7 @@ TEST(UnspentSelector, SelectZcashMaxUnpsents) { TEST(UnspentSelector, SelectZcashMaxUnpsents2) { auto utxos = std::vector(); - utxos.push_back(buildUTXO(transactionOutPoint, 100000)); - utxos.push_back(buildUTXO(transactionOutPoint, 2592)); - utxos.push_back(buildUTXO(transactionOutPoint, 73774)); + buildUTXOs(utxos, {100000, 2592, 73774}); auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); auto selector = UnspentSelector(calculator); From 21ce5a546794b546af66dc9b8e778206a92e108d Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 22 May 2020 11:30:09 +0800 Subject: [PATCH 24/81] Add encode/decode to AnySigner (#962) * Add encode/decode to anysigner * Fix tests on Linux, better comments * Add swift and tests * Add java and android tests --- .../ethereum/TestEthereumTransactionSigner.kt | 20 ++- include/TrustWalletCore/TWAnySigner.h | 8 + jni/cpp/AnySigner.c | 16 ++ jni/cpp/AnySigner.h | 6 + jni/java/wallet/core/java/AnySigner.java | 13 ++ src/Coin.cpp | 12 ++ src/Coin.h | 4 + src/CoinEntry.h | 11 ++ src/Ethereum/Entry.cpp | 14 ++ src/Ethereum/Entry.h | 2 + src/Ethereum/RLP.cpp | 140 ++++++++++++++++++ src/Ethereum/RLP.h | 11 ++ src/HexCoding.h | 6 + src/interface/TWAnySigner.cpp | 14 ++ swift/Sources/AnySigner.swift | 24 +++ swift/Tests/Blockchains/EthereumTests.swift | 23 +++ tests/Ethereum/RLPTests.cpp | 109 ++++++++++++++ tests/Ethereum/TWAnySignerTests.cpp | 40 ++++- tests/interface/TWTestUtilities.h | 7 + 19 files changed, 473 insertions(+), 7 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt index 4a8963e1eca..500b9c9e1cb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt @@ -9,6 +9,7 @@ import wallet.core.java.AnySigner import wallet.core.jni.proto.Ethereum import wallet.core.jni.proto.Ethereum.SigningOutput import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertArrayEquals import wallet.core.jni.CoinType.ETHEREUM class TestEthereumTransactionSigner { @@ -30,10 +31,21 @@ class TestEthereumTransactionSigner { amount = ByteString.copyFrom("0x0de0b6b3a7640000".toHexByteArray()) } - val sign = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + val encoded = AnySigner.encode(signingInput.build(), ETHEREUM) - assertEquals(Numeric.toHexString(sign.v.toByteArray()), "0x25") - assertEquals(Numeric.toHexString(sign.r.toByteArray()), "0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276") - assertEquals(Numeric.toHexString(sign.s.toByteArray()), "0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + assertArrayEquals(output.encoded.toByteArray(), encoded) + assertEquals(Numeric.toHexString(output.v.toByteArray()), "0x25") + assertEquals(Numeric.toHexString(output.r.toByteArray()), "0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276") + assertEquals(Numeric.toHexString(output.s.toByteArray()), "0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + assertEquals(Numeric.toHexString(encoded), "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + } + + @Test + fun testEthereumTransactionDecoding() { + val rawTx = "0xf8a86484b2d05e008277fb9400000000000c2e074ec69a0dfb2997ba6c7d2e1e80b8441896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba4125a0b55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4dfa077b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d".toHexByteArray() + val decoded = AnySigner.decode(rawTx, ETHEREUM) + + assertEquals(String(decoded), """{"gas":"0x77fb","gasPrice":"0xb2d05e00","input":"0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41","nonce":"0x64","r":"0xb55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4df","s":"0x77b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d","to":"0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e","v":"0x25","value":"0x"}""") } } diff --git a/include/TrustWalletCore/TWAnySigner.h b/include/TrustWalletCore/TWAnySigner.h index f4246843baf..2a2ca6be4a3 100644 --- a/include/TrustWalletCore/TWAnySigner.h +++ b/include/TrustWalletCore/TWAnySigner.h @@ -23,6 +23,14 @@ extern TWString *_Nonnull TWAnySignerSignJSON(TWString *_Nonnull json, TWData *_ extern bool TWAnySignerSupportsJSON(enum TWCoinType coin); +/// Encodes serialized SigningInput data to raw platform/coin specific bytes +/// Example: EthereumSigningInput will be encoded as raw RLP bytes which can be sent over JSONRPC (eth_sendRawTransaction) +extern TWData *_Nonnull TWAnySignerEncode(TWData *_Nonnull input, enum TWCoinType coin); + +/// Decodes raw platform/coin specific bytes to representable json data +/// Example: Ethereum RLP bytes will be decoded into same json returned from JSONRPC (eth_getTransactionByHash) +extern TWData *_Nonnull TWAnySignerDecode(TWData *_Nonnull input, enum TWCoinType coin); + /// Plan a transaction (for UTXO chains). extern TWData *_Nonnull TWAnySignerPlan(TWData *_Nonnull input, enum TWCoinType coin); diff --git a/jni/cpp/AnySigner.c b/jni/cpp/AnySigner.c index d9ac054adc4..6a454d2c921 100644 --- a/jni/cpp/AnySigner.c +++ b/jni/cpp/AnySigner.c @@ -19,6 +19,22 @@ jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclas return resultData; } +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeEncode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerEncode(inputData, coin); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} + +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeDecode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerDecode(inputData, coin); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} + jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin) { return TWAnySignerSupportsJSON(coin); } diff --git a/jni/cpp/AnySigner.h b/jni/cpp/AnySigner.h index b51323a6554..afddb70872c 100644 --- a/jni/cpp/AnySigner.h +++ b/jni/cpp/AnySigner.h @@ -15,6 +15,12 @@ TW_EXTERN_C_BEGIN JNIEXPORT jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeEncode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); + +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeDecode(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); + JNIEXPORT jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin); diff --git a/jni/java/wallet/core/java/AnySigner.java b/jni/java/wallet/core/java/AnySigner.java index 47f9b4fce9a..c3d53105103 100644 --- a/jni/java/wallet/core/java/AnySigner.java +++ b/jni/java/wallet/core/java/AnySigner.java @@ -21,6 +21,19 @@ public static T sign(Message input, CoinType coin, Parser } public static native byte[] nativeSign(byte[] data, int coin); + public static byte[] encode(Message input, CoinType coin) throws Exception { + byte[] data = input.toByteArray(); + return nativeEncode(data, coin.value()); + } + + public static native byte[] nativeEncode(byte[] data, int coin); + + public static byte[] decode(byte[] data, CoinType coin) throws Exception { + return nativeDecode(data, coin.value()); + } + + public static native byte[] nativeDecode(byte[] data, int coin); + public static native String signJSON(String json, byte[] key, int coin); public static native boolean supportsJSON(int coin); diff --git a/src/Coin.cpp b/src/Coin.cpp index 9ea55ffbfb9..03e72079a41 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -194,6 +194,18 @@ bool TW::supportsJSONSigning(TWCoinType coinType) { return dispatcher->supportsJSONSigning(); } +void TW::anyCoinEncode(TWCoinType coinType, const Data& dataIn, Data& dataOut) { + auto dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + dispatcher->encodeRawTx(coinType, dataIn, dataOut); +} + +void TW::anyCoinDecode(TWCoinType coinType, const Data& dataIn, Data& dataOut) { + auto dispatcher = coinDispatcher(coinType); + assert(dispatcher != nullptr); + dispatcher->decodeRawTx(coinType, dataIn, dataOut); +} + void TW::anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut) { auto dispatcher = coinDispatcher(coinType); assert(dispatcher != nullptr); diff --git a/src/Coin.h b/src/Coin.h index e5db19ba4ae..12b4012a096 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -84,6 +84,10 @@ std::string anySignJSON(TWCoinType coinType, const std::string& json, const Data bool supportsJSONSigning(TWCoinType coinType); +void anyCoinEncode(TWCoinType coinType, const Data& dataIn, Data& dataOut); + +void anyCoinDecode(TWCoinType coinType, const Data& dataIn, Data& dataOut); + void anyCoinPlan(TWCoinType coinType, const Data& dataIn, Data& dataOut); struct CoinInfo { diff --git a/src/CoinEntry.h b/src/CoinEntry.h index 6fefcf4fc49..e66c054d709 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -32,6 +32,9 @@ class CoinEntry { virtual bool supportsJSONSigning() const { return false; } // It is optional, Signing JSON input with private key virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const { return ""; } + // Sign and encode broadcastable raw transaction + virtual void encodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const { return; } + virtual void decodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const { return; } // Planning, for UTXO chains, in preparation for signing // It is optional, only UTXO chains need it, default impl. leaves empty result. virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const { return; } @@ -48,6 +51,14 @@ void signTemplate(const Data& dataIn, Data& dataOut) { dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } +template +void encodeTemplate(const Data& dataIn, Data& dataOut) { + auto input = Input(); + input.ParseFromArray(dataIn.data(), (int)dataIn.size()); + auto encoded = Signer::sign(input).encoded(); + dataOut.insert(dataOut.end(), encoded.begin(), encoded.end()); +} + // Note: use output parameter to avoid unneeded copies template void planTemplate(const Data& dataIn, Data& dataOut) { diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 35297782d23..bedc2d7e6b9 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -8,6 +8,7 @@ #include "Address.h" #include "Signer.h" +#include "RLP.h" using namespace TW::Ethereum; using namespace std; @@ -32,3 +33,16 @@ void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) con string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +void Entry::encodeRawTx(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + encodeTemplate(dataIn, dataOut); +} + +void Entry::decodeRawTx(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + try { + auto data = RLP::decodeRawTransaction(dataIn); + dataOut.insert(dataOut.end(), data.begin(), data.end()); + } catch(...) { + return; + } +} diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index fa32492ed0d..91134ae54e1 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -31,6 +31,8 @@ class Entry: public CoinEntry { virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual bool supportsJSONSigning() const { return true; } virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + virtual void encodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + virtual void decodeRawTx(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index 2c543cc9309..fd2e8a826dc 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -7,12 +7,18 @@ #include "RLP.h" #include "../Data.h" +#include "../uint256.h" +#include "../BinaryCoding.h" +#include "../HexCoding.h" +#include #include using namespace TW; using namespace TW::Ethereum; +using json = nlohmann::json; + Data RLP::encode(const uint256_t& value) noexcept { using boost::multiprecision::cpp_int; @@ -134,3 +140,137 @@ Data RLP::putint(uint64_t i) noexcept { }; // clang-format on } + +Data RLP::decodeRawTransaction(const Data& data) { + auto decoded = decode(data).decoded; + if (decoded.size() < 9) { + return {}; + } + auto result = json { + {"nonce", hexEncoded(decoded[0])}, + {"gasPrice", hexEncoded(decoded[1])}, + {"gas", hexEncoded(decoded[2])}, + {"to", hexEncoded(decoded[3])}, + {"value", hexEncoded(decoded[4])}, + {"input", hexEncoded(decoded[5])}, + {"v", hexEncoded(decoded[6])}, + {"r", hexEncoded(decoded[7])}, + {"s", hexEncoded(decoded[8])}, + }.dump(); + return Data(result.begin(), result.end()); +} + +static RLP::DecodedItem decodeList(const Data& input) { + RLP::DecodedItem item; + auto remainder = input; + while(true) { + auto listItem = RLP::decode(remainder); + item.decoded.push_back(listItem.decoded[0]); + if (listItem.remainder.size() == 0) { + break; + } else { + remainder = listItem.remainder; + } + } + return item; +} + +static uint64_t decodeLength(const Data& data) { + size_t index = 0; + auto decodedLen = decodeVarInt(data, index); + if (!std::get<0>(decodedLen)) { + throw std::invalid_argument("can't decode length of string/list length"); + } + return std::get<1>(decodedLen); +} + +RLP::DecodedItem RLP::decode(const Data& input) { + if (input.size() == 0) { + throw std::invalid_argument("can't decode empty rlp data"); + } + RLP::DecodedItem item; + auto inputLen = input.size(); + auto prefix = input[0]; + if (prefix <= 0x7f) { + // a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. + item.decoded.push_back(Data{input[0]}); + item.remainder = Data(input.begin() + 1, input.end()); + return item; + } + if (prefix <= 0xb7) { + // short string + // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string + // The range of the first byte is [0x80, 0xb7] + + // empty string + if (prefix == 0x80) { + item.decoded.push_back(Data()); + item.remainder = Data(input.begin() + 1, input.end()); + return item; + } + + auto strLen = prefix - 0x80; + if (strLen == 1 && input[1] <= 0x7f) { + throw std::invalid_argument("single byte below 128 must be encoded as itself"); + } + + item.decoded.push_back(subData(input, 1, strLen)); + item.remainder = Data(input.begin() + 1 + strLen, input.end()); + + return item; + } + if (prefix <= 0xbf) { + // long string + auto lenOfStrLen = prefix - 0xb7; + auto strLen = decodeLength(subData(input, 1, lenOfStrLen)); + if (inputLen < lenOfStrLen || inputLen < lenOfStrLen + strLen) { + throw std::invalid_argument("Invalid rlp encoding length"); + } + auto data = subData(input, 1 + lenOfStrLen, strLen); + item.decoded.push_back(data); + item.remainder = Data(input.begin() + 1 + lenOfStrLen + strLen, input.end()); + return item; + } + if (prefix <= 0xf7) { + // a list between 0-55 bytes long + auto listLen = prefix - 0xc0; + if (inputLen < listLen) { + throw std::invalid_argument("Invalid rlp string length"); + } + + // empty list + if (listLen == 0) { + item.remainder = Data(input.begin() + 1, input.end()); + return item; + } + + // decode list + auto listItem = decodeList(subData(input, 1, listLen)); + for (auto& data : listItem.decoded) { + item.decoded.push_back(data); + } + item.remainder = Data(input.begin() + 1 + listLen, input.end()); + return item; + } + if (prefix <= 0xff) { + auto lenOfListLen = prefix - 0xf7; + auto listLen = decodeLength(subData(input, 1, lenOfListLen)); + if (inputLen < lenOfListLen || inputLen < lenOfListLen + listLen) { + throw std::invalid_argument("Invalid rlp list length"); + } + if (input[1] == 0) { + throw std::invalid_argument("multi-byte length must have no leading zero"); + } + if (listLen < 56) { + throw std::invalid_argument("length below 56 must be encoded in one byte"); + } + // decode list + auto listItem = decodeList(subData(input, 1 + lenOfListLen, listLen)); + for (auto& data : listItem.decoded) { + item.decoded.push_back(data); + } + item.remainder = Data(input.begin() + 1 + lenOfListLen + listLen, input.end()); + return item; + } + throw std::invalid_argument("input don't conform RLP encoding form"); +} diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index fd593e2304a..3d54b08f611 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.h @@ -94,6 +94,17 @@ struct RLP { /// Returns the representation of an integer using the least number of bytes /// needed. static Data putint(uint64_t i) noexcept; + + struct DecodedItem { + std::vector decoded; + Data remainder; + }; + + /// Decodes raw transaction to json data + static Data decodeRawTransaction(const Data& data); + + /// Decodes data, remainder from RLP encoded data + static DecodedItem decode(const Data& data); }; } // namespace TW::Ethereum diff --git a/src/HexCoding.h b/src/HexCoding.h index 9f6b0561a4c..198691bbe60 100644 --- a/src/HexCoding.h +++ b/src/HexCoding.h @@ -44,6 +44,12 @@ inline std::string hex(const T& collection) { return hex(std::begin(collection), std::end(collection)); } +/// same as hex, with 0x prefix +template +inline std::string hexEncoded(const T& collection) { + return hex(std::begin(collection), std::end(collection)).insert(0, "0x"); +} + /// Converts a `uint64_t` value to a hexadecimal string. inline std::string hex(uint64_t value) { auto bytes = reinterpret_cast(&value); diff --git a/src/interface/TWAnySigner.cpp b/src/interface/TWAnySigner.cpp index 64f3dbcc382..cd0f38ef733 100644 --- a/src/interface/TWAnySigner.cpp +++ b/src/interface/TWAnySigner.cpp @@ -27,6 +27,20 @@ extern bool TWAnySignerSupportsJSON(enum TWCoinType coin) { return TW::supportsJSONSigning(coin); } +TWData* _Nonnull TWAnySignerEncode(TWData* _Nonnull data, enum TWCoinType coin) { + const Data& dataIn = *(reinterpret_cast(data)); + Data dataOut; + TW::anyCoinEncode(coin, dataIn, dataOut); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + +TWData* _Nonnull TWAnySignerDecode(TWData* _Nonnull data, enum TWCoinType coin) { + const Data& dataIn = *(reinterpret_cast(data)); + Data dataOut; + TW::anyCoinDecode(coin, dataIn, dataOut); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + TWData* _Nonnull TWAnySignerPlan(TWData* _Nonnull data, enum TWCoinType coin) { const Data& dataIn = *(reinterpret_cast(data)); Data dataOut; diff --git a/swift/Sources/AnySigner.swift b/swift/Sources/AnySigner.swift index 9a6f5862cd6..79dfe0b7a97 100644 --- a/swift/Sources/AnySigner.swift +++ b/swift/Sources/AnySigner.swift @@ -41,6 +41,30 @@ public final class AnySigner { return TWStringNSString(TWAnySignerSignJSON(jsonString, keyData, TWCoinType(rawValue: coin.rawValue))) } + public static func encode(input: SigningInput, coin: CoinType) -> Data { + do { + return nativeEncode(data: try input.serializedData(), coin: coin) + } catch let error { + fatalError(error.localizedDescription) + } + } + + public static func nativeEncode(data: Data, coin: CoinType) -> Data { + let inputData = TWDataCreateWithNSData(data) + defer { + TWDataDelete(inputData) + } + return TWDataNSData(TWAnySignerEncode(inputData, TWCoinType(rawValue: coin.rawValue))) + } + + public static func decode(data: Data, coin: CoinType) -> Data { + let inputData = TWDataCreateWithNSData(data) + defer { + TWDataDelete(inputData) + } + return TWDataNSData(TWAnySignerDecode(inputData, TWCoinType(rawValue: coin.rawValue))) + } + public static func plan(input: SigningInput, coin: CoinType) -> TransactionPlan { do { let outputData = nativePlan(data: try input.serializedData(), coin: coin) diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index 9db6e3b3f23..e0f6837ebb3 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -33,10 +33,13 @@ class EthereumTests: XCTestCase { } let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + let encoded = AnySigner.encode(input: input, coin: .ethereum) + XCTAssertEqual(encoded, output.encoded) XCTAssertEqual(output.v.hexString, "25") XCTAssertEqual(output.r.hexString, "28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276") XCTAssertEqual(output.s.hexString, "67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") + XCTAssertEqual(output.encoded.hexString, "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") } func testSignJSON() { @@ -54,4 +57,24 @@ class EthereumTests: XCTestCase { XCTAssertEqual(result, "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10") } + + func testDecode() { + let rawTx = Data(hexString: "0xf8a86484b2d05e008277fb9400000000000c2e074ec69a0dfb2997ba6c7d2e1e80b8441896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba4125a0b55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4dfa077b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d")! + let decoded = AnySigner.decode(data: rawTx, coin: .ethereum) + + let expected = """ + { + "gas": "0x77fb", + "gasPrice": "0xb2d05e00", + "input": "0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41", + "nonce": "0x64", + "to": "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e", + "value": "0x", + "v": "0x25", + "r": "0xb55e479d5872b7531437621780ead128cd25d8988fb3cda9bcfb4baeb0eda4df", + "s": "0x77b096cf0cb4bee6eb8c756e9cdba95a6cf62af74e05e7e4cdaa8100271a508d" + } + """ + XCTAssertJSONEqual(String(data: decoded, encoding: .utf8)!, expected) + } } diff --git a/tests/Ethereum/RLPTests.cpp b/tests/Ethereum/RLPTests.cpp index 4c65ce9d454..99fdb55505b 100644 --- a/tests/Ethereum/RLPTests.cpp +++ b/tests/Ethereum/RLPTests.cpp @@ -80,3 +80,112 @@ TEST(RLP, Invalid) { ASSERT_TRUE(RLP::encode(-1).empty()); ASSERT_TRUE(RLP::encodeList(std::vector{0, -1}).empty()); } + +TEST(RLP, Decode) { + { + // empty string + auto decoded = RLP::decode(parse_hex("0x80")).decoded[0]; + ASSERT_EQ(std::string(decoded.begin(), decoded.end()), ""); + } + + { + // short string + auto decoded = RLP::decode(parse_hex("0x83636174")).decoded[0]; + ASSERT_EQ(std::string(decoded.begin(), decoded.end()), "cat"); + } + + { + // long string + auto encoded = parse_hex("0xb87674686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e67"); + auto decoded = RLP::decode(encoded).decoded[0]; + ASSERT_EQ(std::string(decoded.begin(), decoded.end()), "this is a a very long string, this is a a very long string, this is a a very long string, this is a a very long string"); + } + + { + // empty list + auto decoded = RLP::decode(parse_hex("0xc0")).decoded; + ASSERT_EQ(decoded.size(), 0); + } + + { + // short list + auto encoded = parse_hex("0xc88363617483646f67"); + auto decoded = RLP::decode(encoded).decoded; + ASSERT_EQ(std::string(decoded[0].begin(), decoded[0].end()), "cat"); + ASSERT_EQ(std::string(decoded[1].begin(), decoded[1].end()), "dog"); + } +} + +TEST(RLP, DecodeList) { + { + // long list, raw ether transfer tx + auto rawTx = parse_hex("0xf86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"); + auto decoded = RLP::decode(rawTx); + + auto expected = std::vector{ + "0xa9", // nonce + "0x051f4d5ce9", // gas price + "0x5208", // gas limit + "0x515778891c99e3d2e7ae489980cb7c77b37b5e76", // to + "0x1b48eb57e000", // amount + "0x", // data + "0x25", // v + "0xad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475", // r + "0x0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65", // s + }; + ASSERT_EQ(decoded.decoded.size(), expected.size()); + for (int i = 0; i < expected.size(); i++) { + EXPECT_EQ(hexEncoded(decoded.decoded[i]), expected[i]); + } + } + + { + // long list, raw token transfer tx + auto rawTx = parse_hex("0xf8aa81d485077359400082db9194dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf0801ca02843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6aca05d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a"); + auto decoded = RLP::decode(rawTx); + + auto expected = std::vector{ + "0xd4", + "0x0773594000", + "0xdb91", + "0xdac17f958d2ee523a2206206994597c13d831ec7", + "0x", + "0xa9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080", + "0x1c", + "0x2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac", + "0x5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a", + }; + ASSERT_EQ(decoded.decoded.size(), expected.size()); + for (int i = 0; i < expected.size(); i++) { + EXPECT_EQ(hexEncoded(decoded.decoded[i]), expected[i]); + } + } +} + +TEST(RLP, DecodeInvalid) { + + // invalid raw tx + EXPECT_EQ(RLP::decodeRawTransaction(parse_hex("0xc0")).size(), 0); + + // decode empty data + EXPECT_THROW(RLP::decode(Data()), std::invalid_argument); + + // incorrect length + EXPECT_THROW(RLP::decode(parse_hex("0x81636174")), std::invalid_argument); + EXPECT_THROW(RLP::decode(parse_hex("0xb9ffff")), std::invalid_argument); + EXPECT_THROW(RLP::decode(parse_hex("0xc883636174")), std::invalid_argument); + + // some tests are from https://github.com/ethereum/tests/blob/develop/RLPTests/invalidRLPTest.json + // int32 overflow + EXPECT_THROW(RLP::decode(parse_hex("0xbf0f000000000000021111")), std::invalid_argument); + + // wrong size list + EXPECT_THROW(RLP::decode(parse_hex("0xf80180")), std::invalid_argument); + + // bytes should be single byte + EXPECT_THROW(RLP::decode(parse_hex("0x8100")), std::invalid_argument); + + // leading zeros in long length list + EXPECT_THROW(RLP::decode(parse_hex("fb00000040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")), std::invalid_argument); + EXPECT_THROW(RLP::decode(parse_hex("f800")), std::invalid_argument); +} diff --git a/tests/Ethereum/TWAnySignerTests.cpp b/tests/Ethereum/TWAnySignerTests.cpp index 5b7e04299d9..c1a1dace516 100644 --- a/tests/Ethereum/TWAnySignerTests.cpp +++ b/tests/Ethereum/TWAnySignerTests.cpp @@ -58,10 +58,29 @@ TEST(TWAnySignerEthereum, Sign) { input.set_payload(data.data(), data.size()); input.set_private_key(key.data(), key.size()); - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); + std::string expected = "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"; - ASSERT_EQ(hex(output.encoded()), "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"); + { + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + } + + { + // encode test + Data encoded; + ANY_ENCODE(input, TWCoinTypeEthereum); + ASSERT_EQ(hex(encoded), expected); + } + + { + // wrong coin + Data encoded; + ANY_ENCODE(input, TWCoinTypeNano); + ASSERT_EQ(hex(encoded), ""); + } } TEST(TWAnySignerEthereum, SignJSON) { @@ -81,3 +100,18 @@ TEST(TWAnySignerEthereum, PlanNotSupported) { auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), TWCoinTypeEthereum)); EXPECT_EQ(TWDataSize(outputTWData.get()), 0); } + +TEST(TWAnySignerEthereum, Decode) { + auto rawData = DATA("f86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"); + auto decodedData = WRAPD(TWAnySignerDecode(rawData.get(), TWCoinTypeEthereum)); + auto jsonData = TW::data(TWDataBytes(decodedData.get()), TWDataSize(decodedData.get())); + auto json = std::string(jsonData.begin(), jsonData.end()); + + EXPECT_EQ(json, R"({"gas":"0x5208","gasPrice":"0x051f4d5ce9","input":"0x","nonce":"0xa9","r":"0xad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475","s":"0x0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65","to":"0x515778891c99e3d2e7ae489980cb7c77b37b5e76","v":"0x25","value":"0x1b48eb57e000"})"); + + // wrong coin + auto emptyData = WRAPD(TWDataCreateWithSize(0)); + auto empty = WRAPD(TWAnySignerDecode(emptyData.get(), TWCoinTypeNano)); + + EXPECT_EQ(TWDataSize(empty.get()), 0); +} diff --git a/tests/interface/TWTestUtilities.h b/tests/interface/TWTestUtilities.h index 11a0bbc6cd9..53ebb485950 100644 --- a/tests/interface/TWTestUtilities.h +++ b/tests/interface/TWTestUtilities.h @@ -42,6 +42,13 @@ std::string getTestTempDir(void); auto outputTWData = WRAPD(TWAnySignerSign(inputTWData.get(), coin));\ output.ParseFromArray(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get()));\ } +#define ANY_ENCODE(input, coin) \ + {\ + auto inputData = input.SerializeAsString();\ + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ + auto encodedData = WRAPD(TWAnySignerEncode(inputTWData.get(), coin));\ + encoded = TW::data(TWDataBytes(encodedData.get()), TWDataSize(encodedData.get()));\ + } #define ANY_PLAN(input, output, coin) \ {\ auto inputData = input.SerializeAsString();\ From be861142d2cfce3c8d4ee1f4c1e781f63d2b5e5d Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Tue, 26 May 2020 00:33:47 +0200 Subject: [PATCH 25/81] [BTC] Minor refactoring in fee handling. (#963) * Rework FeeCalculator. * Extra UnspentSelector test. * Test rename. * Minor refactoring, asserts, typos. * Minor improvement in max_amount amount computation. * Minor change in fee computation in case UTXOs can be selected only with dust. * Include cleanup, copyright. * Bitcoin UnspentSelectorTests: More strict verification. * BTC Tx Plan and UnspentSelector tests: cleaner and stricter verification, common build&compare helpers. * BTC SigningTests: add explicit checks for fee, amount, change, and UTXO selection. * Use_max_amount case: if max_amount selected, amount set does not matter. Add more max_amount tests. * Rework Max_amount case, more tests. * Extra test with empty plan. * BitcoinSignerTests: Add explicit checks on encoded tx size. * TransactionSigner: Minor rearrange for coverage++. * A few more special BitconTransactionSigner tests. * Remove redundant check. * Data type cleanup. * Segwit and non-segwit tx size computation in signer tests. * One extra special transaction plan test (fee more than avail amount). * Minor; add test assert. * Code cleanup, TransactionSigner. * TransactionSigner tests: add validation checks for fee estimation. * Rework verify functions in TxComparisonHelper to return bool, print error. Co-authored-by: Catenocrypt --- src/Bitcoin/FeeCalculator.cpp | 70 ++++ src/Bitcoin/FeeCalculator.h | 32 ++ src/Bitcoin/Script.cpp | 1 + src/Bitcoin/Script.h | 2 +- src/Bitcoin/Transaction.cpp | 61 ++-- src/Bitcoin/Transaction.h | 37 +- src/Bitcoin/TransactionBuilder.cpp | 59 +++ src/Bitcoin/TransactionBuilder.h | 42 +-- src/Bitcoin/TransactionSigner.cpp | 74 ++-- src/Bitcoin/TransactionSigner.h | 4 +- src/Bitcoin/UnspentCalculator.cpp | 41 --- src/Bitcoin/UnspentCalculator.h | 36 -- src/Bitcoin/UnspentSelector.cpp | 40 ++- src/Bitcoin/UnspentSelector.h | 21 +- src/Decred/TransactionBuilder.h | 1 - src/Zcash/Transaction.h | 3 + src/Zcash/TransactionBuilder.h | 1 - tests/Bitcoin/FeeCalculatorTests.cpp | 63 ++++ tests/Bitcoin/TWBitcoinSigningTests.cpp | 375 +++++++++++++------- tests/Bitcoin/TWBitcoinTransactionTests.cpp | 7 +- tests/Bitcoin/TransactionPlanTests.cpp | 307 ++++++++++++---- tests/Bitcoin/TxComparisonHelper.cpp | 153 ++++++++ tests/Bitcoin/TxComparisonHelper.h | 54 +++ tests/Bitcoin/UnspentSelectorTests.cpp | 373 ++++++++++--------- 24 files changed, 1262 insertions(+), 595 deletions(-) create mode 100644 src/Bitcoin/FeeCalculator.cpp create mode 100644 src/Bitcoin/FeeCalculator.h create mode 100644 src/Bitcoin/TransactionBuilder.cpp delete mode 100644 src/Bitcoin/UnspentCalculator.cpp delete mode 100644 src/Bitcoin/UnspentCalculator.h create mode 100644 tests/Bitcoin/FeeCalculatorTests.cpp create mode 100644 tests/Bitcoin/TxComparisonHelper.cpp create mode 100644 tests/Bitcoin/TxComparisonHelper.h diff --git a/src/Bitcoin/FeeCalculator.cpp b/src/Bitcoin/FeeCalculator.cpp new file mode 100644 index 00000000000..27c364da705 --- /dev/null +++ b/src/Bitcoin/FeeCalculator.cpp @@ -0,0 +1,70 @@ +// Copyright © 2017-2020 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. + +#include "FeeCalculator.h" + +using namespace TW; + +namespace TW::Bitcoin { + +DefaultFeeCalculator DefaultFeeCalculator::instance; + +int64_t DefaultFeeCalculator::calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const { + const auto txsize = ((148 * inputs) + (34 * outputs) + 10); + return int64_t(txsize) * byteFee; +} + +int64_t DefaultFeeCalculator::calculateSingleInput(int64_t byteFee) const { + return int64_t(148) * byteFee; +} + +class ZCashFeeCalculator : public FeeCalculator { +public: + int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override { return 10000; } + int64_t calculateSingleInput(int64_t byteFee) const override { return 0; } +}; + +class GroestlcoinFeeCalculator : public FeeCalculator { +public: + int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override { return 20000; } + int64_t calculateSingleInput(int64_t byteFee) const override { return 0; } +}; + +class DecredFeeCalculator : public FeeCalculator { +public: + int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override { + const auto txsize = ((166 * inputs) + (38 * outputs) + 12); + return int64_t(txsize) * byteFee; + } + + int64_t calculateSingleInput(int64_t byteFee) const override { + return int64_t(166) * byteFee; + } +}; + +DefaultFeeCalculator defaultFeeCalculator; +ZCashFeeCalculator zcashFeeCalculator; +GroestlcoinFeeCalculator groestlcoinFeeCalculator; +DecredFeeCalculator decredFeeCalculator; + +FeeCalculator& getFeeCalculator(TWCoinType coinType) { + switch (coinType) { + case TWCoinTypeZelcash: + case TWCoinTypeZcash: + return zcashFeeCalculator; + + case TWCoinTypeGroestlcoin: + return groestlcoinFeeCalculator; + + case TWCoinTypeDecred: + return decredFeeCalculator; + + default: + return defaultFeeCalculator; + } +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/FeeCalculator.h b/src/Bitcoin/FeeCalculator.h new file mode 100644 index 00000000000..67e2c65bb37 --- /dev/null +++ b/src/Bitcoin/FeeCalculator.h @@ -0,0 +1,32 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include + +namespace TW::Bitcoin { + +/// Interface for transaction fee calculator. +class FeeCalculator { +public: + virtual int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const = 0; + virtual int64_t calculateSingleInput(int64_t byteFee) const = 0; +}; + +/// Default Bitcoin transaction fee calculator, non-segwit. +class DefaultFeeCalculator : public FeeCalculator { +public: + int64_t calculate(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1) const override; + int64_t calculateSingleInput(int64_t byteFee) const override; + + static DefaultFeeCalculator instance; +}; + +/// Return the fee calculator for the given coin. +FeeCalculator& getFeeCalculator(TWCoinType coinType); + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 358e030b3e6..5cd6bce391b 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -233,6 +233,7 @@ Script Script::buildPayToWitnessProgram(const Data& program) { script.bytes.push_back(OP_0); script.bytes.push_back(static_cast(program.size())); script.bytes.insert(script.bytes.end(), program.begin(), program.end()); + assert(script.bytes.size() == 22 || script.bytes.size() == 34); return script; } diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index 08dfe6bfe28..2d59a9877c5 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -29,7 +29,7 @@ class Script { Script(It begin, It end) : bytes(begin, end) {} /// Initializaes a script with a collection of raw bytes by moving. - explicit Script(Data&& bytes) : bytes(bytes) {} + explicit Script(const Data& bytes) : bytes(bytes) {} /// Whether the script is empty. bool empty() const { return bytes.empty(); } diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index a7af607385b..86fd07dcd5e 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -14,13 +14,14 @@ #include +using namespace TW; using namespace TW::Bitcoin; -std::vector Transaction::getPreImage(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, uint64_t amount) const { +Data Transaction::getPreImage(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const { assert(index < inputs.size()); - auto data = std::vector{}; + Data data; // Version encode32LE(version, data); @@ -56,7 +57,7 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i auto hashOutputs = getOutputsHash(); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); } else if (hashTypeIsSingle(hashType) && index < outputs.size()) { - auto outputData = std::vector{}; + Data outputData; outputs[index].encode(outputData); auto hashOutputs = TW::Hash::hash(hasher, outputData); copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); @@ -73,8 +74,8 @@ std::vector Transaction::getPreImage(const Script& scriptCode, size_t i return data; } -std::vector Transaction::getPrevoutHash() const { - auto data = std::vector{}; +Data Transaction::getPrevoutHash() const { + Data data; for (auto& input : inputs) { auto& outpoint = reinterpret_cast(input.previousOutput); outpoint.encode(data); @@ -83,8 +84,8 @@ std::vector Transaction::getPrevoutHash() const { return hash; } -std::vector Transaction::getSequenceHash() const { - auto data = std::vector{}; +Data Transaction::getSequenceHash() const { + Data data; for (auto& input : inputs) { encode32LE(input.sequence, data); } @@ -92,8 +93,8 @@ std::vector Transaction::getSequenceHash() const { return hash; } -std::vector Transaction::getOutputsHash() const { - auto data = std::vector{}; +Data Transaction::getOutputsHash() const { + Data data; for (auto& output : outputs) { output.encode(data); } @@ -101,37 +102,43 @@ std::vector Transaction::getOutputsHash() const { return hash; } -void Transaction::encode(bool witness, std::vector& data) const { +void Transaction::encode(bool witness, Data& data) const { encode32LE(version, data); if (witness) { // Use extended format in case witnesses are to be serialized. - data.push_back(0); - data.push_back(1); + data.push_back(0); // marker + data.push_back(1); // flag } + // txins encodeVarInt(inputs.size(), data); for (auto& input : inputs) { input.encode(data); } + // txouts encodeVarInt(outputs.size(), data); for (auto& output : outputs) { output.encode(data); } if (witness) { - for (auto& input : inputs) { - input.encodeWitness(data); - } + encodeWitness(data); } - encode32LE(lockTime, data); + encode32LE(lockTime, data); // nLockTime +} + +void Transaction::encodeWitness(Data& data) const { + for (auto& input : inputs) { + input.encodeWitness(data); + } } -std::vector Transaction::getSignatureHash(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, uint64_t amount, - enum SignatureVersion version) const { +Data Transaction::getSignatureHash(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount, + enum SignatureVersion version) const { switch (version) { case BASE: return getSignatureHashBase(scriptCode, index, hashType); @@ -141,20 +148,20 @@ std::vector Transaction::getSignatureHash(const Script& scriptCode, siz } /// Generates the signature hash for Witness version 0 scripts. -std::vector Transaction::getSignatureHashWitnessV0(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, - uint64_t amount) const { +Data Transaction::getSignatureHashWitnessV0(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, + uint64_t amount) const { auto preimage = getPreImage(scriptCode, index, hashType, amount); auto hash = TW::Hash::hash(hasher, preimage); return hash; } /// Generates the signature hash for for scripts other than witness scripts. -std::vector Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType) const { +Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const { assert(index < inputs.size()); - auto data = std::vector{}; + Data data; encode32LE(version, data); @@ -189,7 +196,7 @@ std::vector Transaction::getSignatureHashBase(const Script& scriptCode, } void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, std::vector& data) const { + enum TWBitcoinSigHashType hashType, Data& data) const { // In case of SIGHASH_ANYONECANPAY, only the input being signed is // serialized if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0) { diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index 0814138b306..48d431b3f45 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/Transaction.h @@ -11,6 +11,7 @@ #include "TransactionInput.h" #include "TransactionOutput.h" #include "../Hash.h" +#include "../Data.h" #include "SignatureVersion.h" #include @@ -18,6 +19,7 @@ namespace TW::Bitcoin { struct Transaction { +public: /// Transaction data format version (note, this is signed) int32_t version = 1; @@ -41,6 +43,10 @@ struct Transaction { TW::Hash::Hasher hasher = TW::Hash::sha256d; + /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) + int previousEstimatedVirtualSize = 0; + +public: Transaction() = default; Transaction(int32_t version, uint32_t lockTime, TW::Hash::Hasher hasher = TW::Hash::sha256d) @@ -50,33 +56,34 @@ struct Transaction { bool empty() const { return inputs.empty() && outputs.empty(); } /// Generates the signature pre-image. - std::vector getPreImage(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, - uint64_t amount) const; - std::vector getPrevoutHash() const; - std::vector getSequenceHash() const; - std::vector getOutputsHash() const; + Data getPreImage(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const; + Data getPrevoutHash() const; + Data getSequenceHash() const; + Data getOutputsHash() const; /// Encodes the transaction into the provided buffer. - void encode(bool witness, std::vector& data) const; + void encode(bool witness, Data& data) const; + + /// Encodes the witness part of the transaction into the provided buffer. + void encodeWitness(Data& data) const; /// Generates the signature hash for this transaction. - std::vector getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, - uint64_t amount, enum SignatureVersion version) const; + Data getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, + uint64_t amount, enum SignatureVersion version) const; - void serializeInput(size_t subindex, const Script&, size_t index, enum TWBitcoinSigHashType hashType, - std::vector& data) const; + void serializeInput(size_t subindex, const Script&, size_t index, enum TWBitcoinSigHashType hashType, Data& data) const; /// Converts to Protobuf model Proto::Transaction proto() const; - private: +private: /// Generates the signature hash for Witness version 0 scripts. - std::vector getSignatureHashWitnessV0(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType, uint64_t amount) const; + Data getSignatureHashWitnessV0(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const; /// Generates the signature hash for for scripts other than witness scripts. - std::vector getSignatureHashBase(const Script& scriptCode, size_t index, - enum TWBitcoinSigHashType hashType) const; + Data getSignatureHashBase(const Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const; }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp new file mode 100644 index 00000000000..e8bc780dc41 --- /dev/null +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -0,0 +1,59 @@ +// Copyright © 2017-2020 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. + +#include "TransactionBuilder.h" +#include "TransactionSigner.h" + +#include +#include + +namespace TW::Bitcoin { + +TransactionPlan TransactionBuilder::plan(const Bitcoin::Proto::SigningInput& input) { + auto plan = TransactionPlan(); + plan.amount = input.amount(); + + auto output_size = 2; + auto& feeCalculator = getFeeCalculator(static_cast(input.coin_type())); + auto unspentSelector = UnspentSelector(feeCalculator); + + // select UTXOs + if (!input.use_max_amount()) { + output_size = 2; // output + change + plan.utxos = unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); + } else { + output_size = 1; // no change + plan.utxos = unspentSelector.selectMaxAmount(input.utxo(), input.byte_fee()); + } + // Note: if utxos.size() == 0, all fields will be computed to 0 + plan.availableAmount = UnspentSelector::sum(plan.utxos); + + // Compute fee. If larger then availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) + plan.fee = feeCalculator.calculate(plan.utxos.size(), output_size, input.byte_fee()); + plan.fee = std::min(plan.availableAmount, plan.fee); + assert(plan.fee >= 0 && plan.fee <= plan.availableAmount); + + // adjust/compute amount + if (!input.use_max_amount()) { + // reduce amount if needed + plan.amount = std::max(Amount(0), std::min(plan.amount, plan.availableAmount - plan.fee)); + } else { + // max available amount + plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); + } + assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); + + // compute change + plan.change = plan.availableAmount - plan.amount - plan.fee; + assert(plan.change >= 0 && plan.change <= plan.availableAmount); + assert(!input.use_max_amount() || plan.change == 0); // change is 0 in max amount case + + assert(plan.amount + plan.change + plan.fee == plan.availableAmount); + + return plan; +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionBuilder.h b/src/Bitcoin/TransactionBuilder.h index dcdd0e4335d..407c4040ad2 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -19,47 +19,7 @@ namespace TW::Bitcoin { class TransactionBuilder { public: /// Plans a transaction by selecting UTXOs and calculating fees. - static TransactionPlan plan(const Bitcoin::Proto::SigningInput& input) { - auto plan = TransactionPlan(); - plan.amount = input.amount(); - - auto output_size = 2; - auto calculator = - UnspentCalculator::getCalculator(static_cast(input.coin_type())); - auto unspentSelector = UnspentSelector(calculator); - if (input.use_max_amount() && UnspentSelector::sum(input.utxo()) == plan.amount) { - output_size = 1; - Amount newAmount = 0; - auto input_size = 0; - - for (auto utxo : input.utxo()) { - if (utxo.amount() > - unspentSelector.calculator.calculateSingleInput(input.byte_fee())) { - input_size++; - newAmount += utxo.amount(); - } - } - - plan.amount = newAmount - unspentSelector.calculator.calculate(input_size, output_size, - input.byte_fee()); - plan.amount = std::max(Amount(0), plan.amount); - } - - plan.utxos = - unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); - plan.fee = - unspentSelector.calculator.calculate(plan.utxos.size(), output_size, input.byte_fee()); - - plan.availableAmount = UnspentSelector::sum(plan.utxos); - - if (plan.amount > plan.availableAmount - plan.fee) { - plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); - } - - plan.change = plan.availableAmount - plan.amount - plan.fee; - - return plan; - } + static TransactionPlan plan(const Bitcoin::Proto::SigningInput& input); /// Builds a transaction by selecting UTXOs and calculating fees. template diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 02f9541c6c5..16a26b2fa42 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -30,17 +30,13 @@ Result TransactionSigner::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); - if (plan.utxos.size() == 0) { - return Result::failure("Plan without UTXOs"); - } const auto hashSingle = hashTypeIsSingle(static_cast(input.hash_type())); - for (auto i = 0; i < plan.utxos.size(); i += 1) { - auto& utxo = plan.utxos[i]; - + for (auto i = 0; i < plan.utxos.size(); i++) { // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output if (hashSingle && i >= transaction.outputs.size()) { continue; } + auto& utxo = plan.utxos[i]; auto script = Script(utxo.script().begin(), utxo.script().end()); if (i < transaction.inputs.size()) { auto result = sign(script, i, utxo); @@ -53,6 +49,11 @@ Result TransactionSigner::sign() { Transaction tx(transaction); tx.inputs = move(signedInputs); tx.outputs = transaction.outputs; + // save estimated size + if ((input.byte_fee()) > 0 && (plan.fee > 0)) { + tx.previousEstimatedVirtualSize = plan.fee / input.byte_fee(); + } + return Result::success(std::move(tx)); } @@ -63,7 +64,6 @@ Result TransactionSigner::sign(Script scr Script redeemScript; std::vector results; - std::vector witnessStack; uint32_t signatureVersion = [this]() { if ((input.hash_type() & TWBitcoinSigHashTypeFork) != 0) { @@ -73,15 +73,15 @@ Result TransactionSigner::sign(Script scr } }(); auto result = signStep(script, index, utxo, signatureVersion); - if (result) { - results = result.payload(); - } else { + if (!result) { return Result::failure(result.error()); } + results = result.payload(); + assert(results.size() >= 1); auto txin = transaction.inputs[index]; if (script.isPayToScriptHash()) { - script = Script(results.front().begin(), results.front().end()); + script = Script(results[0]); auto result = signStep(script, index, utxo, signatureVersion); if (!result) { return Result::failure(result.error()); @@ -91,26 +91,22 @@ Result TransactionSigner::sign(Script scr redeemScript = script; } + std::vector witnessStack; Data data; if (script.matchPayToWitnessPublicKeyHash(data)) { auto witnessScript = Script::buildPayToPublicKeyHash(results[0]); auto result = signStep(witnessScript, index, utxo, WITNESS_V0); if (result) { witnessStack = result.payload(); - } else { - witnessStack.clear(); } results.clear(); } else if (script.matchPayToWitnessScriptHash(data)) { - auto witnessScript = Script(results[0].begin(), results[0].end()); + auto witnessScript = Script(results[0]); auto result = signStep(witnessScript, index, utxo, WITNESS_V0); if (result) { witnessStack = result.payload(); - } else { - witnessStack.clear(); } witnessStack.push_back(move(witnessScript.bytes)); - results.clear(); } else if (script.isWitnessProgram()) { // Error: Unrecognized witness program. @@ -129,7 +125,7 @@ Result TransactionSigner::sign(Script scr template Result> TransactionSigner::signStep( - Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo, uint32_t version) { + Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo, uint32_t version) const { Transaction transactionToSign(transaction); transactionToSign.inputs = signedInputs; transactionToSign.outputs = transaction.outputs; @@ -145,7 +141,8 @@ Result> TransactionSigner::si return Result>::failure("Missing redeem script."); } return Result>::success({redeemScript}); - } else if (script.matchPayToWitnessScriptHash(data)) { + } + if (script.matchPayToWitnessScriptHash(data)) { auto scripthash = TW::Hash::ripemd(data); auto redeemScript = scriptForScriptHash(scripthash); if (redeemScript.empty()) { @@ -153,12 +150,15 @@ Result> TransactionSigner::si return Result>::failure("Missing redeem script."); } return Result>::success({redeemScript}); - } else if (script.matchPayToWitnessPublicKeyHash(data)) { + } + if (script.matchPayToWitnessPublicKeyHash(data)) { return Result>::success({data}); - } else if (script.isWitnessProgram()) { + } + if (script.isWitnessProgram()) { // Error: Invalid sutput script return Result>::failure("Invalid output script."); - } else if (script.matchMultisig(keys, required)) { + } + if (script.matchMultisig(keys, required)) { auto results = std::vector{{}}; // workaround CHECKMULTISIG bug for (auto& pubKey : keys) { if (results.size() >= required + 1) { @@ -180,7 +180,8 @@ Result> TransactionSigner::si } results.resize(required + 1); return Result>::success(std::move(results)); - } else if (script.matchPayToPublicKey(data)) { + } + if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(data)); auto key = keyForPublicKeyHash(keyHash); if (key.empty()) { @@ -194,10 +195,11 @@ Result> TransactionSigner::si return Result>::failure("Failed to sign."); } return Result>::success({signature}); - } else if (script.matchPayToPublicKeyHash(data)) { + } + if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); if (key.empty()) { - // Error: Missing keyxs + // Error: Missing keys return Result>::failure("Missing private key."); } @@ -209,31 +211,29 @@ Result> TransactionSigner::si return Result>::failure("Failed to sign."); } return Result>::success({signature, pubkey.bytes}); - } else { - // Error: Invalid output script - return Result>::failure("Invalid output script."); } + // Error: Invalid output script + return Result>::failure("Invalid output script."); } template Data TransactionSigner::createSignature(const Transaction& transaction, const Script& script, const Data& key, size_t index, Amount amount, - uint32_t version) { - auto sighash = transaction.getSignatureHash(script, index, static_cast(input.hash_type()), amount, + uint32_t version) const { + Data sighash = transaction.getSignatureHash(script, index, static_cast(input.hash_type()), amount, static_cast(version)); auto pk = PrivateKey(key); - auto sig = pk.signAsDER(Data(begin(sighash), end(sighash)), TWCurveSECP256k1); - if (sig.empty()) { - return {}; + auto sig = pk.signAsDER(sighash, TWCurveSECP256k1); + if (!sig.empty()) { + sig.push_back(static_cast(input.hash_type())); } - sig.push_back(static_cast(input.hash_type())); return sig; } template Data TransactionSigner::pushAll(const std::vector& results) { - auto data = Data{}; + Data data; for (auto& result : results) { if (result.empty()) { data.push_back(OP_0); @@ -261,7 +261,7 @@ Data TransactionSigner::keyForPublicKeyHash(con for (auto& key : input.private_key()) { auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(publicKey.bytes)); - if (std::equal(std::begin(keyHash), std::end(keyHash), std::begin(hash), std::end(hash))) { + if (keyHash == hash) { return Data(key.begin(), key.end()); } } @@ -270,7 +270,7 @@ Data TransactionSigner::keyForPublicKeyHash(con template Data TransactionSigner::scriptForScriptHash(const Data& hash) const { - auto hashString = hex(hash.begin(), hash.end()); + auto hashString = hex(hash); auto it = input.scripts().find(hashString); if (it == input.scripts().end()) { // Error: Missing redeem script diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index f6f3f292eb3..1a1b71962c1 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -67,9 +67,9 @@ class TransactionSigner { private: Result sign(Script script, size_t index, const Proto::UnspentTransaction& utxo); Result> signStep(Script script, size_t index, - const Proto::UnspentTransaction& utxo, uint32_t version); + const Proto::UnspentTransaction& utxo, uint32_t version) const; Data createSignature(const Transaction& transaction, const Script& script, const Data& key, - size_t index, Amount amount, uint32_t version); + size_t index, Amount amount, uint32_t version) const; /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Bitcoin/UnspentCalculator.cpp b/src/Bitcoin/UnspentCalculator.cpp deleted file mode 100644 index c998a5c7b0f..00000000000 --- a/src/Bitcoin/UnspentCalculator.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "UnspentCalculator.h" - -using namespace TW; -using namespace TW::Bitcoin; - -UnspentCalculator UnspentCalculator::getCalculator(TWCoinType coinType) { - switch (coinType) { - case TWCoinTypeZelcash: - case TWCoinTypeZcash: { - auto calc = [](size_t inputs, size_t outputs, int64_t byteFee) -> int64_t { return 10000; }; - auto calcInput = [](int64_t byteFee) -> int64_t { return 0; }; - return UnspentCalculator(calc, calcInput); - } - case TWCoinTypeGroestlcoin: { - auto calc = [](size_t inputs, size_t outputs, int64_t byteFee) -> int64_t { return 20000; }; - auto calcInput = [](int64_t byteFee) -> int64_t { return 0; }; - return UnspentCalculator(calc, calcInput); - } - case TWCoinTypeDecred: { - auto calc = [](size_t inputs, size_t outputs, int64_t byteFee) -> int64_t { - const auto txsize = ((166 * inputs) + (38 * outputs) + 12); - return int64_t(txsize) * byteFee; - }; - auto calcInput = [](int64_t byteFee) -> int64_t { - return int64_t(166) * byteFee; - }; - return UnspentCalculator(calc, calcInput); - } - default: - return UnspentCalculator(); - } -} - -int64_t UnspentCalculator::calculateFee(int64_t inputs, int64_t outputs, int64_t byteFee) { - const auto txsize = ((148 * inputs) + (34 * outputs) + 10); - return int64_t(txsize) * byteFee; -} - -int64_t UnspentCalculator::calculateSingleInputFee(int64_t byteFee) { - return int64_t(148) * byteFee; -} diff --git a/src/Bitcoin/UnspentCalculator.h b/src/Bitcoin/UnspentCalculator.h deleted file mode 100644 index ed90c28996e..00000000000 --- a/src/Bitcoin/UnspentCalculator.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2020 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. - -#pragma once - -#include -#include - -namespace TW::Bitcoin { - -using FeeCalculator = std::function; -using SingleInputFeeCalculator = std::function; - -class UnspentCalculator { - public: - static UnspentCalculator getCalculator(TWCoinType coinType); - - FeeCalculator calculate; - SingleInputFeeCalculator calculateSingleInput; - - UnspentCalculator() - : calculate(UnspentCalculator::calculateFee) - , calculateSingleInput(UnspentCalculator::calculateSingleInputFee) {} - UnspentCalculator(FeeCalculator calculateFee, - SingleInputFeeCalculator calculateSingleInputFee) - : calculate(std::move(calculateFee)), calculateSingleInput(std::move(calculateSingleInputFee)) {} - - private: - static int64_t calculateFee(int64_t inputs, int64_t outputs = 2, int64_t byteFee = 1); - static int64_t calculateSingleInputFee(int64_t byteFee); -}; - -} // namespace TW::Bitcoin diff --git a/src/Bitcoin/UnspentSelector.cpp b/src/Bitcoin/UnspentSelector.cpp index e87e0f32047..d14c8cd0efb 100644 --- a/src/Bitcoin/UnspentSelector.cpp +++ b/src/Bitcoin/UnspentSelector.cpp @@ -7,6 +7,7 @@ #include "UnspentSelector.h" #include +#include using namespace TW; using namespace TW::Bitcoin; @@ -20,12 +21,13 @@ struct Selection { }; // Filters utxos that are dust +template std::vector -UnspentSelector::filterDustInput(std::vector selectedUtxos, - int64_t byteFee) { +UnspentSelector::filterDustInput(const T& selectedUtxos, int64_t byteFee) { + auto inputFeeLimit = feeCalculator.calculateSingleInput(byteFee); std::vector filteredUtxos; - for (auto utxo : selectedUtxos) { - if (utxo.amount() > calculator.calculateSingleInput(byteFee)) { + for (auto utxo: selectedUtxos) { + if (utxo.amount() > inputFeeLimit) { filteredUtxos.push_back(utxo); } } @@ -53,15 +55,16 @@ static inline auto slice(const T& elements, size_t sliceSize) { template std::vector UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs) { - // if target value is zero, fee is zero + // if target value is zero, no UTXOs are needed if (targetValue == 0) { return {}; } // total values of utxos should be greater than targetValue - if (sum(utxos) < targetValue || utxos.empty()) { + if (utxos.empty() || sum(utxos) < targetValue) { return {}; } + assert(utxos.size() >= 1); // definitions for the following caluculation const auto doubleTargetValue = targetValue * 2; @@ -82,12 +85,12 @@ UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, in return doubleTargetValue - val; }; - // 1. Find a combination of the fewest outputs that is + // 1. Find a combination of the fewest inputs that is // (1) bigger than what we need // (2) closer to 2x the amount, // (3) and does not produce dust change. for (int64_t numInputs = 1; numInputs <= sortedUtxos.size(); numInputs += 1) { - const auto fee = calculator.calculate(numInputs, numOutputs, byteFee); + const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); const auto targetWithFeeAndDust = targetValue + fee + dustThreshold; auto slices = slice(sortedUtxos, static_cast(numInputs)); slices.erase(std::remove_if(slices.begin(), slices.end(), @@ -106,10 +109,9 @@ UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, in } } - // 2. If not, find a combination of outputs that may produce dust change. - numOutputs = 1; + // 2. If not, find a valid combination of outputs even if they produce dust change. for (int64_t numInputs = 1; numInputs <= sortedUtxos.size(); numInputs += 1) { - const auto fee = calculator.calculate(numInputs, numOutputs, byteFee); + const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); const auto targetWithFee = targetValue + fee; auto slices = slice(sortedUtxos, static_cast(numInputs)); slices.erase( @@ -126,9 +128,13 @@ UnspentSelector::select(const T& utxos, int64_t targetValue, int64_t byteFee, in return {}; } -template std::vector UnspentSelector::select( - const ::google::protobuf::RepeatedPtrField& utxos, - int64_t targetValue, int64_t byteFee, int64_t numOutputs); -template std::vector -UnspentSelector::select(const std::vector& utxos, int64_t targetValue, - int64_t byteFee, int64_t numOutputs); +template +std::vector +UnspentSelector::selectMaxAmount(const T& utxos, int64_t byteFee) { + return filterDustInput(utxos, byteFee); +} + +template std::vector UnspentSelector::select(const ::google::protobuf::RepeatedPtrField& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs); +template std::vector UnspentSelector::select(const std::vector& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs); +template std::vector UnspentSelector::selectMaxAmount(const ::google::protobuf::RepeatedPtrField& utxos, int64_t byteFee); +template std::vector UnspentSelector::selectMaxAmount(const std::vector& utxos, int64_t byteFee); diff --git a/src/Bitcoin/UnspentSelector.h b/src/Bitcoin/UnspentSelector.h index a7c80873306..60f43ebe79b 100644 --- a/src/Bitcoin/UnspentSelector.h +++ b/src/Bitcoin/UnspentSelector.h @@ -9,7 +9,8 @@ #include #include -#include "UnspentCalculator.h" +#include "FeeCalculator.h" +#include #include "../proto/Bitcoin.pb.h" namespace TW::Bitcoin { @@ -27,23 +28,27 @@ class UnspentSelector { std::vector select(const T& utxos, int64_t targetValue, int64_t byteFee, int64_t numOutputs = 2); - UnspentCalculator calculator; + /// Selects UTXOs for max amount; select all except those which would reduce output (dust). + /// One output and no change is assumed. + template + std::vector selectMaxAmount(const T& utxos, int64_t byteFee); - UnspentSelector() : calculator(UnspentCalculator()) {} - explicit UnspentSelector(UnspentCalculator calculator) : calculator(std::move(calculator)) {} + /// Construct, using provided feeCalculator (see getFeeCalculator()). + explicit UnspentSelector(FeeCalculator& feeCalculator) : feeCalculator(feeCalculator) {} + UnspentSelector() : UnspentSelector(getFeeCalculator(TWCoinTypeBitcoin)) {} - public: template static inline int64_t sum(const T& utxos) { int64_t sum = 0; - for (auto& utxo : utxos) + for (auto& utxo : utxos) { sum += utxo.amount(); + } return sum; } private: - std::vector - filterDustInput(std::vector selectedUtxos, int64_t byteFee); + FeeCalculator& feeCalculator; + template std::vector filterDustInput(const T& selectedUtxos, int64_t byteFee); }; } // namespace TW::Bitcoin diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index e294177e4ab..ed037127336 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -9,7 +9,6 @@ #include "Transaction.h" #include "../Bitcoin/TransactionPlan.h" #include "../Bitcoin/TransactionBuilder.h" -#include "../Bitcoin/UnspentSelector.h" #include "../proto/Bitcoin.pb.h" #include "../proto/Decred.pb.h" diff --git a/src/Zcash/Transaction.h b/src/Zcash/Transaction.h index ff138452200..1ec45215711 100644 --- a/src/Zcash/Transaction.h +++ b/src/Zcash/Transaction.h @@ -33,6 +33,9 @@ struct Transaction { std::vector outputs; std::array branchId; + /// Used for diagnostics; store previously estimated virtual size (if any; size in bytes) + int previousEstimatedVirtualSize = 0; + Transaction() = default; Transaction(uint32_t version, uint32_t versionGroupId, uint32_t lockTime, uint32_t expiryHeight, diff --git a/src/Zcash/TransactionBuilder.h b/src/Zcash/TransactionBuilder.h index 4b26d276a40..e903c85888e 100644 --- a/src/Zcash/TransactionBuilder.h +++ b/src/Zcash/TransactionBuilder.h @@ -9,7 +9,6 @@ #include "Transaction.h" #include "../Bitcoin/TransactionBuilder.h" #include "../Bitcoin/TransactionPlan.h" -#include "../Bitcoin/UnspentSelector.h" #include "../proto/Bitcoin.pb.h" #include "../HexCoding.h" #include diff --git a/tests/Bitcoin/FeeCalculatorTests.cpp b/tests/Bitcoin/FeeCalculatorTests.cpp new file mode 100644 index 00000000000..a5f65a9048b --- /dev/null +++ b/tests/Bitcoin/FeeCalculatorTests.cpp @@ -0,0 +1,63 @@ +// Copyright © 2017-2020 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. + +#include "Bitcoin/FeeCalculator.h" + +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(BitcoinFeeCalculator, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 192); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 78); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 158); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2260); +} + +TEST(BitcoinDefaultFeeCalculator, calculate) { + DefaultFeeCalculator defaultFeeCalculator; + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 1, 1), 192); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 2, 1), 78); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 0, 1), 158); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 10), 2260); +} + +TEST(BitcoinDefaultFeeCalculator, calculateSingleInput) { + DefaultFeeCalculator defaultFeeCalculator; + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 148); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(2), 296); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(10), 1480); +} + +TEST(ZCashFeeCalculator, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeZcash); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 10000); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10000); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 10000); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + +TEST(GroestlcoinFeeCalculator, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeGroestlcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 20000); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 20000); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 20000); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + +TEST(DecredFeeCalculator, calculate) { + FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 12); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2540); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 166); +} diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index f718c0d626b..eb056355f70 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -13,6 +13,7 @@ #include "HexCoding.h" #include "PrivateKey.h" #include "proto/Bitcoin.pb.h" +#include "TxComparisonHelper.h" #include "../interface/TWTestUtilities.h" #include @@ -21,11 +22,12 @@ #include #include +#include using namespace TW; using namespace TW::Bitcoin; -TEST(BitcoinSigning, SignP2PKH) { +Proto::SigningInput buildInputP2PKH(bool omitKey = false) { auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); @@ -39,17 +41,24 @@ TEST(BitcoinSigning, SignP2PKH) { auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - ASSERT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); - input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + if (!omitKey) { + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + } - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); + assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + if (!omitKey) { + input.add_private_key(utxoKey1.bytes.data(), utxoKey1.bytes.size()); + } - auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash); + auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash0); Data scriptHash; utxo0Script.matchPayToPublicKeyHash(scriptHash); - ASSERT_EQ(hex(scriptHash), "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + assert(hex(scriptHash) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); auto utxo0 = input.add_utxo(); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); @@ -59,12 +68,23 @@ TEST(BitcoinSigning, SignP2PKH) { utxo0->mutable_out_point()->set_sequence(UINT32_MAX); auto utxo1 = input.add_utxo(); - auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + auto utxo1Script = parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(600'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + return input; +} + +TEST(BitcoinSigning, SignP2PKH) { + auto input = buildInputP2PKH(); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + } // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -74,6 +94,8 @@ TEST(BitcoinSigning, SignP2PKH) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{228, 225, 226})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -86,6 +108,21 @@ TEST(BitcoinSigning, SignP2PKH) { ); } +TEST(BitcoinSigning, SignP2PKH_NegativeMissingKey) { + auto input = buildInputP2PKH(true); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + } + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_FALSE(result); +} + TEST(BitcoinSigning, EncodeP2WPKH) { auto unsignedTx = Transaction(1, 0x11); @@ -103,9 +140,10 @@ TEST(BitcoinSigning, EncodeP2WPKH) { auto outScript1 = Script(parse_hex("76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac")); unsignedTx.outputs.emplace_back(223450000, outScript1); - auto unsignedData = std::vector(); + Data unsignedData; unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" + ASSERT_EQ(unsignedData.size(), 160); + ASSERT_EQ(hex(unsignedData), "01000000" "02" "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffff" @@ -116,49 +154,67 @@ TEST(BitcoinSigning, EncodeP2WPKH) { "11000000"); } -TEST(BitcoinSigning, SignP2WPKH) { +Proto::SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int64_t utxo0Amount, int64_t utxo1Amount, bool useMaxAmount = false) { auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); // Setup input Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(335'790'000); + input.set_hash_type(hashType); + input.set_amount(amount); + input.set_use_max_amount(useMaxAmount); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); + assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + input.add_private_key(utxoKey1.bytes.data(), utxoKey1.bytes.size()); - auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); + auto scriptPub1 = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + Data scriptHash; scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + auto scriptHashHex = hex(scriptHash); + assert(scriptHashHex == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto redeemScript = Script::buildPayToPublicKeyHash(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); (*input.mutable_scripts())[scriptHashHex] = scriptString; auto utxo0 = input.add_utxo(); auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); utxo0->set_script(utxo0Script.data(), utxo0Script.size()); - utxo0->set_amount(625'000'000); + utxo0->set_amount(utxo0Amount); utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); auto utxo1 = input.add_utxo(); - auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + auto utxo1Script = parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); utxo1->set_script(utxo1Script.data(), utxo1Script.size()); - utxo1->set_amount(600'000'000); + utxo1->set_amount(utxo1Amount); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + return input; +} + +TEST(BitcoinSigning, SignP2WPKH) { + auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + } // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -171,6 +227,9 @@ TEST(BitcoinSigning, SignP2WPKH) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); + EXPECT_EQ(serialized.size(), 195); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -181,51 +240,33 @@ TEST(BitcoinSigning, SignP2WPKH) { "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" "0000000000" ); + + { + // Non-segwit encoded, for comparison + Data serialized; + signedTx.encode(false, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); + EXPECT_EQ(serialized.size(), 192); + ASSERT_EQ(hex(serialized), + "01000000" + "01" + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100b6006eb0fe2da8cbbd204f702b1ffdb1e29c49f3de51c4983d420bf9f9125635022032a195b153ccb2c4978333b4aad72aaa7e6a0b334a14621d5d817a42489cb0d301" "ffffffff" + "02" + "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" + ); + } } TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeSingle); - input.set_amount(335'790'000); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeSingle, 210'000'000, 210'000'000); - auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); - - auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHashHex] = scriptString; - - auto utxo0 = input.add_utxo(); - auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); - utxo0->set_script(utxo0Script.data(), utxo0Script.size()); - utxo0->set_amount(210'000'000); - utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); - utxo0->mutable_out_point()->set_index(0); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - - auto utxo1 = input.add_utxo(); - auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo1Script.data(), utxo1Script.size()); - utxo1->set_amount(210'000'000); - utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); - utxo1->mutable_out_point()->set_index(1); - utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 374)); + } // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -235,6 +276,8 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 233, 261})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -251,48 +294,13 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { } TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay); - input.set_amount(335'790'000); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeAnyoneCanPay, 210'000'000, 210'000'000); - auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); - - auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHashHex] = scriptString; - - auto utxo0 = input.add_utxo(); - auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); - utxo0->set_script(utxo0Script.data(), utxo0Script.size()); - utxo0->set_amount(210'000'000); - utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); - utxo0->mutable_out_point()->set_index(0); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - - auto utxo1 = input.add_utxo(); - auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo1Script.data(), utxo1Script.size()); - utxo1->set_amount(210'000'000); - utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); - utxo1->mutable_out_point()->set_index(1); - utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 374)); + } // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -302,6 +310,8 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 232, 260})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -317,6 +327,39 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { ); } +TEST(BitcoinSigning, SignP2WPKH_MaxAmount) { + auto input = buildInputP2WPKH(1'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000, true); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000, 600'000'000}, 1224999660, 340)); + } + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{309, 199, 227})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "02" + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100d173cb8d2f2c42824c49c316f2079e10c8a68cb434178ffa9d03f0081ab582ac02201a5e3874292f5452981f6914a7e8d8bb123b4af016eab91e4f25e38e8c9bdad001" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a01" "00000000" "ffffffff" + "01" + "ec02044900000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "0002" + "473044022058491ed1bee5072a3a4c8cc29a63eccf691de79df47fda7b768834c74a8ea19b022073ef4e72ceeec190133703a3f9eb9a15716ad0e489574469d5dab1a4ae360b010121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" + ); +} + TEST(BitcoinSigning, EncodeP2WSH) { auto unsignedTx = Transaction(1, 0); @@ -326,9 +369,9 @@ TEST(BitcoinSigning, EncodeP2WSH) { auto outScript0 = Script(parse_hex("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac")); unsignedTx.outputs.emplace_back(1000, outScript0); - auto unsignedData = std::vector(); + Data unsignedData; unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" + ASSERT_EQ(hex(unsignedData), "" "01000000" "01" "00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff" @@ -373,6 +416,12 @@ TEST(BitcoinSigning, SignP2WSH) { // Setup input const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -384,6 +433,8 @@ TEST(BitcoinSigning, SignP2WSH) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{196, 85, 113})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" "01" @@ -401,6 +452,12 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { // Setup input const auto input = buildInputP2WSH(TWBitcoinSigHashTypeNone); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -409,6 +466,8 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{197, 85, 113})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" "01" @@ -426,6 +485,12 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { // Setup input const auto input = buildInputP2WSH(TWBitcoinSigHashTypeSingle); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -434,6 +499,8 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{196, 85, 113})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" "01" @@ -451,6 +518,12 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { // Setup input const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAnyoneCanPay); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -459,6 +532,9 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(serialized.size(), 196); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{196, 85, 113})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000" "0001" "01" @@ -473,13 +549,30 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { } TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { - // Setup input, with omitted script const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, true); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_FALSE(result) << result.error(); + ASSERT_FALSE(result); +} + +TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { + // Setup input + auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + auto plan = Bitcoin::TransactionPlan(); + input.mutable_plan()->clear_utxos(); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_FALSE(result); } TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { @@ -494,9 +587,9 @@ TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { auto outScript1 = Script(parse_hex("76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac")); unsignedTx.outputs.emplace_back(800'000'000, outScript1); - auto unsignedData = std::vector(); + Data unsignedData; unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" + ASSERT_EQ(hex(unsignedData), "" "01000000" "01" "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff" @@ -518,24 +611,30 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - ASSERT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "79091972186c449eb1ded22b78e40d009bdf0089"); + ASSERT_EQ(hex(utxoPubkeyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - ASSERT_EQ(hex(scriptHash.begin(), scriptHash.end()), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + ASSERT_EQ(hex(scriptHash), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; auto utxo0 = input.add_utxo(); auto utxo0Script = Script(parse_hex("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387")); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(1000'000'000); + utxo0->set_amount(1'000'000'000); auto hash0 = DATA("db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477"); utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); @@ -547,6 +646,8 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { Data serialized; signedTx.encode(true, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{251, 142, 170})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); ASSERT_EQ(hex(serialized), "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089ffffffff0200c2eb0b000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac1e07af2f000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac02473044022009195d870ecc40f54130008e392904e77d32b738c1add19d1d8ebba4edf812e602204f49de6dc60d9a3c3703e1e642942f8834f3a2cd81a6562a34b293942ce42f40012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687300000000"); } @@ -563,9 +664,9 @@ TEST(BitcoinSigning, EncodeP2SH_P2WSH) { auto outScript1 = Script(parse_hex("76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac")); unsignedTx.outputs.emplace_back(0x00000000052f83c0, outScript1); - auto unsignedData = std::vector(); + Data unsignedData; unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" + ASSERT_EQ(hex(unsignedData), "" "01000000" "01" "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff" @@ -607,7 +708,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { auto redeemScript = Script::buildPayToWitnessScriptHash(parse_hex("a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54")); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; auto witnessScript = Script(parse_hex("" "56" @@ -621,7 +722,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { )); auto witnessScriptHash = Hash::ripemd(Hash::sha256(witnessScript.bytes)); auto witnessScriptString = std::string(witnessScript.bytes.begin(), witnessScript.bytes.end()); - (*input.mutable_scripts())[hex(witnessScriptHash.begin(), witnessScriptHash.end())] = witnessScriptString; + (*input.mutable_scripts())[hex(witnessScriptHash)] = witnessScriptString; auto utxo0Script = Script(parse_hex("a9149993a429037b5d912407a71c252019287b8d27a587")); auto utxo = input.add_utxo(); @@ -665,9 +766,11 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { "56ae" "00000000"; - auto serialized = std::vector(); + Data serialized; signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{800, 154, 316})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); + ASSERT_EQ(hex(serialized), expected); } TEST(BitcoinSigning, Sign_NegativeNoUtxos) { @@ -683,20 +786,26 @@ TEST(BitcoinSigning, Sign_NegativeNoUtxos) { input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); + Data scriptHash; scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + auto scriptHashHex = hex(scriptHash); ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); (*input.mutable_scripts())[scriptHashHex] = scriptString; + { + // plan returns empty, as there are 0 utxos + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {}, 0, 0)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); // Fails as there are 0 utxos - ASSERT_FALSE(result) << result.error(); + ASSERT_FALSE(result); } TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { @@ -718,9 +827,9 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { input.add_private_key(utxoKey1.data(), utxoKey1.size()); auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); + Data scriptHash; scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + auto scriptHashHex = hex(scriptHash); ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); @@ -743,8 +852,14 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { utxo1->mutable_out_point()->set_index(1); utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + } + // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_FALSE(result) << result.error(); + ASSERT_FALSE(result); } diff --git a/tests/Bitcoin/TWBitcoinTransactionTests.cpp b/tests/Bitcoin/TWBitcoinTransactionTests.cpp index 079f7377218..2cb1ec15ad0 100644 --- a/tests/Bitcoin/TWBitcoinTransactionTests.cpp +++ b/tests/Bitcoin/TWBitcoinTransactionTests.cpp @@ -32,9 +32,10 @@ TEST(BitcoinTransaction, Encode) { auto oscript1 = Script(parse_hex("0x76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac")); transaction.outputs.emplace_back(400000000, oscript1); - - auto unsignedData = std::vector(); + + Data unsignedData; transaction.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), "" + ASSERT_EQ(unsignedData.size(), 201); + ASSERT_EQ(hex(unsignedData), "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); } diff --git a/tests/Bitcoin/TransactionPlanTests.cpp b/tests/Bitcoin/TransactionPlanTests.cpp index aa41368a739..6bf27de3766 100644 --- a/tests/Bitcoin/TransactionPlanTests.cpp +++ b/tests/Bitcoin/TransactionPlanTests.cpp @@ -4,11 +4,12 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "TxComparisonHelper.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" -#include "Bitcoin/UnspentSelector.h" #include "Bitcoin/TransactionPlan.h" #include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/FeeCalculator.h" #include "proto/Bitcoin.pb.h" #include @@ -17,96 +18,278 @@ using namespace TW; using namespace TW::Bitcoin; -auto const txOutPoint = OutPoint(std::vector(32), 0); +TEST(TransactionPlan, OneTypical) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto sigingInput = buildSigningInput(50'000, byteFee, utxos); -inline auto sum(const std::vector& utxos) { - int64_t s = 0u; - for (auto& utxo : utxos) { - s += utxo.amount(); - } - return s; + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 226)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 226); +} + +TEST(TransactionPlan, OneInsufficient) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(200'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); } -inline auto buildUTXO(const OutPoint& outPoint, Amount amount) { - Proto::UnspentTransaction utxo; - utxo.set_amount(amount); - utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); - utxo.mutable_out_point()->set_index(outPoint.index); - return utxo; +TEST(TransactionPlan, OneInsufficientEqual) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(100'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, OneInsufficientHigher) { + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(99'900, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, OneFitsExactly) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 1; + auto expectedFee = 226; + auto sigingInput = buildSigningInput(100'000 - expectedFee, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); +} + +TEST(TransactionPlan, OneFitsExactlyHighFee) { + auto utxos = buildTestUTXOs({100'000}); + auto byteFee = 10; + auto expectedFee = 2260; + auto sigingInput = buildSigningInput(100'000 - expectedFee, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); +} + +TEST(TransactionPlan, TwoFirstEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + auto sigingInput = buildSigningInput(15'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {20'000}, 15'000, 226)); +} + +TEST(TransactionPlan, TwoSecondEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + auto sigingInput = buildSigningInput(70'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {80'000}, 70'000, 226)); +} + +TEST(TransactionPlan, TwoBoth) { + auto utxos = buildTestUTXOs({20'000, 80'000}); + auto sigingInput = buildSigningInput(90'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {20'000, 80'000}, 90'000, 374)); +} + +TEST(TransactionPlan, TwoFirstEnoughButSecond) { + auto utxos = buildTestUTXOs({20'000, 22'000}); + auto sigingInput = buildSigningInput(18'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {22'000}, 18'000, 226)); +} + +TEST(TransactionPlan, ThreeNoDust) { + auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); + auto sigingInput = buildSigningInput(100'000 - 226 - 10, 1, utxos); + + // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 10, 374)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(feeCalculator.calculate(2, 2, 1), 374); + + // Now 100'000 fits with no dust; 546 is the dust limit + sigingInput = buildSigningInput(100'000 - 226 - 546, 1, utxos); + txPlan = TransactionBuilder::plan(sigingInput); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 226 - 546, 226)); + + // One more and we are over dust limit + sigingInput = buildSigningInput(100'000 - 226 - 546 + 1, 1, utxos); + txPlan = TransactionBuilder::plan(sigingInput); + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 546 + 1, 374)); } -inline auto buildSigningInput(Amount amount, int byteFee, const std::vector& utxos, bool useMaxAmount, enum TWCoinType coin) { - Proto::SigningInput input; - input.set_amount(amount); - input.set_byte_fee(byteFee); - input.set_use_max_amount(useMaxAmount); - input.set_coin_type(coin); - *input.mutable_utxo() = { utxos.begin(), utxos.end() }; - return input; +TEST(TransactionPlan, TenThree) { + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); + auto sigingInput = buildSigningInput(300'000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {100'000, 125'000, 150'000}, 300'000, 522)); } TEST(TransactionPlan, NonMaxAmount) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, 4000)); - utxos.push_back(buildUTXO(txOutPoint, 2000)); - utxos.push_back(buildUTXO(txOutPoint, 6000)); - utxos.push_back(buildUTXO(txOutPoint, 1000)); - utxos.push_back(buildUTXO(txOutPoint, 50000)); - utxos.push_back(buildUTXO(txOutPoint, 120000)); + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); + auto sigingInput = buildSigningInput(10000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {50000}, 10000, 226)); +} + +TEST(TransactionPlan, UnpsentsInsufficient) { + auto utxos = buildTestUTXOs({4000, 4000, 4000}); + auto sigingInput = buildSigningInput(15000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, NoUTXOs) { + auto utxos = buildTestUTXOs({}); + auto sigingInput = buildSigningInput(15000, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); +} + +TEST(TransactionPlan, CustomCase) { + auto utxos = buildTestUTXOs({794121, 2289357}); + auto byteFee = 61; + auto sigingInput = buildSigningInput(2287189, byteFee, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {794121, 2289357}, 2287189, 22814)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(2, 2, byteFee), 22814); +} + +TEST(TransactionPlan, Target0) { + auto utxos = buildTestUTXOs({2000, 3000}); + auto sigingInput = buildSigningInput(0, 1, utxos); - auto sigingInput = buildSigningInput(10000, 1, utxos, false, TWCoinTypeBitcoin); auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.amount, 10000); - ASSERT_EQ(txPlan.change, 39774); + EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0)); } TEST(TransactionPlan, MaxAmount) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, 4000)); - utxos.push_back(buildUTXO(txOutPoint, 2000)); - utxos.push_back(buildUTXO(txOutPoint, 15000)); - utxos.push_back(buildUTXO(txOutPoint, 15000)); - utxos.push_back(buildUTXO(txOutPoint, 3000)); - utxos.push_back(buildUTXO(txOutPoint, 200)); + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 32; + auto sigingInput = buildSigningInput(39200, byteFee, utxos, true); - ASSERT_EQ(sum(utxos), 39200); + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4736); - auto sigingInput = buildSigningInput(39200, 32, utxos, true, TWCoinTypeBitcoin); + // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.availableAmount, 30000); - ASSERT_EQ(txPlan.amount, 19120); - ASSERT_EQ(txPlan.change, 0); - ASSERT_EQ(txPlan.fee, 10880); + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 19120, 10880)); } -TEST(TransactionPlan, MaxAmountDoge) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, Amount(100000000))); - utxos.push_back(buildUTXO(txOutPoint, Amount(2000000000))); - utxos.push_back(buildUTXO(txOutPoint, Amount(200000000))); +TEST(TransactionPlan, MaxAmountOne) { + auto utxos = buildTestUTXOs({10189534}); + auto sigingInput = buildSigningInput(100, 1, utxos, true); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 192; + EXPECT_TRUE(verifyPlan(txPlan, {10189534}, 10189534 - expectedFee, expectedFee)); +} + +TEST(TransactionPlan, MaxAmountLowerRequested) { + auto utxos = buildTestUTXOs({4000, 2000, 15000, 15000, 3000, 200}); + ASSERT_EQ(sumUTXOs(utxos), 39200); + auto byteFee = 32; + auto sigingInput = buildSigningInput(10, byteFee, utxos, true); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 4736); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 19120, 10880)); +} + +TEST(TransactionPlan, MaxAmount4of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + auto byteFee = 3; + auto sigingInput = buildSigningInput(100, byteFee, utxos, true); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto expectedFee = 1908; + EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1000}, 2'900 - expectedFee, expectedFee)); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 444); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), expectedFee); +} + +TEST(TransactionPlan, One_MaxAmount_FeeMoreThanAvailable) { + auto utxos = buildTestUTXOs({170}); + auto byteFee = 1; + auto expectedFee = 192; + auto sigingInput = buildSigningInput(300, byteFee, utxos, true); + + auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(sum(utxos), Amount(2300000000)); + // Fee is reduced to availableAmount + EXPECT_TRUE(verifyPlan(txPlan, {170}, 0, 170)); + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), expectedFee); +} + +TEST(TransactionPlan, MaxAmountDoge) { + auto utxos = buildTestUTXOs({Amount(100000000), Amount(2000000000), Amount(200000000)}); + ASSERT_EQ(sumUTXOs(utxos), Amount(2300000000)); auto sigingInput = buildSigningInput(Amount(2300000000), 100, utxos, true, TWCoinTypeDogecoin); + auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.availableAmount, Amount(2300000000)); - ASSERT_EQ(txPlan.amount, Amount(2299951200)); - ASSERT_EQ(txPlan.change, 0); - ASSERT_EQ(txPlan.fee, 48800); + EXPECT_TRUE(verifyPlan(txPlan, {100000000, 2000000000, 200000000}, 2299951200, 48800)); } TEST(TransactionPlan, AmountDecred) { - auto utxos = std::vector(); - utxos.push_back(buildUTXO(txOutPoint, Amount(39900000))); - + auto utxos = buildTestUTXOs({Amount(39900000)}); auto sigingInput = buildSigningInput(Amount(10000000), 10, utxos, false, TWCoinTypeDecred); + auto txPlan = TransactionBuilder::plan(sigingInput); - ASSERT_EQ(txPlan.availableAmount, Amount(39900000)); - ASSERT_EQ(txPlan.amount, Amount(10000000)); - ASSERT_EQ(txPlan.change, 29897460); - ASSERT_EQ(txPlan.fee, 2540); + EXPECT_TRUE(verifyPlan(txPlan, {39900000}, 10000000, 2540)); } diff --git a/tests/Bitcoin/TxComparisonHelper.cpp b/tests/Bitcoin/TxComparisonHelper.cpp new file mode 100644 index 00000000000..66cfe17d728 --- /dev/null +++ b/tests/Bitcoin/TxComparisonHelper.cpp @@ -0,0 +1,153 @@ +// Copyright © 2017-2020 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. + +#include "TxComparisonHelper.h" + +#include + +#include "Bitcoin/OutPoint.h" +#include "proto/Bitcoin.pb.h" +#include "Data.h" + +#include +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +auto emptyTxOutPoint = OutPoint(Data(32), 0); + +Proto::UnspentTransaction buildTestUTXO(int64_t amount) { + Proto::UnspentTransaction utxo; + utxo.set_amount(amount); + const auto& outPoint = emptyTxOutPoint; + utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); + utxo.mutable_out_point()->set_index(outPoint.index); + return utxo; +} + +std::vector buildTestUTXOs(const std::vector& amounts) { + std::vector utxos; + for (auto it = amounts.begin(); it != amounts.end(); it++) { + utxos.push_back(buildTestUTXO(*it)); + } + return utxos; +} + +Proto::SigningInput buildSigningInput(Amount amount, int byteFee, const std::vector& utxos, bool useMaxAmount, enum TWCoinType coin) { + Proto::SigningInput input; + input.set_amount(amount); + input.set_byte_fee(byteFee); + input.set_use_max_amount(useMaxAmount); + input.set_coin_type(coin); + *input.mutable_utxo() = { utxos.begin(), utxos.end() }; + return input; +} + +int64_t sumUTXOs(const std::vector& utxos) { + int64_t s = 0u; + for (auto& utxo: utxos) { + s += utxo.amount(); + } + return s; +} + +bool verifySelectedUTXOs(const std::vector& selected, const std::vector& expectedAmounts) { + bool ret = true; + if (selected.size() != expectedAmounts.size()) { + ret = false; + std::cerr << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; + } + for (auto i = 0; i < selected.size() && i < expectedAmounts.size(); ++i) { + if (expectedAmounts[i] != selected[i].amount()) { + ret = false; + std::cerr << "Wrong UTXOs amount, pos " << i << " amount " << selected[i].amount() << " expected " << expectedAmounts[i] << std::endl; + } + } + return ret; +} + +bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmounts, int64_t outputAmount, int64_t fee) { + bool ret = true; + if (!verifySelectedUTXOs(plan.utxos, utxoAmounts)) { + ret = false; + } + if (plan.amount != outputAmount) { + ret = false; + std::cerr << "Mismatch in amount, act " << plan.amount << ", exp " << outputAmount << std::endl; + } + if (plan.fee != fee) { + ret = false; + std::cerr << "Mismatch in fee, act " << plan.fee << ", exp " << fee << std::endl; + } + int64_t sumExpectedUTXOs = 0; + for (auto i = 0; i < utxoAmounts.size(); ++i) { + sumExpectedUTXOs += utxoAmounts[i]; + } + if (plan.availableAmount != sumExpectedUTXOs) { + ret = false; + std::cerr << "Mismatch in availableAmount, act " << plan.availableAmount << ", exp " << sumExpectedUTXOs << std::endl; + } + int64_t expectedChange = sumExpectedUTXOs - outputAmount - fee; + if (plan.change != expectedChange) { + ret = false; + std::cerr << "Mismatch in change, act " << plan.change << ", exp " << expectedChange << std::endl; + } + return ret; +} + +bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2) { + return s1.virtualBytes == s2.virtualBytes && s1.segwit == s2.segwit && s1.nonSegwit == s2.nonSegwit; +} + +EncodedTxSize getEncodedTxSize(const Transaction& tx) { + EncodedTxSize size; + { // full segwit size + Data data; + tx.encode(true, data); + size.segwit = data.size(); + } + { // witness part only + Data data; + tx.encode(false, data); + size.nonSegwit = data.size(); + } + int64_t witnessSize = 0; + { // double check witness part: witness plus 2 bytes is the difference between segwit and non-segwit size + Data data; + tx.encodeWitness(data); + witnessSize = data.size(); + assert(size.segwit - size.nonSegwit == 2 + witnessSize); + } + // compute virtual size: 3/4 of (smaller) non-segwit + 1/4 of segwit size + uint64_t sum = size.nonSegwit * 3 + size.segwit; + size.virtualBytes = sum / 4 + (sum % 4 != 0); + // alternative computation: (smaller) non-segwit + 1/4 of the diff (witness-only) + uint64_t vSize2 = size.nonSegwit + (witnessSize + 2)/ 4 + ((witnessSize + 2) % 4 != 0); + assert(size.virtualBytes == vSize2); + return size; +} + +bool validateEstimatedSize(const Transaction& tx, int smallerTolerance, int biggerTolerance) { + if (tx.previousEstimatedVirtualSize == 0) { + // no estimated size, do nothing + return true; + } + bool ret = true; + auto estSize = tx.previousEstimatedVirtualSize; + uint64_t vsize = getEncodedTxSize(tx).virtualBytes; + int64_t diff = estSize - vsize; + if (diff < smallerTolerance) { + ret = false; + std::cerr << "Estimated size too small! " << std::to_string(estSize) << " vs. " << std::to_string(vsize) << std::endl; + } + if (diff > biggerTolerance) { + ret = false; + std::cerr << "Estimated size too big! " << std::to_string(estSize) << " vs. " << std::to_string(vsize) << std::endl; + } + return ret; +} diff --git a/tests/Bitcoin/TxComparisonHelper.h b/tests/Bitcoin/TxComparisonHelper.h new file mode 100644 index 00000000000..e3faf85c1f9 --- /dev/null +++ b/tests/Bitcoin/TxComparisonHelper.h @@ -0,0 +1,54 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "Bitcoin/Amount.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionPlan.h" +#include "proto/Bitcoin.pb.h" +#include + +#include +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +/// Build a dummy UTXO with the given amount +Proto::UnspentTransaction buildTestUTXO(int64_t amount); + +/// Build a set of dummy UTXO with the given amounts +std::vector buildTestUTXOs(const std::vector& amounts); + +Proto::SigningInput buildSigningInput(Amount amount, int byteFee, const std::vector& utxos, + bool useMaxAmount = false, enum TWCoinType coin = TWCoinTypeBitcoin); + +/// Compare a set of selected UTXOs to the expected set of amounts. +/// Returns false on mismatch, and error is printed (stderr). +bool verifySelectedUTXOs(const std::vector& selected, const std::vector& expectedAmounts); + +/// Compare a transaction plan against expected values (UTXO amounts, amount, fee, change is implicit). +/// Returns false on mismatch, and error is printed (stderr). +bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmounts, int64_t outputAmount, int64_t fee); + +int64_t sumUTXOs(const std::vector& utxos); + +struct EncodedTxSize { + uint64_t segwit; + uint64_t nonSegwit; + uint64_t virtualBytes; +}; +bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2); + +/// Return the encoded size of the transaction, virtual and non-segwit, etc. +EncodedTxSize getEncodedTxSize(const Transaction& tx); + +/// Validate the previously estimated transaction size (if available) with the actual transaction size. +/// Uses segwit byte size (virtual size). Tolerance is estiamte-smaller and estimate-larger, like -1 and 20. +/// Returns false on mismatch, and error is printed (stderr). +bool validateEstimatedSize(const Transaction& tx, int smallerTolerance = -1, int biggerTolerance = 20); diff --git a/tests/Bitcoin/UnspentSelectorTests.cpp b/tests/Bitcoin/UnspentSelectorTests.cpp index cbc07018be3..57c8ce56e90 100644 --- a/tests/Bitcoin/UnspentSelectorTests.cpp +++ b/tests/Bitcoin/UnspentSelectorTests.cpp @@ -6,348 +6,375 @@ #include +#include "TxComparisonHelper.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" #include "Bitcoin/UnspentSelector.h" #include "proto/Bitcoin.pb.h" #include +#include using namespace TW; using namespace TW::Bitcoin; -auto transactionOutPoint = OutPoint(std::vector(32), 0); - -inline auto sum(const std::vector& utxos) { - int64_t s = 0u; - for (auto& utxo : utxos) { - s += utxo.amount(); - } - return s; -} - -inline auto buildUTXO(int64_t amount) { - Proto::UnspentTransaction utxo; - utxo.set_amount(amount); - const auto& outPoint = transactionOutPoint; - utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); - utxo.mutable_out_point()->set_index(outPoint.index); - return utxo; -} - -void buildUTXOs(std::vector& utxos, const std::vector& amounts) { - for (auto it = amounts.begin(); it != amounts.end(); it++) { - utxos.push_back(buildUTXO(*it)); - } -} - -bool verifySelected(const std::vector& selected, const std::vector& expectedAmounts) { - if (selected.size() != expectedAmounts.size()) { - std::cout << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; - return false; - } - auto n = expectedAmounts.size(); - for (auto i = 0; i < n; ++i) { - if (expectedAmounts[i] != selected[i].amount()) { - std::cout << "Wrong UTXOs amount, pos " << i << " amount " << selected[i].amount() << " expected " << expectedAmounts[i] << std::endl; - return false; - } - } - return true; -} - -TEST(UnspentSelector, SelectUnpsents1) { - auto utxos = std::vector(); - buildUTXOs(utxos, {4000, 2000, 6000, 1000, 11000, 12000}); +TEST(BitcoinUnspentSelector, SelectUnpsents1) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 11000, 12000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 5000, 1); - ASSERT_TRUE(verifySelected(selected, {11000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {11000})); } -TEST(UnspentSelector, SelectUnpsents2) { - auto utxos = std::vector(); - buildUTXOs(utxos, {4000, 2000, 6000, 1000, 50000, 120000}); +TEST(BitcoinUnspentSelector, SelectUnpsents2) { + auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 50000, 120000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 10000, 1); - ASSERT_TRUE(verifySelected(selected, {50000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {50000})); } -TEST(UnspentSelector, SelectUnpsents3) { - auto utxos = std::vector(); - buildUTXOs(utxos, {4000, 2000, 5000}); +TEST(BitcoinUnspentSelector, SelectUnpsents3) { + auto utxos = buildTestUTXOs({4000, 2000, 5000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 6000, 1); - ASSERT_TRUE(verifySelected(selected, {4000, 5000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {4000, 5000})); } -TEST(UnspentSelector, SelectUnpsents4) { - auto utxos = std::vector(); - buildUTXOs(utxos, {40000, 30000, 30000}); +TEST(BitcoinUnspentSelector, SelectUnpsents4) { + auto utxos = buildTestUTXOs({40000, 30000, 30000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 50000, 1); - ASSERT_EQ(sum(selected), 70000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {30000, 40000})); } -TEST(UnspentSelector, SelectUnpsents5) { - auto utxos = std::vector(); - buildUTXOs(utxos, {1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); +TEST(BitcoinUnspentSelector, SelectUnpsents5) { + auto utxos = buildTestUTXOs({1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 28000, 1); - ASSERT_EQ(sum(selected), 30000); + EXPECT_TRUE(verifySelectedUTXOs(selected, {6000, 7000, 8000, 9000})); } -TEST(UnspentSelector, SelectUnpsentsInsufficient) { - auto utxos = std::vector(); - buildUTXOs(utxos, {4000, 4000, 4000}); +TEST(BitcoinUnspentSelector, SelectUnpsentsInsufficient) { + auto utxos = buildTestUTXOs({4000, 4000, 4000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 15000, 1); - ASSERT_TRUE(selected.empty()); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectCustomCase) { - auto utxos = std::vector(); - buildUTXOs(utxos, {794121, 2289357}); +TEST(BitcoinUnspentSelector, SelectCustomCase) { + auto utxos = buildTestUTXOs({794121, 2289357}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 2287189, 61); - ASSERT_EQ(sum(selected), 3083478); + EXPECT_TRUE(verifySelectedUTXOs(selected, {794121, 2289357})); } -TEST(UnspentSelector, SelectNegativeNoUtxo) { - auto utxos = std::vector(); - buildUTXOs(utxos, {}); +TEST(BitcoinUnspentSelector, SelectNegativeNoUTXOs) { + auto utxos = buildTestUTXOs({}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 100000, 1); - ASSERT_TRUE(verifySelected(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectNegativeTarget0) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000}); +TEST(BitcoinUnspentSelector, SelectNegativeTarget0) { + auto utxos = buildTestUTXOs({100'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 0, 1); - ASSERT_TRUE(verifySelected(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectOneTypical) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000}); +TEST(BitcoinUnspentSelector, SelectOneTypical) { + auto utxos = buildTestUTXOs({100'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 50'000, 1); - ASSERT_TRUE(verifySelected(selected, {100'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); } -TEST(UnspentSelector, SelectOneInsufficient) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000}); +TEST(BitcoinUnspentSelector, SelectOneInsufficient) { + auto utxos = buildTestUTXOs({100'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 200'000, 1); - ASSERT_TRUE(verifySelected(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectOneInsufficientEqual) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000}); +TEST(BitcoinUnspentSelector, SelectOneInsufficientEqual) { + auto utxos = buildTestUTXOs({100'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 100'000, 1); - ASSERT_TRUE(verifySelected(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectOneInsufficientHigher) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000}); +TEST(BitcoinUnspentSelector, SelectOneInsufficientHigher) { + auto utxos = buildTestUTXOs({100'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 99'900, 1); - ASSERT_TRUE(verifySelected(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectOneFitsExactly) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000}); +TEST(BitcoinUnspentSelector, SelectOneFitsExactly) { + auto utxos = buildTestUTXOs({100'000}); - auto selector = UnspentSelector(); - auto selected = selector.select(utxos, 100'000 - 192, 1); // shuold be 226 + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto expectedFee = 226; + auto selected = selector.select(utxos, 100'000 - expectedFee, 1); - ASSERT_TRUE(verifySelected(selected, {100'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); - ASSERT_EQ(selector.calculator.calculate(1, 2, 1), 226); - ASSERT_EQ(selector.calculator.calculate(1, 1, 1), 192); + ASSERT_EQ(feeCalculator.calculate(1, 2, 1), expectedFee); + ASSERT_EQ(feeCalculator.calculate(1, 1, 1), 192); // 1 sat more and does not fit any more - selected = selector.select(utxos, 100'000 - 192 + 1, 1); + selected = selector.select(utxos, 100'000 - expectedFee + 1, 1); - ASSERT_TRUE(verifySelected(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectThreeNoDust) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100'000, 70'000, 75'000}); +TEST(BitcoinUnspentSelector, SelectOneFitsExactlyHighfee) { + auto utxos = buildTestUTXOs({100'000}); + + const auto byteFee = 10; + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto expectedFee = 2260; + auto selected = selector.select(utxos, 100'000 - expectedFee, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); + + ASSERT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); + ASSERT_EQ(feeCalculator.calculate(1, 1, byteFee), 1920); + + // 1 sat more and does not fit any more + selected = selector.select(utxos, 100'000 - expectedFee + 1, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); +} + +TEST(BitcoinUnspentSelector, SelectThreeNoDust) { + auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 100'000 - 226 - 10, 1); // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust - ASSERT_TRUE(verifySelected(selected, {75'000, 100'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {75'000, 100'000})); // Now 100'000 fits with no dust; 546 is the dust limit selected = selector.select(utxos, 100'000 - 226 - 546, 1); - ASSERT_TRUE(verifySelected(selected, {100'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); // One more and we are over dust limit selected = selector.select(utxos, 100'000 - 226 - 546 + 1, 1); - ASSERT_TRUE(verifySelected(selected, {75'000, 100'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {75'000, 100'000})); } -TEST(UnspentSelector, SelectTwoFirstEnough) { - auto utxos = std::vector(); - buildUTXOs(utxos, {20'000, 80'000}); +TEST(BitcoinUnspentSelector, SelectTwoFirstEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 15'000, 1); - ASSERT_TRUE(verifySelected(selected, {20'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {20'000})); } -TEST(UnspentSelector, SelectTwoSecondEnough) { - auto utxos = std::vector(); - buildUTXOs(utxos, {20'000, 80'000}); +TEST(BitcoinUnspentSelector, SelectTwoSecondEnough) { + auto utxos = buildTestUTXOs({20'000, 80'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 70'000, 1); - ASSERT_TRUE(verifySelected(selected, {80'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {80'000})); } -TEST(UnspentSelector, SelectTwoBoth) { - auto utxos = std::vector(); - buildUTXOs(utxos, {20'000, 80'000}); +TEST(BitcoinUnspentSelector, SelectTwoBoth) { + auto utxos = buildTestUTXOs({20'000, 80'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 90'000, 1); - ASSERT_TRUE(verifySelected(selected, {20'000, 80'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {20'000, 80'000})); } -TEST(UnspentSelector, SelectTwoFirstEnoughButSecond) { - auto utxos = std::vector(); - buildUTXOs(utxos, {20'000, 22'000}); +TEST(BitcoinUnspentSelector, SelectTwoFirstEnoughButSecond) { + auto utxos = buildTestUTXOs({20'000, 22'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 18'000, 1); - ASSERT_TRUE(verifySelected(selected, {22'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {22'000})); } -TEST(UnspentSelector, SelectTenThree) { - auto utxos = std::vector(); - buildUTXOs(utxos, {1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); +TEST(BitcoinUnspentSelector, SelectTenThree) { + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); auto selector = UnspentSelector(); auto selected = selector.select(utxos, 300'000, 1); - ASSERT_TRUE(verifySelected(selected, {100'000, 125'000, 150'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); } -TEST(UnspentSelector, SelectTenThreeExact) { - auto utxos = std::vector(); - buildUTXOs(utxos, {1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); +TEST(BitcoinUnspentSelector, SelectTenThreeExact) { + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); - auto selector = UnspentSelector(); + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); auto selected = selector.select(utxos, 375'000 - 522 - 546, 1); - ASSERT_TRUE(verifySelected(selected, {100'000, 125'000, 150'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000, 125'000, 150'000})); - auto fee = selector.calculator.calculate(3, 2, 1); - ASSERT_EQ(fee, 522); + ASSERT_EQ(feeCalculator.calculate(3, 2, 1), 522); // one more, and it's too much selected = selector.select(utxos, 375'000 - 522 - 546 + 1, 1); - ASSERT_TRUE(verifySelected(selected, {7'000, 100'000, 125'000, 150'000})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {7'000, 100'000, 125'000, 150'000})); } -TEST(UnspentSelector, SelectMaxCase) { - auto utxos = std::vector(); - buildUTXOs(utxos, {10189534}); +TEST(BitcoinUnspentSelector, SelectMaxAmountOne) { + auto utxos = buildTestUTXOs({10189534}); - auto selector = UnspentSelector(); + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto selected = selector.selectMaxAmount(utxos, 1); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); + + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); +} + +TEST(BitcoinUnspentSelector, SelectAllAvail) { + auto utxos = buildTestUTXOs({10189534}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); auto selected = selector.select(utxos, 10189534 - 226, 1); - ASSERT_TRUE(verifySelected(selected, {10189534})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); + + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmount5of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto byteFee = 1; + auto selected = selector.selectMaxAmount(utxos, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {400, 500, 600, 800, 1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 148); + EXPECT_EQ(feeCalculator.calculate(5, 1, byteFee), 784); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmount4of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto byteFee = 3; + auto selected = selector.selectMaxAmount(utxos, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {500, 600, 800, 1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 444); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1908); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmount1of5) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto byteFee = 6; + auto selected = selector.selectMaxAmount(utxos, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {1000})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 888); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 1152); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmountNone) { + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto byteFee = 10; + auto selected = selector.selectMaxAmount(utxos, byteFee); + + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + + EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 1480); +} + +TEST(BitcoinUnspentSelector, SelectMaxAmountNoUTXOs) { + auto utxos = buildTestUTXOs({}); + + auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + auto selector = UnspentSelector(feeCalculator); + auto selected = selector.selectMaxAmount(utxos, 1); - auto fee = selector.calculator.calculate(1, 2, 1); - ASSERT_EQ(fee, 226); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } -TEST(UnspentSelector, SelectZcashUnpsents) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100000, 2592, 73774}); +TEST(BitcoinUnspentSelector, SelectZcashUnpsents) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); - auto selector = UnspentSelector(calculator); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); auto selected = selector.select(utxos, 10000, 1); - ASSERT_EQ(sum(selected), 73774); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {73774})); } -TEST(UnspentSelector, SelectGroestlUnpsents) { - auto utxos = std::vector(); - buildUTXOs(utxos, {499971976}); +TEST(BitcoinUnspentSelector, SelectGroestlUnpsents) { + auto utxos = buildTestUTXOs({499971976}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeGroestlcoin); - auto selector = UnspentSelector(calculator); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); auto selected = selector.select(utxos, 499951976, 1, 1); - ASSERT_EQ(sum(selected), 499971976); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {499971976})); } -TEST(UnspentSelector, SelectZcashMaxUnpsents) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100000, 2592, 73774}); +TEST(BitcoinUnspentSelector, SelectZcashMaxAmount) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); - auto selector = UnspentSelector(calculator); - auto selected = selector.select(utxos, 166366, 1); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); + auto selected = selector.selectMaxAmount(utxos, 1); - ASSERT_EQ(sum(selected), 176366); - ASSERT_TRUE(selected.size() > 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100000, 2592, 73774})); } -TEST(UnspentSelector, SelectZcashMaxUnpsents2) { - auto utxos = std::vector(); - buildUTXOs(utxos, {100000, 2592, 73774}); +TEST(BitcoinUnspentSelector, SelectZcashMaxUnpsents2) { + auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto calculator = UnspentCalculator::getCalculator(TWCoinTypeZcash); - auto selector = UnspentSelector(calculator); - auto selected = selector.select(utxos, 176360, 1); + auto selector = UnspentSelector(getFeeCalculator(TWCoinTypeZcash)); + auto selected = selector.select(utxos, 176366 - 6, 1); - ASSERT_EQ(sum(selected), 0); - ASSERT_TRUE(selected.size() == 0); + EXPECT_TRUE(verifySelectedUTXOs(selected, {})); } From cbbf151927838b77fa13291d704f3931b979c684 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 29 May 2020 04:48:41 +0800 Subject: [PATCH 26/81] bump ndk version (#968) --- .github/workflows/android-ci.yml | 2 +- android/app/build.gradle | 2 +- android/trustwalletcore/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index c38c0dfdf7f..f4bf5297e48 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -17,7 +17,7 @@ jobs: run: brew install boost ninja - name: Install Android Dependencies run: | - $ANDROID_HOME/tools/bin/sdkmanager --verbose "cmake;3.10.2.4988404" "ndk-bundle" + $ANDROID_HOME/tools/bin/sdkmanager --verbose "cmake;3.10.2.4988404" "ndk;21.2.6472646" $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-26;google_apis;x86" - name: Accept Licenses run: echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/tools/bin/sdkmanager --licenses diff --git a/android/app/build.gradle b/android/app/build.gradle index 03b4d26e54b..ba02131f006 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 28 - ndkVersion '21.1.6352462' + ndkVersion '21.2.6472646' defaultConfig { applicationId "com.trustwallet.core.app" minSdkVersion 23 diff --git a/android/trustwalletcore/build.gradle b/android/trustwalletcore/build.gradle index 145624ca9a6..6a24810e5e7 100644 --- a/android/trustwalletcore/build.gradle +++ b/android/trustwalletcore/build.gradle @@ -4,7 +4,7 @@ group='com.github.trustwallet' android { compileSdkVersion 28 - ndkVersion '21.1.6352462' + ndkVersion '21.2.6472646' defaultConfig { minSdkVersion 23 targetSdkVersion 28 From 6a4a1bf47b4f6ec291fd85b8a262b2aa67e2e9a9 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Fri, 29 May 2020 02:02:50 +0200 Subject: [PATCH 27/81] TransferTRC20 contract amount is 256 bits (#970) * Field TransferTRC20Contract.amount is 256 bits (not 64). * Add Android test. Co-authored-by: Catenocrypt --- .../tron/TestTronTransactionSigner.kt | 53 +++++++++++++++++++ src/Tron/Signer.cpp | 4 +- src/proto/Tron.proto | 4 +- tests/Tron/SerializationTests.cpp | 35 +++++++++++- tests/Tron/SignerTests.cpp | 4 +- 5 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt new file mode 100644 index 00000000000..254f79bb860 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt @@ -0,0 +1,53 @@ +package com.trustwallet.core.app.blockchains.tron + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.proto.Tron +import wallet.core.jni.proto.Tron.SigningOutput +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertArrayEquals +import wallet.core.jni.CoinType.TRON + +class TestTronTransactionSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignTransferTrc20Contract() { + val trc20Contract = Tron.TransferTRC20Contract.newBuilder() + .setOwnerAddress("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC") + .setContractAddress("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV") + .setToAddress("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z") + // 1000 + .setAmount(ByteString.copyFrom("0x00000000000000000000000000000000000000000000000000000000000003e8".toHexByteArray())) + + val blockHeader = Tron.BlockHeader.newBuilder() + .setTimestamp(1539295479000) + .setTxTrieRoot(ByteString.copyFrom("0x64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d".toHexByteArray())) + .setParentHash(ByteString.copyFrom("0x00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d".toHexByteArray())) + .setNumber(3111739) + .setWitnessAddress(ByteString.copyFrom("0x415863f6091b8e71766da808b1dd3159790f61de7d".toHexByteArray())) + .setVersion(3) + .build() + + val transaction = Tron.Transaction.newBuilder() + .setTransferTrc20Contract(trc20Contract) + .setTimestamp(1539295479000) + .setBlockHeader(blockHeader) + .build() + + val signingInput = Tron.SigningInput.newBuilder() + .setTransaction(transaction) + .setPrivateKey(ByteString.copyFrom("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54".toHexByteArray())) + + val output = AnySigner.sign(signingInput.build(), TRON, Tron.SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.id.toByteArray()), "0x0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058") + assertEquals(Numeric.toHexString(output.signature.toByteArray()), "0xbec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01") + } +} diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index 1838bf30fc4..3005227d6c7 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -157,8 +157,8 @@ protocol::TriggerSmartContract to_internal(const Proto::TriggerSmartContract& tr protocol::TriggerSmartContract to_internal(const Proto::TransferTRC20Contract& transferTrc20Contract) { auto toAddress = Base58::bitcoin.decodeCheck(transferTrc20Contract.to_address()); - Data amount; - encode64BE(transferTrc20Contract.amount(), amount); + // amount is 256 bits, big endian + Data amount = data(transferTrc20Contract.amount()); // Encode smart contract call parameters auto contract_params = parse_hex(TRANSFER_TOKEN_FUNCTION); diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index f2921e368b8..3a6b635b169 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -38,8 +38,8 @@ message TransferTRC20Contract { // Recipient address. string to_address = 3; - // Amount to send. - int64 amount = 4; + // Amount to send, uint256, big-endian. + bytes amount = 4; } message FreezeBalanceContract { diff --git a/tests/Tron/SerializationTests.cpp b/tests/Tron/SerializationTests.cpp index dd54363b9fc..a32b5799172 100644 --- a/tests/Tron/SerializationTests.cpp +++ b/tests/Tron/SerializationTests.cpp @@ -8,6 +8,7 @@ #include "Tron/Signer.h" #include "PrivateKey.h" #include "HexCoding.h" +#include "uint256.h" #include @@ -151,7 +152,8 @@ namespace TW::Tron { transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - transfer_contract.set_amount(1000); + Data amount = store(uint256_t(1000)); + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); transaction.set_timestamp(1539295479000); @@ -173,4 +175,35 @@ namespace TW::Tron { ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"a9059cbb000000000000000000000041dbd7c53729b3310e1843083000fa84abad99696100000000000000000000000000000000000000000000000000000000000003e8","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db"}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["bec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01"],"txID":"0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058"})"); } + + TEST(TronSerialization, SignTransferTrc20Contract_LargeAmount) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); + transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); + Data amount = store(uint256_t("10000000000000000000000")); // over 64 bits, corresponds to 10000 in case of 18 decimals + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); + + transaction.set_timestamp(1539295479000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"a9059cbb000000000000000000000041dbd7c53729b3310e1843083000fa84abad99696100000000000000000000000000000000000000000000021e19e0c9bab2400000","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db"}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["8207cbae6aff799cfefa1ab4d8a0c52b6a59be43491bd25b4f03754f0e8115b006b5f1393a3934ec3489f5d3c272a7af42658bdc165dc632b36114bd3180da2e00"],"txID":"774422d8d205760876496f22b7d4395cfceda03f139b8362a3693f1f405f0c36"})"); + } } diff --git a/tests/Tron/SignerTests.cpp b/tests/Tron/SignerTests.cpp index 0e255c39b4b..69dc5c37cb5 100644 --- a/tests/Tron/SignerTests.cpp +++ b/tests/Tron/SignerTests.cpp @@ -7,6 +7,7 @@ #include "Bitcoin/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "uint256.h" #include "proto/Tron.pb.h" #include "Tron/Signer.h" @@ -315,7 +316,8 @@ TEST(TronSigner, SignTransferTrc20Contract) { transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - transfer_contract.set_amount(1000); + Data amount = store(uint256_t(1000)); + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); transaction.set_timestamp(1539295479000); From abe125b96787fc735a27690236cff5e18008e553 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 29 May 2020 10:27:41 +0800 Subject: [PATCH 28/81] [iOS CI] Add clean to xcodebuild (#971) * add clean command * replace xcpretty with fastlane scan * Fix iOS build --- .gitignore | 1 + src/Bitcoin/TransactionSigner.cpp | 2 +- tools/ios-test | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index adb11327ba8..a1d015d1a45 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ include/TrustWalletCore/TW*Proto.h # Code coverage files coverage.info coverage/ +swift/test_output/ # Sourcetrail *.srctrldb diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 16a26b2fa42..41b229a0b2a 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -51,7 +51,7 @@ Result TransactionSigner::sign() { tx.outputs = transaction.outputs; // save estimated size if ((input.byte_fee()) > 0 && (plan.fee > 0)) { - tx.previousEstimatedVirtualSize = plan.fee / input.byte_fee(); + tx.previousEstimatedVirtualSize = static_cast(plan.fee / input.byte_fee()); } return Result::success(std::move(tx)); diff --git a/tools/ios-test b/tools/ios-test index 861623c0267..26489644a12 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -7,5 +7,5 @@ set -e pushd swift xcodegen pod install -xcrun xcodebuild -workspace TrustWalletCore.xcworkspace -scheme TrustWalletCore -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11' test | xcpretty +fastlane scan --workspace TrustWalletCore.xcworkspace --scheme TrustWalletCore --sdk iphonesimulator --device='iPhone 11' --clean popd From fc013157c00e7b4c8f96b9f95aceb0c179094ac8 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Sun, 31 May 2020 16:11:48 +0800 Subject: [PATCH 29/81] Update clang / boost version in Dockerfile (#972) * Create docker.yml * update docker image: clang-9 boost 1.70 swift-protobuf 1.9.0 * Lint Dockerfile * cleanup --- .github/workflows/docker.yml | 30 +++++++++++++ .hadolint.yaml | 3 ++ Dockerfile | 52 +++++++++++++++++++++ docker/wallet-core/Dockerfile | 85 ----------------------------------- tools/docker-build | 6 --- tools/install-dependencies | 7 ++- 6 files changed, 90 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/docker.yml create mode 100644 .hadolint.yaml create mode 100644 Dockerfile delete mode 100644 docker/wallet-core/Dockerfile delete mode 100644 tools/docker-build diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000000..369efae9cbd --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,30 @@ +name: Docker CI + +on: + push: + branches: [ master ] + paths: + - Dockerfile + - tools/install-dependencies + pull_request: + branches: [ master ] + paths: + - Dockerfile + - tools/install-dependencies + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Lint Dockerfile + run: | + curl -L https://github.com/hadolint/hadolint/releases/download/v1.17.6/hadolint-Linux-x86_64 -o hadolint && chmod +x hadolint + ./hadolint Dockerfile + + - name: Build Dockerfile + uses: docker/build-push-action@v1.1.0 + with: + repository: trustwallet/wallet-core + tags: latest + push: false diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 00000000000..438a9ddfd1b --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,3 @@ +ignored: + - DL3008 + - DL3015 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..a188031d29e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +FROM ubuntu:18.04 + +# Install some basics +RUN apt-get update \ + && apt-get install -y \ + wget \ + curl \ + git \ + vim \ + unzip \ + xz-utils \ + software-properties-common \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Add latest cmake/boost +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - \ + && apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' \ + && apt-add-repository -y ppa:mhier/libboost-latest + +# Install required packages for dev +RUN apt-get update \ + && apt-get install -y \ + build-essential \ + libtool autoconf pkg-config \ + ninja-build \ + ruby-full \ + clang-9 \ + llvm-9 \ + libc++-dev libc++abi-dev \ + cmake \ + libboost1.70-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +ENV CC=/usr/bin/clang-9 +ENV CXX=/usr/bin/clang++-9 + +# ↑ Setup build environment (could be a base image) +# ↓ Build and compile wallet core + +RUN git clone https://github.com/trustwallet/wallet-core.git +WORKDIR /wallet-core + +# Install dependencies +RUN tools/install-dependencies + +# Build: generate, cmake, and make +RUN tools/generate-files \ + && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ + && make -Cbuild -j12 + +CMD ["/bin/bash"] diff --git a/docker/wallet-core/Dockerfile b/docker/wallet-core/Dockerfile deleted file mode 100644 index 61fef366e8e..00000000000 --- a/docker/wallet-core/Dockerfile +++ /dev/null @@ -1,85 +0,0 @@ -FROM ubuntu:18.04 - -ARG CLANG_VERSION=7.0.1 -ARG CMAKE_VERSION=3.13.4 -ARG PROTOBUF_VERSION=3.9.0 - -# Install some basics -RUN apt-get update \ - && apt-get install -y \ - curl \ - git \ - nano \ - unzip \ - xz-utils \ - && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Install required packages for dev -RUN apt-get update \ - && apt-get install -y \ - autoconf \ - build-essential \ - libcurl4-openssl-dev \ - libicu-dev \ - libreadline-dev \ - libssl-dev \ - libtool \ - ninja-build \ - pkg-config \ - ruby-full \ - && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Install clang -ENV CLANG_VERSION=$CLANG_VERSION -RUN curl -fSsL http://releases.llvm.org/$CLANG_VERSION/clang+llvm-$CLANG_VERSION-x86_64-linux-gnu-ubuntu-18.04.tar.xz -o clang.tar.xz \ - && tar -xJf clang.tar.xz --directory /usr --strip-components=1 \ - && rm -rf clang.tar.xz - -# Install Boost -RUN curl -fSsL https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz -o boost.tar.gz \ - && tar xzf boost.tar.gz \ - && mv boost_1_66_0/boost /usr/include \ - && rm -rf boost* - -# Install CMake, binaries from GitHub -ENV CMAKE_VERSION=$CMAKE_VERSION -RUN cd /usr/local/src \ - && curl -fSsOL https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz \ - && ls -l cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz \ - && tar xf cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz \ - && cd cmake-$CMAKE_VERSION-Linux-x86_64 \ - && cp -r bin /usr/ \ - && cp -r share /usr/ \ - && cp -r doc /usr/share/ \ - && cp -r man /usr/share/ \ - && cd .. \ - && rm -rf cmake* -RUN cmake --version - -# Clone repo -ENV CC=/usr/bin/clang -ENV CXX=/usr/bin/clang++ -RUN git clone https://github.com/TrustWallet/wallet-core.git - -# Prepare dependencies -RUN cd /wallet-core \ - && export PREFIX=/usr/local \ - && tools/install-dependencies - -# Clean Up -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Version checks -RUN ruby --version \ - && clang --version \ - && cmake --version \ - && protoc --version - -# Build library: generate, cmake, and make -# rbenv trick is needed as Ruby is installed to non-standard location -RUN cd /wallet-core \ - && ./tools/generate-files \ - && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ - && make -Cbuild -j12 - -CMD ["/bin/bash"] diff --git a/tools/docker-build b/tools/docker-build deleted file mode 100644 index ff76e32c3af..00000000000 --- a/tools/docker-build +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# -# This script builds the Docker image(s) - -docker build -t trustwallet/wallet-core:latest ./docker/wallet-core -#docker push trustwallet/wallet-core:latest diff --git a/tools/install-dependencies b/tools/install-dependencies index 9fe3d19c611..facc0e840d0 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -27,8 +27,9 @@ tar xzf release-$GTEST_VERSION.tar.gz # Build gtest cd googletest-release-$GTEST_VERSION cmake -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. -make +make -j4 make install +make clean # Download Check export CHECK_VERSION=0.12.0 @@ -45,6 +46,7 @@ cd check-$CHECK_VERSION ./configure --prefix="$PREFIX" make -j4 make install +make clean # Download Nlohmann JSON export JSON_VERSION=3.5.0 @@ -77,7 +79,7 @@ make clean if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then # Download Swift Protobuf sources - export SWIFT_PROTOBUF_VERSION=1.7.0 + export SWIFT_PROTOBUF_VERSION=1.9.0 SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" @@ -98,5 +100,6 @@ cd "$ROOT/protobuf-plugin" cmake -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX make -Cbuild -j12 make -Cbuild install +rm -rf build cd "$ROOT" From 03ac911567158f0fc61117ee7df36279d758b3b7 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 1 Jun 2020 20:10:12 -0600 Subject: [PATCH 30/81] Enable pay-to-self transactions (#974) --- src/Solana/Transaction.h | 21 ++++++++++++++++----- tests/Solana/SignerTests.cpp | 29 +++++++++++++++++++++++++++++ tests/Solana/TWAnySignerTests.cpp | 20 ++++++++++++++++++++ tests/Solana/TransactionTests.cpp | 17 +++++++++++++++++ 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/Solana/Transaction.h b/src/Solana/Transaction.h index b64cce5335d..ce561b8c3e7 100644 --- a/src/Solana/Transaction.h +++ b/src/Solana/Transaction.h @@ -58,9 +58,9 @@ struct CompiledInstruction { : programIdIndex(programIdIndex), accounts(accounts), data(data) {} // This constructor creates a default System Transfer instruction - CompiledInstruction(uint8_t programIdIndex, uint64_t value) : programIdIndex(programIdIndex) { - std::vector accounts = {0, 1}; - this->accounts = accounts; + CompiledInstruction(uint8_t programIdIndex, Data accountIndexes, uint64_t value) + : programIdIndex(programIdIndex) { + this->accounts = accountIndexes; SystemInstruction type = Transfer; auto data = Data(); encode32LE(static_cast(type), data); @@ -198,10 +198,21 @@ class Message { MessageHeader header = {1, 0, 1}; this->header = header; auto programId = Address("11111111111111111111111111111111"); - std::vector

accountKeys = {from, to, programId}; + std::vector
accountKeys; + Data accountIndexes; + uint8_t programIdIndex; + if (from.vector() != to.vector()) { + accountKeys = {from, to, programId}; + accountIndexes = {0, 1}; + programIdIndex = 2; + } else { + accountKeys = {from, programId}; + accountIndexes = {0, 0}; + programIdIndex = 1; + } this->accountKeys = accountKeys; std::vector instructions; - auto instruction = CompiledInstruction(2, value); + auto instruction = CompiledInstruction(programIdIndex, accountIndexes, value); instructions.push_back(instruction); this->instructions = instructions; } diff --git a/tests/Solana/SignerTests.cpp b/tests/Solana/SignerTests.cpp index 10a83837729..0ad4500ecae 100644 --- a/tests/Solana/SignerTests.cpp +++ b/tests/Solana/SignerTests.cpp @@ -54,6 +54,35 @@ TEST(SolanaSigner, SingleSignTransaction) { } } +TEST(SolanaSigner, SignTransactionToSelf) { + const auto privateKey = + PrivateKey(Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), + Base58::bitcoin.decode("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu")); + + const auto from = Address(publicKey); + auto to = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); + Solana::Hash recentBlockhash("11111111111111111111111111111111"); + auto transaction = Transaction(from, to, 42, recentBlockhash); + + std::vector signerKeys; + signerKeys.push_back(privateKey); + Signer::sign(signerKeys, transaction); + + std::vector expectedSignatures; + Signature expectedSignature( + "3CFWDEK51noPJP4v2t8JZ3qj7kC7kLKyws9akfHMyuJnQ35EtzBptHqvaHfeswiLsvUSxzMVNoj4CuRxWtDD9zB1"); + expectedSignatures.push_back(expectedSignature); + ASSERT_EQ(transaction.signatures, expectedSignatures); + + auto expectedString = + "EKUmihvvUPKVN4GSCFwZRtz8WiyAuPvthW69Smo19SCjcPLQ6T7EVZd1HU71WAoe1bfgmPNS5JhU7ZLA9XKG3qbZqe" + "EFJ1xmRwW9ZKw8SKMAL6VRWxp87oLu7PSmf5b8R34vCaww3XLKtZkoP49a7TUK31DqPN5xJCceMB3BZJyaojQaKU8n" + "UkzSGf89LY6abZXp9krKAebvc6bSMzTP8SHSvbmZbf3VtejmpQeN9X6e7WVDn6oDa2bGT"; + ASSERT_EQ(transaction.serialize(), expectedString); +} + TEST(SolanaSigner, MultipleSignTransaction) { const auto privateKey0 = PrivateKey(Base58::bitcoin.decode("96PKHuMPtniu1T74RvUNkbDPXPPRZ8Mg1zXwciCAyaDq")); diff --git a/tests/Solana/TWAnySignerTests.cpp b/tests/Solana/TWAnySignerTests.cpp index c8a31c3230b..3fd6df3afde 100644 --- a/tests/Solana/TWAnySignerTests.cpp +++ b/tests/Solana/TWAnySignerTests.cpp @@ -36,6 +36,26 @@ TEST(TWAnySignerSolana, SignTransfer) { ASSERT_EQ(output.encoded(), expectedString); } +TEST(TWAnySignerSolana, SignTransferToSelf) { + auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); + auto input = Proto::SigningInput(); + + auto& message = *input.mutable_transfer_transaction(); + message.set_recipient("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); + message.set_value((uint64_t)42L); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_recent_blockhash("11111111111111111111111111111111"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + auto expectedString = + "EKUmihvvUPKVN4GSCFwZRtz8WiyAuPvthW69Smo19SCjcPLQ6T7EVZd1HU71WAoe1bfgmPNS5JhU7ZLA9XKG3qbZqe" + "EFJ1xmRwW9ZKw8SKMAL6VRWxp87oLu7PSmf5b8R34vCaww3XLKtZkoP49a7TUK31DqPN5xJCceMB3BZJyaojQaKU8n" + "UkzSGf89LY6abZXp9krKAebvc6bSMzTP8SHSvbmZbf3VtejmpQeN9X6e7WVDn6oDa2bGT"; + ASSERT_EQ(output.encoded(), expectedString); +} + TEST(TWAnySignerSolana, SignDelegateStakeTransaction) { auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); auto input = Solana::Proto::SigningInput(); diff --git a/tests/Solana/TransactionTests.cpp b/tests/Solana/TransactionTests.cpp index 635e87f32a3..428918be2c9 100644 --- a/tests/Solana/TransactionTests.cpp +++ b/tests/Solana/TransactionTests.cpp @@ -48,6 +48,23 @@ TEST(SolanaTransaction, TransferSerializeTransaction) { ASSERT_EQ(transaction.serialize(), expectedString); } +TEST(SolanaTransaction, TransferTransactionPayToSelf) { + auto from = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); + auto to = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); + Solana::Hash recentBlockhash("11111111111111111111111111111111"); + auto transaction = Transaction(from, to, 42, recentBlockhash); + Signature signature( + "3CFWDEK51noPJP4v2t8JZ3qj7kC7kLKyws9akfHMyuJnQ35EtzBptHqvaHfeswiLsvUSxzMVNoj4CuRxWtDD9zB1"); + transaction.signatures.clear(); + transaction.signatures.push_back(signature); + + auto expectedString = + "EKUmihvvUPKVN4GSCFwZRtz8WiyAuPvthW69Smo19SCjcPLQ6T7EVZd1HU71WAoe1bfgmPNS5JhU7ZLA9XKG3qbZqe" + "EFJ1xmRwW9ZKw8SKMAL6VRWxp87oLu7PSmf5b8R34vCaww3XLKtZkoP49a7TUK31DqPN5xJCceMB3BZJyaojQaKU8n" + "UkzSGf89LY6abZXp9krKAebvc6bSMzTP8SHSvbmZbf3VtejmpQeN9X6e7WVDn6oDa2bGT"; + ASSERT_EQ(transaction.serialize(), expectedString); +} + TEST(SolanaTransaction, StakeSerializeTransaction) { auto signer = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); auto voteAddress = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); From a6418f2d6d46d9c72ef94cf6aa9d10263f042306 Mon Sep 17 00:00:00 2001 From: Ravindra Kumar Date: Tue, 9 Jun 2020 03:11:53 +0530 Subject: [PATCH 31/81] Added FrontierWallet to Readme (#981) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 827eca33c04..4bec5c85a50 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ pod 'TrustWalletCore' - [coinpaprika](https://coinpaprika.com/) - [IFWallet](https://www.ifwallet.com/) - [Alice](https://www.alicedapp.com/) +- [FrontierWallet](https://frontierwallet.com/) # Contributing From 06bbce27ed9ce140cb9e2236bcc2917f7a3cb607 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Tue, 9 Jun 2020 15:12:51 +0200 Subject: [PATCH 32/81] [BTC] Fee estimation segwit-compliant, lower and more accurate fees (#965) * Rework FeeCalculator. * Extra UnspentSelector test. * Test rename. * Minor refactoring, asserts, typos. * Minor improvement in max_amount amount computation. * Minor change in fee computation in case UTXOs can be selected only with dust. * Include cleanup, copyright. * Bitcoin UnspentSelectorTests: More strict verification. * BTC Tx Plan and UnspentSelector tests: cleaner and stricter verification, common build&compare helpers. * BTC SigningTests: add explicit checks for fee, amount, change, and UTXO selection. * Use_max_amount case: if max_amount selected, amount set does not matter. Add more max_amount tests. * Rework Max_amount case, more tests. * Extra test with empty plan. * BitcoinSignerTests: Add explicit checks on encoded tx size. * TransactionSigner: Minor rearrange for coverage++. * A few more special BitconTransactionSigner tests. * Remove redundant check. * Data type cleanup. * Segwit and non-segwit tx size computation in signer tests. * One extra special transaction plan test (fee more than avail amount). * Minor; add test assert. * Code cleanup, TransactionSigner. * TransactionSigner tests: add validation checks for fee estimation. * [BTC] Fee estimation at the end of TX planning is segwit-compliant and more precise. * Updates tests to lower fees. * Android test partial fix. * Parameter renames, remove commented-out code. * Separate out Transaction::hasWitness(). * In case precise fee computation fails due to failing signing, use default fee. * Transaction::encode() has 3 modes: NonSegwit, Segwit, and IfHasSegwit. * Adjustment for non-segwit virtual size computation. * Tighten fee validation range to +/1.Tighten fee validation range to +/1. * Improve tests to use IfHasWitness, which is closer to product code. * EncodeTx() in TransactionSigner(). * Signer tests invoke encodeTx() from product code directly, to have same encoding behaviour. * Android test fix. * Android test fix testSignP2PKH. * BTC (LTC) tests: create real LTC transaction tests. * Readjust test cases to produce existing real-world TXs. * IOS test fix (blind fix). * iOS test fix. Co-authored-by: Catenocrypt --- .../blockchains/bitcoin/TestBitcoinSigning.kt | 8 +- src/Bitcoin/Signer.cpp | 7 +- src/Bitcoin/Transaction.cpp | 17 +- src/Bitcoin/Transaction.h | 13 +- src/Bitcoin/TransactionBuilder.cpp | 125 ++-- src/Bitcoin/TransactionSigner.cpp | 4 + src/Bitcoin/TransactionSigner.h | 9 +- src/Groestlcoin/Signer.cpp | 7 +- .../BitcoinTransactionSignerTests.swift | 32 +- .../GroestlcoinTransactionSignerTests.swift | 27 +- tests/Bitcoin/TWBitcoinSigningTests.cpp | 615 +++++++++++------- tests/Bitcoin/TWBitcoinTransactionTests.cpp | 2 +- tests/Bitcoin/TransactionPlanTests.cpp | 54 +- tests/Bitcoin/TxComparisonHelper.cpp | 89 ++- tests/Bitcoin/TxComparisonHelper.h | 3 + tests/BitcoinCash/TWBitcoinCashTests.cpp | 4 + tests/BitcoinGold/TWBitcoinGoldTests.cpp | 31 +- tests/BitcoinGold/TWSignerTests.cpp | 31 +- tests/DigiByte/TWDigiByteTests.cpp | 9 +- .../Groestlcoin/TWGroestlcoinSigningTests.cpp | 54 +- .../Ravencoin/TWRavencoinTransactionTests.cpp | 2 +- 21 files changed, 773 insertions(+), 370 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt index ec8b8b85c5b..7f2688eea4b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt @@ -75,7 +75,7 @@ class TestBitcoinSigning { assertEquals(2, signedTransaction.outputsCount) val encoded = output.encoded - assertEquals("0x0100000001fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000049483045022100b6006eb0fe2da8cbbd204f702b1ffdb1e29c49f3de51c4983d420bf9f9125635022032a195b153ccb2c4978333b4aad72aaa7e6a0b334a14621d5d817a42489cb0d301ffffffff02b0bf0314000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088acaefd3c11000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000", + assertEquals("0x0100000001fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000049483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01ffffffff02b0bf0314000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088acd0fd3c11000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000", Numeric.toHexString(encoded.toByteArray())); } @@ -133,8 +133,8 @@ class TestBitcoinSigning { val plan = AnySigner.plan(input.build(), BITCOIN, Bitcoin.TransactionPlan.parser()) assertEquals(55_000, plan.amount) assertEquals(75_000, plan.availableAmount) - assertEquals(3740, plan.fee) - assertEquals(16260, plan.change) + assertEquals(2610, plan.fee) + assertEquals(17390, plan.change) // Set the precomputed plan input.plan = plan @@ -148,7 +148,7 @@ class TestBitcoinSigning { assertEquals(2, signedTransaction.outputsCount) val encoded = output.encoded - assertEquals("0x01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000049483045022100991ea84c8f22cbcbdee114a687b31bc80fca181161adc354e37b16b0f4664a6f022016e34b232524a1296a636026f8bb1f5f3635d88bf936532aae70a499c52f77d201ffffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02d8d60000000000001976a914a6d85a488bb777a540f24bf777d30d1486036f6188ac843f0000000000001976a9147d77e6cfb05a9cfc123824279f6caf8b66ac267688ac000247304402200ebd8fe637d7344984dd173d4a3089c4fc03a51117ee0363d04c714f033b33cf02204e2831939fff068068cc08fe35d84950f244fd2fe39795d839bfb8795484cc230121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635700000000", + assertEquals("0x01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008d49c4d7cc5ab93c01a67ce3f4ed2c45c59d4da6c76c891a9b56e67eda2e8cb4022078849134c697b1c70c1a19b900d94d8cab00ad7bcc8afe7ad1f6b184c13effa601ffffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02d8d60000000000001976a914a6d85a488bb777a540f24bf777d30d1486036f6188acee430000000000001976a9147d77e6cfb05a9cfc123824279f6caf8b66ac267688ac0002473044022074573d7f7828ae193fbea6d72c0fe2df6cee5c02bf455ea3d9312e16d6a9576502203861c5a3b3a83d4fe372034073f60201a8a944fb4536be0ea7544ab177b967600121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635700000000", Numeric.toHexString(encoded.toByteArray())); } } diff --git a/src/Bitcoin/Signer.cpp b/src/Bitcoin/Signer.cpp index 64a9a41d109..c9ac3119030 100644 --- a/src/Bitcoin/Signer.cpp +++ b/src/Bitcoin/Signer.cpp @@ -32,14 +32,13 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { *output.mutable_transaction() = tx.proto(); Data encoded; - auto hasWitness = std::any_of(tx.inputs.begin(), tx.inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); - tx.encode(hasWitness, encoded); + signer.encodeTx(tx, encoded); output.set_encoded(encoded.data(), encoded.size()); Data txHashData = encoded; - if (hasWitness) { + if (tx.hasWitness()) { txHashData.clear(); - tx.encode(false, txHashData); + tx.encode(txHashData, Transaction::SegwitFormatMode::NonSegwit); } auto txHash = Hash::sha256d(txHashData.data(), txHashData.size()); std::reverse(txHash.begin(), txHash.end()); diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index 86fd07dcd5e..bfcf6d32151 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -102,10 +102,17 @@ Data Transaction::getOutputsHash() const { return hash; } -void Transaction::encode(bool witness, Data& data) const { +void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { + bool useWitnessFormat = true; + switch (segwitFormat) { + case NonSegwit: useWitnessFormat = false; break; + case IfHasWitness: useWitnessFormat = hasWitness(); break; + case Segwit: useWitnessFormat = true; break; + } + encode32LE(version, data); - if (witness) { + if (useWitnessFormat) { // Use extended format in case witnesses are to be serialized. data.push_back(0); // marker data.push_back(1); // flag @@ -123,7 +130,7 @@ void Transaction::encode(bool witness, Data& data) const { output.encode(data); } - if (witness) { + if (useWitnessFormat) { encodeWitness(data); } @@ -136,6 +143,10 @@ void Transaction::encodeWitness(Data& data) const { } } +bool Transaction::hasWitness() const { + return std::any_of(inputs.begin(), inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); +} + Data Transaction::getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount, enum SignatureVersion version) const { diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index 48d431b3f45..5da4a7faade 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/Transaction.h @@ -61,12 +61,23 @@ struct Transaction { Data getSequenceHash() const; Data getOutputsHash() const; + enum SegwitFormatMode { + NonSegwit, + IfHasWitness, + Segwit + }; + /// Encodes the transaction into the provided buffer. - void encode(bool witness, Data& data) const; + void encode(Data& data, enum SegwitFormatMode segwitFormat) const; + + /// Default one-parameter version, needed for templated usage. + void encode(Data& data) const { encode(data, SegwitFormatMode::IfHasWitness); } /// Encodes the witness part of the transaction into the provided buffer. void encodeWitness(Data& data) const; + bool hasWitness() const; + /// Generates the signature hash for this transaction. Data getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount, enum SignatureVersion version) const; diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp index e8bc780dc41..915610a5728 100644 --- a/src/Bitcoin/TransactionBuilder.cpp +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -12,48 +12,95 @@ namespace TW::Bitcoin { +/// Estimate encoded size by invoking sign(sizeOnly), get actual size +int64_t estimateSegwitFee(FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const Bitcoin::Proto::SigningInput& input) { + // duplicate input, with the current plan + auto inputWithPlan = std::move(input); + *inputWithPlan.mutable_plan() = plan.proto(); + + auto signer = TransactionSigner(std::move(inputWithPlan), true); + auto result = signer.sign(); + if (!result) { + // signing failed; return default estimate + return feeCalculator.calculate(plan.utxos.size(), outputSize, input.byte_fee()); + } + + // Obtain the encoded size + auto transaction = result.payload(); + Data dataNonSegwit; + transaction.encode(dataNonSegwit, Transaction::SegwitFormatMode::NonSegwit); + int64_t sizeNonSegwit = dataNonSegwit.size(); + uint64_t vSize = 0; + // Check if there is segwit + if (!transaction.hasWitness()) { + // no segwit, virtual size is defined as non-segwit size + vSize = sizeNonSegwit; + } else { + Data dataWitness; + transaction.encodeWitness(dataWitness); + int64_t witnessSize = 2 + dataWitness.size(); + // compute virtual size: (smaller) non-segwit + 1/4 of the diff (witness-only) + // (in other way: 3/4 of (smaller) non-segwit + 1/4 of segwit size) + vSize = sizeNonSegwit + witnessSize/4 + (witnessSize % 4 != 0); + } + uint64_t fee = input.byte_fee() * vSize; + + return fee; +} + TransactionPlan TransactionBuilder::plan(const Bitcoin::Proto::SigningInput& input) { - auto plan = TransactionPlan(); + auto plan = TransactionPlan(); + plan.amount = input.amount(); + + auto output_size = 2; + auto& feeCalculator = getFeeCalculator(static_cast(input.coin_type())); + auto unspentSelector = UnspentSelector(feeCalculator); + + // select UTXOs + if (!input.use_max_amount()) { + output_size = 2; // output + change + plan.utxos = unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); + } else { + output_size = 1; // no change + plan.utxos = unspentSelector.selectMaxAmount(input.utxo(), input.byte_fee()); + } + // Note: if utxos.size() == 0, all fields will be computed to 0 + plan.availableAmount = UnspentSelector::sum(plan.utxos); + + // Compute fee. + // must preliminary set change so that there is a second output + if (!input.use_max_amount()) { plan.amount = input.amount(); + plan.fee = 0; + plan.change = plan.availableAmount - plan.amount; + } else { + plan.amount = plan.availableAmount; + plan.fee = 0; + plan.change = 0; + } + plan.fee = estimateSegwitFee(feeCalculator, plan, output_size, input); + // If fee is larger then availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) + plan.fee = std::min(plan.availableAmount, plan.fee); + assert(plan.fee >= 0 && plan.fee <= plan.availableAmount); + + // adjust/compute amount + if (!input.use_max_amount()) { + // reduce amount if needed + plan.amount = std::max(Amount(0), std::min(plan.amount, plan.availableAmount - plan.fee)); + } else { + // max available amount + plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); + } + assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); + + // compute change + plan.change = plan.availableAmount - plan.amount - plan.fee; + assert(plan.change >= 0 && plan.change <= plan.availableAmount); + assert(!input.use_max_amount() || plan.change == 0); // change is 0 in max amount case + + assert(plan.amount + plan.change + plan.fee == plan.availableAmount); - auto output_size = 2; - auto& feeCalculator = getFeeCalculator(static_cast(input.coin_type())); - auto unspentSelector = UnspentSelector(feeCalculator); - - // select UTXOs - if (!input.use_max_amount()) { - output_size = 2; // output + change - plan.utxos = unspentSelector.select(input.utxo(), plan.amount, input.byte_fee(), output_size); - } else { - output_size = 1; // no change - plan.utxos = unspentSelector.selectMaxAmount(input.utxo(), input.byte_fee()); - } - // Note: if utxos.size() == 0, all fields will be computed to 0 - plan.availableAmount = UnspentSelector::sum(plan.utxos); - - // Compute fee. If larger then availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) - plan.fee = feeCalculator.calculate(plan.utxos.size(), output_size, input.byte_fee()); - plan.fee = std::min(plan.availableAmount, plan.fee); - assert(plan.fee >= 0 && plan.fee <= plan.availableAmount); - - // adjust/compute amount - if (!input.use_max_amount()) { - // reduce amount if needed - plan.amount = std::max(Amount(0), std::min(plan.amount, plan.availableAmount - plan.fee)); - } else { - // max available amount - plan.amount = std::max(Amount(0), plan.availableAmount - plan.fee); - } - assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); - - // compute change - plan.change = plan.availableAmount - plan.amount - plan.fee; - assert(plan.change >= 0 && plan.change <= plan.availableAmount); - assert(!input.use_max_amount() || plan.change == 0); // change is 0 in max amount case - - assert(plan.amount + plan.change + plan.fee == plan.availableAmount); - - return plan; + return plan; } } // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 41b229a0b2a..d6268dec913 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -221,6 +221,10 @@ Data TransactionSigner::createSignature(const T const Script& script, const Data& key, size_t index, Amount amount, uint32_t version) const { + if (estimationMode) { + // Don't sign, only estimate signature size. It is 71-72 bytes. Return placeholder. + return Data(72); + } Data sighash = transaction.getSignatureHash(script, index, static_cast(input.hash_type()), amount, static_cast(version)); auto pk = PrivateKey(key); diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index 1a1b71962c1..a7969eb7236 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -43,9 +43,13 @@ class TransactionSigner { /// List of signed inputs. std::vector signedInputs; + bool estimationMode = false; + public: /// Initializes a transaction signer with signing input. - TransactionSigner(const Bitcoin::Proto::SigningInput& input) : input(input) { + /// estimationMode: is set, no real signing is performed, only as much as needed to get the almost-exact signed size + TransactionSigner(const Bitcoin::Proto::SigningInput& input, bool estimationMode = false) : + input(input), estimationMode(estimationMode) { if (input.has_plan()) { plan = TransactionPlan(input.plan()); } else { @@ -61,6 +65,9 @@ class TransactionSigner { /// \returns the signed transaction or an error. Result sign(); + // helper, return binary encoded transaction (used right after sign()) + static void encodeTx(const Transaction& tx, Data& outData) { tx.encode(outData); } + // internal, public for testability and Decred static Data pushAll(const std::vector& results); diff --git a/src/Groestlcoin/Signer.cpp b/src/Groestlcoin/Signer.cpp index 2525d59ed93..01e16a7256f 100644 --- a/src/Groestlcoin/Signer.cpp +++ b/src/Groestlcoin/Signer.cpp @@ -34,14 +34,13 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { *output.mutable_transaction() = tx.proto(); Data encoded; - auto hasWitness = std::any_of(tx.inputs.begin(), tx.inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); - tx.encode(hasWitness, encoded); + signer.encodeTx(tx, encoded); output.set_encoded(encoded.data(), encoded.size()); Data txHashData = encoded; - if (hasWitness) { + if (tx.hasWitness()) { txHashData.clear(); - tx.encode(false, txHashData); + tx.encode(txHashData, Transaction::SegwitFormatMode::NonSegwit); } auto txHash = Hash::sha256(txHashData.data(), txHashData.size()); std::reverse(txHash.begin(), txHash.end()); diff --git a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift index 851bf5d5d1b..92b35d87440 100644 --- a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift @@ -39,8 +39,8 @@ class BitcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .bitcoin) XCTAssertEqual(plan.amount, 1000) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 0) + XCTAssertEqual(plan.fee, 147) + XCTAssertEqual(plan.change, 79) let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) XCTAssertTrue(output.error.isEmpty) @@ -49,27 +49,31 @@ class BitcoinTransactionSignerTests: XCTestCase { XCTAssertEqual(signedTx.version, 1) let txId = output.transactionID - XCTAssertEqual(txId, "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd") + XCTAssertEqual(txId, "dc60991ff61a6061f55854ce6fb3203b7c8291ed7b2ce799040114c608391583") XCTAssertEqual(signedTx.inputs.count, 1) // Only one UTXO available XCTAssertEqual(signedTx.inputs[0].script.hexString, "") - XCTAssertEqual(signedTx.outputs.count, 1) // Exact amount + XCTAssertEqual(signedTx.outputs.count, 2) // Exact amount XCTAssertEqual(signedTx.outputs[0].value, 1000) + XCTAssertEqual(signedTx.outputs[1].value, 79) let encoded = output.encoded let witnessHash = Data(Hash.sha256SHA256(data: encoded).reversed()) - XCTAssertEqual(witnessHash.hexString, "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a") - XCTAssertEqual(encoded.hexString, "01000000" + - "0001" + - "01" + + XCTAssertEqual(witnessHash.hexString, "ec57de0d46eb45e8019b82e388e458e72fb834dba971e3a45ff8fa7bb7bdb799") + XCTAssertEqual(encoded.hexString, + "01000000" + // version + "0001" + // marker & flag + "01" + // inputs "0001000000000000000000000000000000000000000000000000000000000000" + "00000000" + "00" + "ffffffff" + - "01" + - "e803000000000000" + "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + - "02" + - "4730440220252e92b8757f1e5577c54ce5deb8072914c1f03333128777dee96ebceeb6a99b02202b7298789316779d0aa7595abeedc03054405c42ab9859e67d9253d2c9a0cdfa01232103596d3451025c" + - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + - "00000000" + "02" + // outputs + "e803000000000000" + "19" + "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + + "4f00000000000000" + "19" + "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + + // witness + "02" + + "48" + "30450221009eefc1befe96158f82b74e6804f1f713768c6172636ca11fcc975c316ea86f75022057914c48bc24f717498b851a47a2926f96242e3943ebdf08d5a97a499efc8b9001" + + "23" + "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + + "00000000" // nLockTime ) } } diff --git a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift index 542434189be..6d3cd150e3b 100644 --- a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift @@ -34,8 +34,13 @@ class GroestlcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .groestlcoin) XCTAssertEqual(plan.amount, 2500) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 2048) + XCTAssertEqual(plan.fee, 145) + XCTAssertEqual(plan.change, 2129) + + // Supply plan for signing, to match fee of previously-created real TX + input.plan = plan + input.plan.fee = 226 + input.plan.change = 2048 // https://blockbook.groestlcoin.org/tx/40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64 let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .groestlcoin) @@ -82,8 +87,13 @@ class GroestlcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .groestlcoin) XCTAssertEqual(plan.amount, 2500) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 2274) + XCTAssertEqual(plan.fee, 221) + XCTAssertEqual(plan.change, 2279) + + // Supply plan for signing, to match fee of previously-created real TX + input.plan = plan + input.plan.fee = 226 + input.plan.change = 2274 // https://blockbook.groestlcoin.org/tx/74a0dd12bc178cfcc1e0982a2a5b2c01a50e41abbb63beb031bcd21b3e28eac0 let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .groestlcoin) @@ -130,8 +140,13 @@ class GroestlcoinTransactionSignerTests: XCTestCase { let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .groestlcoin) XCTAssertEqual(plan.amount, 5000) - XCTAssertEqual(plan.fee, 226) - XCTAssertEqual(plan.change, 4774) + XCTAssertEqual(plan.fee, 167) + XCTAssertEqual(plan.change, 4833) + + // Supply plan for signing, to match fee of previously-created real TX + input.plan = plan + input.plan.fee = 226 + input.plan.change = 4774 // https://blockbook.groestlcoin.org/tx/8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895 let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .groestlcoin) diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index eb056355f70..4338c2f1df7 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -87,24 +87,24 @@ TEST(BitcoinSigning, SignP2PKH) { } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); + signer.encodeTx(signedTx, serialized); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{228, 225, 226})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "01" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "6a47304402202819d70d4bec472113a1392cadc0860a7a1b34ea0869abb4bdce3290c3aba086022023eff75f410ad19cdbe6c6a017362bd554ce5fb906c13534ddc306be117ad30a012103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ffffffff" - "02" - "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "0000000000" + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "01" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "6a" "47304402202819d70d4bec472113a1392cadc0860a7a1b34ea0869abb4bdce3290c3aba086022023eff75f410ad19cdbe6c6a017362bd554ce5fb906c13534ddc306be117ad30a012103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "aefd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" // nLockTime ); } @@ -118,7 +118,8 @@ TEST(BitcoinSigning, SignP2PKH_NegativeMissingKey) { } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_FALSE(result); } @@ -141,17 +142,22 @@ TEST(BitcoinSigning, EncodeP2WPKH) { unsignedTx.outputs.emplace_back(223450000, outScript1); Data unsignedData; - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(unsignedData.size(), 160); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + ASSERT_EQ(unsignedData.size(), 164); ASSERT_EQ(hex(unsignedData), - "01000000" - "02" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff" - "02" - "202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" - "9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" - "11000000"); + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "00" "" "eeffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "202cb20600000000" "19" "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" + "9093510d00000000" "19" "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" + // witness + "00" + "00" + "11000000" // nLockTime + ); } Proto::SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int64_t utxo0Amount, int64_t utxo1Amount, bool useMaxAmount = false) { @@ -213,48 +219,45 @@ TEST(BitcoinSigning, SignP2WPKH) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 192)); } - // Sign - auto result = TransactionSigner(std::move(input)).sign(); + // Signs + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "03b30d55430f08365d19a62d3bd32e459ab50984fbcf22921ecc85f1e09dc6ed" - // witid = "20bc58d07d91a3bae9e6f4d617d8f6271723d1a7673e486cc0ecaf9e758e2c22" - Data serialized; - signedTx.encode(true, serialized); + signer.encodeTx(signedTx, serialized); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); - EXPECT_EQ(serialized.size(), 195); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "01" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100b6006eb0fe2da8cbbd204f702b1ffdb1e29c49f3de51c4983d420bf9f9125635022032a195b153ccb2c4978333b4aad72aaa7e6a0b334a14621d5d817a42489cb0d301" "ffffffff" - "02" - "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "0000000000" + EXPECT_EQ(serialized.size(), 192); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "01" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "d0fd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" // nLockTime ); { // Non-segwit encoded, for comparison Data serialized; - signedTx.encode(false, serialized); + signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); EXPECT_EQ(serialized.size(), 192); - ASSERT_EQ(hex(serialized), - "01000000" - "01" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100b6006eb0fe2da8cbbd204f702b1ffdb1e29c49f3de51c4983d420bf9f9125635022032a195b153ccb2c4978333b4aad72aaa7e6a0b334a14621d5d817a42489cb0d301" "ffffffff" - "02" - "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "00000000" + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "01" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "d0fd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" // nLockTime ); } } @@ -265,31 +268,33 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 374)); + EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 261)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); + signer.encodeTx(signedTx, serialized); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 233, 261})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "02" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "0100000000" "ffffffff" - "02" - "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "0002" - "47304402206b91d2c69022a54652731b4302eabe59c87949cf62f4c5674c7d4c0d1fbf898102200cee8eeb6ef9542426788c06ed51004799b730083ae3d4daf3c3d5fdc2275d1d0321025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "00000000" + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4bf0040500000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "00" + "02" "47" "30440220096d20c7e92f991c2bf38dc28118feb34019ae74ec1c17179b28cb041de7517402204594f46a911f24bdc7109ca192e6860ebf2f3a0087579b3c128d5ce0cd5ed46803" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" // nLockTime ); } @@ -299,31 +304,33 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 374)); + EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 261)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 232, 260})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "02" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "4847304402206ed3e388d440cb845eef2fce0740b83bdd77764ad0e7dd815a20760718291a5302203f78d743350d80aa2508e90d5a984636c5503d02c1e8656442f0f0275db95baa80" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "ffffffff" - "02" - "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "0002" - "483045022100a5eedab7da09317141e35730256ef9b76da0c2442995a1c2b5458ee7d8834ba302201dc10b47cd4e2e53c7253770cd6907c94c828317d217e3065db009345acf41ac8021025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "00000000" + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{344, 233, 261})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100e21fb2f1cfd59bdb3703fd45db38fd680d0c06e5d0be86fb7dc233c07ee7ab2f02207367220a73e43df4352a6831f6f31d8dc172c83c9f613a9caf679f0f15621c5e80" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4bf0040500000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "00" + "02" "48" "304502210095f9cc913d2f0892b953f2380112533e8930b67c53e00a7bbd7a01d547156adc022026efe3a684aa7432a00a919dbf81b63e635fb92d3149453e95b4a7ccea59f7c480" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" // nLockTime ); } @@ -333,30 +340,32 @@ TEST(BitcoinSigning, SignP2WPKH_MaxAmount) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000, 600'000'000}, 1224999660, 340)); + EXPECT_TRUE(verifyPlan(plan, {625'000'000, 600'000'000}, 1224999773, 227)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{309, 199, 227})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "02" - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100d173cb8d2f2c42824c49c316f2079e10c8a68cb434178ffa9d03f0081ab582ac02201a5e3874292f5452981f6914a7e8d8bb123b4af016eab91e4f25e38e8c9bdad001" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a01" "00000000" "ffffffff" - "01" - "ec02044900000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "0002" - "473044022058491ed1bee5072a3a4c8cc29a63eccf691de79df47fda7b768834c74a8ea19b022073ef4e72ceeec190133703a3f9eb9a15716ad0e489574469d5dab1a4ae360b010121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "00000000" + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{310, 199, 227})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100a8b3c1619e985923994e80efdc0be0eac12f2419e11ce5e4286a0a5ac27c775d02205d6feee85ffe19ae0835cba1562beb3beb172107cd02ac4caf24a8be3749811f01" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "01" // outputs + "5d03044900000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + // witness + "00" + "02" "48" "3045022100db1199de92f6fb638a0ba706d13ec686bb01138a254dec2c397616cd74bad30e02200d7286d6d2d4e00d145955bf3d3b848b03c0d1eef8899e4645687a3035d7def401" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" // nLockTime ); } @@ -370,14 +379,15 @@ TEST(BitcoinSigning, EncodeP2WSH) { unsignedTx.outputs.emplace_back(1000, outScript0); Data unsignedData; - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData), "" - "01000000" - "01" - "00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff" - "01" - "e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac" - "00000000"); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "01" // outputs + "e803000000000000" "19" "76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac" + "00000000" // nLockTime + ); } Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false) { @@ -419,32 +429,31 @@ TEST(BitcoinSigning, SignP2WSH) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" - // witid = "16a17dd8f6e507220010c56c07a8479e3f909f87791683577d4e6aad61ab113a" - Data serialized; - signedTx.encode(true, serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{196, 85, 113})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), "01000000" - "0001" - "01" - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" - "01" - "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "02" - "4730440220252e92b8757f1e5577c54ce5deb8072914c1f03333128777dee96ebceeb6a99b02202b7298789316779d0aa7595abeedc03054405c42ab9859e67d9253d2c9a0cdfa01232103596d3451025c" - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "48" "30450221009eefc1befe96158f82b74e6804f1f713768c6172636ca11fcc975c316ea86f75022057914c48bc24f717498b851a47a2926f96242e3943ebdf08d5a97a499efc8b9001" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime ); } @@ -455,29 +464,31 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{197, 85, 113})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), "01000000" - "0001" - "01" - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" - "01" - "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "02" - "483045022100caa585732cfc50226a90834a306d23d5d2ab1e94af2c66136a637e3d9bad3688022069028750908e53a663bb1f434fd655bcc0cf8d394c6fa1fd5a4983790135722e02232103596d3451025c" - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "48" "3045022100caa585732cfc50226a90834a306d23d5d2ab1e94af2c66136a637e3d9bad3688022069028750908e53a663bb1f434fd655bcc0cf8d394c6fa1fd5a4983790135722e02" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime ); } @@ -488,29 +499,31 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{196, 85, 113})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), "01000000" - "0001" - "01" - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" - "01" - "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "02" - "47304402201ba80b2c48fe82915297dc9782ae2141e40263001fafd21b02c04a092503f01e0220666d6c63475c6c52abd09371c200ac319bcf4a7c72eb3782e95790f5c847f0b903232103596d3451025c" - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{230, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "47" "304402201ba80b2c48fe82915297dc9782ae2141e40263001fafd21b02c04a092503f01e0220666d6c63475c6c52abd09371c200ac319bcf4a7c72eb3782e95790f5c847f0b903" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime ); } @@ -521,30 +534,32 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - EXPECT_EQ(serialized.size(), 196); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{196, 85, 113})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), "01000000" - "0001" - "01" - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "ffffffff" - "01" - "e803000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "02" - "47304402206fc6f499c9b0080dd444b410ca0599b59321e7891fc8e59ab215f6d2995b2e5f0220182466b434e91d14c9d247d3726d3c7f22a2a1cbf6c172314e1155b307f467b080232103596d3451025c" - "19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(serialized.size(), 231); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "48" "3045022100d14699fc9b7337768bcd1430098d279cfaf05f6abfa75dd542da2dc038ae1700022063f0751c08796c086ac23b39c25f4320f432092e0c11bec46af0723cc4f55a3980" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime ); } @@ -558,7 +573,8 @@ TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_FALSE(result); } @@ -570,7 +586,8 @@ TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { input.mutable_plan()->clear_utxos(); // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_FALSE(result); } @@ -588,15 +605,16 @@ TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { unsignedTx.outputs.emplace_back(800'000'000, outScript1); Data unsignedData; - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData), "" - "01000000" - "01" - "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff" - "02" - "b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" - "0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" - "92040000"); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "01" // inputs + "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" "01000000" "00" "" "feffffff" + "02" // outputs + "b8b4eb0b00000000" "19" "76a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" + "0008af2f00000000" "19" "76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" + "92040000" // nLockTime + ); } TEST(BitcoinSigning, SignP2SH_P2WPKH) { @@ -632,23 +650,32 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 170)); } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - // txid = "060046204220fd00b81fd6426e391acb9670d1e61e8f0224f37276cc34f49e8c" - // witid = "3911b16643972437d27a759b5647a552c7a2e433364b531374f3761967bf8fd7" - Data serialized; - signedTx.encode(true, serialized); + signer.encodeTx(signedTx, serialized); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{251, 142, 170})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); - ASSERT_EQ(hex(serialized), "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089ffffffff0200c2eb0b000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac1e07af2f000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac02473044022009195d870ecc40f54130008e392904e77d32b738c1add19d1d8ebba4edf812e602204f49de6dc60d9a3c3703e1e642942f8834f3a2cd81a6562a34b293942ce42f40012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687300000000"); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" "01000000" "17" "16001479091972186c449eb1ded22b78e40d009bdf0089" "ffffffff" + "02" // outputs + "00c2eb0b00000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "5607af2f00000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "47" "3044022062b408cc7f92c8add622f3297b8992d68403849c6421ef58274ed6fc077102f30220250696eacc0aad022f55882d742dda7178bea780c03705bf9cdbee9f812f785301" "21" "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873" + "00000000" // nLockTime + ); } TEST(BitcoinSigning, EncodeP2SH_P2WSH) { @@ -665,15 +692,16 @@ TEST(BitcoinSigning, EncodeP2SH_P2WSH) { unsignedTx.outputs.emplace_back(0x00000000052f83c0, outScript1); Data unsignedData; - unsignedTx.encode(false, unsignedData); - ASSERT_EQ(hex(unsignedData), "" - "01000000" - "01" - "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff" - "02" - "00e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" - "c0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" - "00000000"); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "01" // inputs + "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e" "01000000" "00" "" "ffffffff" + "02" // outputs + "00e9a43500000000" "19" "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" + "c0832f0500000000" "19" "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" + "00000000" // nLockTime + ); } TEST(BitcoinSigning, SignP2SH_P2WSH) { @@ -740,36 +768,23 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); - auto expected = "" - "01000000" - "0001" - "01" - "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff" - "02" - "00e9a43500000000" "1976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" - "c0832f0500000000" "1976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" - "08" - "00" - "47304402201992f5426ae0bab04cf206d7640b7e00410297bfe5487637f6c2427ee8496be002204ad4e64dc2d269f593cc4820db1fc1e8dc34774f602945115ce485940e05c64200" - "47304402201e412363fa554b994528fd44149f3985b18bb901289ef6b71105b27c7d0e336c0220595e4a1e67154337757562ed5869127533e3e5084c3c2e128518f5f0b85b721800" - "473044022003b0a20ccf545b3f12c5ade10db8717e97b44da2e800387adfd82c95caf529d902206aee3a2395530d52f476d0ddd9d20ba062820ae6f4e1be4921c3630395743ad900" - "483045022100ed7a0eeaf72b84351bceac474b0c0510f67065b1b334f77e6843ed102f968afe022004d97d0cfc4bf5651e46487d6f87bd4af6aef894459f9778f2293b0b2c5b7bc700" - "483045022100934a0c364820588154aed2d519cbcc61969d837b91960f4abbf0e374f03aa39d022036b5c58b754bd44cb5c7d34806c89d9778ea1a1c900618a841e9fbfbe805ff9b00" - "473044022044e3b59b06931d46f857c82fa1d53d89b116a40a581527eac35c5eb5b7f0785302207d0f8b5d063ffc6749fb4e133db7916162b540c70dee40ec0b21e142d8843b3a00" - "cf56" - "210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3" - "2103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b" - "21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a" - "21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4" - "2103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16" - "2102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b" - "56ae" - "00000000"; + auto expected = + "01000000" // version + "0001" // marker & flag + "01" // inputs + "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e" "01000000" "23" "220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54" "ffffffff" + "02" // outputs + "00e9a43500000000" "19" "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" + "c0832f0500000000" "19" "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" + // witness + "08" "00" "" "47" "304402201992f5426ae0bab04cf206d7640b7e00410297bfe5487637f6c2427ee8496be002204ad4e64dc2d269f593cc4820db1fc1e8dc34774f602945115ce485940e05c64200" "47" "304402201e412363fa554b994528fd44149f3985b18bb901289ef6b71105b27c7d0e336c0220595e4a1e67154337757562ed5869127533e3e5084c3c2e128518f5f0b85b721800" "47" "3044022003b0a20ccf545b3f12c5ade10db8717e97b44da2e800387adfd82c95caf529d902206aee3a2395530d52f476d0ddd9d20ba062820ae6f4e1be4921c3630395743ad900" "48" "3045022100ed7a0eeaf72b84351bceac474b0c0510f67065b1b334f77e6843ed102f968afe022004d97d0cfc4bf5651e46487d6f87bd4af6aef894459f9778f2293b0b2c5b7bc700" "48" "3045022100934a0c364820588154aed2d519cbcc61969d837b91960f4abbf0e374f03aa39d022036b5c58b754bd44cb5c7d34806c89d9778ea1a1c900618a841e9fbfbe805ff9b00" "47" "3044022044e3b59b06931d46f857c82fa1d53d89b116a40a581527eac35c5eb5b7f0785302207d0f8b5d063ffc6749fb4e133db7916162b540c70dee40ec0b21e142d8843b3a00" "cf" "56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae" + "00000000" // nLockTime + ; Data serialized; - signedTx.encode(true, serialized); + signer.encodeTx(signedTx, serialized); EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{800, 154, 316})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 130)); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); ASSERT_EQ(hex(serialized), expected); } @@ -802,7 +817,8 @@ TEST(BitcoinSigning, Sign_NegativeNoUtxos) { } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); // Fails as there are 0 utxos ASSERT_FALSE(result); @@ -859,7 +875,150 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { } // Sign - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_FALSE(result); } + +TEST(BitcoinSigning, SignLitecoinReal_a85f) { + auto coin = TWCoinTypeLitecoin; + auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; + auto ownPrivateKey = "b820f41f96c8b7442f3260acd23b3897e1450b8c7c6580136a3c2d3a14e34674"; + + // Setup input + Proto::SigningInput input; + input.set_coin_type(coin); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(3'899'774); + input.set_use_max_amount(true); + input.set_byte_fee(1); + input.set_to_address("ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"); + input.set_change_address(ownAddress); + + auto privKey = parse_hex(ownPrivateKey); + input.add_private_key(privKey.data(), privKey.size()); + + auto utxo0Script = Script::buildForAddress(ownAddress, coin); + Data keyHash0; + EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); + EXPECT_EQ(hex(keyHash0), "5c74be45eb45a3459050667529022d9df8a1ecff"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[std::string(keyHash0.begin(), keyHash0.end())] = scriptString; + + auto utxo0 = input.add_utxo(); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(3'900'000); + auto hash0 = parse_hex("7051cd18189401a844abf0f9c67e791315c4c154393870453f8ad98a818efdb5"); + std::reverse(hash0.begin(), hash0.end()); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(9); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX - 1); + + // set plan, to match real tx + input.mutable_plan()->set_available_amount(3'900'000); + input.mutable_plan()->set_amount(3'899'774); + input.mutable_plan()->set_fee(226); + input.mutable_plan()->set_change(0); + input.mutable_plan()->add_utxos(); + *input.mutable_plan()->mutable_utxos(0) = input.utxo(0); + EXPECT_TRUE(verifyPlan(input.plan(), {3'900'000}, 3'899'774, 226)); + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + signer.encodeTx(signedTx, serialized); + + // https://blockchair.com/litecoin/transaction/a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407 + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "b5fd8e818ad98a3f4570383954c1c41513797ec6f9f0ab44a801941818cd5170" "09000000" "00" "" "feffffff" + "01" // outputs + "7e813b0000000000" "16" "00147b59c096c20fd9a273e240846b23276c69d35815" + // witness + "02" + "47" "3044022029153096af176f9cca0ba9b827e947689a8bb8d11dda570c880f9108bc590b3002202410c78b666722ade1ef4547ad85a128ddcbd4695c40f942457bea3d043b9bb301" + "21" "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignLitecoinReal_8435) { + auto coin = TWCoinTypeLitecoin; + auto ownAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; + auto ownPrivateKey = "690b34763f34e0226ad2a4d47098269322e0402f847c97166e8f39959fcaff5a"; + + // Setup input + Proto::SigningInput input; + input.set_coin_type(coin); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(1'200'000); + input.set_use_max_amount(false); + input.set_byte_fee(1); + input.set_to_address("ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"); + input.set_change_address(ownAddress); + + auto privKey = parse_hex(ownPrivateKey); + input.add_private_key(privKey.data(), privKey.size()); + + auto utxo0Script = Script::buildForAddress(ownAddress, coin); + Data keyHash0; + EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); + EXPECT_EQ(hex(keyHash0), "7b59c096c20fd9a273e240846b23276c69d35815"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[std::string(keyHash0.begin(), keyHash0.end())] = scriptString; + + auto utxo0 = input.add_utxo(); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(3'899'774); + auto hash0 = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); + std::reverse(hash0.begin(), hash0.end()); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{222, 113, 141})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + + // https://blockchair.com/litecoin/transaction/8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8 + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8" "00000000" "00" "" "ffffffff" + "02" // outputs + "804f120000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" + "7131290000000000" "16" "00147b59c096c20fd9a273e240846b23276c69d35815" + // witness + "02" + "47" "304402204139b82927dd80445f27a5d2c29fa4881dbd2911714452a4a706145bc43cc4bf022016fbdf4b09bc5a9c43e79edb1c1061759779a20c35535082bdc469a61ed0771f01" + "21" "02499e327a05cc8bb4b3c34c8347ecfcb152517c9927c092fa273be5379fde3226" + "00000000" // nLockTime + ); +} diff --git a/tests/Bitcoin/TWBitcoinTransactionTests.cpp b/tests/Bitcoin/TWBitcoinTransactionTests.cpp index 2cb1ec15ad0..4086289a438 100644 --- a/tests/Bitcoin/TWBitcoinTransactionTests.cpp +++ b/tests/Bitcoin/TWBitcoinTransactionTests.cpp @@ -34,7 +34,7 @@ TEST(BitcoinTransaction, Encode) { transaction.outputs.emplace_back(400000000, oscript1); Data unsignedData; - transaction.encode(false, unsignedData); + transaction.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ(unsignedData.size(), 201); ASSERT_EQ(hex(unsignedData), "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); diff --git a/tests/Bitcoin/TransactionPlanTests.cpp b/tests/Bitcoin/TransactionPlanTests.cpp index 6bf27de3766..6767a3ca21f 100644 --- a/tests/Bitcoin/TransactionPlanTests.cpp +++ b/tests/Bitcoin/TransactionPlanTests.cpp @@ -25,7 +25,7 @@ TEST(TransactionPlan, OneTypical) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 226)); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 50'000, 147)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 226); @@ -61,29 +61,29 @@ TEST(TransactionPlan, OneInsufficientHigher) { TEST(TransactionPlan, OneFitsExactly) { auto utxos = buildTestUTXOs({100'000}); auto byteFee = 1; - auto expectedFee = 226; - auto sigingInput = buildSigningInput(100'000 - expectedFee, byteFee, utxos); + auto expectedFee = 147; + auto sigingInput = buildSigningInput(100'000 - 226, byteFee, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - expectedFee, expectedFee)); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 226, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 226); } TEST(TransactionPlan, OneFitsExactlyHighFee) { auto utxos = buildTestUTXOs({100'000}); auto byteFee = 10; - auto expectedFee = 2260; - auto sigingInput = buildSigningInput(100'000 - expectedFee, byteFee, utxos); + auto expectedFee = 1470; + auto sigingInput = buildSigningInput(100'000 - 2260, byteFee, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - expectedFee, expectedFee)); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 2260, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); + EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 2260); } TEST(TransactionPlan, TwoFirstEnough) { @@ -92,7 +92,7 @@ TEST(TransactionPlan, TwoFirstEnough) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {20'000}, 15'000, 226)); + EXPECT_TRUE(verifyPlan(txPlan, {20'000}, 15'000, 147)); } TEST(TransactionPlan, TwoSecondEnough) { @@ -101,7 +101,7 @@ TEST(TransactionPlan, TwoSecondEnough) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {80'000}, 70'000, 226)); + EXPECT_TRUE(verifyPlan(txPlan, {80'000}, 70'000, 147)); } TEST(TransactionPlan, TwoBoth) { @@ -110,7 +110,7 @@ TEST(TransactionPlan, TwoBoth) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {20'000, 80'000}, 90'000, 374)); + EXPECT_TRUE(verifyPlan(txPlan, {20'000, 80'000}, 90'000, 215)); } TEST(TransactionPlan, TwoFirstEnoughButSecond) { @@ -119,7 +119,7 @@ TEST(TransactionPlan, TwoFirstEnoughButSecond) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {22'000}, 18'000, 226)); + EXPECT_TRUE(verifyPlan(txPlan, {22'000}, 18'000, 147)); } TEST(TransactionPlan, ThreeNoDust) { @@ -129,7 +129,7 @@ TEST(TransactionPlan, ThreeNoDust) { // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 10, 374)); + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 10, 215)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 226); @@ -138,12 +138,12 @@ TEST(TransactionPlan, ThreeNoDust) { // Now 100'000 fits with no dust; 546 is the dust limit sigingInput = buildSigningInput(100'000 - 226 - 546, 1, utxos); txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 226 - 546, 226)); + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 226 - 546, 147)); // One more and we are over dust limit sigingInput = buildSigningInput(100'000 - 226 - 546 + 1, 1, utxos); txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 546 + 1, 374)); + EXPECT_TRUE(verifyPlan(txPlan, {75'000, 100'000}, 100'000 - 226 - 546 + 1, 215)); } TEST(TransactionPlan, TenThree) { @@ -152,7 +152,7 @@ TEST(TransactionPlan, TenThree) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {100'000, 125'000, 150'000}, 300'000, 522)); + EXPECT_TRUE(verifyPlan(txPlan, {100'000, 125'000, 150'000}, 300'000, 283)); } TEST(TransactionPlan, NonMaxAmount) { @@ -161,7 +161,7 @@ TEST(TransactionPlan, NonMaxAmount) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {50000}, 10000, 226)); + EXPECT_TRUE(verifyPlan(txPlan, {50000}, 10000, 147)); } TEST(TransactionPlan, UnpsentsInsufficient) { @@ -189,7 +189,7 @@ TEST(TransactionPlan, CustomCase) { auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {794121, 2289357}, 2287189, 22814)); + EXPECT_TRUE(verifyPlan(txPlan, {794121, 2289357}, 2287189, 13115)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(2, 2, byteFee), 22814); @@ -216,7 +216,7 @@ TEST(TransactionPlan, MaxAmount) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 19120, 10880)); + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - 5792, 5792)); } TEST(TransactionPlan, MaxAmountOne) { @@ -225,7 +225,7 @@ TEST(TransactionPlan, MaxAmountOne) { auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 192; + auto expectedFee = 113; EXPECT_TRUE(verifyPlan(txPlan, {10189534}, 10189534 - expectedFee, expectedFee)); } @@ -241,7 +241,7 @@ TEST(TransactionPlan, MaxAmountLowerRequested) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 19120, 10880)); + EXPECT_TRUE(verifyPlan(txPlan, {15000, 15000}, 30000 - 5792, 5792)); } TEST(TransactionPlan, MaxAmount4of5) { @@ -252,27 +252,27 @@ TEST(TransactionPlan, MaxAmount4of5) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 1908; + auto expectedFee = 951; EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1000}, 2'900 - expectedFee, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculateSingleInput(byteFee), 444); - EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), expectedFee); + EXPECT_EQ(feeCalculator.calculate(4, 1, byteFee), 1908); } TEST(TransactionPlan, One_MaxAmount_FeeMoreThanAvailable) { auto utxos = buildTestUTXOs({170}); auto byteFee = 1; - auto expectedFee = 192; + auto expectedFee = 113; auto sigingInput = buildSigningInput(300, byteFee, utxos, true); auto txPlan = TransactionBuilder::plan(sigingInput); // Fee is reduced to availableAmount - EXPECT_TRUE(verifyPlan(txPlan, {170}, 0, 170)); + EXPECT_TRUE(verifyPlan(txPlan, {170}, 170 - expectedFee, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), expectedFee); + EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 192); } TEST(TransactionPlan, MaxAmountDoge) { diff --git a/tests/Bitcoin/TxComparisonHelper.cpp b/tests/Bitcoin/TxComparisonHelper.cpp index 66cfe17d728..d7246846750 100644 --- a/tests/Bitcoin/TxComparisonHelper.cpp +++ b/tests/Bitcoin/TxComparisonHelper.cpp @@ -9,8 +9,12 @@ #include #include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" #include "proto/Bitcoin.pb.h" #include "Data.h" +#include "PrivateKey.h" +#include "HexCoding.h" +#include "BinaryCoding.h" #include #include @@ -19,7 +23,7 @@ using namespace TW; using namespace TW::Bitcoin; -auto emptyTxOutPoint = OutPoint(Data(32), 0); +auto emptyTxOutPoint = OutPoint(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"), 0); Proto::UnspentTransaction buildTestUTXO(int64_t amount) { Proto::UnspentTransaction utxo; @@ -27,6 +31,9 @@ Proto::UnspentTransaction buildTestUTXO(int64_t amount) { const auto& outPoint = emptyTxOutPoint; utxo.mutable_out_point()->set_hash(outPoint.hash.data(), outPoint.hash.size()); utxo.mutable_out_point()->set_index(outPoint.index); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxo1Script = parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo.set_script(utxo1Script.data(), utxo1Script.size()); return utxo; } @@ -44,7 +51,16 @@ Proto::SigningInput buildSigningInput(Amount amount, int byteFee, const std::vec input.set_byte_fee(byteFee); input.set_use_max_amount(useMaxAmount); input.set_coin_type(coin); + + auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); + assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + input.add_private_key(utxoKey.bytes.data(), utxoKey.bytes.size()); + *input.mutable_utxo() = { utxos.begin(), utxos.end() }; + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); return input; } @@ -108,12 +124,12 @@ EncodedTxSize getEncodedTxSize(const Transaction& tx) { EncodedTxSize size; { // full segwit size Data data; - tx.encode(true, data); + tx.encode(data, Transaction::SegwitFormatMode::Segwit); size.segwit = data.size(); } - { // witness part only + { // non-segwit Data data; - tx.encode(false, data); + tx.encode(data, Transaction::SegwitFormatMode::NonSegwit); size.nonSegwit = data.size(); } int64_t witnessSize = 0; @@ -151,3 +167,68 @@ bool validateEstimatedSize(const Transaction& tx, int smallerTolerance, int bigg } return ret; } + +void prettyPrintScript(const Script& script) { + Data data; + encodeVarInt(script.bytes.size(), data); + std::cout << " \"" << hex(data) << "\""; + std::cout << " \"" << hex(script.bytes) << "\""; +} + +void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { + Data data; + encode32LE(tx.version, data); + std::cout << " \"" << hex(data) << "\" // version\n"; + + if (useWitnessFormat) { + std::cout << " \"0001\" // marker & flag\n"; + } + + // txins + data.clear(); + encodeVarInt(tx.inputs.size(), data); + std::cout << " \"" << hex(data) << "\" // inputs\n"; + for (auto& input: tx.inputs) { + auto& outpoint = reinterpret_cast(input.previousOutput); + std::cout << " \"" << hex(outpoint.hash) << "\""; + data.clear(); + encode32LE(outpoint.index, data); + std::cout << " \"" << hex(data) << "\""; + prettyPrintScript(input.script); + data.clear(); + encode32LE(input.sequence, data); + std::cout << " \"" << hex(data) << "\"\n"; + } + + // txouts + data.clear(); + encodeVarInt(tx.outputs.size(), data); + std::cout << " \"" << hex(data) << "\" // outputs\n"; + for (auto& output: tx.outputs) { + data.clear(); + encode64LE(output.value, data); + std::cout << " \"" << hex(data) << "\""; + prettyPrintScript(output.script); + std::cout << "\n"; + } + + if (useWitnessFormat) { + std::cout << " // witness\n"; + for (auto& input: tx.inputs) { + data.clear(); + encodeVarInt(input.scriptWitness.size(), data); + std::cout << " \"" << hex(data) << "\"\n"; + for (auto& item: input.scriptWitness) { + data.clear(); + encodeVarInt(item.size(), data); + std::cout << " \"" << hex(data) << "\""; + std::cout << " \"" << hex(item) << "\"\n"; + } + } + } + + data.clear(); + encode32LE(tx.lockTime, data); // nLockTime + std::cout << " \"" << hex(data) << "\" // nLockTime\n"; + std::cout << "\n"; +} diff --git a/tests/Bitcoin/TxComparisonHelper.h b/tests/Bitcoin/TxComparisonHelper.h index e3faf85c1f9..0fdc88264bc 100644 --- a/tests/Bitcoin/TxComparisonHelper.h +++ b/tests/Bitcoin/TxComparisonHelper.h @@ -52,3 +52,6 @@ EncodedTxSize getEncodedTxSize(const Transaction& tx); /// Uses segwit byte size (virtual size). Tolerance is estiamte-smaller and estimate-larger, like -1 and 20. /// Returns false on mismatch, and error is printed (stderr). bool validateEstimatedSize(const Transaction& tx, int smallerTolerance = -1, int biggerTolerance = 20); + +/// Print out a transaction in a nice format, as structured hex strings. +void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat = true); diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/BitcoinCash/TWBitcoinCashTests.cpp index bddceeaf3f6..f5c9fa06465 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/BitcoinCash/TWBitcoinCashTests.cpp @@ -129,6 +129,10 @@ TEST(BitcoinCash, SignTransaction) { Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeBitcoinCash); + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), amount); + EXPECT_EQ(output.transaction().outputs(1).value(), 4325); + EXPECT_EQ(output.encoded().length(), 226); ASSERT_EQ(hex(output.encoded()), "01000000" "01" diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp index 0c209135b7a..53f3c595980 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -99,27 +99,26 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { utxo0->mutable_out_point()->set_sequence(0xfffffffd); // Sign - auto txSinger = TransactionSigner(std::move(input)); - txSinger.transaction.lockTime = 0x00098971; - auto result = txSinger.sign(); + auto txSigner = TransactionSigner(std::move(input)); + txSigner.transaction.lockTime = 0x00098971; + auto result = txSigner.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized), - "01000000" - "0001" - "01" - "5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f" "00000000" "00" "fdffffff" - "02" - "8813000000000000" "160014db746a75d9aae8995d135b1e19a04d7765242a8f" - "a612000000000000" "160014ebae10950c8a35a506e0e265b928305233e802ab" - "02" - "483045022100bf1dcc37c2d3794e216b0b1cfcb04c7f49ef360ae941e46dc9b168f54f5447fe02205a0912bf3a3c0ac0e490c665bcde5239f553c013b2447a6fb5df6387ac029c8c41" - "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" - "71890900" + txSigner.encodeTx(signedTx, serialized); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f" "00000000" "00" "" "fdffffff" + "02" // outputs + "8813000000000000" "16" "0014db746a75d9aae8995d135b1e19a04d7765242a8f" + "fb12000000000000" "16" "0014ebae10950c8a35a506e0e265b928305233e802ab" + // witness + "02" "47" "304402202b371b7cae885463c06357d1fc6ca95ab155613f212711bc7fb115500654946d0220430af77cbbb30afe7d7dcaccb72a55da802ee0a2bfea790dfe7c4e1a4c53fd7d41" "21" "03e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" + "71890900" // nLockTime ); } diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/BitcoinGold/TWSignerTests.cpp index 9a5c587c66c..9addba24260 100644 --- a/tests/BitcoinGold/TWSignerTests.cpp +++ b/tests/BitcoinGold/TWSignerTests.cpp @@ -18,6 +18,7 @@ #include "Bitcoin/TransactionSigner.h" #include "HexCoding.h" #include "../interface/TWTestUtilities.h" +#include "../Bitcoin/TxComparisonHelper.h" using namespace TW; using namespace TW::Bitcoin; @@ -57,17 +58,31 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(0xfffffffd); + + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {99'000}, amount, 141)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(149); + input.mutable_plan()->set_change(88851); + // Sign - auto txSinger = TransactionSigner(std::move(input)); - txSinger.transaction.lockTime = 0x00098971; - auto result = txSinger.sign(); + auto txSigner = TransactionSigner(std::move(input)); + txSigner.transaction.lockTime = 0x00098971; + auto result = txSigner.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - // BitcoinGold Mainnet: https://btg2.trezor.io/tx/3e818ad25d73123b6c1c8099ed462aa5413a4ef57d66d9d260306c012753ba43 + txSigner.encodeTx(signedTx, serialized); + // BitcoinGold Mainnet: https://btg2.trezor.io/tx/db26faec66d070045df0da56140349beb5a12bd14bca12b162fded8f84d18afa + EXPECT_EQ(serialized.size(), 222); ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -75,11 +90,11 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { "1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03" "01000000" "00" "fdffffff" "02" "1027000000000000" "160014db746a75d9aae8995d135b1e19a04d7765242a8f" - "c65a010000000000" "160014ebae10950c8a35a506e0e265b928305233e802ab" + "135b010000000000" "160014ebae10950c8a35a506e0e265b928305233e802ab" "02" - "473044022029b81a6b8f57f76aaf510d8a222ca835bd806936e329aead433f120007d6847002203afa611ff7823ec2a6770359901b0cacf56527cbf947b226ed86b61811545e2b41" + "4730440220325c56363b17e1b1329efeb400c0933a3d9adfb304f29889b3ef01084aef19e302202a69d9be9ef668b5a5517fbfa42e1fc26b3f8b582c721bd1eabd721322bc2b6c41" "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" "71890900" - ); + ); } diff --git a/tests/DigiByte/TWDigiByteTests.cpp b/tests/DigiByte/TWDigiByteTests.cpp index 68f66ae695a..d2e0de76899 100644 --- a/tests/DigiByte/TWDigiByteTests.cpp +++ b/tests/DigiByte/TWDigiByteTests.cpp @@ -69,7 +69,7 @@ TEST(DigiByteTransaction, SignTransaction) { ASSERT_EQ(fee, signer.plan.fee); Data serialized; - signedTx.encode(false, serialized); + signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), "01000000" @@ -119,13 +119,14 @@ TEST(DigiByteTransaction, SignP2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - auto result = TransactionSigner(std::move(input)).sign(); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; - signedTx.encode(true, serialized); - ASSERT_EQ(hex(serialized), "0100000000010180a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea59400100000000ffffffff0280841e00000000001600145c91bc8d2073529224e8be0764128ac22f0005647e351e00000000001600144b62694cfdd7bdac59cbed211288ccd5c0dabd02024730440220346eed84d79a2e45f0f291c9af91bc71d6217cff3488047b35c492a3690cd2ce02204724529e4689060532f9286532c87e3b6efb877008e8e44218c1fced7edf949a012102ac2e56f40d38530fcbf21f1eba0c3c668aa839cda8f2c615e99df44b6447772600000000"); + signer.encodeTx(signedTx, serialized); + ASSERT_EQ(hex(serialized), "0100000000010180a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea59400100000000ffffffff0280841e00000000001600145c91bc8d2073529224e8be0764128ac22f000564d3351e00000000001600144b62694cfdd7bdac59cbed211288ccd5c0dabd0202473044022057b876880b6c98511d9e5baab00428c50bf96868bdf4dc50bd61c2477ed8438b0220312ff89a078ab5a38b7b909ceb58310d93a5b4e2d637b37b77c4d7baf35a1815012102ac2e56f40d38530fcbf21f1eba0c3c668aa839cda8f2c615e99df44b6447772600000000"); } TEST(DigiByteTransaction, LockScripts) { diff --git a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp index c8a098a7aff..7e81ec429b1 100644 --- a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -11,6 +11,7 @@ #include "PrivateKey.h" #include "proto/Bitcoin.pb.h" #include "../interface/TWTestUtilities.h" +#include "../Bitcoin/TxComparisonHelper.h" #include #include @@ -43,9 +44,25 @@ TEST(GroestlcoinSigning, SignP2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(226); + input.mutable_plan()->set_change(2048); + Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeGroestlcoin); + // https://blockbook.groestlcoin.org/tx/40b539c578934c9863a93c966e278fbeb3e67b0da4eb9e3030092c1b717e7a64 + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), 2500); + EXPECT_EQ(output.transaction().outputs(1).value(), 2048); ASSERT_EQ(hex(output.encoded()), "010000000001019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f0100000000ffffffff02c40900000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700080000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88ac024830450221009bbd0228dcb7343828633ded99d216555d587b74db40c4a46f560187eca222dd022032364cf6dbf9c0213076beb6b4a20935d4e9c827a551c3f6f8cbb22d8b464467012102e9c9b9b76e982ad8fa9a7f48470eafbeeba9bf6d287579318c517db5157d936e00000000"); } @@ -69,9 +86,25 @@ TEST(GroestlcoinSigning, SignP2PKH) { utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {5000}, 2500, 221)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(226); + input.mutable_plan()->set_change(2274); + Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeGroestlcoin); + // https://blockbook.groestlcoin.org/tx/74a0dd12bc178cfcc1e0982a2a5b2c01a50e41abbb63beb031bcd21b3e28eac0 + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), 2500); + EXPECT_EQ(output.transaction().outputs(1).value(), 2274); ASSERT_EQ(hex(output.encoded()), "01000000019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f000000006a47304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19012103b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91ffffffff02c4090000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09cee20800000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700000000"); } @@ -106,10 +139,25 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); + EXPECT_TRUE(verifyPlan(plan, {10'000}, 5000, 167)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(226); + input.mutable_plan()->set_change(4774); + Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeGroestlcoin); // https://blockbook.groestlcoin.org/tx/8f4ecc7844e19aa1d3183e47eee89d795f9e7c875a55ec0203946d6c9eb06895 + EXPECT_EQ(output.transaction().outputs_size(), 2); + EXPECT_EQ(output.transaction().outputs(0).value(), 5000); + EXPECT_EQ(output.transaction().outputs(1).value(), 4774); ASSERT_EQ(hex(output.encoded()), "01000000000101fdae0772d7d1d33804a6b1ca0e391668b116bb7a70028427d3d82232189ce86300000000171600142fc7d70acef142d1f7b5ef2f20b1a9b759797674ffffffff0288130000000000001976a91498af0aaca388a7e1024f505c033626d908e3b54a88aca6120000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce024730440220614df57babf74029afaa6dda202afa47d3555cca7a0f20a22e466aeb7029e7d002207974b4c16f346811aff6720d09b9c58d0c4e01e8d258c3d203cc3c1ad228c61a012102fb6ad115761ea928f1367befb2bee79c0b3497314b45e0b734cd150f0601706c00000000"); } @@ -136,10 +184,6 @@ TEST(GroestlcoinSigning, PlanP2WPKH) { Proto::TransactionPlan plan; ANY_PLAN(input, plan, TWCoinTypeGroestlcoin); - EXPECT_EQ(plan.amount(), 2500); - EXPECT_EQ(plan.available_amount(), 4774); - EXPECT_EQ(plan.fee(), 226); - EXPECT_EQ(plan.change(), 2048); - EXPECT_EQ(plan.utxos_size(), 1); + EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); EXPECT_EQ(plan.branch_id(), ""); } diff --git a/tests/Ravencoin/TWRavencoinTransactionTests.cpp b/tests/Ravencoin/TWRavencoinTransactionTests.cpp index 33516634dd5..5fde9bdddb2 100644 --- a/tests/Ravencoin/TWRavencoinTransactionTests.cpp +++ b/tests/Ravencoin/TWRavencoinTransactionTests.cpp @@ -73,7 +73,7 @@ TEST(RavencoinTransaction, SignTransaction) { ASSERT_EQ(fee, signer.plan.fee); Data serialized; - signedTx.encode(false, serialized); + signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000" From 1721b743730727ff551e402672c0e77c778c0358 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Thu, 11 Jun 2020 07:18:32 +0200 Subject: [PATCH 33/81] [Testonly] Extra Cbor tests for coverage. (#983) * Extra Cbor tests for coverage. * Extra verification checks. Co-authored-by: Catenocrypt --- src/Cbor.cpp | 12 ++---- tests/CborTests.cpp | 103 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 13 deletions(-) diff --git a/src/Cbor.cpp b/src/Cbor.cpp index 51304779b21..a22e8213469 100644 --- a/src/Cbor.cpp +++ b/src/Cbor.cpp @@ -163,6 +163,7 @@ Decode::TypeDesc Decode::getTypeDesc() const { TypeDesc typeDesc; typeDesc.isIndefiniteValue = false; typeDesc.majorType = (MajorType)(getByte(0) >> 5); + assert((int)typeDesc.majorType >= 0 && (int)typeDesc.majorType <= 7); auto minorType = (TW::byte)((uint8_t)getByte(0) & 0x1F); if (minorType < 24) { // direct value @@ -225,13 +226,12 @@ uint32_t Decode::getTotalLen() const { return getCompoundLength(1); case MT_map: return getCompoundLength(2); + default: case MT_tag: { uint32_t dataLen = skipClone(typeDesc.byteCount).getTotalLen(); return typeDesc.byteCount + dataLen; } - default: - throw std::invalid_argument("CBOR length type not supported"); } } @@ -373,11 +373,9 @@ bool Decode::isValid() const { return true; } + default: case MT_tag: return skipClone(typeDesc.byteCount).isValid(); - - default: - return false; } } catch (exception& ex) { return false; @@ -440,6 +438,7 @@ string Decode::dumpToStringInternal() const { s << "tag " << typeDesc.value << " " << getTagElement().dumpToStringInternal(); break; + default: case MT_special: // float or simple if (typeDesc.isIndefiniteValue) { // skip break command @@ -447,9 +446,6 @@ string Decode::dumpToStringInternal() const { s << "spec " << typeDesc.value; } break; - - default: - throw std::invalid_argument("CBOR dump: type not supported"); } return s.str(); } diff --git a/tests/CborTests.cpp b/tests/CborTests.cpp index 48f454ad2fe..2c07cc1b1dc 100644 --- a/tests/CborTests.cpp +++ b/tests/CborTests.cpp @@ -80,6 +80,8 @@ TEST(Cbor, EncNegInt) { EXPECT_EQ("3b0000000100000000", hex(Encode::negInt(0x0000000100000001).encoded())); EXPECT_EQ("3b876543210fedcba9", hex(Encode::negInt(0x876543210fedcbaa).encoded())); EXPECT_EQ("3bfffffffffffffffe", hex(Encode::negInt(0xffffffffffffffff).encoded())); + + EXPECT_EQ("-9", Decode(Encode::negInt(9).encoded()).dumpToString()); } @@ -92,10 +94,18 @@ TEST(Cbor, EncString) { "590102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", hex(Encode::bytes(long258).encoded()) ); + + EXPECT_EQ("\"abcde\"", Decode(Encode::string("abcde").encoded()).dumpToString()); + EXPECT_EQ("h\"6162636465\"", Decode(Encode::bytes(parse_hex("6162636465")).encoded()).dumpToString()); } TEST(Cbor, EncTag) { - EXPECT_EQ("c506", hex(Encode::tag(5, Encode::uint(6)).encoded())); + { + Data cbor = Encode::tag(5, Encode::uint(6)).encoded(); + EXPECT_EQ("c506", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("tag 5 6", Decode(cbor).dumpToString()); + } EXPECT_EQ("d94321191234", hex(Encode::tag(0x4321, Encode::uint(0x1234)).encoded())); } @@ -146,7 +156,7 @@ TEST(Cbor, DecInt) { EXPECT_EQ(0xffffffffffffffff, Decode(parse_hex("1bffffffffffffffff")).getValue()); } -TEST(Cbor, DecMinortypeInvlalid) { +TEST(Cbor, DecMinortypeInvalid) { EXPECT_FALSE(Decode(parse_hex("1c")).isValid()); // 28 unused EXPECT_FALSE(Decode(parse_hex("1d")).isValid()); // 29 unused EXPECT_FALSE(Decode(parse_hex("1e")).isValid()); // 30 unused @@ -195,11 +205,11 @@ TEST(Cbor, DecMemoryref) { delete dummy; } -TEST(Cbor, getValue) { +TEST(Cbor, GetValue) { EXPECT_EQ(5, Decode(parse_hex("05")).getValue()); } -TEST(Cbor, getValueInvalid) { +TEST(Cbor, GetValueInvalid) { try { Decode(parse_hex("83010203")).getValue(); // array } catch (exception& ex) { @@ -208,7 +218,7 @@ TEST(Cbor, getValueInvalid) { FAIL() << "Expected exception"; } -TEST(Cbor, getString) { +TEST(Cbor, GetString) { // bytes/string and getString/getBytes work in all combinations EXPECT_EQ("abcde", Decode(parse_hex("656162636465")).getString()); EXPECT_EQ("abcde", Decode(parse_hex("456162636465")).getString()); @@ -216,6 +226,26 @@ TEST(Cbor, getString) { EXPECT_EQ("6162636465", hex(Decode(parse_hex("456162636465")).getBytes())); } +TEST(Cbor, GetStringInvalidType) { + try { + Decode cbor = Decode(Encode::uint(5).encoded()); + cbor.getBytes(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, GetStringInvalidTooShort) { + try { + Decode cbor = Decode(parse_hex("65616263")); // too short + cbor.getBytes(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + TEST(Cbor, ArrayEmpty) { Data cbor = Encode::array({}).encoded(); @@ -364,6 +394,45 @@ TEST(Cbor, MapNested) { EXPECT_EQ("c", decode.getMapElements()[0].second.getMapElements()[0].second.getString()); } +TEST(Cbor, MapIndef) { + Decode cbor = Decode(parse_hex("bf01020304ff")); + EXPECT_EQ("{_ 1: 2, 3: 4}", cbor.dumpToString()); + EXPECT_EQ(2, cbor.getMapElements().size()); + EXPECT_EQ(1, cbor.getMapElements()[0].first.getValue()); + EXPECT_EQ(2, cbor.getMapElements()[0].second.getValue()); +} + +TEST(Cbor, MapIsValidInvalidTooShort) { + { + Decode cbor = Decode(parse_hex("a301020304")); // too short + EXPECT_FALSE(cbor.isValid()); + } + { + Decode cbor = Decode(parse_hex("a3010203")); // too short, partial element + EXPECT_FALSE(cbor.isValid()); + } +} + +TEST(Cbor, MapGetInvalidTooShort1) { + try { + Decode cbor = Decode(parse_hex("a301020304")); // too short + auto elems = cbor.getMapElements(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, MapGetInvalidTooShort2) { + try { + Decode cbor = Decode(parse_hex("a3010203")); // too short, partial element + auto elems = cbor.getMapElements(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + TEST(Cbor, ArrayIndef) { Data cbor = Encode::indefArray() .addIndefArrayElem(Encode::uint(1)) @@ -380,6 +449,10 @@ TEST(Cbor, ArrayIndef) { EXPECT_EQ(2, decode.getArrayElements().size()); EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); EXPECT_EQ(2, decode.getArrayElements()[1].getValue()); + + EXPECT_EQ("[_ 1, 2]", Decode(parse_hex("9f0102ff")).dumpToString()); + EXPECT_EQ("", Decode(parse_hex("ff")).dumpToString()); + EXPECT_EQ("spec 1", Decode(parse_hex("e1")).dumpToString()); } TEST(Cbor, ArrayInfefErrorAddNostart) { @@ -418,3 +491,23 @@ TEST(Cbor, ArrayInfefErrorNoBreak) { // without break it's invalid EXPECT_FALSE(Decode(parse_hex("9f0102")).isValid()); } + +TEST(Cbor, GetTagValueNotTag) { + try { + Decode cbor = Decode(Encode::string("abc").encoded()); + cbor.getTagValue(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, GetTagElementNotTag) { + try { + Decode cbor = Decode(Encode::string("abc").encoded()); + Decode tagElement = cbor.getTagElement(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} From 893977b63afe7e2557dfa2e0231f25f8d66020d4 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Thu, 11 Jun 2020 17:45:39 +0800 Subject: [PATCH 34/81] add android-release action (#985) --- .github/workflows/android-release.yml | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/android-release.yml diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml new file mode 100644 index 00000000000..67ebb2d2c71 --- /dev/null +++ b/.github/workflows/android-release.yml @@ -0,0 +1,40 @@ +name: Android Release + +on: + push: + tags: + - '*' + pull_request: + tags: + - '*' + +jobs: + release: + runs-on: macos-10.15 + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: brew install boost ninja + - name: Install Android Dependencies + run: | + $ANDROID_HOME/tools/bin/sdkmanager --verbose "cmake;3.10.2.4988404" "ndk;21.2.6472646" + $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-26;google_apis;x86" + - name: Accept Licenses + run: echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/tools/bin/sdkmanager --licenses + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Upload to maven + run: | + tools/generate-files + tools/maven-android-upload + env: + BINTRAY_KEY: ${{ secrets.BINTRAY_API_KEY }} + BINTRAY_USER: ${{ secrets.BINTRAY_USER }} From 4cc4dacdfbae128a4145ca6f7edc701eb1d98a4e Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 12 Jun 2020 09:22:44 +0800 Subject: [PATCH 35/81] Migrate iOS build from s3 to release/assets (#986) * upload build to gh release (wip) * change api username * Fix download url * Fix api rate limit * read GITHUB_USER env --- .github/workflows/android-ci.yml | 2 +- .github/workflows/android-release.yml | 3 ++- .github/workflows/ios-ci.yml | 2 +- .github/workflows/ios-release.yml | 37 +++++++++++++++++++++++++++ .github/workflows/linux-ci.yml | 2 +- tools/ios-release | 23 ++++++++++++++--- 6 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/ios-release.yml diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index f4bf5297e48..abb17665fb3 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: [macos-10.15] + runs-on: macos-10.15 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 67ebb2d2c71..474c194ccd5 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -31,9 +31,10 @@ jobs: run: | tools/install-dependencies if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Generate files + run: tools/generate-files - name: Upload to maven run: | - tools/generate-files tools/maven-android-upload env: BINTRAY_KEY: ${{ secrets.BINTRAY_API_KEY }} diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index e3ad8eb88a2..9afd0f7c19b 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: [macos-10.15] + runs-on: macos-10.15 steps: - uses: actions/checkout@v2 - name: Install system dependencies diff --git a/.github/workflows/ios-release.yml b/.github/workflows/ios-release.yml new file mode 100644 index 00000000000..5f8dc4e6003 --- /dev/null +++ b/.github/workflows/ios-release.yml @@ -0,0 +1,37 @@ +name: iOS Release + +on: + push: + tags: + - '*' + pull_request: + tags: + - '*' + +jobs: + release: + runs-on: macos-10.15 + steps: + - uses: actions/checkout@v2 + - name: Install system dependencies + run: | + brew install boost ninja xcodegen + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v1.1.2 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Generate files + run: tools/generate-files + - name: Build and release + run: | + tools/ios-release + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + GITHUB_TOKEN: ${{ secrets.WC_GITHUB_TOKEN }} + GITHUB_USER: ${{ secrets.WC_GITHUB_USER }} diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index d199fd14617..a1556dffeab 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: [ubuntu-18.04] + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: Install system dependencies diff --git a/tools/ios-release b/tools/ios-release index a357705a9b0..ce6fea9b705 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -5,9 +5,19 @@ require 'open3' require 'tempfile' +require 'json' version = ARGV[0] || `git describe --long --tags | cut -f 1 -d "-"`.strip +puts "Processing version: #{version}" + +# Get release by tag +release = `curl -u #{ENV['GITHUB_USER']}:#{ENV['GITHUB_TOKEN']} https://api.github.com/repos/trustwallet/wallet-core/releases/tags/#{version}` +release_hash = JSON.parse(release) +upload_url = release_hash['upload_url'] +puts "asset upload url: #{upload_url}" +upload_url.slice!('{?name,label}') + # First build puts 'Building...' _, stderr, status = Open3.capture3('tools/ios-build') @@ -30,16 +40,21 @@ if status != 0 end # Upload archive -system("aws s3 cp #{file_name} s3://wallet-core/ --acl public-read") +puts 'Uploading...' +upload_url = "#{upload_url}?name=#{file_name}" +puts "asset upload url: #{upload_url}" +upload_output = `curl -u #{ENV['GITHUB_USER']}:#{ENV['GITHUB_TOKEN']} -X POST -H 'Content-Type: application/octet-stream' --data-binary @#{file_name} #{upload_url}` +download_url = JSON.parse(upload_output)['browser_download_url'] +puts "final download url: #{download_url}" -# Upload Cocoapod +# Upload to Cocoapod puts 'Publishing...' podspec = <<-PODSPEC Pod::Spec.new do |s| s.name = 'TrustWalletCore' s.version = '#{version}' s.summary = 'Trust Wallet core data structures and algorithms.' - s.homepage = 'https://github.com/TrustWallet/wallet-core' + s.homepage = 'https://github.com/trustwallet/wallet-core' s.license = 'MIT' s.authors = { 'Alejandro Isaza' => 'al@isaza.ca' } @@ -48,7 +63,7 @@ Pod::Spec.new do |s| s.swift_version = '5.1' s.source = { - http: "https://s3.amazonaws.com/wallet-core/TrustWalletCore-iOS-#{version}.zip" + http: "#{download_url}" } s.default_subspec = 'Core' From 0f1de9715d9681dd143aaca3a0ccb0050dd55e74 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Sat, 13 Jun 2020 08:24:36 +0800 Subject: [PATCH 36/81] NSCAssert will be removed from release (#990) --- swift/Sources/SecRandom.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/swift/Sources/SecRandom.m b/swift/Sources/SecRandom.m index 1f441d48895..52518c110a1 100644 --- a/swift/Sources/SecRandom.m +++ b/swift/Sources/SecRandom.m @@ -3,12 +3,16 @@ uint32_t random32(void) { uint32_t value; - int status = SecRandomCopyBytes(kSecRandomDefault, sizeof(value), &value); - NSCAssert(status == errSecSuccess, @"Failed to generate random number"); + if (SecRandomCopyBytes(kSecRandomDefault, sizeof(value), &value) != errSecSuccess) { + // failed to generate random number + abort(); + } return value; } void random_buffer(uint8_t *buf, size_t len) { - int status = SecRandomCopyBytes(kSecRandomDefault, len, buf); - NSCAssert(status == errSecSuccess, @"Failed to generate random number"); + if (SecRandomCopyBytes(kSecRandomDefault, len, buf) != errSecSuccess) { + // failed to generate random number + abort(); + } } From 792e0e802991e3b08bdde4757ccd285ff6c44c91 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Mon, 15 Jun 2020 23:58:29 +0200 Subject: [PATCH 37/81] [Tests] New TWBitcoinAddressTests. (#992) * New TWBitcoinAddressTests. * Expand TWPrivateKey tests. * Expand PrivateKey tests. * Extend TWPublicKey tests. * Fix for TWPublicKeyRecover(), add size check. * Simplify constructor for ED25519Blake2b case, impossible case removed. * Extend TWPublicKey tests. * Move PublicKeyRecover from TW up to cpp implementation. * Optimize PublicKey::compressed(). * Refactor if back to switch. Co-authored-by: Catenocrypt --- src/PrivateKey.cpp | 2 +- src/PublicKey.cpp | 28 +++- src/PublicKey.h | 3 + src/interface/TWPublicKey.cpp | 12 +- tests/Bitcoin/TWBitcoinAddressTests.cpp | 82 ++++++++++++ tests/PrivateKeyTests.cpp | 83 +++++++++++- tests/PublicKeyTests.cpp | 170 +++++++++++++++++++++--- tests/interface/TWPrivateKeyTests.cpp | 93 ++++++++----- tests/interface/TWPublicKeyTests.cpp | 30 +++++ 9 files changed, 437 insertions(+), 66 deletions(-) create mode 100644 tests/Bitcoin/TWBitcoinAddressTests.cpp diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 7e70a9a0110..822276f0573 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -89,7 +89,7 @@ PrivateKey::PrivateKey(const Data& data) { } PrivateKey::PrivateKey(const Data& data, const Data& ext, const Data& chainCode) { - if (!isValid(data) || !isValid(data) || !isValid(chainCode)) { + if (!isValid(data) || !isValid(ext) || !isValid(chainCode)) { throw std::invalid_argument("Invalid private key or extended key data"); } bytes = data; diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 8628881e950..97a3faedf59 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -68,11 +68,8 @@ PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) : type(type) { break; case TWPublicKeyTypeED25519Blake2b: bytes.reserve(ed25519Size); - if (data.size() == ed25519Size + 1) { - std::copy(std::begin(data) + 1, std::end(data), std::back_inserter(bytes)); - } else { - std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); - } + assert(data.size() == ed25519Size); // ensured by isValid() above + std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); break; case TWPublicKeyTypeED25519Extended: bytes.reserve(ed25519ExtendedSize); @@ -86,17 +83,19 @@ PublicKey PublicKey::compressed() const { } Data newBytes(secp256k1Size); + assert(bytes.size() >= 65); newBytes[0] = 0x02 | (bytes[64] & 0x01); + assert(type == TWPublicKeyTypeSECP256k1Extended || type == TWPublicKeyTypeNIST256p1Extended); switch (type) { case TWPublicKeyTypeSECP256k1Extended: std::copy(bytes.begin() + 1, bytes.begin() + secp256k1Size, newBytes.begin() + 1); return PublicKey(newBytes, TWPublicKeyTypeSECP256k1); + case TWPublicKeyTypeNIST256p1Extended: + default: std::copy(bytes.begin() + 1, bytes.begin() + secp256k1Size, newBytes.begin() + 1); return PublicKey(newBytes, TWPublicKeyTypeNIST256p1); - default: - return *this; } } @@ -180,4 +179,19 @@ Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) return result; } +PublicKey PublicKey::recover(const Data& signature, const Data& message) { + if (signature.size() < 65) { + throw std::invalid_argument("signature too short"); + } + auto v = signature[64]; + if (v >= 27) { + v -= 27; + } + TW::Data result(65); + if (ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signature.data(), message.data(), v) != 0) { + throw std::invalid_argument("recover failed"); + } + return PublicKey(result, TWPublicKeyTypeSECP256k1Extended); +} + } // namespace TW diff --git a/src/PublicKey.h b/src/PublicKey.h index ed9d975fe7c..368d87b40a6 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -69,6 +69,9 @@ class PublicKey { /// The public key hash is computed by applying the hasher to the public key /// bytes and then prepending the prefix. Data hash(const Data& prefix, Hash::Hasher hasher = Hash::sha256ripemd, bool skipTypeByte = false) const; + + /// Recover public key from signature (SECP256k1Extended) + static PublicKey recover(const Data& signature, const Data& message); }; inline bool operator==(const PublicKey& lhs, const PublicKey& rhs) { diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 83f2c9c4789..10d68e60a7f 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -70,14 +70,10 @@ TWString *_Nonnull TWPublicKeyDescription(struct TWPublicKey *_Nonnull publicKey } struct TWPublicKey *_Nullable TWPublicKeyRecover(TWData *_Nonnull signature, TWData *_Nonnull message) { - auto signatureBytes = TWDataBytes(signature); - auto v = signatureBytes[64]; - if (v >= 27) { - v -= 27; - } - TW::Data result(65); - if (ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signatureBytes, TWDataBytes(message), v) != 0) { + try { + const PublicKey publicKey = PublicKey::recover(*((TW::Data*)signature), *((TW::Data*)message)); + return new TWPublicKey{ publicKey }; + } catch (...) { return nullptr; } - return new TWPublicKey{ PublicKey(result, TWPublicKeyTypeSECP256k1Extended) }; } diff --git a/tests/Bitcoin/TWBitcoinAddressTests.cpp b/tests/Bitcoin/TWBitcoinAddressTests.cpp new file mode 100644 index 00000000000..79cacb6445a --- /dev/null +++ b/tests/Bitcoin/TWBitcoinAddressTests.cpp @@ -0,0 +1,82 @@ +// Copyright © 2017-2020 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. + +#include "../interface/TWTestUtilities.h" + +#include +#include +#include +#include + +#include + +const auto addr1Valid = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; +const auto addr1Data = "00769bdff96a02f9135a1d19b749db6a78fe07dc90"; + +TEST(TWBitcoinAddress, Create) { + { + TWBitcoinAddress* addr = TWBitcoinAddressCreateWithString(STRING(addr1Valid).get()); + EXPECT_TRUE(addr != nullptr); + TWBitcoinAddressDelete(addr); + } + { + TWBitcoinAddress* addr = TWBitcoinAddressCreateWithData(DATA(addr1Data).get()); + EXPECT_TRUE(addr != nullptr); + TWBitcoinAddressDelete(addr); + } +} + +TEST(TWBitcoinAddress, IsValid) { + EXPECT_TRUE(TWBitcoinAddressIsValidString(STRING(addr1Valid).get())); + EXPECT_TRUE(TWBitcoinAddressIsValid(DATA(addr1Data).get())); +} + +TEST(TWBitcoinAddress, CreateWithPublicKey) { + const auto pubKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0239de350077b204f8fa1b63542b33580b8f125c4b9b827d5fc65cbe47fc1d9a52").get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(pubKey != nullptr); + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey.get(), TWCoinTypeP2shPrefix(TWCoinTypeBitcoin))); + EXPECT_TRUE(addr.get() != nullptr); +} + +TEST(TWBitcoinAddress, Description) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING(addr1Valid).get())); + EXPECT_TRUE(addr.get() != nullptr); + EXPECT_EQ(std::string(TWStringUTF8Bytes(TWBitcoinAddressDescription(addr.get()))), addr1Valid); +} + +TEST(TWBitcoinAddress, PrefixAndHash) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING(addr1Valid).get())); + EXPECT_TRUE(addr.get() != nullptr); + EXPECT_EQ(TWBitcoinAddressPrefix(addr.get()), 0x00); + const auto keyhash = WRAPD(TWBitcoinAddressKeyhash(addr.get())); + EXPECT_EQ(TW::hex(TW::data(TWDataBytes(keyhash.get()), TWDataSize(keyhash.get()))), addr1Data + 2); +} + +TEST(TWBitcoinAddress, Equal) { + const auto addr1 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING(addr1Valid).get())); + EXPECT_TRUE(addr1.get() != nullptr); + const auto addr2 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(DATA(addr1Data).get())); + EXPECT_TRUE(addr2.get() != nullptr); + EXPECT_TRUE(TWBitcoinAddressEqual(addr1.get(), addr2.get())); +} + +TEST(TWBitcoinAddress, CreateWithStringInvalid) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithString(STRING("__INVALID__").get())); + EXPECT_TRUE(addr.get() == nullptr); +} + +TEST(TWBitcoinAddress, CreateWithDataInvalid) { + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithData(DATA("deadbeef").get())); + EXPECT_TRUE(addr.get() == nullptr); +} + +TEST(TWBitcoinAddress, CreateWithPublicKeyInvalid) { + const auto pubKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3").get(), + TWPublicKeyTypeED25519)); + EXPECT_TRUE(pubKey != nullptr); + const auto addr = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey.get(), TWCoinTypeP2shPrefix(TWCoinTypeBitcoin))); + EXPECT_TRUE(addr.get() == nullptr); +} diff --git a/tests/PrivateKeyTests.cpp b/tests/PrivateKeyTests.cpp index 3a40e922994..9a640218cd6 100644 --- a/tests/PrivateKeyTests.cpp +++ b/tests/PrivateKeyTests.cpp @@ -55,6 +55,43 @@ TEST(PrivateKey, InvalidSECP256k1) { } } +string TestInvalidExtended(const Data& data, const Data& ext, const Data& chainCode) { + try { + auto privateKey = PrivateKey(data, ext, chainCode); + return hex(privateKey.bytes); + } catch (invalid_argument& ex) { + // expected exception + return string("EXCEPTION: ") + string(ex.what()); + } +} + +TEST(PrivateKey, CreateExtendedInvalid) { + { + string res = TestInvalidExtended( + parse_hex("deadbeed"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") + ); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } + { + string res = TestInvalidExtended( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("deadbeed"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4") + ); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } + { + string res = TestInvalidExtended( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("deadbeed") + ); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } +} + TEST(PrivateKey, Valid) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); @@ -65,26 +102,33 @@ TEST(PrivateKey, PublicKey) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(privKeyData); { - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ( "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", hex(publicKey.bytes) ); } { - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ( "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", hex(publicKey.bytes) ); } { - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ( "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", hex(publicKey.bytes) ); } + { + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); + EXPECT_EQ( + "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae", + hex(publicKey.bytes) + ); + } } TEST(PrivateKey, ClearMemory) { @@ -151,7 +195,6 @@ TEST(PrivateKey, Sign) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(privKeyData); Data messageData = TW::data("hello"); - Data result(32); Data hash = Hash::keccak256(messageData); Data actual = privateKey.sign(hash, TWCurveSECP256k1); @@ -160,3 +203,35 @@ TEST(PrivateKey, Sign) { hex(actual) ); } + +TEST(PrivateKey, SignExtended) { + const auto privateKeyExt = PrivateKey(parse_hex( + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + )); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + Data actual = privateKeyExt.sign(hash, TWCurveED25519Extended); + + EXPECT_EQ( + "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08", + hex(actual) + ); +} + +TEST(PrivateKey, SignSchnorr) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); + const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); + EXPECT_EQ(hex(signature), + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" + ); +} + +TEST(PrivateKey, SignSchnorrWrongType) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); + const auto signature = privateKey.signSchnorr(digest, TWCurveNIST256p1); + EXPECT_EQ(signature.size(), 0); +} diff --git a/tests/PublicKeyTests.cpp b/tests/PublicKeyTests.cpp index dba84ba4392..571b4319b8d 100644 --- a/tests/PublicKeyTests.cpp +++ b/tests/PublicKeyTests.cpp @@ -41,6 +41,20 @@ TEST(PublicKeyTests, CreateInvalid) { FAIL() << "Missing expected exception"; } +TEST(PublicKeyTests, CreateBlake) { + const auto privateKeyHex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + const auto publicKeyKeyHex = "b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"; + { + auto publicKey = PrivateKey(parse_hex(privateKeyHex)).getPublicKey(TWPublicKeyTypeED25519Blake2b); + EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); + EXPECT_EQ(publicKey.bytes.size(), 32); + } + { + const auto publicKey = PublicKey(parse_hex(publicKeyKeyHex), TWPublicKeyTypeED25519Blake2b); + EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); + } +} + TEST(PublicKeyTests, CompressedExtended) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(key); @@ -65,36 +79,162 @@ TEST(PublicKeyTests, CompressedExtended) { EXPECT_EQ(compressed.isCompressed(), true); EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeSECP256k1)); EXPECT_EQ(hex(compressed.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); + + auto extended2 = extended.extended(); + EXPECT_EQ(extended2.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(extended2.bytes.size(), 65); + EXPECT_EQ(extended2.isCompressed(), false); + + auto compressed2 = compressed.compressed(); + EXPECT_EQ(compressed2.type, TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(compressed2 == publicKey); + EXPECT_EQ(compressed2.bytes.size(), 33); + EXPECT_EQ(compressed2.isCompressed(), true); } -TEST(PublicKeyTests, Verify) { +TEST(PublicKeyTests, CompressedExtendedNist) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); auto privateKey = PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeNIST256p1); + EXPECT_EQ(publicKey.bytes.size(), 33); + EXPECT_EQ(publicKey.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeNIST256p1)); + EXPECT_EQ(hex(publicKey.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); - const char* message = "Hello"; - const Data messageData = TW::data(message); - const Data digest = Hash::keccak256(messageData); + auto extended = publicKey.extended(); + EXPECT_EQ(extended.type, TWPublicKeyTypeNIST256p1Extended); + EXPECT_EQ(extended.bytes.size(), 65); + EXPECT_EQ(extended.isCompressed(), false); + EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeNIST256p1Extended)); + EXPECT_EQ(hex(extended.bytes), std::string("046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae")); + + auto compressed = extended.compressed(); + EXPECT_EQ(compressed.type, TWPublicKeyTypeNIST256p1); + EXPECT_TRUE(compressed == publicKey); + EXPECT_EQ(compressed.bytes.size(), 33); + EXPECT_EQ(compressed.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeNIST256p1)); + EXPECT_EQ(hex(compressed.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); + + auto extended2 = extended.extended(); + EXPECT_EQ(extended2.type, TWPublicKeyTypeNIST256p1Extended); + EXPECT_EQ(extended2.bytes.size(), 65); + EXPECT_EQ(extended2.isCompressed(), false); + + auto compressed2 = compressed.compressed(); + EXPECT_EQ(compressed2.type, TWPublicKeyTypeNIST256p1); + EXPECT_TRUE(compressed2 == publicKey); + EXPECT_EQ(compressed2.bytes.size(), 33); + EXPECT_EQ(compressed2.isCompressed(), true); +} - auto signature = privateKey.sign(digest, TWCurveSECP256k1); +TEST(PublicKeyTests, CompressedExtendedED25519) { + const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeED25519); + EXPECT_EQ(publicKey.bytes.size(), 32); + EXPECT_EQ(publicKey.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeED25519)); + EXPECT_EQ(hex(publicKey.bytes), std::string("4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867")); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.verify(signature, digest)); + auto extended = publicKey.extended(); + EXPECT_EQ(extended.type, TWPublicKeyTypeED25519); + EXPECT_TRUE(extended == publicKey); + EXPECT_EQ(extended.bytes.size(), 32); + EXPECT_EQ(extended.isCompressed(), true); + + auto compressed = publicKey.compressed(); + EXPECT_EQ(compressed.type, TWPublicKeyTypeED25519); + EXPECT_TRUE(compressed == publicKey); + EXPECT_EQ(compressed.bytes.size(), 32); + EXPECT_EQ(compressed.isCompressed(), true); +} + +TEST(PublicKeyTests, IsValidWrongType) { + EXPECT_FALSE(PublicKey::isValid(parse_hex("deadbeef"), (enum TWPublicKeyType)99)); } -TEST(PublicKeyTests, VerifyEd25519) { +TEST(PublicKeyTests, Verify) { const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - auto privateKey = PrivateKey(key); + const auto privateKey = PrivateKey(key); const char* message = "Hello"; const Data messageData = TW::data(message); const Data digest = Hash::sha256(messageData); - auto signature = privateKey.sign(digest, TWCurveED25519); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + { + const auto signature = privateKey.sign(digest, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f01"); + } + { + const auto signature = privateKey.sign(digest, TWCurveED25519); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); + } + { + const auto signature = privateKey.sign(digest, TWCurveED25519Blake2bNano); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); + } + { + const auto signature = privateKey.sign(digest, TWCurveNIST256p1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "2e4655831f0c60729583595c103bf0d862af6313e4326f03f512682106c792822f5a9cd21e7d4a3316c2d337e5eee649b09c34f7b4407344f0d32e8d33167d8901"); + } +} + +TEST(PublicKeyTests, VerifyEd25519Extended) { + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(key); + + const Data messageData = TW::data("Hello"); + const Data digest = Hash::sha256(messageData); + + try { + privateKey.sign(digest, TWCurveED25519Extended); + } catch (const std::invalid_argument&) { + return; // OK, not implemented + } + FAIL() << "Missing expected exception"; +} + +TEST(PublicKeyTests, VerifySchnorr) { + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(key); + + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); - auto signature2 = privateKey.sign(digest, TWCurveED25519Blake2bNano); - auto publicKey2 = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(publicKey.verifySchnorr(signature, digest)); + EXPECT_EQ(hex(signature), "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); +} + +TEST(PublicKeyTests, VerifySchnorrWrongType) { + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(key); + + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); + + const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + EXPECT_FALSE(publicKey.verifySchnorr(signature, digest)); +} - EXPECT_TRUE(publicKey.verify(signature, digest)); - EXPECT_TRUE(publicKey2.verify(signature2, digest)); +TEST(PublicKeyTests, Recover) { + const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + const auto publicKey = PublicKey::recover(signature, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); } diff --git a/tests/interface/TWPrivateKeyTests.cpp b/tests/interface/TWPrivateKeyTests.cpp index 2598f6a99e8..392e2de86e9 100644 --- a/tests/interface/TWPrivateKeyTests.cpp +++ b/tests/interface/TWPrivateKeyTests.cpp @@ -16,15 +16,34 @@ #include -TEST(PrivateKeyTests, CreateInvalid) { - uint8_t bytes[] = {0xde, 0xad, 0xbe, 0xef}; - auto data = WRAPD(TWDataCreateWithBytes(bytes, 4)); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); +const auto key1Hex = "22667b69166481c9f334756f49c8dddfd72c6bcdd68a7386886e97a82f741130"; + +TEST(TWPrivateKeyTests, Create) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get())); + ASSERT_TRUE(privateKey.get() != nullptr); + const auto data = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TW::hex(TW::data(TWDataBytes(data.get()), TWDataSize(data.get()))), key1Hex); +} + +TEST(TWPrivateKeyTests, CreateNewRandom) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreate()); + ASSERT_TRUE(privateKey.get() != nullptr); +} + +TEST(TWPrivateKeyTests, CreateInvalid) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("deadbeef").get())); ASSERT_EQ(privateKey.get(), nullptr); } -TEST(PrivateKeyTests, AllZeros) { +TEST(TWPrivateKeyTests, CreateCopy) { + const auto privateKey1 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get())); + ASSERT_TRUE(privateKey1.get() != nullptr); + const auto privateKey2 = WRAP(TWPrivateKey, TWPrivateKeyCreateCopy(privateKey1.get())); + ASSERT_TRUE(privateKey2.get() != nullptr); +} + +TEST(TWPrivateKeyTests, AllZeros) { auto bytes = TW::Data(32); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); @@ -32,7 +51,7 @@ TEST(PrivateKeyTests, AllZeros) { ASSERT_EQ(privateKey.get(), nullptr); } -TEST(PrivateKeyTests, Invalid) { +TEST(TWPrivateKeyTests, Invalid) { auto bytes = TW::parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); auto valid = TW::PrivateKey::isValid(bytes, TWCurveSECP256k1); @@ -43,27 +62,30 @@ TEST(PrivateKeyTests, Invalid) { ASSERT_EQ(valid2, false); } -TEST(PrivateKeyTests, IsValid) { - uint8_t bytes[] = {0xaf, 0xee, 0xfc, 0xa7, 0x4d, 0x9a, 0x32, 0x5c, 0xf1, 0xd6, 0xb6, 0x91, 0x1d, 0x61, 0xa6, 0x5c, 0x32, 0xaf, 0xa8, 0xe0, 0x2b, 0xd5, 0xe7, 0x8e, 0x2e, 0x4a, 0xc2, 0x91, 0x0b, 0xab, 0x45, 0xf5}; - auto data = WRAPD(TWDataCreateWithBytes(bytes, 32)); +TEST(TWPrivateKeyTests, IsValid) { + const auto data = DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); ASSERT_TRUE(TWPrivateKeyIsValid(data.get(), TWCurveSECP256k1)); ASSERT_TRUE(TWPrivateKeyIsValid(data.get(), TWCurveED25519)); } -TEST(PrivateKeyTests, PublicKey) { - uint8_t bytes[] = {0xaf, 0xee, 0xfc, 0xa7, 0x4d, 0x9a, 0x32, 0x5c, 0xf1, 0xd6, 0xb6, 0x91, 0x1d, 0x61, 0xa6, 0x5c, 0x32, 0xaf, 0xa8, 0xe0, 0x2b, 0xd5, 0xe7, 0x8e, 0x2e, 0x4a, 0xc2, 0x91, 0x0b, 0xab, 0x45, 0xf5}; - auto data = WRAPD(TWDataCreateWithBytes(bytes, 32)); - auto privateKey = TWPrivateKeyCreateWithData(data.get()); - auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey, false); - - uint8_t expected[] = {0x04, 0x99, 0xc6, 0xf5, 0x1a, 0xd6, 0xf9, 0x8c, 0x9c, 0x58, 0x3f, 0x8e, 0x92, 0xbb, 0x77, 0x58, 0xab, 0x2c, 0xa9, 0xa0, 0x41, 0x10, 0xc0, 0xa1, 0x12, 0x6e, 0xc4, 0x3e, 0x54, 0x53, 0xd1, 0x96, 0xc1, 0x66, 0xb4, 0x89, 0xa4, 0xb7, 0xc4, 0x91, 0xe7, 0x68, 0x8e, 0x6e, 0xbe, 0xa3, 0xa7, 0x1f, 0xc3, 0xa1, 0xa4, 0x8d, 0x60, 0xf9, 0x8d, 0x5c, 0xe8, 0x4c, 0x93, 0xb6, 0x5e, 0x42, 0x3f, 0xde, 0x91}; - for (auto i = 0; i < sizeof(expected); i += 1) { - ASSERT_EQ(publicKey->impl.bytes[i], expected[i]); +TEST(TWPrivateKeyTests, PublicKey) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + { + const auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false); + ASSERT_EQ(TW::hex(publicKey->impl.bytes), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); + } + { + const auto publicKey = TWPrivateKeyGetPublicKeyNist256p1(privateKey.get()); + ASSERT_EQ(TW::hex(publicKey->impl.bytes), "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"); + } + { + const auto publicKey = TWPrivateKeyGetPublicKeyCurve25519(privateKey.get()); + ASSERT_EQ(TW::hex(publicKey->impl.bytes), "686cfce9108566dd43fc6aa75e31f9a9f319c9e9c04d6ad0a52505b86bc17c3a"); } } -TEST(PrivateKeyTests, ClearMemory) { +TEST(TWPrivateKeyTests, ClearMemory) { auto privKey = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; auto privKeyData = TW::parse_hex(privKey); auto data = WRAPD(TWDataCreateWithBytes(privKeyData.data(), privKeyData.size())); @@ -82,19 +104,28 @@ TEST(PrivateKeyTests, ClearMemory) { ASSERT_GE(countDifferent, 32*2/3); } -TEST(PrivateKeyTests, Sign) { - uint8_t bytes[] = {0xaf, 0xee, 0xfc, 0xa7, 0x4d, 0x9a, 0x32, 0x5c, 0xf1, 0xd6, 0xb6, 0x91, 0x1d, 0x61, 0xa6, 0x5c, 0x32, 0xaf, 0xa8, 0xe0, 0x2b, 0xd5, 0xe7, 0x8e, 0x2e, 0x4a, 0xc2, 0x91, 0x0b, 0xab, 0x45, 0xf5}; - auto keyData = WRAPD(TWDataCreateWithBytes(bytes, 32)); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get())); +TEST(TWPrivateKeyTests, Sign) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); - auto message = "hello"; - auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); - auto hash = WRAPD(TWHashKeccak256(data.get())); + const auto message = "hello"; + const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); + const auto hash = WRAPD(TWHashKeccak256(data.get())); - auto actual = WRAPD(TWPrivateKeySign(privateKey.get(), hash.get(), TWCurveSECP256k1)); + const auto actual = WRAPD(TWPrivateKeySign(privateKey.get(), hash.get(), TWCurveSECP256k1)); - uint8_t expected[] = {0x87, 0x20, 0xa4, 0x6b, 0x5b, 0x39, 0x63, 0x79, 0x0d, 0x94, 0xbc, 0xc6, 0x1a, 0xd5, 0x7c, 0xa0, 0x2f, 0xd1, 0x53, 0x58, 0x43, 0x15, 0xbf, 0xa1, 0x61, 0xed, 0x34, 0x55, 0xe3, 0x36, 0xba, 0x62, 0x4d, 0x68, 0xdf, 0x01, 0x0e, 0xd9, 0x34, 0xb8, 0x79, 0x2c, 0x5b, 0x6a, 0x57, 0xba, 0x86, 0xc3, 0xda, 0x31, 0xd0, 0x39, 0xf9, 0x61, 0x2b, 0x44, 0xd1, 0xbf, 0x05, 0x41, 0x32, 0x25, 0x4d, 0xe9, 0x01}; - for (auto i = 0; i < sizeof(expected); i += 1) { - ASSERT_EQ(TWDataBytes(actual.get())[i], expected[i]); - } + ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), + "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); +} + +TEST(TWPrivateKeyTests, SignAsDER) { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + + const auto message = "hello"; + const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); + const auto hash = WRAPD(TWHashKeccak256(data.get())); + + auto actual = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), hash.get(), TWCurveSECP256k1)); + + ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), + "30450221008720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba6202204d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9"); } diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index 5dad2ea0572..b9ae558bc4e 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -18,6 +18,14 @@ using namespace TW; +TEST(TWPublicKeyTests, Create) { + const auto publicKeyHex = "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"; + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(publicKey != nullptr); + const auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(hex(*((Data*)(publicKeyData.get()))), publicKeyHex); +} + TEST(TWPublicKeyTests, CreateFromPrivateSecp256k1) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); @@ -33,6 +41,11 @@ TEST(TWPublicKeyTests, CreateFromPrivateSecp256k1) { TWPublicKeyDelete(publicKey); } +TEST(TWPublicKeyTests, CreateInvalid) { + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("deadbeef").get(), TWPublicKeyTypeSECP256k1)); + EXPECT_EQ(publicKey, nullptr); +} + TEST(TWPublicKeyTests, CompressedExtended) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); @@ -87,3 +100,20 @@ TEST(TWPublicKeyTests, VerifyEd25519) { ASSERT_TRUE(TWPublicKeyVerify(publicKey, signature.get(), digest.get())); ASSERT_TRUE(TWPublicKeyVerify(publicKey2, signature2.get(), digest.get())); } + +TEST(TWPublicKeyTests, Recover) { + const auto message = DATA("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = DATA("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyRecover(signature.get(), message.get())); + EXPECT_TRUE(publicKey.get() != nullptr); + EXPECT_EQ(TWPublicKeyKeyType(publicKey.get()), TWPublicKeyTypeSECP256k1Extended); + const auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(hex(*((Data*)(publicKeyData.get()))), + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); +} + +TEST(TWPublicKeyTests, RecoverInvalid) { + const auto deadbeef = DATA("deadbeef"); + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyRecover(deadbeef.get(), deadbeef.get())); + EXPECT_EQ(publicKey.get(), nullptr); +} From ca75f79239a335e1e75892f71a57145947371a0b Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Wed, 17 Jun 2020 09:39:33 +0800 Subject: [PATCH 38/81] Fixes protobuf related warnings (#995) * 1. build swift protobuf for distribution 2. ignore warnings in protobuf lib 3. enable -Wshorten-64-to-32 4. fixes some unused variable warnings --- CMakeLists.txt | 11 ++++++++++- cmake/Protobuf.cmake | 2 +- jni/cpp/Random.cpp | 2 +- jni/cpp/TWJNIData.cpp | 5 +++-- src/Base32.h | 5 ++++- src/Bitcoin/CashAddress.cpp | 4 +++- src/Coin.cpp | 6 ++++-- src/Encrypt.cpp | 4 ++-- src/Ethereum/RLP.cpp | 4 ++-- src/Keystore/EncryptionParameters.cpp | 4 ++-- swift/Podfile | 6 ++++++ swift/Podfile.lock | 4 ++-- swift/project.yml | 5 +++-- tests/Ethereum/TWEthereumAbiEncoderTests.cpp | 14 +++++++------- tests/Filecoin/TWAnySignerTests.cpp | 2 +- tests/interface/TWStoredKeyTests.cpp | 2 +- tests/interface/TWTestUtilities.h | 6 +++--- walletconsole/lib/Util.cpp | 4 ++-- 18 files changed, 57 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1877f80c137..68ccefb1558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,11 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) project(TrustWalletCore) +# Configure warnings +set(TW_CXX_WARNINGS "-Wshorten-64-to-32 -Wtautological-constant-out-of-range-compare -Wshift-count-overflow") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ ${TW_CXX_WARNINGS}") set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -18,6 +20,13 @@ else() set(PREFIX "$ENV{PREFIX}") endif() +# Configure CCache if available +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif(CCACHE_FOUND) + include_directories(${PREFIX}/include) link_directories(${PREFIX}/lib) diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake index 7f27286911e..026b61ac3a4 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -156,4 +156,4 @@ set_target_properties( LINK_FLAGS -no-undefined ) -target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1) +target_compile_options(protobuf PRIVATE -DHAVE_PTHREAD=1 -Wno-inconsistent-missing-override -Wno-shorten-64-to-32) diff --git a/jni/cpp/Random.cpp b/jni/cpp/Random.cpp index d0d9616e164..de97baff225 100644 --- a/jni/cpp/Random.cpp +++ b/jni/cpp/Random.cpp @@ -35,7 +35,7 @@ void random_buffer(uint8_t *buf, size_t len) { jobject random = env->NewObject(secureRandomClass, constructor); //byte array[] = new byte[len]; - jbyteArray array = env->NewByteArray(len); + jbyteArray array = env->NewByteArray(static_cast(len)); //random.nextBytes(bytes); jmethodID nextBytes = env->GetMethodID(secureRandomClass, "nextBytes", "([B)V"); diff --git a/jni/cpp/TWJNIData.cpp b/jni/cpp/TWJNIData.cpp index 9d468ec4231..c0577f31442 100644 --- a/jni/cpp/TWJNIData.cpp +++ b/jni/cpp/TWJNIData.cpp @@ -10,8 +10,9 @@ #include "TWJNIData.h" jbyteArray TWDataJByteArray(TWData *_Nonnull data, JNIEnv *env) { - jbyteArray array = env->NewByteArray(TWDataSize(data)); - env->SetByteArrayRegion(array, 0, TWDataSize(data), (jbyte *) TWDataBytes(data)); + jsize dataSize = static_cast(TWDataSize(data)); + jbyteArray array = env->NewByteArray(dataSize); + env->SetByteArrayRegion(array, 0, dataSize, (jbyte *) TWDataBytes(data)); TWDataDelete(data); return array; } diff --git a/src/Base32.h b/src/Base32.h index dd94800f3fa..7119a9944be 100644 --- a/src/Base32.h +++ b/src/Base32.h @@ -45,7 +45,10 @@ inline std::string encode(const Data& val, const char* alphabet = nullptr) { } // perform the base32 encode char* retval = base32_encode(val.data(), inLen, buf, outLen, alphabet); - assert(retval != nullptr); + if (retval == nullptr) { + // return empty string if failed + return std::string(); + } // make sure there is a terminator ath the end buf[outLen - 1] = '\0'; return std::string(buf); diff --git a/src/Bitcoin/CashAddress.cpp b/src/Bitcoin/CashAddress.cpp index 1d22447c961..43eaa408b6c 100644 --- a/src/Bitcoin/CashAddress.cpp +++ b/src/Bitcoin/CashAddress.cpp @@ -78,7 +78,9 @@ CashAddress::CashAddress(const PublicKey& publicKey) { size_t outlen = 0; auto success = cash_addr_to_data(bytes.data(), &outlen, payload.data(), 21) != 0; - assert(success && outlen == CashAddress::size); + if (!success || outlen != CashAddress::size) { + throw std::invalid_argument("unable to cash_addr_to_data"); + } } std::string CashAddress::string() const { diff --git a/src/Coin.cpp b/src/Coin.cpp index 03e72079a41..2df5a0e5537 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -111,8 +111,10 @@ void setupDispatchers() { for (auto c : dispCoins) { assert(dispatchMap.find(c) == dispatchMap.end()); // each coin must appear only once dispatchMap[c] = d; - auto setResult = coinTypes.emplace(c); - assert(setResult.second == true); // each coin must appear only once + if (coinTypes.emplace(c).second != true) { + // each coin must appear only once + abort(); + }; } } return; diff --git a/src/Encrypt.cpp b/src/Encrypt.cpp index 7492fdb8b54..59fd22d6835 100644 --- a/src/Encrypt.cpp +++ b/src/Encrypt.cpp @@ -89,7 +89,7 @@ Data AESCTREncrypt(const Data& key, const Data& data, Data& iv) { } Data result(data.size()); - aes_ctr_encrypt(data.data(), result.data(), data.size(), iv.data(), aes_ctr_cbuf_inc, &ctx); + aes_ctr_encrypt(data.data(), result.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); return result; } @@ -100,7 +100,7 @@ Data AESCTRDecrypt(const Data& key, const Data& data, Data& iv) { } Data result(data.size()); - aes_ctr_decrypt(data.data(), result.data(), data.size(), iv.data(), aes_ctr_cbuf_inc, &ctx); + aes_ctr_decrypt(data.data(), result.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); return result; } diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index fd2e8a826dc..494a6c0f71b 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -222,7 +222,7 @@ RLP::DecodedItem RLP::decode(const Data& input) { if (prefix <= 0xbf) { // long string auto lenOfStrLen = prefix - 0xb7; - auto strLen = decodeLength(subData(input, 1, lenOfStrLen)); + auto strLen = static_cast(decodeLength(subData(input, 1, lenOfStrLen))); if (inputLen < lenOfStrLen || inputLen < lenOfStrLen + strLen) { throw std::invalid_argument("Invalid rlp encoding length"); } @@ -254,7 +254,7 @@ RLP::DecodedItem RLP::decode(const Data& input) { } if (prefix <= 0xff) { auto lenOfListLen = prefix - 0xf7; - auto listLen = decodeLength(subData(input, 1, lenOfListLen)); + auto listLen = static_cast(decodeLength(subData(input, 1, lenOfListLen))); if (inputLen < lenOfListLen || inputLen < lenOfListLen + listLen) { throw std::invalid_argument("Invalid rlp list length"); } diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 279cc76761c..87d075f6446 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -41,7 +41,7 @@ EncryptionParameters::EncryptionParameters(const Data& password, const Data& dat Data iv = cipherParams.iv; encrypted = Data(data.size()); - aes_ctr_encrypt(data.data(), encrypted.data(), data.size(), iv.data(), aes_ctr_cbuf_inc, &ctx); + aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); } @@ -83,7 +83,7 @@ Data EncryptionParameters::decrypt(const Data& password) const { auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); assert(result != EXIT_FAILURE); - aes_ctr_decrypt(encrypted.data(), decrypted.data(), encrypted.size(), iv.data(), + aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); } else if (cipher == "aes-128-cbc") { aes_decrypt_ctx ctx; diff --git a/swift/Podfile b/swift/Podfile index 556109f432e..8c3a5145bf2 100644 --- a/swift/Podfile +++ b/swift/Podfile @@ -9,3 +9,9 @@ target 'TrustWalletCore' do target 'TrustWalletCoreTests' end + +post_install do |installer| + installer.pods_project.build_configurations.each do |config| + config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' + end +end diff --git a/swift/Podfile.lock b/swift/Podfile.lock index 46f957ed3b2..ae81306f6a7 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -15,6 +15,6 @@ SPEC CHECKSUMS: SwiftLint: 009a898ef2a1c851f45e1b59349bf6ff2ddc990d SwiftProtobuf: 4fd9645e69b72cbae6ec8da5be0cdd20ca6565dd -PODFILE CHECKSUM: 9af33e2495c8b16bfcea0f7779a0fcfe29e3ab60 +PODFILE CHECKSUM: 4382612b240ed6d49e9a3b9bbb107c581e72b8ea -COCOAPODS: 1.9.1 +COCOAPODS: 1.9.3 diff --git a/swift/project.yml b/swift/project.yml index 0d0db778479..120ca294056 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -6,7 +6,7 @@ settings: base: HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/src SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include - CLANG_CXX_LANGUAGE_STANDARD: gnu++17 + CLANG_CXX_LANGUAGE_STANDARD: c++17 SWIFT_VERSION: 5.1 configs: release: @@ -47,6 +47,7 @@ targets: BUILD_LIBRARY_FOR_DISTRIBUTION: true INFOPLIST_FILE: 'Info.plist' CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR + OTHER_CFLAGS: $(inherited) -Wno-comma postCompileScripts: - script: ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml name: SwiftLint @@ -205,4 +206,4 @@ targets: - protobuf/google/protobuf/wrappers.pb.cc settings: GCC_WARN_64_TO_32_BIT_CONVERSION: NO - OTHER_CFLAGS: -DHAVE_PTHREAD=1 + OTHER_CFLAGS: $(inherited) -DHAVE_PTHREAD=1 -Wno-comma -Wno-unreachable-code diff --git a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp b/tests/Ethereum/TWEthereumAbiEncoderTests.cpp index 638c4159e9c..4ccb7fef0f9 100644 --- a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp +++ b/tests/Ethereum/TWEthereumAbiEncoderTests.cpp @@ -34,12 +34,12 @@ TEST(TWEthereumAbi, FuncCreate1) { TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); - int p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); + auto p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); EXPECT_EQ(0, p1index); - int p2index = TWEthereumAbiFunctionAddParamUInt64(func, 9, true); + auto p2index = TWEthereumAbiFunctionAddParamUInt64(func, 9, true); EXPECT_EQ(0, p2index); // check back get value - int p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); + auto p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); EXPECT_EQ(9, p2val2); TWString* type = TWEthereumAbiFunctionGetType(func); @@ -56,13 +56,13 @@ TEST(TWEthereumAbi, FuncCreate2) { TWString* p1valStr = TWStringCreateWithUTF8Bytes("0045"); TWData* p1val = TWDataCreateWithHexString(p1valStr); - int p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val, false); + auto p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val, false); EXPECT_EQ(0, p1index); //TWDataDelete(p1val); TWStringDelete(p1valStr); Data dummy(0); - int p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); + auto p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); EXPECT_EQ(0, p2index); // check back get value @@ -83,7 +83,7 @@ TEST(TWEthereumAbi, EncodeFuncCase1) { EXPECT_EQ(0, TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("64617665")), false)); EXPECT_EQ(1, TWEthereumAbiFunctionAddParamBool(func, true, false)); - int paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); + auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); EXPECT_EQ(2, paramArrIdx); EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("01")))); EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("02")))); @@ -115,7 +115,7 @@ TEST(TWEthereumAbi, EncodeFuncCase2) { EXPECT_TRUE(func != nullptr); EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt256(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0123")), false)); - int paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); + auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); EXPECT_EQ(1, paramArrIdx); EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x456)); EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x789)); diff --git a/tests/Filecoin/TWAnySignerTests.cpp b/tests/Filecoin/TWAnySignerTests.cpp index e1dd177277e..96594f3ea1d 100644 --- a/tests/Filecoin/TWAnySignerTests.cpp +++ b/tests/Filecoin/TWAnySignerTests.cpp @@ -48,7 +48,7 @@ TEST(TWAnySignerFilecoin, Sign) { "befa485903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); Proto::SigningOutput output; - output.ParseFromArray(TWDataBytes(outputData), TWDataSize(outputData)); + output.ParseFromArray(TWDataBytes(outputData), static_cast(TWDataSize(outputData))); ASSERT_EQ(hex(output.encoded()), "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c57" diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index 66704ee4c6f..6a1c0238419 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -156,7 +156,7 @@ TEST(TWStoredKey, storeAndImportJSON) { ifstream ifs(outFileName); // get length of file: ifs.seekg (0, ifs.end); - int length = ifs.tellg(); + auto length = ifs.tellg(); ifs.seekg (0, ifs.beg); EXPECT_TRUE(length > 20); diff --git a/tests/interface/TWTestUtilities.h b/tests/interface/TWTestUtilities.h index 53ebb485950..788ad58dd4d 100644 --- a/tests/interface/TWTestUtilities.h +++ b/tests/interface/TWTestUtilities.h @@ -40,21 +40,21 @@ std::string getTestTempDir(void); auto inputData = input.SerializeAsString();\ auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ auto outputTWData = WRAPD(TWAnySignerSign(inputTWData.get(), coin));\ - output.ParseFromArray(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get()));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ } #define ANY_ENCODE(input, coin) \ {\ auto inputData = input.SerializeAsString();\ auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ auto encodedData = WRAPD(TWAnySignerEncode(inputTWData.get(), coin));\ - encoded = TW::data(TWDataBytes(encodedData.get()), TWDataSize(encodedData.get()));\ + encoded = TW::data(TWDataBytes(encodedData.get()), static_cast(TWDataSize(encodedData.get())));\ } #define ANY_PLAN(input, output, coin) \ {\ auto inputData = input.SerializeAsString();\ auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), coin));\ - output.ParseFromArray(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get()));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ } #define DUMP_PROTO(input) \ { \ diff --git a/walletconsole/lib/Util.cpp b/walletconsole/lib/Util.cpp index 01bcba85e9f..689b35c5143 100644 --- a/walletconsole/lib/Util.cpp +++ b/walletconsole/lib/Util.cpp @@ -74,7 +74,7 @@ bool Util::fileR(const string& fileName, string& res) { ifstream infile(fileName, std::ios::in | std::ios::binary); // get length of file: infile.seekg (0, infile.end); - int length = infile.tellg(); + auto length = infile.tellg(); infile.seekg (0, infile.beg); char* buffer = new char[length]; if (!infile.read(buffer, length)) { @@ -82,7 +82,7 @@ bool Util::fileR(const string& fileName, string& res) { delete[] buffer; return false; } - int red = infile.gcount(); + auto red = infile.gcount(); infile.close(); res = string(TW::hex(data((const byte*)buffer, red))); delete[] buffer; From 0125c0bdd291551c858b6bc9e7b4d3fdef6a32e7 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Wed, 17 Jun 2020 09:27:35 +0200 Subject: [PATCH 39/81] [BTC Signing] Do not swallow error in case of missing keys with segwit (#997) * BTC Signing: Do not swallow error in case of missing keys with segwit scripts, #996. * BTC fees: Estimation works correctly even with missing keys. * Remove a debug print from test. * Add swift litecoin test * Adjust PlanAndSign iOS test. * Minor test adjustment. * comment Co-authored-by: Catenocrypt Co-authored-by: hewigovens <360470+hewigovens@users.noreply.github.com> --- src/Bitcoin/TransactionSigner.cpp | 22 ++- .../BitcoinTransactionSignerTests.swift | 10 +- swift/Tests/Blockchains/LitecoinTests.swift | 65 ++++++++ tests/Bitcoin/TWBitcoinSigningTests.cpp | 143 ++++++++++++------ tests/Decred/SignerTests.cpp | 1 - tests/Decred/TWAnySignerTests.cpp | 10 +- 6 files changed, 191 insertions(+), 60 deletions(-) diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index d6268dec913..2c52504ef80 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -96,16 +96,18 @@ Result TransactionSigner::sign(Script scr if (script.matchPayToWitnessPublicKeyHash(data)) { auto witnessScript = Script::buildPayToPublicKeyHash(results[0]); auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (result) { - witnessStack = result.payload(); + if (!result) { + return Result::failure(result.error()); } + witnessStack = result.payload(); results.clear(); } else if (script.matchPayToWitnessScriptHash(data)) { auto witnessScript = Script(results[0]); auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (result) { - witnessStack = result.payload(); + if (!result) { + return Result::failure(result.error()); } + witnessStack = result.payload(); witnessStack.push_back(move(witnessScript.bytes)); results.clear(); } else if (script.isWitnessProgram()) { @@ -166,7 +168,7 @@ Result> TransactionSigner::si } auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(pubKey)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && !estimationMode) { // Error: missing key return Result>::failure("Missing private key."); } @@ -184,7 +186,7 @@ Result> TransactionSigner::si if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(data)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && !estimationMode) { // Error: Missing key return Result>::failure("Missing private key."); } @@ -198,18 +200,22 @@ Result> TransactionSigner::si } if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); - if (key.empty()) { + if (key.empty() && !estimationMode) { // Error: Missing keys return Result>::failure("Missing private key."); } - auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); auto signature = createSignature(transactionToSign, script, key, index, utxo.amount(), version); if (signature.empty()) { // Error: Failed to sign return Result>::failure("Failed to sign."); } + if (key.empty() && estimationMode) { + // estimation mode, key is missing: use placeholder for public key + return Result>::success({signature, Data(32)}); + } + auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); return Result>::success({signature, pubkey.bytes}); } // Error: Invalid output script diff --git a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift index 92b35d87440..878af97da6d 100644 --- a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift @@ -13,6 +13,7 @@ class BitcoinTransactionSignerTests: XCTestCase { } func testSignP2WSH() throws { + // set up input var input = BitcoinSigningInput.with { $0.hashType = BitcoinSigHashType.all.rawValue $0.amount = 1000 @@ -21,9 +22,6 @@ class BitcoinTransactionSignerTests: XCTestCase { $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" } - input.privateKey.append(Data(hexString: "ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")!) - input.privateKey.append(Data(hexString: "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")!) - input.scripts["593128f9f90e38b706c18623151e37d2da05c229"] = Data(hexString: "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")! let p2sh = BitcoinScript.buildPayToWitnessScriptHash(scriptHash: Data(hexString: "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")!) @@ -36,12 +34,18 @@ class BitcoinTransactionSignerTests: XCTestCase { } input.utxo.append(utxo0) + // Plan let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .bitcoin) XCTAssertEqual(plan.amount, 1000) XCTAssertEqual(plan.fee, 147) XCTAssertEqual(plan.change, 79) + // Extend input with private key + input.privateKey.append(Data(hexString: "ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")!) + input.privateKey.append(Data(hexString: "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")!) + + // Sign let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) XCTAssertTrue(output.error.isEmpty) diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index 51e951bd321..115a7f7954d 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -89,4 +89,69 @@ class LitecoinTests: XCTestCase { XCTAssertEqual(SegwitAddress(hrp: .litecoin, publicKey: zpubAddr4).description, "ltc1qcgnevr9rp7aazy62m4gen0tfzlssa52axwytt6") XCTAssertEqual(SegwitAddress(hrp: .litecoin, publicKey: zpubAddr11).description, "ltc1qy072y8968nzp6mz3j292h8lp72d678fcmms6vl") } + + func testPlanAndSign_8435() throws { + let address = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum" + let lockScript = BitcoinScript.buildForAddress(address: address, coin: .litecoin) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(Data(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407")!.reversed()) + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = lockScript.data + $0.amount = 3899774 + } + ] + + // redeem p2pwkh + let scriptHash = lockScript.matchPayToWitnessPublicKeyHash()! + var input = BitcoinSigningInput.with { + $0.toAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00" + $0.changeAddress = address + $0.hashType = BitcoinSigHashType.all.rawValue + $0.amount = 1200000 + $0.coinType = CoinType.litecoin.rawValue + $0.byteFee = 1 + $0.utxo = utxos + $0.useMaxAmount = false + $0.scripts = [ + scriptHash.hexString: BitcoinScript.buildPayToPublicKeyHash(hash: scriptHash).data + ] + } + + // Plan + let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .litecoin) + + XCTAssertEqual(plan.amount, 1200000) + XCTAssertEqual(plan.fee, 141) + XCTAssertEqual(plan.change, 2699633) + + // Extend input with private key + input.privateKey = [Data(hexString: "690b34763f34e0226ad2a4d47098269322e0402f847c97166e8f39959fcaff5a")!] + input.plan = plan + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .litecoin) + XCTAssertTrue(output.error.isEmpty) + + // https://blockchair.com/litecoin/transaction/8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8 + let txId = output.transactionID + XCTAssertEqual(txId, "8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8") + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "01000000" + // version + "0001" + // marker & flag + "01" + // inputs + "07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8" + "00000000" + "00" + "ffffffff" + + "02" + // outputs + "804f120000000000" + "16" + "00145c74be45eb45a3459050667529022d9df8a1ecff" + + "7131290000000000" + "16" + "00147b59c096c20fd9a273e240846b23276c69d35815" + + // witness + "02" + + "47" + "304402204139b82927dd80445f27a5d2c29fa4881dbd2911714452a4a706145bc43cc4bf022016fbdf4b09bc5a9c43e79edb1c1061759779a20c35535082bdc469a61ed0771f01" + + "21" + "02499e327a05cc8bb4b3c34c8347ecfcb152517c9927c092fa273be5379fde3226" + + "00000000" // nLockTime + ) + } } diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 4338c2f1df7..47b1bd828d6 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -87,7 +87,7 @@ TEST(BitcoinSigning, SignP2PKH) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -112,13 +112,13 @@ TEST(BitcoinSigning, SignP2PKH_NegativeMissingKey) { auto input = buildInputP2PKH(true); { - // test plan (but do not reuse plan result) + // test plan (but do not reuse plan result). Plan works even with missing keys. auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 225)); } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_FALSE(result); @@ -223,7 +223,7 @@ TEST(BitcoinSigning, SignP2WPKH) { } // Signs - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -272,7 +272,7 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -308,7 +308,7 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -344,7 +344,7 @@ TEST(BitcoinSigning, SignP2WPKH_MaxAmount) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -390,7 +390,7 @@ TEST(BitcoinSigning, EncodeP2WSH) { ); } -Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false) { +Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false, bool omitKeys = false) { Proto::SigningInput input; input.set_hash_type(hashType); input.set_amount(1000); @@ -398,11 +398,13 @@ Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omi input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - auto utxoKey0 = parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); + if (!omitKeys) { + auto utxoKey0 = parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + } if (!omitScript) { auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); @@ -433,7 +435,7 @@ TEST(BitcoinSigning, SignP2WSH) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -468,7 +470,7 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -503,7 +505,7 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -538,7 +540,7 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -573,7 +575,23 @@ TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + +TEST(BitcoinSigning, SignP2WSH_NegativeMissingKeys) { + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, false, true); + + { + // test plan (but do not reuse plan result). Plan works even with missing keys. + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_FALSE(result); @@ -586,7 +604,7 @@ TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { input.mutable_plan()->clear_utxos(); // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_FALSE(result); @@ -617,7 +635,7 @@ TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { ); } -TEST(BitcoinSigning, SignP2SH_P2WPKH) { +Proto::SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = false) { // Setup input Proto::SigningInput input; input.set_hash_type(TWBitcoinSigHashTypeAll); @@ -629,14 +647,18 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - ASSERT_EQ(hex(utxoPubkeyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); - input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + assert(hex(utxoPubkeyHash) == "79091972186c449eb1ded22b78e40d009bdf0089"); + if (!omitKeys) { + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + } - auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - ASSERT_EQ(hex(scriptHash), "4733f37cf4db86fbc2efed2500b4f4e49f312023"); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash)] = scriptString; + if (!omitScript) { + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + assert(hex(scriptHash) == "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; + } auto utxo0 = input.add_utxo(); auto utxo0Script = Script(parse_hex("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387")); @@ -647,6 +669,11 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + return input; +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH) { + auto input = buildInputP2SH_P2WPKH(); { // test plan (but do not reuse plan result) auto plan = TransactionBuilder::plan(input); @@ -654,7 +681,7 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -678,6 +705,36 @@ TEST(BitcoinSigning, SignP2SH_P2WPKH) { ); } +TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeOmitScript) { + auto input = buildInputP2SH_P2WPKH(true, false); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 226)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeOmitKeys) { + auto input = buildInputP2SH_P2WPKH(false, true); + { + // test plan (but do not reuse plan result). Plan works even with missing keys. + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 170)); + } + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_FALSE(result); +} + TEST(BitcoinSigning, EncodeP2SH_P2WSH) { auto unsignedTx = Transaction(1, 0); @@ -817,7 +874,7 @@ TEST(BitcoinSigning, Sign_NegativeNoUtxos) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); // Fails as there are 0 utxos @@ -875,13 +932,13 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { } // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_FALSE(result); } -TEST(BitcoinSigning, SignLitecoinReal_a85f) { +TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { auto coin = TWCoinTypeLitecoin; auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; auto ownPrivateKey = "b820f41f96c8b7442f3260acd23b3897e1450b8c7c6580136a3c2d3a14e34674"; @@ -927,7 +984,7 @@ TEST(BitcoinSigning, SignLitecoinReal_a85f) { EXPECT_TRUE(verifyPlan(input.plan(), {3'900'000}, 3'899'774, 226)); // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); @@ -952,12 +1009,12 @@ TEST(BitcoinSigning, SignLitecoinReal_a85f) { ); } -TEST(BitcoinSigning, SignLitecoinReal_8435) { +TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { auto coin = TWCoinTypeLitecoin; auto ownAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; auto ownPrivateKey = "690b34763f34e0226ad2a4d47098269322e0402f847c97166e8f39959fcaff5a"; - // Setup input + // Setup input for Plan Proto::SigningInput input; input.set_coin_type(coin); input.set_hash_type(TWBitcoinSigHashTypeAll); @@ -967,9 +1024,6 @@ TEST(BitcoinSigning, SignLitecoinReal_8435) { input.set_to_address("ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"); input.set_change_address(ownAddress); - auto privKey = parse_hex(ownPrivateKey); - input.add_private_key(privKey.data(), privKey.size()); - auto utxo0Script = Script::buildForAddress(ownAddress, coin); Data keyHash0; EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); @@ -988,14 +1042,17 @@ TEST(BitcoinSigning, SignLitecoinReal_8435) { utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); - } + // Plan + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); + + // Extend input with keys and plan, for Sign + auto privKey = parse_hex(ownPrivateKey); + input.add_private_key(privKey.data(), privKey.size()); + *input.mutable_plan() = plan.proto(); // Sign - auto signer = TransactionSigner(std::move(input)); + auto signer = TransactionSigner(std::move(input)); auto result = signer.sign(); ASSERT_TRUE(result) << result.error(); diff --git a/tests/Decred/SignerTests.cpp b/tests/Decred/SignerTests.cpp index 2614662dd5e..e022d6b0d4f 100644 --- a/tests/Decred/SignerTests.cpp +++ b/tests/Decred/SignerTests.cpp @@ -336,7 +336,6 @@ TEST(DecredSigner, SignP2PKH_Noplan) { const auto result = signer.sign(); ASSERT_TRUE(result); - std::cerr << "result.payload().inputs " << result.payload().inputs.size() << "\n"; ASSERT_TRUE(result.payload().inputs.size() >= 1); const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" diff --git a/tests/Decred/TWAnySignerTests.cpp b/tests/Decred/TWAnySignerTests.cpp index 4fc02091ff7..21bbe5595eb 100644 --- a/tests/Decred/TWAnySignerTests.cpp +++ b/tests/Decred/TWAnySignerTests.cpp @@ -77,8 +77,8 @@ TEST(TWAnySignerDecred, Plan) { EXPECT_EQ(plan.amount(), 10000000); EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 254); - EXPECT_EQ(plan.change(), 29899746); + EXPECT_EQ(plan.fee(), 225); + EXPECT_EQ(plan.change(), 29899775); EXPECT_EQ(plan.utxos_size(), 1); EXPECT_EQ(plan.branch_id(), ""); } @@ -91,8 +91,8 @@ TEST(TWAnySignerDecred, PlanAndSign) { EXPECT_EQ(plan.amount(), 10000000); EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 254); - EXPECT_EQ(plan.change(), 29899746); + EXPECT_EQ(plan.fee(), 225); + EXPECT_EQ(plan.change(), 29899775); EXPECT_EQ(plan.utxos_size(), 1); EXPECT_EQ(plan.branch_id(), ""); @@ -103,7 +103,7 @@ TEST(TWAnySignerDecred, PlanAndSign) { ANY_SIGN(input, TWCoinTypeDecred); ASSERT_TRUE(output.error().empty()); - ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ace23bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402203e6ee9e16d6bc36bb4242f7a4cac333a1c2a150ea16143412b88b721f6ae16bf02201019affdf815a5c22e4b0fb7e4685c4707094922d6a41354f9055d3bb0f26e630121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); + ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188acff3bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6b483045022100e5c8ad7ebd733c8b3e4d7b2f5c1f70b1fd661af01990df2ff6080b267a3f458e02207397b08af7064e7429b7e99350a431c4c8e30457547a0c37919a8adbf4b711860121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); } TEST(TWAnySignerDecred, SupportsJSON) { From 51351be4b0067f64f5e6b65c93627bef275ed0a6 Mon Sep 17 00:00:00 2001 From: Ravindra Kumar Date: Thu, 18 Jun 2020 01:07:50 +0530 Subject: [PATCH 40/81] Add BandChain Support (#980) * Added BandChain Support Coin Definition * Generated Skeleton * Added all the missing tests for BandChain * Fixed Android Compiler Error * Fixed Android Test * Corrected Test BandChain Address * Added missing Android Signing Tests --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../bandchain/TestBandChainAddress.kt | 32 ++ .../bandchain/TestBandChainSigner.kt | 107 +++++ coins.json | 24 ++ docs/coins.md | 1 + include/TrustWalletCore/TWCoinType.h | 1 + src/Cosmos/Entry.h | 1 + src/interface/TWAnyAddress.cpp | 1 + swift/Tests/Blockchains/BandChainTests.swift | 405 ++++++++++++++++++ swift/Tests/CoinAddressDerivationTests.swift | 3 + swift/Tests/HDWalletTests.swift | 8 + tests/BandChain/TWCoinTypeTests.cpp | 34 ++ tests/CoinAddressValidationTests.cpp | 8 + tests/interface/TWHRPTests.cpp | 3 + 14 files changed, 629 insertions(+) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt create mode 100644 swift/Tests/Blockchains/BandChainTests.swift create mode 100644 tests/BandChain/TWCoinTypeTests.cpp 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 63fbfbbe7c7..f48de1d0a76 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 @@ -85,5 +85,6 @@ class CoinAddressDerivationTests { NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) + BANDCHAIN -> assertEquals("band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt new file mode 100644 index 00000000000..d988eed5bb1 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt @@ -0,0 +1,32 @@ +// Copyright © 2017-2020 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. + +package com.trustwallet.core.app.blockchains.bandchain + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestBandChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(publicKey, CoinType.BANDCHAIN) + val expected = AnyAddress("band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", CoinType.BANDCHAIN) + + assertEquals(publicKey.data().toHex(), "0x035df185566521d6a7802319ee06e1a28e97b7772dfb5fdd13ca6f0575518968e4") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt new file mode 100644 index 00000000000..0e25638c1de --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt @@ -0,0 +1,107 @@ +// Copyright © 2017-2020 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. + +package com.trustwallet.core.app.blockchains.bandchain + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType.BANDCHAIN +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput + +class TestBandChainSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigningTransaction() { + val key = + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, BANDCHAIN).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = 1 + denom = "uband" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = 200 + denom = "uband" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + accountNumber = 1037 + chainId = "band-wenchang-testnet2" + memo = "" + sequence = 8 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, BANDCHAIN, SigningOutput.parser()) + val jsonPayload = output.json + + val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"uband"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"uband"}],"from_address":"band1hsk6jryyqjfhp5dhc55tc9jtckygx0epw4d0hz","to_address":"band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"B1ZG7pUWW2mPxE7WzBt8SafZMtEtFWgUJePJ+Dj/q7cHxj4scGmopQUG4+AZcJbRQgjrMGM11Yhm3vXYQYtSDA=="}]}}""" + assertEquals(expectedJsonPayload, jsonPayload) + + } + + @Test + fun testSigningJSON() { + val json = """ + { + "accountNumber": "8733", + "chainId": "band-wenchang-testnet2", + "fee": { + "amounts": [{ + "denom": "uband", + "amount": "5000" + }], + "gas": "200000" + }, + "memo": "Testing", + "messages": [{ + "sendCoinsMessage": { + "fromAddress": "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", + "toAddress": "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh", + "amounts": [{ + "denom": "uband", + "amount": "995000" + }] + } + }] + } + """ + val key = "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() + val result = AnySigner.signJSON(json, key, BANDCHAIN.value()) + + assertEquals(result, """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uband"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uband"}],"from_address":"band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3","to_address":"band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"sw2YPxjQ5DiKjd2o70sQb44OSzMH2Pm4V+Z8ld1uYiNbMXWQBK8SH2tcKUIU3SwYZ1qvi2DbmxqHyONksJ0Rmg=="}]}}""") + } +} diff --git a/coins.json b/coins.json index 4c1e7e99ab6..233a381ff3f 100644 --- a/coins.json +++ b/coins.json @@ -1380,5 +1380,29 @@ "clientPublic": "https://api.elrond.com", "clientDocs": "https://docs.elrond.com" } + }, + { + "id": "band", + "name": "BandChain", + "symbol": "BAND", + "decimals": 6, + "blockchain": "Cosmos", + "derivationPath": "m/44'/494'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "band", + "explorer": { + "url": "https://scan-wenchang-testnet2.bandchain.org/", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173", + "sampleAccount": "band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp" + }, + "info": { + "url": "https://bandprotocol.com/", + "client": "https://github.com/bandprotocol/bandchain", + "clientPublic": "https://api-wt2-lb.bandchain.org", + "clientDocs": "https://docs.bandchain.org/" + } } ] diff --git a/docs/coins.md b/docs/coins.md index 7457ad39952..1c19c8c2c3c 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -42,6 +42,7 @@ This list is generated from [./coins.json](../coins.json) | 457 | Aeternity | AE | | | | 459 | Kava | KAVA | | | | 461 | Filecoin | FIL | | | +| 494 | BandChain | BAND | | | | 500 | Theta | THETA | | | | 501 | Solana | SOL | | | | 508 | Elrond | ERD | | | diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 98095785c80..c9e45360b74 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -81,6 +81,7 @@ enum TWCoinType { TWCoinTypePolkadot = 354, TWCoinTypeFilecoin = 461, TWCoinTypeElrond = 508, + TWCoinTypeBandChain = 494, }; /// Returns the blockchain for a coin type. diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index 0eceffcdf3d..a625522a59c 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -19,6 +19,7 @@ class Entry: public CoinEntry { TWCoinTypeCosmos, TWCoinTypeKava, TWCoinTypeTerra, + TWCoinTypeBandChain, }; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index dd6ee615f4c..93cac16bfc1 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -76,6 +76,7 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { case TWCoinTypeCosmos: case TWCoinTypeKava: case TWCoinTypeTerra: + case TWCoinTypeBandChain: case TWCoinTypeIoTeX: { Cosmos::Address addr; if (!Cosmos::Address::decode(string, addr)) { diff --git a/swift/Tests/Blockchains/BandChainTests.swift b/swift/Tests/Blockchains/BandChainTests.swift new file mode 100644 index 00000000000..536d6f2e5fc --- /dev/null +++ b/swift/Tests/Blockchains/BandChainTests.swift @@ -0,0 +1,405 @@ +// Copyright © 2017-2020 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. + +import TrustWalletCore +import XCTest + +class BandChainTests: XCTestCase { + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + + func testAddress() { + let address = CoinType.bandChain.deriveAddress(privateKey: privateKey) + + XCTAssertEqual(address, "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3") + XCTAssertTrue(CoinType.bandChain.validate(address: "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh")) + XCTAssertTrue(CoinType.bandChain.validate(address: "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3")) + XCTAssertFalse(CoinType.bandChain.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) + } + + func testSigningTransaction() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .bandChain).description + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress + $0.toAddress = "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh" + $0.amounts = [CosmosAmount.with { + $0.amount = 1000000 + $0.denom = "uband" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON: String = +""" +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "1000000", + "denom": "uband" + } + ], + "from_address": "band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", + "to_address": "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "C6X5J08I1kkebqxa9LiFRRSJsp8U9E/IulruGTtOvpcpn/kMwWAxbFTDzvrDV5SnTWDSlimTkeZq8OuwL7j9nQ==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testStaking() { + // https://scan-wenchang-testnet2.bandchain.org/tx/ca86ad13b7d3add04a926f9f6184f1134b964f4f67f39c3a7169540553119915 + let stakeMessage = CosmosMessage.Delegate.with { + $0.delegatorAddress = "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz" + $0.validatorAddress = "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + $0.amount = CosmosAmount.with { + $0.amount = 1000000 + $0.denom = "uband" + } + } + + let message = CosmosMessage.with { + $0.stakeMessage = stakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 1 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uband" + }, + "delegator_address": "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz", + "validator_address": "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "My+OzXqBOImtuDOoqoO9YBdlAhl6weWZvtJfkm4KDZ8I/wnQHNFBa41ql1e2LYSk3jnR/14LZ6E3pY8YW3WU9w==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testWithdraw() { + // https://scan-wenchang-testnet2.bandchain.org/tx/0df1a6742647224646ed0213abfc012fca54ca42311414c158a3b8799fb61d56 + let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz" + $0.validatorAddress = "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + } + + let message = CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdrawMessage + } + + let fee = CosmosFee.with { + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + $0.gas = 200000 + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "band13nzgys7y9c693u0pq089an4pq6q87hf9kqgkrz", + "validator_address": "bandvaloper13fwr8rmugu2mfuurfx4sfmyv05haw9sujnqzd8" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "yV2ORYqs66hq9wECEqOgLoBx7OghdRCnN8MqZk5cY/40PAm1EGjFKkdNVLNTXuBrskcT0pP/AT0XfMyywvhkWg==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testUndelegate() { + // https://scan-wenchang-testnet2.bandchain.org/tx/cbf84d227f5a8d36e7b529d94b0f93e7107d3706148c79d8c539c09ce4698447 + let unstakeMessage = CosmosMessage.Undelegate.with { + $0.delegatorAddress = "band13tug898kgtwprg7fevzzqgh45draa3cyffw3kp" + $0.validatorAddress = "bandvaloper1jp633fleakzv4uxxvl707j9u2jj6j5x2rg7glv" + $0.amount = CosmosAmount.with { + $0.amount = 500000 + $0.denom = "uband" + } + } + + let message = CosmosMessage.with { + $0.unstakeMessage = unstakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 3 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgUndelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uband" + }, + "delegator_address": "band13tug898kgtwprg7fevzzqgh45draa3cyffw3kp", + "validator_address": "bandvaloper1jp633fleakzv4uxxvl707j9u2jj6j5x2rg7glv" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "mdWnaIfRASZoCs0HKEk0dCL3S3ky1fbh1wp76M7Cov0D8fiByoxOfNknGgDwZecmwhZ4Gf66E+25B5hBCJpY/A==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testRedlegate() { + // https://scan-wenchang-testnet2.bandchain.org/tx/92b351e9aa2f7faae6cd556db04737a1fff778ea0306e2dfb064ccec76a41b13 + let restakeMessage = CosmosMessage.BeginRedelegate.with { + $0.delegatorAddress = "band1hln9scsl9yqup8nxyum06rmggql5m5zqzslg52" + $0.validatorSrcAddress = "bandvaloper1hln9scsl9yqup8nxyum06rmggql5m5zqwxmt3p" + $0.validatorDstAddress = "bandvaloper1hydxm5h8v6tty2x623az65x3r39tl3paxyxtr0" + $0.amount = CosmosAmount.with { + $0.amount = 500000 + $0.denom = "uband" + } + } + + let message = CosmosMessage.with { + $0.restakeMessage = restakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = 100 + $0.denom = "uband" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 204 + $0.chainID = "band-wenchang-testnet2" + $0.memo = "" + $0.sequence = 4 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .bandChain) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "100", + "denom": "uband" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgBeginRedelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uband" + }, + "delegator_address": "band1hln9scsl9yqup8nxyum06rmggql5m5zqzslg52", + "validator_dst_address": "bandvaloper1hydxm5h8v6tty2x623az65x3r39tl3paxyxtr0", + "validator_src_address": "bandvaloper1hln9scsl9yqup8nxyum06rmggql5m5zqwxmt3p" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "Y+nCZxjvrXPs++VDLiJxDQmp/59Mdwv7OEhgaH4oObtZ7N9+ZVraAiAcxJO26bbcW3cptFf88jxGpWdp6XG9Tg==" + } + ] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } +} \ No newline at end of file diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 6535122f94f..fcdec76a55b 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -184,6 +184,9 @@ class CoinAddressDerivationTests: XCTestCase { case .kava: let expectedResult = "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bandChain: + let expectedResult = "band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r" + AssetCoinDerivation(coin, expectedResult, derivedAddress, address) case .cardano: let expectedResult = "addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 46248d088d2..7375c1f2b79 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -388,4 +388,12 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "kava1zrst72upua78pylhku9csxd5zmhsyrk7xhrdlf") } + + func testDeriveBandChain() { + let coin = CoinType.bandChain + let key = HDWallet.test.getKeyForCoin(coin: coin) + let address = coin.deriveAddress(privateKey: key) + + XCTAssertEqual(address, "band1pe8xm2r46rmctsukuqu7gl900vzprfsp4sguc3") + } } diff --git a/tests/BandChain/TWCoinTypeTests.cpp b/tests/BandChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..695573b6f5c --- /dev/null +++ b/tests/BandChain/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 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. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "../interface/TWTestUtilities.h" +#include +#include + + +TEST(TWBandChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBandChain)); + auto txId = TWStringCreateWithUTF8Bytes("473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBandChain, txId)); + auto accId = TWStringCreateWithUTF8Bytes("band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBandChain, accId)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBandChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBandChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBandChain), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeBandChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBandChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBandChain)); + assertStringsEqual(symbol, "BAND"); + assertStringsEqual(txUrl, "https://scan-wenchang-testnet2.bandchain.org//tx/473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173"); + assertStringsEqual(accUrl, "https://scan-wenchang-testnet2.bandchain.org//account/band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp"); + assertStringsEqual(id, "band"); + assertStringsEqual(name, "BandChain"); +} diff --git a/tests/CoinAddressValidationTests.cpp b/tests/CoinAddressValidationTests.cpp index 86a0d7f309a..35b38ff2ea8 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/CoinAddressValidationTests.cpp @@ -322,6 +322,14 @@ TEST(Coin, ValidateAddresKavaa) { EXPECT_FALSE(validateAddress(TWCoinTypeKava, "kava1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z09wt000")); } +TEST(Coin, ValidateAddresBand) { + EXPECT_TRUE(validateAddress(TWCoinTypeBandChain, "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc87jh")); + // wrong prefix + EXPECT_FALSE(validateAddress(TWCoinTypeBandChain, "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp")); + // wrong checksum + EXPECT_FALSE(validateAddress(TWCoinTypeBandChain, "band1pnndgfwsrff86263xzpc5cd3t6yfvgjyqc8000")); +} + TEST(Coin, ValidateAddresCardano) { // valid V3 address EXPECT_TRUE(validateAddress(TWCoinTypeCardano, "addr1s3hdtrqgs47l7ue5srga8wmk9dzw279x9e7lxadalt6z0fk64nnn2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59j5lempe")); diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index 3b04ad4f8c3..18517e8710a 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -27,6 +27,7 @@ TEST(TWHRP, StringForHRP) { ASSERT_STREQ(stringForHRP(TWHRPTerra), "terra"); ASSERT_STREQ(stringForHRP(TWHRPMonacoin), "mona"); ASSERT_STREQ(stringForHRP(TWHRPKava), "kava"); + ASSERT_STREQ(stringForHRP(TWHRPBandChain), "band"); ASSERT_STREQ(stringForHRP(TWHRPCardano), "addr"); ASSERT_STREQ(stringForHRP(TWHRPElrond), "erd"); } @@ -47,6 +48,7 @@ TEST(TWHRP, HRPForString) { ASSERT_EQ(hrpForString("terra"), TWHRPTerra); ASSERT_EQ(hrpForString("mona"), TWHRPMonacoin); ASSERT_EQ(hrpForString("kava"), TWHRPKava); + ASSERT_EQ(hrpForString("band"), TWHRPBandChain); ASSERT_EQ(hrpForString("addr"), TWHRPCardano); ASSERT_EQ(hrpForString("erd"), TWHRPElrond); } @@ -66,6 +68,7 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPTerra, TWCoinTypeHRP(TWCoinTypeTerra)); ASSERT_EQ(TWHRPMonacoin, TWCoinTypeHRP(TWCoinTypeMonacoin)); ASSERT_EQ(TWHRPKava, TWCoinTypeHRP(TWCoinTypeKava)); + ASSERT_EQ(TWHRPBandChain, TWCoinTypeHRP(TWCoinTypeBandChain)); ASSERT_EQ(TWHRPCardano, TWCoinTypeHRP(TWCoinTypeCardano)); ASSERT_EQ(TWHRPElrond, TWCoinTypeHRP(TWCoinTypeElrond)); From 6fe0d641d71a66656d15fa09e26429027a746fe4 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:36:15 +0200 Subject: [PATCH 41/81] [BTC fee] Fix for no-private-key fee estimation, wrong fee for many UTXO case (#998) * New test case for multi-input tx fee. * Use 33 for public key size in estimation, instead of 32. * Update tests. Co-authored-by: Catenocrypt --- src/Bitcoin/TransactionSigner.cpp | 2 +- tests/Bitcoin/TWBitcoinSigningTests.cpp | 62 ++++++++++++++++++++++++- tests/Decred/TWAnySignerTests.cpp | 10 ++-- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index 2c52504ef80..e2533807ba8 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -213,7 +213,7 @@ Result> TransactionSigner::si } if (key.empty() && estimationMode) { // estimation mode, key is missing: use placeholder for public key - return Result>::success({signature, Data(32)}); + return Result>::success({signature, Data(PublicKey::secp256k1Size)}); } auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); return Result>::success({signature, pubkey.bytes}); diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 47b1bd828d6..cd8a7e2b2ba 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -114,7 +114,7 @@ TEST(BitcoinSigning, SignP2PKH_NegativeMissingKey) { { // test plan (but do not reuse plan result). Plan works even with missing keys. auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 225)); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); } // Sign @@ -938,6 +938,66 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { ASSERT_FALSE(result); } +TEST(BitcoinSigning, Plan_10input_MaxAmount) { + auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; + auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; + + Proto::SigningInput input; + + for (int i = 0; i < 10; ++i) { + auto utxoScript = Script::buildForAddress(ownAddress, TWCoinTypeBitcoin); + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[std::string(keyHash.begin(), keyHash.end())] = scriptString; + + auto utxo = input.add_utxo(); + utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo->set_amount(1'000'000 + i * 10'000); + auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); + std::reverse(hash.begin(), hash.end()); + utxo->mutable_out_point()->set_hash(hash.data(), hash.size()); + utxo->mutable_out_point()->set_index(0); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + } + + input.set_coin_type(coin); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_use_max_amount(true); + input.set_amount(2'000'000); + input.set_byte_fee(1); + input.set_to_address("bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"); + input.set_change_address(ownAddress); + + // Plan. + // Estimated size: witness size: 10 * (1 + 1 + 72 + 1 + 33) + 2 = 1082; base 451; raw 451 + 1082 = 1533; vsize 451 + 1082/4 --> 722 + // Actual size: witness size: 1078; base 451; raw 451 + 1078 = 1529; vsize 451 + 1078/4 --> 721 + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000, 1'010'000, 1'020'000, 1'030'000, 1'040'000, 1'050'000, 1'060'000, 1'070'000, 1'080'000, 1'090'000}, 10'449'278, 722)); + + // Extend input with keys, reuse plan, Sign + auto privKey = parse_hex(ownPrivateKey); + input.add_private_key(privKey.data(), privKey.size()); + *input.mutable_plan() = plan.proto(); + + // Sign + auto signer = TransactionSigner(std::move(input)); + auto result = signer.sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + signer.encodeTx(signedTx, serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{1529, 451, 721})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + + ASSERT_EQ(serialized.size(), 1529); +} + TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { auto coin = TWCoinTypeLitecoin; auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; diff --git a/tests/Decred/TWAnySignerTests.cpp b/tests/Decred/TWAnySignerTests.cpp index 21bbe5595eb..67ec42410bd 100644 --- a/tests/Decred/TWAnySignerTests.cpp +++ b/tests/Decred/TWAnySignerTests.cpp @@ -77,8 +77,8 @@ TEST(TWAnySignerDecred, Plan) { EXPECT_EQ(plan.amount(), 10000000); EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 225); - EXPECT_EQ(plan.change(), 29899775); + EXPECT_EQ(plan.fee(), 226); + EXPECT_EQ(plan.change(), 29899774); EXPECT_EQ(plan.utxos_size(), 1); EXPECT_EQ(plan.branch_id(), ""); } @@ -91,8 +91,8 @@ TEST(TWAnySignerDecred, PlanAndSign) { EXPECT_EQ(plan.amount(), 10000000); EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 225); - EXPECT_EQ(plan.change(), 29899775); + EXPECT_EQ(plan.fee(), 226); + EXPECT_EQ(plan.change(), 29899774); EXPECT_EQ(plan.utxos_size(), 1); EXPECT_EQ(plan.branch_id(), ""); @@ -103,7 +103,7 @@ TEST(TWAnySignerDecred, PlanAndSign) { ANY_SIGN(input, TWCoinTypeDecred); ASSERT_TRUE(output.error().empty()); - ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188acff3bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6b483045022100e5c8ad7ebd733c8b3e4d7b2f5c1f70b1fd661af01990df2ff6080b267a3f458e02207397b08af7064e7429b7e99350a431c4c8e30457547a0c37919a8adbf4b711860121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); + ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188acfe3bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402201e9ac0063e57099241f117462a56d20df169fd727157cfae4299223261ef6e2902206b920a08953847167a29a2ec3721b5101a29aa6d728c5db30c7b459f7bb0eedb0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); } TEST(TWAnySignerDecred, SupportsJSON) { From 28299f62818c3c8024fe288dce394c4d0d0fb960 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Fri, 19 Jun 2020 10:07:54 +0200 Subject: [PATCH 42/81] [Decred fee] Use simple estimation for non-segwit coins (#1000) * Decred fee: Use simple estimation for non-segwit coins. * Update Decred tests (and one BTC test with wrong cointype). --- src/Bitcoin/TransactionBuilder.cpp | 17 +++++++++++++++-- tests/Bitcoin/TWBitcoinSigningTests.cpp | 2 +- tests/Decred/TWAnySignerTests.cpp | 11 ++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp index 915610a5728..e4ac68fcfdc 100644 --- a/src/Bitcoin/TransactionBuilder.cpp +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -7,13 +7,26 @@ #include "TransactionBuilder.h" #include "TransactionSigner.h" +#include "../Coin.h" + #include #include namespace TW::Bitcoin { +/// Estimate encoded size by simple formula +int64_t estimateSimpleFee(FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const Bitcoin::Proto::SigningInput& input) { + return feeCalculator.calculate(plan.utxos.size(), outputSize, input.byte_fee()); +} + /// Estimate encoded size by invoking sign(sizeOnly), get actual size int64_t estimateSegwitFee(FeeCalculator& feeCalculator, const TransactionPlan& plan, int outputSize, const Bitcoin::Proto::SigningInput& input) { + TWPurpose coinPurpose = TW::purpose(static_cast(input.coin_type())); + if (coinPurpose != TWPurposeBIP84) { + // not segwit, return default simple estimate + return estimateSimpleFee(feeCalculator, plan, outputSize, input); + } + // duplicate input, with the current plan auto inputWithPlan = std::move(input); *inputWithPlan.mutable_plan() = plan.proto(); @@ -21,8 +34,8 @@ int64_t estimateSegwitFee(FeeCalculator& feeCalculator, const TransactionPlan& p auto signer = TransactionSigner(std::move(inputWithPlan), true); auto result = signer.sign(); if (!result) { - // signing failed; return default estimate - return feeCalculator.calculate(plan.utxos.size(), outputSize, input.byte_fee()); + // signing failed; return default simple estimate + return estimateSimpleFee(feeCalculator, plan, outputSize, input); } // Obtain the encoded size diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index cd8a7e2b2ba..87bbd88c4b3 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -964,7 +964,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { utxo->mutable_out_point()->set_sequence(UINT32_MAX); } - input.set_coin_type(coin); + input.set_coin_type(TWCoinTypeBitcoin); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_use_max_amount(true); input.set_amount(2'000'000); diff --git a/tests/Decred/TWAnySignerTests.cpp b/tests/Decred/TWAnySignerTests.cpp index 67ec42410bd..c8febd6c718 100644 --- a/tests/Decred/TWAnySignerTests.cpp +++ b/tests/Decred/TWAnySignerTests.cpp @@ -77,8 +77,8 @@ TEST(TWAnySignerDecred, Plan) { EXPECT_EQ(plan.amount(), 10000000); EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 226); - EXPECT_EQ(plan.change(), 29899774); + EXPECT_EQ(plan.fee(), 254); + EXPECT_EQ(plan.change(), 29899746); EXPECT_EQ(plan.utxos_size(), 1); EXPECT_EQ(plan.branch_id(), ""); } @@ -91,8 +91,8 @@ TEST(TWAnySignerDecred, PlanAndSign) { EXPECT_EQ(plan.amount(), 10000000); EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 226); - EXPECT_EQ(plan.change(), 29899774); + EXPECT_EQ(plan.fee(), 254); + EXPECT_EQ(plan.change(), 29899746); EXPECT_EQ(plan.utxos_size(), 1); EXPECT_EQ(plan.branch_id(), ""); @@ -103,7 +103,8 @@ TEST(TWAnySignerDecred, PlanAndSign) { ANY_SIGN(input, TWCoinTypeDecred); ASSERT_TRUE(output.error().empty()); - ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188acfe3bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402201e9ac0063e57099241f117462a56d20df169fd727157cfae4299223261ef6e2902206b920a08953847167a29a2ec3721b5101a29aa6d728c5db30c7b459f7bb0eedb0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); + ASSERT_EQ(output.encoded().size(), 251); + ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ace23bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402203e6ee9e16d6bc36bb4242f7a4cac333a1c2a150ea16143412b88b721f6ae16bf02201019affdf815a5c22e4b0fb7e4685c4707094922d6a41354f9055d3bb0f26e630121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); } TEST(TWAnySignerDecred, SupportsJSON) { From b265f12cf9dbdcd3fa4f39bc092d3998984bcae1 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 19 Jun 2020 16:08:28 +0800 Subject: [PATCH 43/81] Better lock script naming (#1003) * Add real world P2SH_P2WPKH test case * rename lock script method --- include/TrustWalletCore/TWBitcoinScript.h | 4 +- .../trust/walletcore/example/MainActivity.kt | 2 +- src/Bitcoin/Script.cpp | 4 +- src/Bitcoin/Script.h | 4 +- src/Bitcoin/TransactionBuilder.h | 4 +- src/Decred/TransactionBuilder.h | 4 +- src/interface/TWBitcoinScript.cpp | 4 +- ...onSignerTests.swift => BitcoinTests.swift} | 41 +++++++++++++++++++ swift/Tests/Blockchains/BitconCashTests.swift | 6 +-- swift/Tests/Blockchains/DecredTests.swift | 2 +- swift/Tests/Blockchains/LitecoinTests.swift | 2 +- tests/Bitcoin/TWBitcoinScriptTests.cpp | 18 ++++---- tests/Bitcoin/TWBitcoinSigningTests.cpp | 6 +-- tests/BitcoinCash/TWBitcoinCashTests.cpp | 4 +- tests/BitcoinGold/TWBitcoinGoldTests.cpp | 4 +- tests/Dash/TWDashTests.cpp | 4 +- tests/Decred/TWDecredTests.cpp | 4 +- tests/DigiByte/TWDigiByteTests.cpp | 6 +-- tests/Dogecoin/TWDogeTests.cpp | 4 +- tests/Groestlcoin/TWGroestlcoinTests.cpp | 6 +-- tests/Litecoin/TWLitecoinTests.cpp | 14 +++---- tests/Monacoin/TWMonacoinAddressTests.cpp | 12 +++--- tests/Monacoin/TWMonacoinTransactionTests.cpp | 6 +-- tests/Qtum/TWQtumAddressTests.cpp | 6 +-- .../Ravencoin/TWRavencoinTransactionTests.cpp | 4 +- tests/Viacoin/TWViacoinAddressTests.cpp | 14 +++---- tests/Zcash/TWZcashAddressTests.cpp | 4 +- tests/Zcash/TWZcashTransactionTests.cpp | 2 +- tests/Zcoin/TWZCoinAddressTests.cpp | 4 +- 29 files changed, 120 insertions(+), 79 deletions(-) rename swift/Tests/Blockchains/{BitcoinTransactionSignerTests.swift => BitcoinTests.swift} (63%) diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index a382328c45d..73240b7e99e 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -110,8 +110,8 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWDa TW_EXPORT_STATIC_METHOD struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *_Nonnull scriptHash); -/// Builds a pay-to-public-key-hash (P2PKH) script appropriate for the given address. +/// Builds a appropriate lock script for the given address. TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildForAddress(TWString *_Nonnull address, enum TWCoinType coin); +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin); TW_EXTERN_C_END diff --git a/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt b/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt index fcfd305e051..0dec5d96180 100644 --- a/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt +++ b/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt @@ -64,7 +64,7 @@ class MainActivity : AppCompatActivity() { val secretPrivateKeyBtc = wallet.getKeyForCoin(coinBtc) val toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" val changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" - val script = BitcoinScript.buildForAddress(addressBtc, coinBtc).data() + val script = BitcoinScript.lockScriptForAddress(addressBtc, coinBtc).data() val outPoint = Bitcoin.OutPoint.newBuilder().apply { this.hash = ByteString.copyFrom(utxoTxId) diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 5cd6bce391b..443b2aacf4a 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -252,7 +252,7 @@ void Script::encode(Data& data) const { std::copy(std::begin(bytes), std::end(bytes), std::back_inserter(data)); } -Script Script::buildForAddress(const std::string& string, enum TWCoinType coin) { +Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType coin) { if (Address::isValid(string)) { auto address = Address(string); auto p2pkh = TW::p2pkhPrefix(coin); @@ -278,7 +278,7 @@ Script Script::buildForAddress(const std::string& string, enum TWCoinType coin) } else if (CashAddress::isValid(string)) { auto address = CashAddress(string); auto bitcoinAddress = address.legacyAddress(); - return buildForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); + return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); } else if (Decred::Address::isValid(string)) { auto bytes = Base58::bitcoin.decodeCheck(string, Hash::blake256d); if (bytes[1] == TW::p2pkhPrefix(TWCoinTypeDecred)) { diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index 2d59a9877c5..e40c4b733bc 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -83,9 +83,9 @@ class Script { /// Builds a pay-to-witness-script-hash (P2WSH) script from a script hash. static Script buildPayToWitnessScriptHash(const Data& scriptHash); - /// Builds a pay-to-public-key-hash (P2PKH) script appropriate for the given + /// Builds a appropriate lock script for the given /// address. - static Script buildForAddress(const std::string& address, enum TWCoinType coin); + static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin); /// Encodes the script. void encode(Data& data) const; diff --git a/src/Bitcoin/TransactionBuilder.h b/src/Bitcoin/TransactionBuilder.h index 407c4040ad2..a4221a65251 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -25,7 +25,7 @@ class TransactionBuilder { template static Transaction build(const TransactionPlan& plan, const std::string& toAddress, const std::string& changeAddress, enum TWCoinType coin) { - auto lockingScriptTo = Script::buildForAddress(toAddress, coin); + auto lockingScriptTo = Script::lockScriptForAddress(toAddress, coin); if (lockingScriptTo.empty()) { return {}; } @@ -34,7 +34,7 @@ class TransactionBuilder { tx.outputs.push_back(TransactionOutput(plan.amount, lockingScriptTo)); if (plan.change > 0) { - auto lockingScriptChange = Script::buildForAddress(changeAddress, coin); + auto lockingScriptChange = Script::lockScriptForAddress(changeAddress, coin); tx.outputs.push_back(TransactionOutput(plan.change, lockingScriptChange)); } diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index ed037127336..9c612ac8288 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -28,7 +28,7 @@ struct TransactionBuilder { static Transaction build(const Bitcoin::TransactionPlan& plan, const std::string& toAddress, const std::string& changeAddress) { auto coin = TWCoinTypeDecred; - auto lockingScriptTo = Bitcoin::Script::buildForAddress(toAddress, coin); + auto lockingScriptTo = Bitcoin::Script::lockScriptForAddress(toAddress, coin); if (lockingScriptTo.empty()) { return {}; } @@ -37,7 +37,7 @@ struct TransactionBuilder { tx.outputs.emplace_back(TransactionOutput(plan.amount, /* version: */ 0, lockingScriptTo)); if (plan.change > 0) { - auto lockingScriptChange = Bitcoin::Script::buildForAddress(changeAddress, coin); + auto lockingScriptChange = Bitcoin::Script::lockScriptForAddress(changeAddress, coin); tx.outputs.emplace_back( TransactionOutput(plan.change, /* version: */ 0, lockingScriptChange)); } diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index b316b1d0fe6..05391c36312 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -141,8 +141,8 @@ struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *scrip return new TWBitcoinScript{ .impl = script }; } -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildForAddress(TWString *_Nonnull address, enum TWCoinType coin) { +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin) { auto s = reinterpret_cast(address); - auto script = Script::buildForAddress(*s, coin); + auto script = Script::lockScriptForAddress(*s, coin); return new TWBitcoinScript{ .impl = script }; } diff --git a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift similarity index 63% rename from swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift rename to swift/Tests/Blockchains/BitcoinTests.swift index 878af97da6d..c83ced5468c 100644 --- a/swift/Tests/Blockchains/BitcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -80,4 +80,45 @@ class BitcoinTransactionSignerTests: XCTestCase { "00000000" // nLockTime ) } + + func testSignP2SH_P2WPKH() { + let address = "3LGoLac9mtCwDy2q8PYyvwL8kMyrCWCYQW" + let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoin) + let key = PrivateKey(data: Data(hexString: "e240ef3419d038577e48426c8c37c3c13bec1a0ed3f5270b82e7377bc48699dd")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(Data(hexString: "8b5f4861c6d4a4ea361aa4066d720067f73854d9a1b1d01e2b0e3c9e150bc5a3")!.reversed()) + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = lockScript.data + $0.amount = 54700 + } + ] + + let plan = BitcoinTransactionPlan.with { + $0.amount = 43980 + $0.fee = 10720 + $0.change = 0 + $0.utxos = utxos + } + + // redeem p2wpkh nested in p2sh + let scriptHash = lockScript.matchPayToScriptHash()! + let input = BitcoinSigningInput.with { + $0.toAddress = "3NqULUrjZ7NL36YtBGsSVzqr5q1x9CJWwu" + $0.hashType = BitcoinSigHashType.all.rawValue + $0.coinType = CoinType.bitcoin.rawValue + $0.scripts = [ + scriptHash.hexString: BitcoinScript.buildPayToWitnessPubkeyHash(hash: pubkey.bitcoinKeyHash).data + ] + $0.privateKey = [key.data] + $0.plan = plan + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoin) + + // https://blockchair.com/bitcoin/transaction/da2a9ce5d71ff7490bc9025e2888ca109b68ec0bd0e7d26195e1783305c00117 + XCTAssertEqual(output.encoded.hexString, "01000000000101a3c50b159e3c0e2b1ed0b1a1d95438f76700726d06a41a36eaa4d4c661485f8b00000000171600140a3cca78017f46ac23e463148adb7231aef81956ffffffff01ccab00000000000017a914e7f40472c54fc93078c5129568cf95c27be3b2c287024830450221008dc29a5430facd4078ad93e72517d87b298d7a73b55d2828acab040ccf713ed5022063a13e348655fa7cdcfff084380611629babf165607b529bcc35bf6ddfab1f8101210370386469db8302c3092955724f56bcca9a36f31df82655aa79be46b08744cd1200000000") + } } diff --git a/swift/Tests/Blockchains/BitconCashTests.swift b/swift/Tests/Blockchains/BitconCashTests.swift index b157880c6c9..d9110c3aa0b 100644 --- a/swift/Tests/Blockchains/BitconCashTests.swift +++ b/swift/Tests/Blockchains/BitconCashTests.swift @@ -44,11 +44,11 @@ class BitcoinCashTests: XCTestCase { func testLockScript() { let address = AnyAddress(string: "pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l", coin: .bitcoinCash)! - let script = BitcoinScript.buildForAddress(address: address.description, coin: .bitcoinCash) + let script = BitcoinScript.lockScriptForAddress(address: address.description, coin: .bitcoinCash) XCTAssertEqual(script.data.hexString, "a914b9604b7820876bc510009b8247316c4b801aff8a87") let address2 = AnyAddress(string: "qphr8l8ns8wd99a8653ctfe5qcrxaumz5qpmqlk2ex", coin: .bitcoinCash)! - let script2 = BitcoinScript.buildForAddress(address: address2.description, coin: .bitcoinCash) + let script2 = BitcoinScript.lockScriptForAddress(address: address2.description, coin: .bitcoinCash) XCTAssertEqual(script2.data.hexString, "76a9146e33fcf381dcd297a7d52385a73406066ef362a088ac") } @@ -61,7 +61,7 @@ class BitcoinCashTests: XCTestCase { $0.outPoint.index = 2 // outpoint index of this this UTXO $0.outPoint.sequence = UINT32_MAX $0.amount = 5151 // value of this UTXO - $0.script = BitcoinScript.buildForAddress(address: address, coin: .bitcoinCash).data // Build lock script from address or public key hash + $0.script = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoinCash).data // Build lock script from address or public key hash } let input = BitcoinSigningInput.with { diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index 6025f543327..3c73672f7ce 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -33,7 +33,7 @@ class DecredTests: XCTestCase { let txHash = Data(Data(hexString: "5015d14dcfd78998cfa13e0325798a74d95bbe75f167a49467303f70dde9bffd")!.reversed()) let utxoAddress = CoinType.decred.deriveAddress(privateKey: key) - let script = BitcoinScript.buildForAddress(address: utxoAddress, coin: .decred) + let script = BitcoinScript.lockScriptForAddress(address: utxoAddress, coin: .decred) print(txHash.hexString) let amount = Int64(10_000_000) diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index 115a7f7954d..e8bd938667f 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -92,7 +92,7 @@ class LitecoinTests: XCTestCase { func testPlanAndSign_8435() throws { let address = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum" - let lockScript = BitcoinScript.buildForAddress(address: address, coin: .litecoin) + let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .litecoin) let utxos = [ BitcoinUnspentTransaction.with { $0.outPoint.hash = Data(Data(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407")!.reversed()) diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/Bitcoin/TWBitcoinScriptTests.cpp index 5337008e9a7..be4297a8df5 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/Bitcoin/TWBitcoinScriptTests.cpp @@ -158,47 +158,47 @@ TEST(TWBitcoinScript, RedeemScript) { } TEST(TWBitcoinScript, LockScriptForP2PKHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H").get(), TWCoinTypeBitcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac"); - auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV").get(), TWCoinTypeBitcoin)); + auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV").get(), TWCoinTypeBitcoin)); auto scriptPub2Data = WRAPD(TWBitcoinScriptData(scriptPub2.get())); assertHexEqual(scriptPub2Data, "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac"); } TEST(TWBitcoinScript, LockScriptForP2SHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("37rHiL4DN2wkt8pgCAUfYJRxhir98ZGN1y").get(), TWCoinTypeBitcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("37rHiL4DN2wkt8pgCAUfYJRxhir98ZGN1y").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9144391adbec172cad6a9fc3eebca36aeec6640abda87"); - auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("3HV63hgTNAgdiEp4FbJRPSVrjaV4ZoX4Bs").get(), TWCoinTypeBitcoin)); + auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("3HV63hgTNAgdiEp4FbJRPSVrjaV4ZoX4Bs").get(), TWCoinTypeBitcoin)); auto scriptPub2Data = WRAPD(TWBitcoinScriptData(scriptPub2.get())); assertHexEqual(scriptPub2Data, "a914ad40768af6419a20bdb94d83c06b6c8c94721dc087"); } TEST(TWBitcoinScript, LockScriptForP2WPKHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1q6hppaw7uld68amnnu5vpp5dd5u7k92c2vtdtkq").get(), TWCoinTypeBitcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bc1q6hppaw7uld68amnnu5vpp5dd5u7k92c2vtdtkq").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014d5c21ebbdcfb747eee73e51810d1ada73d62ab0a"); - auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1qqw0jllft9pcr7r5uw0x08njkft0thd0g5yus0x").get(), TWCoinTypeBitcoin)); + auto scriptPub2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bc1qqw0jllft9pcr7r5uw0x08njkft0thd0g5yus0x").get(), TWCoinTypeBitcoin)); auto scriptPub2Data = WRAPD(TWBitcoinScriptData(scriptPub2.get())); assertHexEqual(scriptPub2Data, "0014039f2ffd2b28703f0e9c73ccf3ce564adebbb5e8"); } TEST(TWBitcoinScript, LockScriptForP2WSHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bc1qcuqamesrt803xld4l2j2vxx8rxmrx7aq82mkw7xwxh643wzqjlnqutkcv2").get(), TWCoinTypeBitcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bc1qcuqamesrt803xld4l2j2vxx8rxmrx7aq82mkw7xwxh643wzqjlnqutkcv2").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0020c701dde60359df137db5faa4a618c719b6337ba03ab76778ce35f558b84097e6"); } TEST(TWBitcoinScript, LockScriptForCashAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a914b1fb7e043152fd1eed7bfaf66679ad3b6c9068f387"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); } diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 87bbd88c4b3..2311acccbed 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -945,7 +945,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { Proto::SigningInput input; for (int i = 0; i < 10; ++i) { - auto utxoScript = Script::buildForAddress(ownAddress, TWCoinTypeBitcoin); + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); Data keyHash; EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); @@ -1016,7 +1016,7 @@ TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { auto privKey = parse_hex(ownPrivateKey); input.add_private_key(privKey.data(), privKey.size()); - auto utxo0Script = Script::buildForAddress(ownAddress, coin); + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); Data keyHash0; EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); EXPECT_EQ(hex(keyHash0), "5c74be45eb45a3459050667529022d9df8a1ecff"); @@ -1084,7 +1084,7 @@ TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { input.set_to_address("ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"); input.set_change_address(ownAddress); - auto utxo0Script = Script::buildForAddress(ownAddress, coin); + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); Data keyHash0; EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); EXPECT_EQ(hex(keyHash0), "7b59c096c20fd9a273e240846b23276c69d35815"); diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/BitcoinCash/TWBitcoinCashTests.cpp index f5c9fa06465..01c04ff23e0 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/BitcoinCash/TWBitcoinCashTests.cpp @@ -33,7 +33,7 @@ TEST(BitcoinCash, ValidAddress) { auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinCash)); ASSERT_NE(address.get(), nullptr); - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(string.get(), TWCoinTypeBitcoinCash)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(string.get(), TWCoinTypeBitcoinCash)); ASSERT_FALSE(TWBitcoinScriptSize(script.get()) == 0); } @@ -67,7 +67,7 @@ TEST(BitcoinCash, LockScript) { auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l").get(), TWCoinTypeBitcoinCash)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("pzukqjmcyzrkh3gsqzdcy3e3d39cqxhl3g0f405k5l").get(), TWCoinTypeBitcoinCash)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914b9604b7820876bc510009b8247316c4b801aff8a87"); } diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp index 53f3c595980..3af9c09e51d 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -25,11 +25,11 @@ using namespace TW; using namespace TW::Bitcoin; TEST(TWBitcoinGoldScript, LockScriptTest) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("btg1q6572ulr0kmywle8a30lvagm9xsg9k9n5cmzfdj").get(), TWCoinTypeBitcoinGold)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("btg1q6572ulr0kmywle8a30lvagm9xsg9k9n5cmzfdj").get(), TWCoinTypeBitcoinGold)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07").get(), TWCoinTypeBitcoinGold)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("btg1qawhpp9gv3g662phqufjmj2ps2ge7sq4thy5g07").get(), TWCoinTypeBitcoinGold)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "0014ebae10950c8a35a506e0e265b928305233e802ab"); } diff --git a/tests/Dash/TWDashTests.cpp b/tests/Dash/TWDashTests.cpp index 5ae94be6fd5..c1ad9c58117 100644 --- a/tests/Dash/TWDashTests.cpp +++ b/tests/Dash/TWDashTests.cpp @@ -11,11 +11,11 @@ #include TEST(Dash, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("XgkpWEFe59pX3aMhx6PrDawjNnoZKHeZbp").get(), TWCoinTypeDash)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("XgkpWEFe59pX3aMhx6PrDawjNnoZKHeZbp").get(), TWCoinTypeDash)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91442914f5b70c61619eca5359df57d0b9bdcf8ccff88ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("7eprxeVjKfVgS8p2RNsZ89K72YV61xg4gq").get(), TWCoinTypeDash)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("7eprxeVjKfVgS8p2RNsZ89K72YV61xg4gq").get(), TWCoinTypeDash)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9148835ae54f297ad069552a1401e535dfe5f396f6187"); } diff --git a/tests/Decred/TWDecredTests.cpp b/tests/Decred/TWDecredTests.cpp index b43172f8b8d..39e3f7b22f4 100644 --- a/tests/Decred/TWDecredTests.cpp +++ b/tests/Decred/TWDecredTests.cpp @@ -35,11 +35,11 @@ TEST(Decred, DerivePubkeyFromDpub) { } TEST(Decred, Lockscripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx").get(), TWCoinTypeDecred)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx").get(), TWCoinTypeDecred)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a914f5916158e3e2c4551c1796708db8367207ed13bb87"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("DsfD7KYsJtRraYXPZM61ui7779oYJCakYvH").get(), TWCoinTypeDecred)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DsfD7KYsJtRraYXPZM61ui7779oYJCakYvH").get(), TWCoinTypeDecred)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9149c417596dea6570f8e546674555b5ce5087ce2c288ac"); } diff --git a/tests/DigiByte/TWDigiByteTests.cpp b/tests/DigiByte/TWDigiByteTests.cpp index d2e0de76899..f39ea2186da 100644 --- a/tests/DigiByte/TWDigiByteTests.cpp +++ b/tests/DigiByte/TWDigiByteTests.cpp @@ -132,17 +132,17 @@ TEST(DigiByteTransaction, SignP2WPKH) { TEST(DigiByteTransaction, LockScripts) { // https://dgb2.trezor.io/tx/966b80caa754148158339a0fa70363996f15819599adf72de0eb3590c3bd63ea - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4").get(), TWCoinTypeDigiByte)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4").get(), TWCoinTypeDigiByte)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("dgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3d").get(), TWCoinTypeDigiByte)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("dgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3d").get(), TWCoinTypeDigiByte)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "0014885534ab5dc680b68d95c0af49ec2acc2e9915c4"); // https://dgb2.trezor.io/tx/965eb4afcd0aa6e3f4f8fc3513ca042f09e6e2235367fa006cbd1f546c293a2a - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo").get(), TWCoinTypeDigiByte)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo").get(), TWCoinTypeDigiByte)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a91452356ed3d2d31eb8b263ace5d164e3cf3b37096687"); } diff --git a/tests/Dogecoin/TWDogeTests.cpp b/tests/Dogecoin/TWDogeTests.cpp index d073bc2293f..76f83bdb96a 100644 --- a/tests/Dogecoin/TWDogeTests.cpp +++ b/tests/Dogecoin/TWDogeTests.cpp @@ -11,11 +11,11 @@ #include TEST(Doge, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("DLSSSUS3ex7YNDACJDxMER1ZMW579Vy8Zy").get(), TWCoinTypeDogecoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DLSSSUS3ex7YNDACJDxMER1ZMW579Vy8Zy").get(), TWCoinTypeDogecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914a7d191ec42aa113e28cd858cceaa7c733ba2f77788ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("AETZJzedcmLM2rxCM6VqCGF3YEMUjA3jMw").get(), TWCoinTypeDogecoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("AETZJzedcmLM2rxCM6VqCGF3YEMUjA3jMw").get(), TWCoinTypeDogecoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914f191149f72f235548746654f5b473c58258f7fb687"); } diff --git a/tests/Groestlcoin/TWGroestlcoinTests.cpp b/tests/Groestlcoin/TWGroestlcoinTests.cpp index e0eab5b94d7..474293a5d3f 100644 --- a/tests/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/Groestlcoin/TWGroestlcoinTests.cpp @@ -31,19 +31,19 @@ TEST(Groestlcoin, Address) { } TEST(Groestlcoin, BuildForLegacyAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM").get(), TWCoinTypeGroestlcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM").get(), TWCoinTypeGroestlcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91498af0aaca388a7e1024f505c033626d908e3b54a88ac"); } TEST(Groestlcoin, BuildForSegwitP2SHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P").get(), TWCoinTypeGroestlcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P").get(), TWCoinTypeGroestlcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e487"); } TEST(Groestlcoin, BuildForNativeSegwitAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne").get(), TWCoinTypeGroestlcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne").get(), TWCoinTypeGroestlcoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "00147557920fbc32a1ef4ef26bae5e8ce3f95abf09ce"); } diff --git a/tests/Litecoin/TWLitecoinTests.cpp b/tests/Litecoin/TWLitecoinTests.cpp index 963e246378f..5bd649dde4c 100644 --- a/tests/Litecoin/TWLitecoinTests.cpp +++ b/tests/Litecoin/TWLitecoinTests.cpp @@ -32,14 +32,14 @@ TEST(Litecoin, Address) { assertStringsEqual(string, "ltc1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asjnaxwu"); } -TEST(Litecoin, BuildForAddressL) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); +TEST(Litecoin, LockScriptForAddressL) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } -TEST(Litecoin, BuildForAddressM) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); +TEST(Litecoin, LockScriptForAddressM) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); } @@ -87,15 +87,15 @@ TEST(Litecoin, DeriveFromZpub) { } TEST(Litecoin, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("ltc1qs32zgdhe2tpzcnz55r7d9jvhce33063sfht3q0").get(), TWCoinTypeLitecoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("ltc1qs32zgdhe2tpzcnz55r7d9jvhce33063sfht3q0").get(), TWCoinTypeLitecoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "001484542436f952c22c4c54a0fcd2c997c66317ea30"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeLitecoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("LgKiekick9Ka7gYoYzAWGrEq8rFBJzYiyf").get(), TWCoinTypeLitecoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } diff --git a/tests/Monacoin/TWMonacoinAddressTests.cpp b/tests/Monacoin/TWMonacoinAddressTests.cpp index 6b99f74dea1..e2889c2b1d1 100644 --- a/tests/Monacoin/TWMonacoinAddressTests.cpp +++ b/tests/Monacoin/TWMonacoinAddressTests.cpp @@ -34,19 +34,19 @@ TEST(Monacoin, Address) { } TEST(Monacoin, BuildForP2PKHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MFMy9FwJsV6HiN5eZDqDETw4pw52q3UGrb").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MFMy9FwJsV6HiN5eZDqDETw4pw52q3UGrb").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91451dadacc7021440cbe4ca148a5db563b329b4c0388ac"); } TEST(Monacoin, BuildForP2SHAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("PHjTKtgYLTJ9D2Bzw2f6xBB41KBm2HeGfg").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("PHjTKtgYLTJ9D2Bzw2f6xBB41KBm2HeGfg").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146449f568c9cd2378138f2636e1567112a184a9e887"); } TEST(Monacoin, BuildForBech32Address) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("mona1q4kpn6psthgd5ur894auhjj2g02wlgmp8ke08ne").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("mona1q4kpn6psthgd5ur894auhjj2g02wlgmp8ke08ne").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014ad833d060bba1b4e0ce5af797949487a9df46c27"); } @@ -94,15 +94,15 @@ TEST(Monacoin, DeriveFromXpub) { } TEST(Monacoin, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("mona1qw508d6qejxtdg4y5r3zarvary0c5xw7kg5lnx5").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("mona1qw508d6qejxtdg4y5r3zarvary0c5xw7kg5lnx5").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "0014751e76e8199196d454941c45d1b3a323f1433bd6"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("PCTzdjWauNipkYtToRZEHDMXb2adj9Evp8").get(), TWCoinTypeMonacoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("PCTzdjWauNipkYtToRZEHDMXb2adj9Evp8").get(), TWCoinTypeMonacoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MBamfEqEFDy5dsLWwu48BCizM1zpCoKw3U").get(), TWCoinTypeMonacoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MBamfEqEFDy5dsLWwu48BCizM1zpCoKw3U").get(), TWCoinTypeMonacoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac"); } diff --git a/tests/Monacoin/TWMonacoinTransactionTests.cpp b/tests/Monacoin/TWMonacoinTransactionTests.cpp index df153f0d713..a70196cc3f6 100644 --- a/tests/Monacoin/TWMonacoinTransactionTests.cpp +++ b/tests/Monacoin/TWMonacoinTransactionTests.cpp @@ -75,21 +75,21 @@ TEST(MonacoinTransaction, LockScripts) { // P2PKH // https://blockbook.electrum-mona.org/tx/79ebdce15e4ac933328e62dbe92302fc8b4833786e46df8a4f18295cb824fb67 - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("M8aShwteMWyAbUw4SGS4EHLqfo1EfnKHcM").get(), TWCoinTypeMonacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("M8aShwteMWyAbUw4SGS4EHLqfo1EfnKHcM").get(), TWCoinTypeMonacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914076df984229a2731cbf465ec8fbd35b8da94380f88ac"); // P2SH // https://blockbook.electrum-mona.org/tx/726ae7d5179bfd8c7d51a5b956c3d6a262fe5190c36ed7bcb3799dc5759d5830 - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("P91UYtoBS4XAD39fEzaeMaq7YmMa42FFNd").get(), TWCoinTypeMonacoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("P91UYtoBS4XAD39fEzaeMaq7YmMa42FFNd").get(), TWCoinTypeMonacoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914049880fc73bb6a5e0140404713cabe2592fb2c5587"); // BECH32 // https://blockbook.electrum-mona.org/tx/6d7ebe444cc12c14625fa526ed9d81058b04d2f0c3b5dad2fb0032eeec3ba511 - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("mona1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asju3qmd").get(), TWCoinTypeMonacoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("mona1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asju3qmd").get(), TWCoinTypeMonacoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "001422e6014ad3631f1939281c3625bc98db808fbfb0"); } diff --git a/tests/Qtum/TWQtumAddressTests.cpp b/tests/Qtum/TWQtumAddressTests.cpp index 8f620b3fe70..66e80f759aa 100644 --- a/tests/Qtum/TWQtumAddressTests.cpp +++ b/tests/Qtum/TWQtumAddressTests.cpp @@ -34,15 +34,15 @@ TEST(Qtum, Address) { } TEST(Qtum, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeQtum)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("MHhghmmCTASDnuwpgsPUNJVPTFaj61GzaG").get(), TWCoinTypeQtum)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("QYJHEEt8kS8TzUuCy1ia7aYe1cpNg4QYnn").get(), TWCoinTypeQtum)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("QYJHEEt8kS8TzUuCy1ia7aYe1cpNg4QYnn").get(), TWCoinTypeQtum)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a91480485018e46a9c8176282adf0acb4ff3e0de93ff88ac"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("qc1qxssrzt03ncm0uda02vd8tuvrk0eg9wrz8qm2qe").get(), TWCoinTypeQtum)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("qc1qxssrzt03ncm0uda02vd8tuvrk0eg9wrz8qm2qe").get(), TWCoinTypeQtum)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "00143420312df19e36fe37af531a75f183b3f282b862"); } diff --git a/tests/Ravencoin/TWRavencoinTransactionTests.cpp b/tests/Ravencoin/TWRavencoinTransactionTests.cpp index 5fde9bdddb2..8a62bcb5abf 100644 --- a/tests/Ravencoin/TWRavencoinTransactionTests.cpp +++ b/tests/Ravencoin/TWRavencoinTransactionTests.cpp @@ -84,14 +84,14 @@ TEST(RavencoinTransaction, LockScripts) { // P2PKH // https://blockbook.ravencoin.org/tx/3717b528eb4925461d9de5a596d2eefe175985740b4fda153255e10135f236a6 - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88").get(), TWCoinTypeRavencoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88").get(), TWCoinTypeRavencoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac"); // P2SH // https://ravencoin.network/api/tx/f600d07814677d1f60545c8f7f71260238595c4928d6fb87caa0f9dd732e9bb5 - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get(), TWCoinTypeRavencoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get(), TWCoinTypeRavencoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914bd92088bb7e82d611a9b94fbb74a0908152b784f87"); } diff --git a/tests/Viacoin/TWViacoinAddressTests.cpp b/tests/Viacoin/TWViacoinAddressTests.cpp index 544c125de9b..38b7ced5585 100644 --- a/tests/Viacoin/TWViacoinAddressTests.cpp +++ b/tests/Viacoin/TWViacoinAddressTests.cpp @@ -33,14 +33,14 @@ TEST(Viacoin, Address) { assertStringsEqual(string, "via1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asu2r3d2"); } -TEST(Viacoin, BuildForAddressV) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); +TEST(Viacoin, LockScriptForAddressV) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } -TEST(Viacoin, BuildForAddressE) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); +TEST(Viacoin, LockScriptForAddressE) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); } @@ -103,15 +103,15 @@ TEST(Viacoin, DeriveFromZpub) { } TEST(Viacoin, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("via1qs32zgdhe2tpzcnz55r7d9jvhce33063s8w4xre").get(), TWCoinTypeViacoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("via1qs32zgdhe2tpzcnz55r7d9jvhce33063s8w4xre").get(), TWCoinTypeViacoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "001484542436f952c22c4c54a0fcd2c997c66317ea30"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("ESxRxvhJP6ZKtYaMGjj48As1kgCh6hXa6X").get(), TWCoinTypeViacoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a9146b85b3dac9340f36b9d32bbacf2ffcb0851ef17987"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("Vw6bJFaF5Hyiveko7dpqRjVvcTAsjz7eYa").get(), TWCoinTypeViacoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } diff --git a/tests/Zcash/TWZcashAddressTests.cpp b/tests/Zcash/TWZcashAddressTests.cpp index 231c4e2b821..3deb054f8e8 100644 --- a/tests/Zcash/TWZcashAddressTests.cpp +++ b/tests/Zcash/TWZcashAddressTests.cpp @@ -77,11 +77,11 @@ TEST(TWZcash, DerivePubkeyFromXpub2) { } TEST(TWZcash, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("t1bjVPEY8NbpGxT2PgayX3HevfJ2YU5X2DS").get(), TWCoinTypeZcash)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("t1bjVPEY8NbpGxT2PgayX3HevfJ2YU5X2DS").get(), TWCoinTypeZcash)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a914c3e968851fdb2bb943662befdb8b8573ecd4d08e88ac"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm").get(), TWCoinTypeZcash)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm").get(), TWCoinTypeZcash)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914ef8b3e86db855eb48bcf0b7585a90b6b9ece75c087"); } diff --git a/tests/Zcash/TWZcashTransactionTests.cpp b/tests/Zcash/TWZcashTransactionTests.cpp index e238ae2e69b..d312a424d7d 100644 --- a/tests/Zcash/TWZcashTransactionTests.cpp +++ b/tests/Zcash/TWZcashTransactionTests.cpp @@ -165,7 +165,7 @@ TEST(TWZcashTransaction, BlossomSigning) { // real key 1p "m/44'/133'/0'/0/14" auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); - auto script0 = Bitcoin::Script::buildForAddress(utxoAddr0, TWCoinTypeZcash); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); diff --git a/tests/Zcoin/TWZCoinAddressTests.cpp b/tests/Zcoin/TWZCoinAddressTests.cpp index 4f0261b4304..05ddf6a64e6 100644 --- a/tests/Zcoin/TWZCoinAddressTests.cpp +++ b/tests/Zcoin/TWZCoinAddressTests.cpp @@ -53,11 +53,11 @@ TEST(TWZcoin, DeriveFromXpub) { } TEST(TWZcoin, LockScripts) { - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeZcoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeZcoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9142a10f88e30768d2712665c279922b9621ce58bc788ac"); - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeZcoin)); + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeZcoin)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a914f010b17a9189e0f2737d71ae9790359eb5bbc13787"); } From 6b59bb794f8d755d792de3c356aae68dd235e060 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Fri, 19 Jun 2020 18:22:44 +0800 Subject: [PATCH 44/81] zip LICENSE for podspec (#991) --- tools/ios-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ios-release b/tools/ios-release index ce6fea9b705..8dca62ef2f3 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -31,7 +31,7 @@ puts 'Archiving...' includes = Dir.glob('include/**/*.h') + Dir.glob('lib/**/*.{h,hpp}') sources = Dir.glob('swift/Sources/**/*.{swift,h,m}') libs = Dir.glob('build/ios/*.a') -files = includes + sources + libs +files = includes + sources + libs + ['LICENSE'] file_name = "TrustWalletCore-iOS-#{version}.zip" _, stderr, status = Open3.capture3('zip', file_name, *files) if status != 0 From 4a13a725362952821421722e36bf48d7e273c5a6 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Sun, 21 Jun 2020 13:54:44 +0800 Subject: [PATCH 45/81] Add EthereumAbiDecoder and tests (#1005) --- .../ethereum/TestEthereumAbiDecoder.kt | 28 +++++++++++++++++ .../TWEthereumAbiValueDecoder.h | 22 ++++++++++++++ src/Ethereum/ABI/ValueDecoder.cpp | 17 +++++++++++ src/Ethereum/ABI/ValueDecoder.h | 18 +++++++++++ src/interface/TWEthereumAbiValueDecoder.cpp | 19 ++++++++++++ .../Tests/Blockchains/EthereumAbiTests.swift | 13 ++++++++ .../TWEthereumAbiValueDecoderTests.cpp | 30 +++++++++++++++++++ tests/Ethereum/ValueDecoderTests.cpp | 27 +++++++++++++++++ 8 files changed, 174 insertions(+) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt create mode 100644 include/TrustWalletCore/TWEthereumAbiValueDecoder.h create mode 100644 src/Ethereum/ABI/ValueDecoder.cpp create mode 100644 src/Ethereum/ABI/ValueDecoder.h create mode 100644 src/interface/TWEthereumAbiValueDecoder.cpp create mode 100644 tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp create mode 100644 tests/Ethereum/ValueDecoderTests.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt new file mode 100644 index 00000000000..23b5488ef0b --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt @@ -0,0 +1,28 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.EthereumAbiValueDecoder + +class TestEthereumAbiDecoder { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumAbiDecoder() { + val expected = "1234567890987654321" + val inputs = listOf( + "112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb10000000000000000000000000000000000000000000000000000000000000000" + ) + for (input in inputs) { + val data = Numeric.hexStringToByteArray(input) + assertEquals(expected, EthereumAbiValueDecoder.decodeUInt256(data)) + } + } +} diff --git a/include/TrustWalletCore/TWEthereumAbiValueDecoder.h b/include/TrustWalletCore/TWEthereumAbiValueDecoder.h new file mode 100644 index 00000000000..445a3177ad0 --- /dev/null +++ b/include/TrustWalletCore/TWEthereumAbiValueDecoder.h @@ -0,0 +1,22 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_CLASS +struct TWEthereumAbiValueDecoder; + +/// Decodes input data (bytes longer than 32 will be truncated) as uint256 +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumAbiValueDecoderDecodeUInt256(TWData* _Nonnull input); + +TW_EXTERN_C_END diff --git a/src/Ethereum/ABI/ValueDecoder.cpp b/src/Ethereum/ABI/ValueDecoder.cpp new file mode 100644 index 00000000000..cb79ecd418c --- /dev/null +++ b/src/Ethereum/ABI/ValueDecoder.cpp @@ -0,0 +1,17 @@ +// Copyright © 2017-2020 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. + +#include "ValueDecoder.h" + +namespace TW::Ethereum::ABI { + +uint256_t ValueDecoder::decodeUInt256(Data& data) { + if (data.size() > 32) { + return load(subData(data, 0, 32)); + } + return load(data); +} +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueDecoder.h b/src/Ethereum/ABI/ValueDecoder.h new file mode 100644 index 00000000000..e073933f864 --- /dev/null +++ b/src/Ethereum/ABI/ValueDecoder.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include +#include + +namespace TW::Ethereum::ABI { + +class ValueDecoder { +public: + static uint256_t decodeUInt256(Data& data); +}; +} // namespace TW::Ethereum::ABI diff --git a/src/interface/TWEthereumAbiValueDecoder.cpp b/src/interface/TWEthereumAbiValueDecoder.cpp new file mode 100644 index 00000000000..d16df43b9a5 --- /dev/null +++ b/src/interface/TWEthereumAbiValueDecoder.cpp @@ -0,0 +1,19 @@ +// Copyright © 2017-2020 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. + +#include + +#include +#include + +using namespace TW::Ethereum; +using namespace TW; + +TWString* _Nonnull TWEthereumAbiValueDecoderDecodeUInt256(TWData* _Nonnull input) { + auto data = TW::data(TWDataBytes(input), TWDataSize(input)); + auto decoded = Ethereum::ABI::ValueDecoder::decodeUInt256(data); + return TWStringCreateWithUTF8Bytes(TW::toString(decoded).c_str()); +} diff --git a/swift/Tests/Blockchains/EthereumAbiTests.swift b/swift/Tests/Blockchains/EthereumAbiTests.swift index b2e2a618e76..decc52126f9 100644 --- a/swift/Tests/Blockchains/EthereumAbiTests.swift +++ b/swift/Tests/Blockchains/EthereumAbiTests.swift @@ -40,4 +40,17 @@ class EthereumAbiTests: XCTestCase { let data2 = EthereumAbiValueEncoder.encodeInt32(value: 69) XCTAssertEqual(data2.hexString, "0000000000000000000000000000000000000000000000000000000000000045") } + + func testValueDecoder() { + let expected = "1234567890987654321" + let inputs = [ + "112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb10000000000000000000000000000000000000000000000000000000000000000" + ] + for input in inputs { + let data = Data(hexString: input)! + XCTAssertEqual(expected, EthereumAbiValueDecoder.decodeUInt256(input: data)) + } + } } diff --git a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp b/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp new file mode 100644 index 00000000000..fbe2f79bf2f --- /dev/null +++ b/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp @@ -0,0 +1,30 @@ +// Copyright © 2017-2020 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. + +#include + +#include "Data.h" +#include "HexCoding.h" +#include "../interface/TWTestUtilities.h" +#include + +using namespace TW; +using namespace std; + +TEST(TWEthereumAbiValueDecoder, decodeUInt256) { + const char * expected = "10020405371567"; + auto inputs = vector{ + "091d0eb3e2af", + "0000000000000000000000000000000000000000000000000000091d0eb3e2af", + "0000000000000000000000000000000000000000000000000000091d0eb3e2af0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000091d0eb3e2af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }; + for (auto &input: inputs) { + auto data = WRAPD(TWDataCreateWithHexString(STRING(input.c_str()).get())); + auto result = WRAPS(TWEthereumAbiValueDecoderDecodeUInt256(data.get())); + assertStringsEqual(result, expected); + } +} diff --git a/tests/Ethereum/ValueDecoderTests.cpp b/tests/Ethereum/ValueDecoderTests.cpp new file mode 100644 index 00000000000..9f9f9ecfcc4 --- /dev/null +++ b/tests/Ethereum/ValueDecoderTests.cpp @@ -0,0 +1,27 @@ +// Copyright © 2017-2020 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. + +#include "Ethereum/ABI/ValueDecoder.h" +#include + +#include + +using namespace TW; +using namespace TW::Ethereum; + +uint256_t decodeFromHex(std::string s) { + auto data = parse_hex(s); + return ABI::ValueDecoder::decodeUInt256(data); +} + +TEST(EthereumAbiValueDecoder, decodeUInt256) { + EXPECT_EQ(uint256_t(0), decodeFromHex("")); + EXPECT_EQ(uint256_t(0), decodeFromHex("0000000000000000000000000000000000000000000000000000000000000000")); + EXPECT_EQ(uint256_t(1), decodeFromHex("0000000000000000000000000000000000000000000000000000000000000001")); + EXPECT_EQ(uint256_t(123456), decodeFromHex("01e240")); + EXPECT_EQ(uint256_t(10020405371567), decodeFromHex("0000000000000000000000000000000000000000000000000000091d0eb3e2af")); + EXPECT_EQ(uint256_t(10020405371567), decodeFromHex("0000000000000000000000000000000000000000000000000000091d0eb3e2af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); +} From 728f2da15fbc461849eb66c8a15f6f2aea8e8692 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Sun, 21 Jun 2020 21:43:48 +0200 Subject: [PATCH 46/81] Fix compiler warnings. (#1004) * Fix compiler warnings. * Refine error handling for aes_encrypt_key(). Co-authored-by: Catenocrypt --- codegen/lib/templates/jni/method_forward.erb | 4 ++++ src/Algorand/BinaryCoding.h | 4 +++- src/Ethereum/RLP.cpp | 14 +++++++------- src/Keystore/EncryptionParameters.cpp | 15 ++++++++------- src/Keystore/ScryptParameters.cpp | 4 +++- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/codegen/lib/templates/jni/method_forward.erb b/codegen/lib/templates/jni/method_forward.erb index 466ea03e488..e5ceb379935 100644 --- a/codegen/lib/templates/jni/method_forward.erb +++ b/codegen/lib/templates/jni/method_forward.erb @@ -19,7 +19,9 @@ } <% end -%> result = TWDataJByteArray(resultData, env); +<% if method.return_type.is_nullable %> cleanup: +<% end -%> <%= render('jni/parameter_release.erb', { method: method }) -%> <% if !method.static %> <%= render('jni/instance_release.erb', { entity: entity }) %> @@ -36,7 +38,9 @@ cleanup: } <% end -%> result = TWStringJString(resultString, env); +<% if method.return_type.is_nullable %> cleanup: +<% end -%> <%= render('jni/parameter_release.erb', { method: method }) -%> <% if !method.static %> <%= render('jni/instance_release.erb', { entity: entity }) %> diff --git a/src/Algorand/BinaryCoding.h b/src/Algorand/BinaryCoding.h index 3a169cf3e00..8a3bce78823 100644 --- a/src/Algorand/BinaryCoding.h +++ b/src/Algorand/BinaryCoding.h @@ -11,6 +11,8 @@ namespace TW::Algorand { +#pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" + static inline void encodeString(std::string string, Data& data) { // encode string header auto bytes = Data(string.begin(), string.end()); @@ -25,7 +27,7 @@ static inline void encodeString(std::string string, Data& data) { // str 16 data.push_back(static_cast(0xda)); encode16BE(static_cast(bytes.size()), data); - } else if (bytes.size() < 0x100000000) { + } else if (bytes.size() < 0x100000000) { // depending on size_t size on platform, may be always true // str 32 data.push_back(static_cast(0xdb)); encode32BE(static_cast(bytes.size()), data); diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index 494a6c0f71b..51d24062abb 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -80,27 +80,27 @@ Data RLP::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexce Data RLP::putint(uint64_t i) noexcept { // clang-format off - if (i < (1l << 8)) + if (i < (1ULL << 8)) return {static_cast(i)}; - if (i < (1l << 16)) + if (i < (1ULL << 16)) return { static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 24)) + if (i < (1ULL << 24)) return { static_cast(i >> 16), static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 32)) + if (i < (1ULL << 32)) return { static_cast(i >> 24), static_cast(i >> 16), static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 40)) + if (i < (1ULL << 40)) return { static_cast(i >> 32), static_cast(i >> 24), @@ -108,7 +108,7 @@ Data RLP::putint(uint64_t i) noexcept { static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 48)) + if (i < (1ULL << 48)) return { static_cast(i >> 40), static_cast(i >> 32), @@ -117,7 +117,7 @@ Data RLP::putint(uint64_t i) noexcept { static_cast(i >> 8), static_cast(i), }; - if (i < (1l << 56)) + if (i < (1ULL << 56)) return { static_cast(i >> 48), static_cast(i >> 40), diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 87d075f6446..6dcdc1a7e6f 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -36,14 +36,15 @@ EncryptionParameters::EncryptionParameters(const Data& password, const Data& dat scryptParams.desiredKeyLength); aes_encrypt_ctx ctx; - auto result = aes_encrypt_key(derivedKey.data(), 16, &ctx); - assert(result != EXIT_FAILURE); + auto result = aes_encrypt_key128(derivedKey.data(), &ctx); + assert(result == EXIT_SUCCESS); + if (result == EXIT_SUCCESS) { + Data iv = cipherParams.iv; + encrypted = Data(data.size()); + aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - Data iv = cipherParams.iv; - encrypted = Data(data.size()); - aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + } } EncryptionParameters::~EncryptionParameters() { diff --git a/src/Keystore/ScryptParameters.cpp b/src/Keystore/ScryptParameters.cpp index 685301fba3d..16ccd489c4c 100644 --- a/src/Keystore/ScryptParameters.cpp +++ b/src/Keystore/ScryptParameters.cpp @@ -16,8 +16,10 @@ ScryptParameters::ScryptParameters() : salt(32) { random_buffer(salt.data(), salt.size()); } +#pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" + std::optional ScryptParameters::validate() const { - if (desiredKeyLength > ((static_cast(1) << 32) - 1) * 32) { + if (desiredKeyLength > ((1ULL << 32) - 1) * 32) { // depending on size_t size on platform, may be always false return ScryptValidationError::desiredKeyLengthTooLarge; } if (static_cast(r) * static_cast(p) >= (1 << 30)) { From 2e86f8625c48824f4794d10c02b6a2e4434deecb Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Mon, 22 Jun 2020 10:32:49 +0200 Subject: [PATCH 47/81] Remove unneeded warning flags (#1006) * Make: remove no-longer-needed warning ignores. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68ccefb1558..a571eb1f40c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) project(TrustWalletCore) # Configure warnings -set(TW_CXX_WARNINGS "-Wshorten-64-to-32 -Wtautological-constant-out-of-range-compare -Wshift-count-overflow") +set(TW_CXX_WARNINGS "-Wshorten-64-to-32") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ ${TW_CXX_WARNINGS}") From a1689f8f0c8cff0bb97af79719204e5612877470 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Mon, 22 Jun 2020 22:04:21 +0200 Subject: [PATCH 48/81] [TestOnly] TWData and TWBase58 tests (#1007) * Add missing Base58 tests. * Add negative Base58 tests. * Add TWData tests. * One extra Base58 test. Co-authored-by: Catenocrypt --- tests/interface/TWBase58Tests.cpp | 68 ++++++++++++++ tests/interface/TWDataTests.cpp | 144 +++++++++++++++++++++++++++--- tests/interface/TWTestUtilities.h | 4 +- 3 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 tests/interface/TWBase58Tests.cpp diff --git a/tests/interface/TWBase58Tests.cpp b/tests/interface/TWBase58Tests.cpp new file mode 100644 index 00000000000..74a8d7ddced --- /dev/null +++ b/tests/interface/TWBase58Tests.cpp @@ -0,0 +1,68 @@ +// Copyright © 2017-2020 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. + +#include "TWTestUtilities.h" + +#include + +#include + +TEST(TWBase58, Encode) { + const auto input = DATA("00769bdff96a02f9135a1d19b749db6a78fe07dc90"); + auto result = WRAPS(TWBase58Encode(input.get())); + assertStringsEqual(result, "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); +} + +TEST(TWBase58, EncodeNoCheck) { + const auto input = DATA("00769bdff96a02f9135a1d19b749db6a78fe07dc90c3507da5"); + auto result = WRAPS(TWBase58EncodeNoCheck(input.get())); + assertStringsEqual(result, "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); +} + +TEST(TWBase58, EncodeNoCheck2) { + const auto input = DATA("00769bdff96a02f9135a1d19b749db6a78fe07dc90deadbeef"); + auto result = WRAPS(TWBase58EncodeNoCheck(input.get())); + assertStringsEqual(result, "1Bp9U1ogV3A14FMvKbRJms7ctyso5FdSz2"); +} + +TEST(TWBase58, Decode) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + auto result = WRAPS(TWBase58Decode(input.get())); + assertHexEqual(result, "00769bdff96a02f9135a1d19b749db6a78fe07dc90"); +} + +TEST(TWBase58, DecodeNoCheck) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + auto result = WRAPS(TWBase58DecodeNoCheck(input.get())); + assertHexEqual(result, "00769bdff96a02f9135a1d19b749db6a78fe07dc90c3507da5"); +} + +TEST(TWBase58, Decode_WrongChecksum) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso5FdSz2"); + auto result = WRAPS(TWBase58Decode(input.get())); + ASSERT_EQ(result.get(), nullptr); +} + +TEST(TWBase58, DecodeNoCheck_WrongChecksum) { + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso5FdSz2"); + auto result = WRAPS(TWBase58DecodeNoCheck(input.get())); + // decodes despite wrong checksum + assertHexEqual(result, "00769bdff96a02f9135a1d19b749db6a78fe07dc90deadbeef"); +} + +TEST(TWBase58, Decode_InvalidChar) { + // 0 is invalid + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tc0"); + auto result = WRAPS(TWBase58Decode(input.get())); + ASSERT_EQ(result.get(), nullptr); +} + +TEST(TWBase58, DecodeNoCheck_InvalidChar) { + // 0 is invalid + const auto input = STRING("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tc0"); + auto result = WRAPS(TWBase58DecodeNoCheck(input.get())); + ASSERT_EQ(result.get(), nullptr); +} diff --git a/tests/interface/TWDataTests.cpp b/tests/interface/TWDataTests.cpp index 496074ed0fd..59c63c72308 100644 --- a/tests/interface/TWDataTests.cpp +++ b/tests/interface/TWDataTests.cpp @@ -4,26 +4,144 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include #include "TWTestUtilities.h" #include -TEST(DataTests, ParseHex) { - auto zero = DATA("0x0"); - ASSERT_EQ(TWDataSize(zero.get()), 1); - ASSERT_EQ(TWDataBytes(zero.get())[0], 0); +TEST(TWData, CreateWithHexString) { + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + ASSERT_EQ(TWDataSize(data.get()), 4); + EXPECT_EQ(TWDataBytes(data.get())[0], 0xde); + EXPECT_EQ(TWDataBytes(data.get())[1], 0xad); + EXPECT_EQ(TWDataBytes(data.get())[2], 0xbe); + EXPECT_EQ(TWDataBytes(data.get())[3], 0xef); + assertHexEqual(data, "deadbeef"); + } - auto num = DATA("0xdeadbeef"); - ASSERT_EQ(TWDataSize(num.get()), 4); - ASSERT_EQ(TWDataBytes(num.get())[0], 0xde); - ASSERT_EQ(TWDataBytes(num.get())[1], 0xad); - ASSERT_EQ(TWDataBytes(num.get())[2], 0xbe); - ASSERT_EQ(TWDataBytes(num.get())[3], 0xef); + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("00").get())); + ASSERT_EQ(TWDataSize(data.get()), 1); + EXPECT_EQ(TWDataBytes(data.get())[0], 0); + assertHexEqual(data, "00"); + } + + { + // with 0x prefix + const auto data = WRAPD(TWDataCreateWithHexString(STRING("0xdeadbeef").get())); + assertHexEqual(data, "deadbeef"); + } + + { + // capitals + const auto data = WRAPD(TWDataCreateWithHexString(STRING("DEADBEEF").get())); + assertHexEqual(data, "deadbeef"); + } +} + +TEST(TWData, CreateWithBytes) { + const uint8_t bytes[] = {0xde, 0xad, 0xbe, 0xef}; + const auto data = WRAPD(TWDataCreateWithBytes(bytes, 4)); + assertHexEqual(data, "deadbeef"); } -TEST(DataTests, Equal) { - auto data1 = DATA("0xdeadbeef"); - auto data2 = DATA("0xdeadbeef"); +TEST(TWData, CreateWithSize) { + int n = 12; + const auto data = WRAPD(TWDataCreateWithSize(n)); + ASSERT_EQ(TWDataSize(data.get()), n); + for (int i = 0; i < n; ++i) { + EXPECT_EQ(TWDataBytes(data.get())[i], 0); + } +} + +TEST(TWData, CreateWithData) { + const auto data1 = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + const auto data2 = WRAPD(TWDataCreateWithData(data1.get())); + assertHexEqual(data2, "deadbeef"); +} + +TEST(TWData, Delete) { + const auto data = TWDataCreateWithSize(8); + ASSERT_TRUE(data != nullptr); + TWDataDelete(data); +} + +TEST(TWData, Get) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + EXPECT_EQ(TWDataGet(data.get(), 0), 0xde); + EXPECT_EQ(TWDataGet(data.get(), 1), 0xad); + EXPECT_EQ(TWDataGet(data.get(), 2), 0xbe); + EXPECT_EQ(TWDataGet(data.get(), 3), 0xef); +} + +TEST(TWData, Set) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataSet(data.get(), 1, 0xff); + assertHexEqual(data, "deffbeef"); +} + +TEST(TWData, CopyBytes) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + uint8_t buffer[4]; + TWDataCopyBytes(data.get(), 0, 4, buffer); + const auto data2 = WRAPD(TWDataCreateWithBytes(buffer, 4)); + assertHexEqual(data2, "deadbeef"); +} + +TEST(TWData, ReplaceBytes) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + const uint8_t bytes[] = {0x12, 0x34}; + TWDataReplaceBytes(data.get(), 1, 2, bytes); + assertHexEqual(data, "de1234ef"); +} + +TEST(TWData, Append) { + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataAppendByte(data.get(), 0x12); + assertHexEqual(data, "deadbeef12"); + } + + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + const uint8_t bytes[] = {0x12, 0x34}; + TWDataAppendBytes(data.get(), bytes, 2); + assertHexEqual(data, "deadbeef1234"); + } + + { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + const auto data2 = WRAPD(TWDataCreateWithHexString(STRING("1234").get())); + assertHexEqual(data2, "1234"); + TWDataAppendData(data.get(), data2.get()); + assertHexEqual(data, "deadbeef1234"); + } +} + +TEST(TWData, Reverse) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataReverse(data.get()); + assertHexEqual(data, "efbeadde"); +} + +TEST(TWData, Reset) { + const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + assertHexEqual(data, "deadbeef"); + TWDataReset(data.get()); + assertHexEqual(data, "00000000"); +} +TEST(TWData, Equal) { + const auto data1 = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); + const auto data2 = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); ASSERT_TRUE(TWDataEqual(data1.get(), data2.get())); } diff --git a/tests/interface/TWTestUtilities.h b/tests/interface/TWTestUtilities.h index 788ad58dd4d..c2a7b6e4dfb 100644 --- a/tests/interface/TWTestUtilities.h +++ b/tests/interface/TWTestUtilities.h @@ -19,11 +19,11 @@ #define STRING(x) std::shared_ptr(TWStringCreateWithUTF8Bytes(x), TWStringDelete) #define DATA(x) std::shared_ptr(TWDataCreateWithHexString(STRING(x).get()), TWDataDelete) -inline void assertStringsEqual(std::shared_ptr& string, const char* expected) { +inline void assertStringsEqual(const std::shared_ptr& string, const char* expected) { ASSERT_STREQ(TWStringUTF8Bytes(string.get()), expected); } -inline void assertHexEqual(std::shared_ptr& data, const char* expected) { +inline void assertHexEqual(const std::shared_ptr& data, const char* expected) { auto hex = WRAPS(TWStringCreateWithHexData(data.get())); assertStringsEqual(hex, expected); } From 95a089ca86d9b74c643fe89d0ccd7fb1aa9c8a47 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Wed, 24 Jun 2020 11:37:09 +0200 Subject: [PATCH 49/81] TWData cleanup: remove hex parsing code, resume parse_hex. (#1010) Co-authored-by: Catenocrypt --- include/TrustWalletCore/TWData.h | 2 +- src/interface/TWData+Hex.cpp | 48 -------------------- src/interface/TWData.cpp | 13 ++++++ tests/Ethereum/TWEthereumAbiEncoderTests.cpp | 8 ++-- tests/interface/TWDataTests.cpp | 15 +++++- 5 files changed, 32 insertions(+), 54 deletions(-) delete mode 100644 src/interface/TWData+Hex.cpp diff --git a/include/TrustWalletCore/TWData.h b/include/TrustWalletCore/TWData.h index 25ff75a14fa..6ebe32aff78 100644 --- a/include/TrustWalletCore/TWData.h +++ b/include/TrustWalletCore/TWData.h @@ -27,7 +27,7 @@ TWData *_Nonnull TWDataCreateWithSize(size_t size); /// Creates a block of data by copying another block of data. TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data); -/// Creates a block of data from a hexadecimal string. +/// Creates a block of data from a hexadecimal string. Odd length is invalid (intended grouping to bytes is not obvious). TWData *_Nullable TWDataCreateWithHexString(const TWString *_Nonnull hex); /// Returns the size in bytes. diff --git a/src/interface/TWData+Hex.cpp b/src/interface/TWData+Hex.cpp deleted file mode 100644 index cdc40659f33..00000000000 --- a/src/interface/TWData+Hex.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2017-2020 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. - -#include -#include - -static inline uint8_t value(uint8_t c) { - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - - // Invalid digit - return 0; - } - -TWData *TWDataCreateWithHexString(const TWString *hex) { - size_t stringIndex = 0; - if (TWStringSize(hex) >= 2 && TWStringGet(hex, 0) == '0' && TWStringGet(hex, 1) == 'x') { - stringIndex += 2; - } - - const size_t count = (TWStringSize(hex) - stringIndex + 1) / 2; - TWData *data = TWDataCreateWithSize(count); - - size_t dataIndex = 0; - while (stringIndex < TWStringSize(hex)) { - uint8_t high = value(TWStringGet(hex, stringIndex)); - stringIndex += 1; - if (stringIndex >= TWStringSize(hex)) { - TWDataSet(data, dataIndex, high); - break; - } - - uint8_t low = value(TWStringGet(hex, stringIndex)); - stringIndex += 1; - - TWDataSet(data, dataIndex, static_cast((high << 4) | low)); - dataIndex += 1; - } - - return data; -} diff --git a/src/interface/TWData.cpp b/src/interface/TWData.cpp index ed290bfccf2..cb3afcd52ca 100644 --- a/src/interface/TWData.cpp +++ b/src/interface/TWData.cpp @@ -5,9 +5,14 @@ // file LICENSE at the root of the source code distribution tree. #include +#include +#include "Data.h" +#include "HexCoding.h" #include #include +using namespace TW; + TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size) { auto data = new std::vector(); data->reserve(size); @@ -26,6 +31,14 @@ TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data) { return copy; } +TWData* TWDataCreateWithHexString(const TWString* hex) { + if (hex == nullptr) { + return nullptr; + } + Data data = parse_hex(std::string(TWStringUTF8Bytes(hex))); + return TWDataCreateWithBytes(data.data(), data.size()); +} + size_t TWDataSize(TWData *_Nonnull data) { auto v = reinterpret_cast*>(data); return v->size(); diff --git a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp b/tests/Ethereum/TWEthereumAbiEncoderTests.cpp index 4ccb7fef0f9..2079c9f57c5 100644 --- a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp +++ b/tests/Ethereum/TWEthereumAbiEncoderTests.cpp @@ -164,8 +164,8 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { TWEthereumAbiFunctionAddParamString(func, TWStringCreateWithUTF8Bytes("Hello, world!"), false); TWEthereumAbiFunctionAddParamAddress(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")), false); - TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353")), false); - TWEthereumAbiFunctionAddParamBytesFix(func, 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353")), false); + TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435")), false); + TWEthereumAbiFunctionAddParamBytesFix(func, 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435")), false); TWEthereumAbiFunctionAddInArrayParamUInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); TWEthereumAbiFunctionAddInArrayParamUInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), 2); @@ -183,8 +183,8 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { TWEthereumAbiFunctionAddInArrayParamString(func, TWEthereumAbiFunctionAddParamArray(func, false), TWStringCreateWithUTF8Bytes("Hello, world!")); TWEthereumAbiFunctionAddInArrayParamAddress(func, TWEthereumAbiFunctionAddParamArray(func, false), TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077"))); - TWEthereumAbiFunctionAddInArrayParamBytes(func, TWEthereumAbiFunctionAddParamArray(func, false), TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353"))); - TWEthereumAbiFunctionAddInArrayParamBytesFix(func, TWEthereumAbiFunctionAddParamArray(func, false), 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("31323334353"))); + TWEthereumAbiFunctionAddInArrayParamBytes(func, TWEthereumAbiFunctionAddParamArray(func, false), TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435"))); + TWEthereumAbiFunctionAddInArrayParamBytesFix(func, TWEthereumAbiFunctionAddParamArray(func, false), 5, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("3132333435"))); // check back out params EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, false)); diff --git a/tests/interface/TWDataTests.cpp b/tests/interface/TWDataTests.cpp index 59c63c72308..6e648769c57 100644 --- a/tests/interface/TWDataTests.cpp +++ b/tests/interface/TWDataTests.cpp @@ -34,10 +34,23 @@ TEST(TWData, CreateWithHexString) { } { - // capitals + // uppercase const auto data = WRAPD(TWDataCreateWithHexString(STRING("DEADBEEF").get())); assertHexEqual(data, "deadbeef"); } + + { + // odd length is invalid (intended grouping to bytes is not obvious) + const auto data = WRAPD(TWDataCreateWithHexString(STRING("012").get())); + assertHexEqual(data, ""); + } + + { + // null input + TWString* nullstring = nullptr; + const auto data = WRAPD(TWDataCreateWithHexString(nullstring)); + ASSERT_EQ(data.get(), nullptr); + } } TEST(TWData, CreateWithBytes) { From 65e096c23fc0c648c74195fe75f8956f0bbc6d15 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Thu, 25 Jun 2020 16:23:48 +0200 Subject: [PATCH 50/81] HDWallet tests and code cleanup (#1008) * Refactor TWHDVersionIsPublic/Private, so that default is false for both. * HDWallet: add some extra checks, remove some unreachable paths. * Extra tests. * Review comments (minor). * HDWallet: Better error handling for invalid Curve, TWCurveNone. * Change curve for default (missing) coin to CurveNone (in generated CoinInfoData.cpp!). Co-authored-by: Catenocrypt --- codegen/lib/templates/CoinInfoData.cpp.erb | 2 +- include/TrustWalletCore/TWCurve.h | 1 + src/HDWallet.cpp | 47 ++++++++------- src/PrivateKey.cpp | 21 ++++++- src/interface/TWHDVersion.cpp | 15 +++-- tests/HDWalletTests.cpp | 36 ++++++++++++ tests/interface/TWHDWalletTests.cpp | 68 ++++++++++++++++++++-- 7 files changed, 152 insertions(+), 38 deletions(-) diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index 4cbd4561d37..dcda64a0663 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.erb @@ -22,7 +22,7 @@ static const CoinInfo defaultsForMissing = { "?", TWBlockchainBitcoin, TWPurposeBIP44, - TWCurveED25519, + TWCurveNone, TWHDVersionNone, TWHDVersionNone, DerivationPath(), diff --git a/include/TrustWalletCore/TWCurve.h b/include/TrustWalletCore/TWCurve.h index 89590c62694..a23a1c7985d 100644 --- a/include/TrustWalletCore/TWCurve.h +++ b/include/TrustWalletCore/TWCurve.h @@ -18,6 +18,7 @@ enum TWCurve { TWCurveCurve25519 /* "curve25519" */, TWCurveNIST256p1 /* "nist256p1" */, TWCurveED25519Extended /* "ed25519-cardano-seed" */, + TWCurveNone }; TW_EXTERN_C_END diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 52d2deab333..8f9461e0a6d 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -54,10 +54,11 @@ HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase) HDWallet::HDWallet(const Data& data, const std::string& passphrase) : seed(), mnemonic(), passphrase(passphrase) { std::array mnemonic_chars; - mnemonic_from_data(data.data(), data.size(), mnemonic_chars.data()); - mnemonic_to_seed(mnemonic_chars.data(), passphrase.c_str(), seed.data(), nullptr); - mnemonic = mnemonic_chars.data(); - updateEntropy(); + if (mnemonic_from_data(data.data(), data.size(), mnemonic_chars.data())) { + mnemonic_to_seed(mnemonic_chars.data(), passphrase.c_str(), seed.data(), nullptr); + mnemonic = mnemonic_chars.data(); + updateEntropy(); + } } HDWallet::~HDWallet() { @@ -148,29 +149,21 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string& e if (!deserialize(extended, curve, hasher, &node)) { return {}; } + if (node.curve->params == nullptr) { + return {}; + } hdnode_public_ckd(&node, path.change()); hdnode_public_ckd(&node, path.address()); hdnode_fill_public_key(&node); - switch (curve) { - case TWCurveSECP256k1: + // These public key type are not applicable. Handled above, as node.curve->params is null + assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519Extended && curve != TWCurveCurve25519); + if (curve == TWCurveSECP256k1) { return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); - case TWCurveED25519: - return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeED25519); - case TWCurveED25519Blake2bNano: - return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeED25519Blake2b); - case TWCurveED25519Extended: - { - // concatenate public key and chain code (2x32 bytes) - Data concat(node.public_key, node.public_key + 32); - append(concat, Data(node.chain_code, node.chain_code + 32)); - return PublicKey(concat, TWPublicKeyTypeED25519Extended); - } - case TWCurveCurve25519: - return PublicKey(Data(node.public_key, node.public_key + 32), TWPublicKeyTypeCURVE25519); - case TWCurveNIST256p1: + } else if (curve == TWCurveNIST256p1) { return PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeNIST256p1); } + return {}; } std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, const DerivationPath& path) { @@ -224,9 +217,14 @@ std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version return Base58::bitcoin.encodeCheck(node_data, hasher); } -bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode *node) { +bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node) { memset(node, 0, sizeof(HDNode)); - node->curve = get_curve_by_name(curveName(curve)); + const char* curveNameStr = curveName(curve); + if (curveNameStr == nullptr || ::strlen(curveNameStr) == 0) { + return false; + } + node->curve = get_curve_by_name(curveNameStr); + assert(node->curve != nullptr); const auto node_data = Base58::bitcoin.decodeCheck(extended, hasher); if (node_data.size() != 78) { @@ -292,8 +290,13 @@ const char* curveName(TWCurve curve) { return ED25519_NAME; case TWCurveED25519Blake2bNano: return ED25519_BLAKE2B_NANO_NAME; + case TWCurveED25519Extended: + return ED25519_CARDANO_NAME; case TWCurveNIST256p1: return NIST256P1_NAME; + case TWCurveCurve25519: + return CURVE25519_NAME; + case TWCurveNone: default: return ""; } diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 822276f0573..75e23e0f8b5 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -57,6 +57,8 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) case TWCurveED25519Blake2bNano: case TWCurveED25519Extended: case TWCurveCurve25519: + case TWCurveNone: + default: break; } @@ -151,7 +153,7 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { Data PrivateKey::sign(const Data& digest, TWCurve curve) const { Data result; - bool success = true; + bool success = false; switch (curve) { case TWCurveSECP256k1: { result.resize(65); @@ -162,17 +164,20 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { result.resize(64); const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); ed25519_sign(digest.data(), digest.size(), bytes.data(), publicKey.bytes.data(), result.data()); + success = true; } break; case TWCurveED25519Blake2bNano: { result.resize(64); const auto publicKey = getPublicKey(TWPublicKeyTypeED25519Blake2b); ed25519_sign_blake2b(digest.data(), digest.size(), bytes.data(), publicKey.bytes.data(), result.data()); + success = true; } break; case TWCurveED25519Extended: { result.resize(64); const auto publicKey = getPublicKey(TWPublicKeyTypeED25519Extended); ed25519_sign_ext(digest.data(), digest.size(), bytes.data(), extensionBytes.data(), publicKey.bytes.data(), result.data()); + success = true; } break; case TWCurveCurve25519: { result.resize(64); @@ -182,12 +187,16 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { const auto sign_bit = publicKey.bytes[31] & 0x80; result[63] = result[63] & 127; result[63] |= sign_bit; + success = true; } break; case TWCurveNIST256p1: { result.resize(65); success = ecdsa_sign_digest(&nist256p1, bytes.data(), digest.data(), result.data(), result.data() + 64, nullptr) == 0; } break; + case TWCurveNone: + default: + break; } if (!success) { @@ -215,6 +224,9 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)( success = ecdsa_sign_digest(&nist256p1, bytes.data(), digest.data(), result.data() + 1, result.data(), canonicalChecker) == 0; } break; + case TWCurveNone: + default: + break; } if (!success) { @@ -254,10 +266,13 @@ Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { case TWCurveED25519: case TWCurveED25519Blake2bNano: case TWCurveED25519Extended: - case TWCurveCurve25519: { + case TWCurveCurve25519: + case TWCurveNone: + default: // not support - } break; + break; } + if (!success) { return {}; } diff --git a/src/interface/TWHDVersion.cpp b/src/interface/TWHDVersion.cpp index 7573820320f..e093336537a 100644 --- a/src/interface/TWHDVersion.cpp +++ b/src/interface/TWHDVersion.cpp @@ -20,6 +20,14 @@ bool TWHDVersionIsPublic(enum TWHDVersion version) { case TWHDVersionDGUB: return true; + case TWHDVersionNone: + default: + return false; + } +} + +bool TWHDVersionIsPrivate(enum TWHDVersion version) { + switch (version) { case TWHDVersionXPRV: case TWHDVersionYPRV: case TWHDVersionZPRV: @@ -27,7 +35,7 @@ bool TWHDVersionIsPublic(enum TWHDVersion version) { case TWHDVersionMTPV: case TWHDVersionDPRV: case TWHDVersionDGPV: - return false; + return true; case TWHDVersionNone: default: @@ -35,9 +43,4 @@ bool TWHDVersionIsPublic(enum TWHDVersion version) { } } -bool TWHDVersionIsPrivate(enum TWHDVersion version) { - if (version == TWHDVersionNone) return false; - return !TWHDVersionIsPublic(version); -} - #pragma clang diagnostic pop diff --git a/tests/HDWalletTests.cpp b/tests/HDWalletTests.cpp index 7da132a108e..07fc895748c 100644 --- a/tests/HDWalletTests.cpp +++ b/tests/HDWalletTests.cpp @@ -21,6 +21,7 @@ namespace TW { TEST(HDWallet, privateKeyFromXPRV) { const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + ASSERT_TRUE(privateKey); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Bitcoin::CashAddress(publicKey); @@ -28,6 +29,41 @@ TEST(HDWallet, privateKeyFromXPRV) { EXPECT_EQ(address.string(), "bitcoincash:qp3y0dyg6ya8nt4n3algazn073egswkytqs00z7rz4"); } +TEST(HDWallet, privateKeyFromXPRV_Invalid) { + const std::string xprv = "xprv9y0000"; + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + ASSERT_FALSE(privateKey); +} + +TEST(HDWallet, privateKeyFromXPRV_InvalidVersion) { + { + // Version bytes (first 4) are invalid, 0x00000000 + const std::string xprv = "11117pE7xwz2GARukXY8Vj2ge4ozfX4HLgy5ztnJXjr5btzJE8EbtPhZwrcPWAodW2aFeYiXkXjSxJYm5QrnhSKFXDgACcFdMqGns9VLqESCq3"; + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + ASSERT_FALSE(privateKey); + } + { + // Version bytes (first 4) are invalid, 0xdeadbeef + const std::string xprv = "pGoh3VZXR4mTkT4bfqj4paog12KmHkAWkdLY8HNsZagD1ihVccygLr1ioLBhVQsny47uEh5swP3KScFc4JJrazx1Y7xvzmH2y5AseLgVMwomBTg2"; + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + ASSERT_FALSE(privateKey); + } +} + +TEST(HDWallet, privateKeyFromExtended_InvalidCurve) { + // invalid coin & curve, should fail + const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, (TWCoinType)123456, 0, 0, 0)); + ASSERT_FALSE(privateKey); +} + +TEST(HDWallet, privateKeyFromXPRV_Invalid45) { + // 45th byte is not 0 + const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbhw2dJ8QexahgVSfkjxU4FgmN4GLGN3Ui8oLqC6433CeyPUNVHHh"; + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + ASSERT_FALSE(privateKey); +} + TEST(HDWallet, privateKeyFromMptv) { const std::string mptv = "Mtpv7SkyM349Svcf1WiRtB5hC91ZZkVsGuv3kz1V7tThGxBFBzBLFnw6LpaSvwpHHuy8dAfMBqpBvaSAHzbffvhj2TwfojQxM7Ppm3CzW67AFL5"; auto privateKey = HDWallet::getPrivateKeyFromExtended(mptv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 4)); diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 6b286eee3cb..80f2d2254f5 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -21,8 +21,10 @@ #include #include -auto words = STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); -auto passphrase = STRING("TREZOR"); +const auto wordsStr = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; +const auto words = STRING(wordsStr); +const auto seedHex = "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"; +const auto passphrase = STRING("TREZOR"); auto valid = STRING("credit expect life fade cover suit response wash pear what skull force"); auto invalidWord = STRING("ripple scissors hisc mammal hire column oak again sun offer wealth tomorrow"); @@ -30,14 +32,26 @@ auto invalidWord1 = STRING("high culture ostrich wrist exist ignore interest hyb auto invalidChecksum = STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow"); auto invalidWordCount = STRING("credit expect life fade cover suit response wash what skull force"); -inline void assertSeedEq(std::shared_ptr& wallet, const char* expected) { - auto seed = WRAPD(TWHDWalletSeed(wallet.get())); +inline void assertSeedEq(const std::shared_ptr& wallet, const char* expected) { + const auto seed = WRAPD(TWHDWalletSeed(wallet.get())); assertHexEqual(seed, expected); } +inline void assertMnemonicEq(const std::shared_ptr& wallet, const char* expected) { + const auto mnemonic = WRAPS(TWHDWalletMnemonic(wallet.get())); + assertStringsEqual(mnemonic, expected); +} + +TEST(HDWallet, Mnemonic) { + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + assertSeedEq(wallet, seedHex); + assertMnemonicEq(wallet, wordsStr); +} + TEST(HDWallet, Seed) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); - assertSeedEq(wallet, "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithData(DATA("ba5821e8c356c05ba5f025d9532fe0f21f65d594").get(), passphrase.get())); + assertSeedEq(wallet, seedHex); + assertMnemonicEq(wallet, wordsStr); } TEST(HDWallet, IsValid) { @@ -279,6 +293,12 @@ TEST(HDWallet, PublicKeyFromX) { assertHexEqual(data9, "03786c1d274f2c804ff9a57d8e7289c281d4aef15e17187ad9f9c3722d81a6ae66"); } +TEST(HDWallet, PublicKeyInvalid) { + auto xpub = STRING("xpub0000"); + auto xpubAddr = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/0").get()); + ASSERT_EQ(xpubAddr, nullptr); +} + TEST(HDWallet, PublicKeyFromY) { auto ypub = STRING("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP"); auto ypubAddr3 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), STRING("m/44'/0'/0'/0/3").get()); @@ -307,6 +327,35 @@ TEST(HDWallet, PublicKeyFromZ) { assertStringsEqual(address4, "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"); } +TEST(HDWallet, PublicKeyFromExtended_NIST256p1) { + const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/888'/0'/0/0").get())); // Neo + ASSERT_NE(xpubAddr.get(), nullptr); + auto data = WRAPD(TWPublicKeyData(xpubAddr.get())); + ASSERT_NE(data.get(), nullptr); + assertHexEqual(data, "03774c910fcf07fa96886ea794f0d5caed9afe30b44b83f7e213bb92930e7df4bd"); +} + +TEST(HDWallet, PublicKeyFromExtended_Negative) { + const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); + { // Ed25519 + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/501'/0'").get())); // Solana + EXPECT_EQ(xpubAddr.get(), nullptr); + } + { // Ed25519Extended + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/1852'/1815'/0'/0/0").get())); // Cardano + EXPECT_EQ(xpubAddr.get(), nullptr); + } + { // Ed25519Blake2bNano + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/165'/0'").get())); // Nano + EXPECT_EQ(xpubAddr.get(), nullptr); + } + { // Curve25519 + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/5741564'/0'/0'/0'").get())); // Waves + EXPECT_EQ(xpubAddr.get(), nullptr); + } +} + TEST(HDWallet, MultipleThreads) { auto passphrase = STRING(""); @@ -326,3 +375,10 @@ TEST(HDWallet, MultipleThreads) { th2.join(); th3.join(); } + +TEST(HDWallet, GetKeyBIP44) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + const auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyBIP44(wallet.get(), TWCoinTypeBitcoin, 0, 0, 0)); + const auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + assertHexEqual(privateKeyData, "1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac"); +} From 474b39da89bfccecb851c38e9520e22246aec2e8 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Mon, 29 Jun 2020 16:36:01 +0200 Subject: [PATCH 51/81] [BTC] New hashtype logic (#1016) * New HashTypeForCoin helper method to TWBitcoinScript, incorporate in signing tests. * Update Android test to use hashTypeForCoin(). * Update Android sample app to latest wc release. * Update OSX sample app to latest wc release, add BTC signing example. * Always take latest walletcore; Review comment Co-authored-by: Viktor Radchenko <1641795+vikmeup@users.noreply.github.com> * IOS sample app Podfile.lock cocoapods version 1.9.3. Co-authored-by: Catenocrypt Co-authored-by: Viktor Radchenko <1641795+vikmeup@users.noreply.github.com> --- .../blockchains/bitcoin/TestBitcoinSigning.kt | 7 ++- include/TrustWalletCore/TWBitcoinScript.h | 5 ++ samples/android/app/build.gradle | 2 +- .../trust/walletcore/example/MainActivity.kt | 11 ++-- samples/osx/cocoapods/Podfile | 3 +- samples/osx/cocoapods/Podfile.lock | 20 +++---- .../WalletCoreExample/ViewController.swift | 58 ++++++++++++++++--- src/Bitcoin/SigHashType.h | 14 +++++ src/interface/TWBitcoinScript.cpp | 5 ++ swift/Tests/Blockchains/BitcoinTests.swift | 2 +- tests/Bitcoin/TWBitcoinScriptTests.cpp | 22 +++++++ tests/Bitcoin/TWBitcoinSigningTests.cpp | 13 +++-- tests/BitcoinCash/TWBitcoinCashTests.cpp | 3 +- tests/BitcoinGold/TWBitcoinGoldTests.cpp | 3 +- tests/BitcoinGold/TWSignerTests.cpp | 3 +- 15 files changed, 135 insertions(+), 36 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt index 7f2688eea4b..2e94deba595 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt @@ -6,6 +6,9 @@ import com.trustwallet.core.app.utils.toHexBytes import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinScript +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType import wallet.core.jni.CoinType.BITCOIN import wallet.core.jni.proto.Bitcoin import wallet.core.jni.proto.Bitcoin.SigningOutput @@ -20,7 +23,7 @@ class TestBitcoinSigning { fun testSignP2WPKH() { val input = Bitcoin.SigningInput.newBuilder() .setAmount(335_790_000) - .setHashType(0x01) + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN).value()) .setToAddress("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx") .setChangeAddress("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU") .setByteFee(1) @@ -84,7 +87,7 @@ class TestBitcoinSigning { fun testSignP2PKH() { val input = Bitcoin.SigningInput.newBuilder() .setAmount(55_000) - .setHashType(0x01) + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN).value()) .setToAddress("1GDCMHsTLBkawQXP8dqcZtr8zGgb4XpCug") .setChangeAddress("1CSR6tXqngr1CfwVF23V4bQotttJmzXqpf") .setByteFee(10) diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index 73240b7e99e..32e766a4438 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -10,6 +10,7 @@ #include "TWData.h" #include "TWPublicKey.h" #include "TWCoinType.h" +#include "TWBitcoinSigHashType.h" TW_EXTERN_C_BEGIN @@ -114,4 +115,8 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWDa TW_EXPORT_STATIC_METHOD struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin); +// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +TW_EXPORT_STATIC_METHOD +enum TWBitcoinSigHashType TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType); + TW_EXTERN_C_END diff --git a/samples/android/app/build.gradle b/samples/android/app/build.gradle index 6dcd7757e4a..6003ffe28e5 100644 --- a/samples/android/app/build.gradle +++ b/samples/android/app/build.gradle @@ -24,7 +24,7 @@ android { } project.ext { - walletcore_version = "2.0.5" + walletcore_version = "2.1.0" } dependencies { diff --git a/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt b/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt index 0dec5d96180..eb2b4dc0c9b 100644 --- a/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt +++ b/samples/android/app/src/main/java/com/trust/walletcore/example/MainActivity.kt @@ -77,21 +77,24 @@ class MainActivity : AppCompatActivity() { }.build() val input = Bitcoin.SigningInput.newBuilder().apply { this.amount = 600 - this.hashType = BitcoinSigHashType.ALL.value().or(BitcoinSigHashType.FORK.value()) + this.hashType = BitcoinSigHashType.ALL.value() this.toAddress = toAddress this.changeAddress = changeAddress - this.byteFee = 1 + this.byteFee = 2 this.coinType = coinBtc.value() this.addUtxo(utxo) this.addPrivateKey(ByteString.copyFrom(secretPrivateKeyBtc.data())) } // Calculate fee (plan a tranaction) - val plan = AnySigner.plan(input.build(), CoinType.BITCOIN, Bitcoin.TransactionPlan.parser()) + val plan = AnySigner.plan(input.build(), coinBtc, Bitcoin.TransactionPlan.parser()) + showLog("Planned fee: ${plan.fee} amount: ${plan.amount} avail_amount: ${plan.availableAmount} change: ${plan.change}") // Set the precomputed plan input.plan = plan - val output = AnySigner.sign(input.build(), CoinType.BITCOIN, Bitcoin.SigningOutput.parser()) + input.amount = plan.amount + + val output = AnySigner.sign(input.build(), coinBtc, Bitcoin.SigningOutput.parser()) assert(output.error.isEmpty()) val signedTransaction = output.encoded?.toByteArray() diff --git a/samples/osx/cocoapods/Podfile b/samples/osx/cocoapods/Podfile index 320e1550d7c..23e019aa5d9 100644 --- a/samples/osx/cocoapods/Podfile +++ b/samples/osx/cocoapods/Podfile @@ -2,7 +2,6 @@ platform :osx, '10.12' target 'WalletCoreExample' do use_frameworks! - pod 'TrustWalletCore', '~> 2.0.0' + pod 'TrustWalletCore' end - diff --git a/samples/osx/cocoapods/Podfile.lock b/samples/osx/cocoapods/Podfile.lock index 06dec5ec6ee..4d9eb6faebb 100644 --- a/samples/osx/cocoapods/Podfile.lock +++ b/samples/osx/cocoapods/Podfile.lock @@ -1,14 +1,14 @@ PODS: - - SwiftProtobuf (1.8.0) - - TrustWalletCore (2.0.3): - - TrustWalletCore/Core (= 2.0.3) - - TrustWalletCore/Core (2.0.3): + - SwiftProtobuf (1.9.0) + - TrustWalletCore (2.1.1): + - TrustWalletCore/Core (= 2.1.1) + - TrustWalletCore/Core (2.1.1): - TrustWalletCore/Types - - TrustWalletCore/Types (2.0.3): + - TrustWalletCore/Types (2.1.1): - SwiftProtobuf DEPENDENCIES: - - TrustWalletCore (~> 2.0.0) + - TrustWalletCore (~> 2.1.0) SPEC REPOS: trunk: @@ -16,9 +16,9 @@ SPEC REPOS: - TrustWalletCore SPEC CHECKSUMS: - SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70 - TrustWalletCore: 43c900920c15fd1ccb8774a051b1055a660f2e91 + SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932 + TrustWalletCore: cd0373c69fd9bf92700783287544ce6a9aab041b -PODFILE CHECKSUM: 9f88ed3f7ceadcf38049ea37584a81f309d04e83 +PODFILE CHECKSUM: a34fe0b289ed6fb4db3ae7964f62fa6b21ff0c0d -COCOAPODS: 1.9.0 +COCOAPODS: 1.9.3 diff --git a/samples/osx/cocoapods/WalletCoreExample/ViewController.swift b/samples/osx/cocoapods/WalletCoreExample/ViewController.swift index bbcd4e29a0d..bd0f1a911ca 100644 --- a/samples/osx/cocoapods/WalletCoreExample/ViewController.swift +++ b/samples/osx/cocoapods/WalletCoreExample/ViewController.swift @@ -16,13 +16,14 @@ class ViewController: NSViewController { let wallet = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "") print("Mnemonic: ", wallet.mnemonic) - let coin: CoinType = .ethereum + // Ethereum example + var coin: CoinType = .ethereum // Get the default address - let address = wallet.getAddressForCoin(coin: coin) - print("Default address: ", address) + let addressEth = wallet.getAddressForCoin(coin: coin) + print("Default ETH address: ", addressEth) // Signing a transaction (using AnySigner) - let secretPrivateKey = wallet.getKeyForCoin(coin: coin) + let secretPrivateKeyEth = wallet.getKeyForCoin(coin: coin) let dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986" let signerInput = EthereumSigningInput.with { $0.chainID = Data(hexString: "01")! @@ -30,10 +31,53 @@ class ViewController: NSViewController { $0.gasLimit = Data(hexString: "5208")! // decimal 21000 $0.toAddress = dummyReceiverAddress $0.amount = Data(hexString: "0348bca5a16000")! - $0.privateKey = secretPrivateKey.data + $0.privateKey = secretPrivateKeyEth.data } - let output: EthereumSigningOutput = AnySigner.sign(input: signerInput, coin: .ethereum) + let outputEth: EthereumSigningOutput = AnySigner.sign(input: signerInput, coin: coin) print("Signed transaction:") - print(" data: ", output.encoded.hexString) + print(" data: ", outputEth.encoded.hexString) + + // Bitcoin example + coin = .bitcoin + // Get the default address + let addressBtc = wallet.getAddressForCoin(coin: coin) + print("Default BTC address: ", addressBtc) + + // Build a transaction + let toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + let changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" + let secretPrivateKeyBtc = wallet.getKeyForCoin(coin: coin) + let outPoint = BitcoinOutPoint.with { + $0.hash = Data(hexString: "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2")! + $0.index = 2 + } + let utxo = BitcoinUnspentTransaction.with { + $0.amount = 5151 + $0.outPoint = outPoint + $0.script = BitcoinScript.lockScriptForAddress(address: addressBtc, coin: coin).data + } + var input = BitcoinSigningInput.with { + $0.amount = 600 + $0.hashType = TWBitcoinSigHashTypeAll.rawValue + $0.toAddress = toAddress + $0.changeAddress = changeAddress + $0.byteFee = 2 + $0.coinType = coin.rawValue + } + input.utxo.append(utxo) + input.privateKey.append(secretPrivateKeyBtc.data) + + // Calculate fee (plan a tranaction) + let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: coin) + print("Planned fee: ", plan.fee, "amount:", plan.amount, "avail_amount:", plan.availableAmount, "change:", plan.change) + + // Set the precomputed plan + input.plan = plan + input.amount = plan.amount + + let outputBtc: BitcoinSigningOutput = AnySigner.sign(input: input, coin: coin) + + print("Signed transaction:") + print(" data: ", outputBtc.encoded.hexString) } } diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h index 690cfb2ed0b..79ae828b120 100644 --- a/src/Bitcoin/SigHashType.h +++ b/src/Bitcoin/SigHashType.h @@ -7,6 +7,7 @@ #pragma once #include +#include namespace TW::Bitcoin { @@ -14,6 +15,19 @@ namespace TW::Bitcoin { // outputs are signed. static const uint8_t SigHashMask = 0x1f; +// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +inline enum TWBitcoinSigHashType hashTypeForCoin(enum TWCoinType coinType) { + // set fork hash type for BCH + switch (coinType) { + case TWCoinTypeBitcoinCash: + return (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeFork); + case TWCoinTypeBitcoinGold: + return (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeForkBTG); + default: + return TWBitcoinSigHashTypeAll; + } +} + inline bool hashTypeIsSingle(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeSingle; } inline bool hashTypeIsNone(enum TWBitcoinSigHashType type) { return (type & SigHashMask) == TWBitcoinSigHashTypeNone; } diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 05391c36312..3e8e2241b44 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -7,6 +7,7 @@ #include #include "../Bitcoin/Script.h" +#include "../Bitcoin/SigHashType.h" using namespace TW::Bitcoin; @@ -146,3 +147,7 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_ auto script = Script::lockScriptForAddress(*s, coin); return new TWBitcoinScript{ .impl = script }; } + +enum TWBitcoinSigHashType TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { + return TW::Bitcoin::hashTypeForCoin(coinType); +} diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index c83ced5468c..d599cdc72e4 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -15,7 +15,7 @@ class BitcoinTransactionSignerTests: XCTestCase { func testSignP2WSH() throws { // set up input var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin).rawValue $0.amount = 1000 $0.byteFee = 1 $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/Bitcoin/TWBitcoinScriptTests.cpp index be4297a8df5..39cfd28c8d9 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/Bitcoin/TWBitcoinScriptTests.cpp @@ -202,3 +202,25 @@ TEST(TWBitcoinScript, LockScriptForCashAddress) { auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); } + +TEST(TWBitcoinSigHashType, HashTypeForCoin) { + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoin), TWBitcoinSigHashTypeAll); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeLitecoin), TWBitcoinSigHashTypeAll); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeZcash), TWBitcoinSigHashTypeAll); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinCash), (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeFork)); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinGold), (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeForkBTG)); +} + +TEST(TWBitcoinSigHashType, IsSingle) { + EXPECT_TRUE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeSingle)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeAll)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeNone)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsSingle(TWBitcoinSigHashTypeFork)); +} + +TEST(TWBitcoinSigHashType, IsNone) { + EXPECT_TRUE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeNone)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeSingle)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeAll)); + EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeFork)); +} diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 2311acccbed..91017c961ab 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -9,6 +9,7 @@ #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" +#include "Bitcoin/SigHashType.h" #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" @@ -33,7 +34,7 @@ Proto::SigningInput buildInputP2PKH(bool omitKey = false) { // Setup input Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); input.set_amount(335'790'000); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); @@ -426,7 +427,7 @@ Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omi TEST(BitcoinSigning, SignP2WSH) { // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + const auto input = buildInputP2WSH(hashTypeForCoin(TWCoinTypeBitcoin)); { // test plan (but do not reuse plan result) @@ -638,7 +639,7 @@ TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { Proto::SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = false) { // Setup input Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); input.set_amount(200'000'000); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); @@ -965,7 +966,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { } input.set_coin_type(TWCoinTypeBitcoin); - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoin)); input.set_use_max_amount(true); input.set_amount(2'000'000); input.set_byte_fee(1); @@ -1006,7 +1007,7 @@ TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { // Setup input Proto::SigningInput input; input.set_coin_type(coin); - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(coin)); input.set_amount(3'899'774); input.set_use_max_amount(true); input.set_byte_fee(1); @@ -1077,7 +1078,7 @@ TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { // Setup input for Plan Proto::SigningInput input; input.set_coin_type(coin); - input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(coin)); input.set_amount(1'200'000); input.set_use_max_amount(false); input.set_byte_fee(1); diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/BitcoinCash/TWBitcoinCashTests.cpp index 01c04ff23e0..84999b1b628 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/BitcoinCash/TWBitcoinCashTests.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Bitcoin/Address.h" +#include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "proto/Bitcoin.pb.h" #include "../interface/TWTestUtilities.h" @@ -107,7 +108,7 @@ TEST(BitcoinCash, SignTransaction) { // https://blockchair.com/bitcoin-cash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 auto input = Proto::SigningInput(); - input.set_hash_type(TWBitcoinSigHashTypeFork | TWBitcoinSigHashTypeAll); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinCash)); input.set_amount(amount); input.set_byte_fee(1); input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp index 3af9c09e51d..67e69db3791 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -18,6 +18,7 @@ #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" +#include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "../interface/TWTestUtilities.h" @@ -69,7 +70,7 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { // Setup input Proto::SigningInput input; input.set_coin_type(TWCoinTypeBitcoinGold); - input.set_hash_type(TWBitcoinSigHashTypeAll | TWBitcoinSigHashTypeForkBTG); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinGold)); input.set_amount(amount); input.set_byte_fee(1); input.set_to_address("btg1qmd6x5awe4t5fjhgntv0pngzdwajjg250wxdcs0"); diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/BitcoinGold/TWSignerTests.cpp index 9addba24260..f731709700c 100644 --- a/tests/BitcoinGold/TWSignerTests.cpp +++ b/tests/BitcoinGold/TWSignerTests.cpp @@ -16,6 +16,7 @@ #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" +#include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "../interface/TWTestUtilities.h" #include "../Bitcoin/TxComparisonHelper.h" @@ -29,7 +30,7 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { // Setup input Proto::SigningInput input; input.set_coin_type(TWCoinTypeBitcoinGold); - input.set_hash_type(TWBitcoinSigHashTypeAll | TWBitcoinSigHashTypeForkBTG); + input.set_hash_type(hashTypeForCoin(TWCoinTypeBitcoinGold)); input.set_amount(amount); input.set_byte_fee(1); input.set_to_address("btg1qmd6x5awe4t5fjhgntv0pngzdwajjg250wxdcs0"); From 8d88104ef899cbb48ae62cfbc6831a99f5589945 Mon Sep 17 00:00:00 2001 From: hewig <360470+hewigovens@users.noreply.github.com> Date: Tue, 30 Jun 2020 12:30:34 +0800 Subject: [PATCH 52/81] Update PULL_REQUEST_TEMPLATE.md (#1020) --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 88420d082c5..7f3a3e9b492 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,4 +23,5 @@ - [ ] Prefix PR title with `[WIP]` if necessary. - [ ] Add tests to cover changes as needed. - [ ] Update documentation as needed. +- [ ] I have read the [guidelines](https://developer.trustwallet.com/wallet-core/newblockchain#integration-criteria) for adding a new blockchain - [ ] If there is a related Issue, mention it in the description (e.g. Fixes # ). From 2330f73e453876929d1502a240452a9a42e60c22 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Thu, 2 Jul 2020 04:25:09 +0200 Subject: [PATCH 53/81] Include building of sample apps in CI. (#1017) * Include building of sample apps in CI. * Sample android app build fix. * Upgrade wallet core version used in sample apps. * Keep ios and android build, but leave out CPP and GO for now. --- .github/workflows/android-ci.yml | 3 +++ .github/workflows/ios-ci.yml | 3 +++ samples/android/.gitignore | 2 +- samples/android/gradle.properties | 1 + samples/osx/cocoapods/Podfile | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 samples/android/gradle.properties diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index abb17665fb3..4724d5602a3 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -35,3 +35,6 @@ jobs: run: | tools/generate-files tools/android-test + - name: Build sample app + run: | + tools/samples-build android diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 9afd0f7c19b..4d922a6db57 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -30,3 +30,6 @@ jobs: run: | tools/generate-files tools/ios-test + - name: Build sample app + run: | + tools/samples-build ios diff --git a/samples/android/.gitignore b/samples/android/.gitignore index 2f56990dfa4..88ba88714fc 100644 --- a/samples/android/.gitignore +++ b/samples/android/.gitignore @@ -52,5 +52,5 @@ gen-external-apklibs tn/trustnative.aar .idea/misc.xml -gradle.properties +#gradle.properties /app/src/main/play/listings/* \ No newline at end of file diff --git a/samples/android/gradle.properties b/samples/android/gradle.properties new file mode 100644 index 00000000000..5bac8ac5046 --- /dev/null +++ b/samples/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/samples/osx/cocoapods/Podfile b/samples/osx/cocoapods/Podfile index 23e019aa5d9..922910ccdc8 100644 --- a/samples/osx/cocoapods/Podfile +++ b/samples/osx/cocoapods/Podfile @@ -3,5 +3,5 @@ platform :osx, '10.12' target 'WalletCoreExample' do use_frameworks! pod 'TrustWalletCore' - end + From 3b04dfa852a7a8c017ddb91860149d4e3e2f5879 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Thu, 2 Jul 2020 10:25:25 +0800 Subject: [PATCH 54/81] Add TWAESPaddingMode and tests (#1022) --- include/TrustWalletCore/TWAES.h | 9 ++-- include/TrustWalletCore/TWAESPaddingMode.h | 19 ++++++++ src/Encrypt.cpp | 12 ++--- src/Encrypt.h | 12 ++--- src/FIO/Encryption.cpp | 5 ++- src/interface/TWAES.cpp | 12 ++--- swift/Tests/AESTests.swift | 35 +++++++++++++++ .../{SlipTests.swift => CoinTypeTests.swift} | 2 +- tests/EncryptTests.cpp | 44 ++++++++++--------- tests/interface/TWAESTests.cpp | 29 +++++++++--- 10 files changed, 125 insertions(+), 54 deletions(-) create mode 100644 include/TrustWalletCore/TWAESPaddingMode.h create mode 100644 swift/Tests/AESTests.swift rename swift/Tests/{SlipTests.swift => CoinTypeTests.swift} (97%) diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h index cb0196e8d17..510d2f32291 100644 --- a/include/TrustWalletCore/TWAES.h +++ b/include/TrustWalletCore/TWAES.h @@ -8,6 +8,7 @@ #include "TWBase.h" #include "TWData.h" +#include "TWAESPaddingMode.h" TW_EXTERN_C_BEGIN @@ -22,7 +23,7 @@ struct TWAES { /// \param data data to encrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCBCEncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); /// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// @@ -30,7 +31,7 @@ TWData *_Nullable TWAESCBCEncrypt(TWData *_Nonnull key, TWData *_Nonnull data, T /// \param data data to decrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCBCDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); /// Encrypts a block of data using AES in Counter (CTR) mode. /// @@ -38,7 +39,7 @@ TWData *_Nullable TWAESCBCDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, T /// \param data data to encrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); /// Decrypts a block of data using AES in Counter (CTR) mode. /// @@ -46,6 +47,6 @@ TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, T /// \param data data to decrypt. /// \param iv initialization vector. TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESCTRDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); +TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWAESPaddingMode.h b/include/TrustWalletCore/TWAESPaddingMode.h new file mode 100644 index 00000000000..9e4713d0ed6 --- /dev/null +++ b/include/TrustWalletCore/TWAESPaddingMode.h @@ -0,0 +1,19 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_ENUM(uint32_t) +enum TWAESPaddingMode { + TWAESPaddingModeZero = 0, // padding value is zero + TWAESPaddingModePKCS7 = 1, // padding value is the number of padding bytes; for even size add an extra block +}; + +TW_EXTERN_C_END diff --git a/src/Encrypt.cpp b/src/Encrypt.cpp index 59fd22d6835..2b89542f94b 100644 --- a/src/Encrypt.cpp +++ b/src/Encrypt.cpp @@ -11,10 +11,10 @@ namespace TW::Encrypt { -size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode) { +size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode) { if (origSize % blockSize == 0) { // even blocks - if (paddingMode == PadWithPaddingSize) { + if (paddingMode == TWAESPaddingModePKCS7) { return blockSize; } return 0; @@ -23,7 +23,7 @@ size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode) { return blockSize - origSize % blockSize; } -Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode) { +Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode) { aes_encrypt_ctx ctx; if (aes_encrypt_key(key.data(), static_cast(key.size()), &ctx) == EXIT_FAILURE) { throw std::invalid_argument("Invalid key"); @@ -41,7 +41,7 @@ Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd // last block if (idx < resultSize) { uint8_t padded[blockSize] = {0}; - if (paddingMode == PadWithPaddingSize) { + if (paddingMode == TWAESPaddingModePKCS7) { std::memset(padded, static_cast(padding), blockSize); } std::memcpy(padded, data.data() + idx, data.size() - idx); @@ -51,7 +51,7 @@ Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd return result; } -Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode) { +Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode) { const size_t blockSize = AES_BLOCK_SIZE; if (data.size() % blockSize != 0) { throw std::invalid_argument("Invalid data size"); @@ -68,7 +68,7 @@ Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd aes_cbc_decrypt(data.data() + i, result.data() + i, blockSize, iv.data(), &ctx); } - if (paddingMode == PadWithPaddingSize && result.size() > 0) { + if (paddingMode == TWAESPaddingModePKCS7 && result.size() > 0) { // need to remove padding assert(result.size() > 0); const byte paddingSize = result[result.size() - 1]; diff --git a/src/Encrypt.h b/src/Encrypt.h index 01c2ef43ac1..5b514b3b8c5 100644 --- a/src/Encrypt.h +++ b/src/Encrypt.h @@ -6,17 +6,13 @@ #pragma once +#include #include "Data.h" namespace TW::Encrypt { -enum PaddingMode { - PadWithZeros = 0, // padding value is zero - PadWithPaddingSize // padding value is the number of padding bytes; for even size add an extra block (PKCS#7) -}; - /// Determind needed padding size (used internally) -size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode); +size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode); /// Encrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// @@ -25,7 +21,7 @@ size_t paddingSize(size_t origSize, size_t blockSize, PaddingMode paddingMode); /// \param iv initialization vector. /// \param paddingMode If PadWithZeroes (default), data is padded with 0's to even block size. /// If PadWithPaddingSize, pad value is padding size, and even size input is padded with an extra block. -Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode = PadWithZeros); +Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode = TWAESPaddingModeZero); /// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// @@ -34,7 +30,7 @@ Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, PaddingMode padd /// \param iv initialization vector. /// \param paddingMode If PadWithZeroes (default), padding is not removed. /// If PadWithPaddingSize, padding is removed. -Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, PaddingMode paddingMode = PadWithZeros); +Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode = TWAESPaddingModeZero); /// Encrypts a block of data using AES in Counter (CTR) mode. /// diff --git a/src/FIO/Encryption.cpp b/src/FIO/Encryption.cpp index 5b10a8ca229..dbea7aac7b1 100644 --- a/src/FIO/Encryption.cpp +++ b/src/FIO/Encryption.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -44,7 +45,7 @@ Data Encryption::checkEncrypt(const Data& secret, const Data& message, Data& iv) TW::append(ivOrig, iv); // Encrypt. Padding is done (PKCS#7) - const Data C = Encrypt::AESCBCEncrypt(Ke, message, iv, Encrypt::PadWithPaddingSize); + const Data C = Encrypt::AESCBCEncrypt(Ke, message, iv, TWAESPaddingModePKCS7); // HMAC. Include in the HMAC input everything that impacts the decryption. Data hmacIn(0); @@ -82,7 +83,7 @@ Data Encryption::checkDecrypt(const Data& secret, const Data& message) { } // Decrypt, unpadding is done - const Data unencrypted = Encrypt::AESCBCDecrypt(Ke, C, iv, Encrypt::PadWithPaddingSize); + const Data unencrypted = Encrypt::AESCBCDecrypt(Ke, C, iv, TWAESPaddingModePKCS7); return unencrypted; } diff --git a/src/interface/TWAES.cpp b/src/interface/TWAES.cpp index 64cef9f8ee7..04f86c8faed 100644 --- a/src/interface/TWAES.cpp +++ b/src/interface/TWAES.cpp @@ -10,25 +10,25 @@ using namespace TW; -TWData *_Nullable TWAESCBCEncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode) { try { - Data encrypted = Encrypt::AESCBCEncrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); + Data encrypted = Encrypt::AESCBCEncrypt(*((Data*)key), *((Data*)data), *((Data*)iv), mode); return TWDataCreateWithData(&encrypted); } catch (...) { return nullptr; } } -TWData *_Nullable TWAESCBCDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode) { try { - Data decrypted = Encrypt::AESCBCDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); + Data decrypted = Encrypt::AESCBCDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv), mode); return TWDataCreateWithData(&decrypted); } catch (...) { return nullptr; } } -TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { try { Data encrypted = Encrypt::AESCTREncrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); return TWDataCreateWithData(&encrypted); @@ -37,7 +37,7 @@ TWData *_Nullable TWAESCTREncrypt(TWData *_Nonnull key, TWData *_Nonnull data, T } } -TWData *_Nullable TWAESCTRDecrypt(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { +TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { try { Data decrypted = Encrypt::AESCTRDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); return TWDataCreateWithData(&decrypted); diff --git a/swift/Tests/AESTests.swift b/swift/Tests/AESTests.swift new file mode 100644 index 00000000000..6e3fb4d5f37 --- /dev/null +++ b/swift/Tests/AESTests.swift @@ -0,0 +1,35 @@ +// Copyright © 2017-2020 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. + +import XCTest +import TrustWalletCore + +class AESTests: XCTestCase { + func testDecrypt() throws { + let key = Data(hexString: "5caa3a74154cee16bd1b570a1330be46e086474ac2f4720530662ef1a469662c")! + let iv = Data(hexString: "89ef1d6728bac2f1dcde2ef9330d2bb8")! + let cipher = Data(hexString: "1b3db3674de082d65455eba0ae61cfe7e681c8ef1132e60c8dbd8e52daf18f4fea42cc76366c83351dab6dca52682ff81f828753f89a21e1cc46587ca51ccd353914ffdd3b0394acfee392be6c22b3db9237d3f717a3777e3577dd70408c089a4c9c85130a68c43b0a8aadb00f1b8a8558798104e67aa4ff027b35d4b989e7fd3988d5dcdd563105767670be735b21c4")! + let expected = """ + {"id":1554098597199736,"jsonrpc":"2.0","method":"wc_sessionUpdate","params":[{"approved":false,"chainId":null,"accounts":null}]} + """ + + let decrypted = AES.decryptCBC(key: key, data: cipher, iv: iv, mode: .pkcs7)! + + XCTAssertEqual(String(data: decrypted, encoding: .utf8)!, expected) + } + + func testEncrypt() throws { + let key = Data(hexString: "bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96")! + let iv = Data(hexString: "5b3a1a561e395d7ad7fe9c92abdacd17")! + let plain = """ + {"jsonrpc":"2.0","id":1554343834752446,"error":{"code":-32000,"message":"Session Rejected"}} + """ + + let encrypted = AES.encryptCBC(key: key, data: Data(plain.utf8), iv: iv, mode: .pkcs7)! + + XCTAssertEqual(encrypted.hexString, "6a93788fcd6d266d06ccff35d1ed328d634605a7f2734f1519256b9950d86d6ca34fe4a13ff0ed513025b49427e6fe15268c84d6dfad6c0c8a21abc9306a5308f545b08d946a2a24b7cd18526bcefd6d9740db9b8e21f4511df148d9b9b03ad9") + } +} diff --git a/swift/Tests/SlipTests.swift b/swift/Tests/CoinTypeTests.swift similarity index 97% rename from swift/Tests/SlipTests.swift rename to swift/Tests/CoinTypeTests.swift index da38e2248d3..9ca1d5c39b4 100644 --- a/swift/Tests/SlipTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -7,7 +7,7 @@ import XCTest import TrustWalletCore -class SlipTests: XCTestCase { +class CoinTypeTests: XCTestCase { func testCoinType() { XCTAssertEqual(CoinType.bitcoin.rawValue, 0) diff --git a/tests/EncryptTests.cpp b/tests/EncryptTests.cpp index 5b59d696136..9870f4e2937 100644 --- a/tests/EncryptTests.cpp +++ b/tests/EncryptTests.cpp @@ -8,6 +8,8 @@ #include "Data.h" #include "HexCoding.h" +#include + #include using namespace TW::Encrypt; @@ -20,24 +22,24 @@ inline void assertHexEqual(const Data& data, const char* expected) { } TEST(Encrypt, paddingSize) { - EXPECT_EQ(paddingSize(0, 16, PadWithZeros), 0); - EXPECT_EQ(paddingSize(1, 16, PadWithZeros), 15); - EXPECT_EQ(paddingSize(8, 16, PadWithZeros), 8); - EXPECT_EQ(paddingSize(15, 16, PadWithZeros), 1); - EXPECT_EQ(paddingSize(16, 16, PadWithZeros), 0); - EXPECT_EQ(paddingSize(17, 16, PadWithZeros), 15); - EXPECT_EQ(paddingSize(24, 16, PadWithZeros), 8); - EXPECT_EQ(paddingSize(31, 16, PadWithZeros), 1); - EXPECT_EQ(paddingSize(32, 16, PadWithZeros), 0); - EXPECT_EQ(paddingSize(0, 16, PadWithPaddingSize), 16); - EXPECT_EQ(paddingSize(1, 16, PadWithPaddingSize), 15); - EXPECT_EQ(paddingSize(8, 16, PadWithPaddingSize), 8); - EXPECT_EQ(paddingSize(15, 16, PadWithPaddingSize), 1); - EXPECT_EQ(paddingSize(16, 16, PadWithPaddingSize), 16); - EXPECT_EQ(paddingSize(17, 16, PadWithPaddingSize), 15); - EXPECT_EQ(paddingSize(24, 16, PadWithPaddingSize), 8); - EXPECT_EQ(paddingSize(31, 16, PadWithPaddingSize), 1); - EXPECT_EQ(paddingSize(32, 16, PadWithPaddingSize), 16); + EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModeZero), 0); + EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModeZero), 15); + EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModeZero), 8); + EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModeZero), 1); + EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModeZero), 0); + EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModeZero), 15); + EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModeZero), 8); + EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModeZero), 1); + EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModeZero), 0); + EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModePKCS7), 16); + EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModePKCS7), 15); + EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModePKCS7), 8); + EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModePKCS7), 1); + EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModePKCS7), 16); + EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModePKCS7), 15); + EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModePKCS7), 8); + EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModePKCS7), 1); + EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModePKCS7), 16); } TEST(Encrypt, AESCBCEncrypt) { @@ -53,13 +55,13 @@ TEST(Encrypt, AESCBCEncryptWithPadding) { const Data message = TW::data("secret message"); { Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - Data encrypted = AESCBCEncrypt(key, message, iv, PadWithPaddingSize); + Data encrypted = AESCBCEncrypt(key, message, iv, TWAESPaddingModePKCS7); assertHexEqual(encrypted, "7f896315e90e172bed65d005138f224d"); } { // with no padding Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - Data encrypted = AESCBCEncrypt(key, message, iv, PadWithZeros); + Data encrypted = AESCBCEncrypt(key, message, iv, TWAESPaddingModeZero); assertHexEqual(encrypted, "11bcbfebb2db19fb5a5cbf458e0f699e"); } } @@ -77,7 +79,7 @@ TEST(Encrypt, AESCBCDecryptWithPadding) { { const Data encryptedPadded = parse_hex("7f896315e90e172bed65d005138f224d"); Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - Data encrypted = AESCBCDecrypt(key, encryptedPadded, iv, PadWithPaddingSize); + Data encrypted = AESCBCDecrypt(key, encryptedPadded, iv, TWAESPaddingModePKCS7); assertHexEqual(encrypted, hex(TW::data("secret message")).c_str()); } { diff --git a/tests/interface/TWAESTests.cpp b/tests/interface/TWAESTests.cpp index 5616d41dd1c..8f1229ae5ca 100644 --- a/tests/interface/TWAESTests.cpp +++ b/tests/interface/TWAESTests.cpp @@ -11,28 +11,45 @@ #include auto key = DATA("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); +auto key2 = DATA("bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96"); -TEST(TWAES, CBCEncrypt) { +TEST(TWAES, CBCEncryptZeroPadding) { auto iv = DATA("000102030405060708090A0B0C0D0E0F"); auto data = DATA("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = WRAPD(TWAESCBCEncrypt(key.get(), data.get(), iv.get())); + auto encryptResult = WRAPD(TWAESEncryptCBC(key.get(), data.get(), iv.get(), TWAESPaddingModeZero)); assertHexEqual(encryptResult, "f58c4c04d6e5f1ba779eabfb5f7bfbd6"); } -TEST(TWAES, CBCDecrypt) { +TEST(TWAES, CBCDecryptZeroPadding) { auto iv = DATA("000102030405060708090A0B0C0D0E0F"); auto cipher = DATA("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - auto decryptResult = WRAPD(TWAESCBCDecrypt(key.get(), cipher.get(), iv.get())); + auto decryptResult = WRAPD(TWAESDecryptCBC(key.get(), cipher.get(), iv.get(), TWAESPaddingModeZero)); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } +TEST(TWAES, CBCEncryptPKCS7Padding) { + auto iv = DATA("37f8687086d31852979e228f4a97925b"); + auto data = DATA("7b226a736f6e727063223a22322e30222c226964223a313535343334333833343735323434362c226572726f72223a7b22636f6465223a2d33323030302c226d657373616765223a2253657373696f6e2052656a6563746564227d7d"); + + auto encryptResult = WRAPD(TWAESEncryptCBC(key2.get(), data.get(), iv.get(), TWAESPaddingModePKCS7)); + assertHexEqual(encryptResult, "23c75d1b3228742ddb12eeef5a5016e37a8980a77fabc6dd01e6a355d88851c611d37e0d17a2f9c30f659da6d42ba77aca9b84bd6a95e3924f47d9093fbf16e0fb55b165ec193489645b4f7d2573959305c8fa70f88fe5affc43e3084a5878d1"); +} + +TEST(TWAES, CBCDecryptPKCS7Padding) { + auto iv = DATA("debb62725b21c7577e4e498e10f096c7"); + auto cipher = DATA("e7df9810ce66defcc03023ee945f5958c1d4697bf97945daeab5059c2bc6262642cbca82982ac690e77e16671770c200f348f743a7c6e5df5c74eb892ef9b45a9b5ddf0f08fa60c49e5b694688d1b0b521b43975e65b4e8d557a83f4d1aab0af"); + + auto decryptResult = WRAPD(TWAESDecryptCBC(key2.get(), cipher.get(), iv.get(), TWAESPaddingModePKCS7)); + assertHexEqual(decryptResult, "7b226a736f6e727063223a22322e30222c226964223a313535343334333833343735323434362c226572726f72223a7b22636f6465223a2d33323030302c226d657373616765223a2253657373696f6e2052656a6563746564227d7d"); +} + TEST(TWAES, CTREncrypt) { auto iv = DATA("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto data = DATA("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = WRAPD(TWAESCTREncrypt(key.get(), data.get(), iv.get())); + auto encryptResult = WRAPD(TWAESEncryptCTR(key.get(), data.get(), iv.get())); assertHexEqual(encryptResult, "601ec313775789a5b7a7f504bbf3d228"); } @@ -40,6 +57,6 @@ TEST(TWAES, CTRDecrypt) { auto iv = DATA("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto cipher = DATA("601ec313775789a5b7a7f504bbf3d228"); - auto decryptResult = WRAPD(TWAESCTRDecrypt(key.get(), cipher.get(), iv.get())); + auto decryptResult = WRAPD(TWAESDecryptCTR(key.get(), cipher.get(), iv.get())); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } From 4ff8fad589313473a592faa8ec0a2a3c7a5550ea Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:09:14 +0800 Subject: [PATCH 55/81] Add swift playground (#1029) * Add swift playground * set render option --- swift/Playground.playground/Contents.swift | 22 +++++++++++++++++++ .../contents.xcplayground | 4 ++++ 2 files changed, 26 insertions(+) create mode 100644 swift/Playground.playground/Contents.swift create mode 100644 swift/Playground.playground/contents.xcplayground diff --git a/swift/Playground.playground/Contents.swift b/swift/Playground.playground/Contents.swift new file mode 100644 index 00000000000..832b3e213a6 --- /dev/null +++ b/swift/Playground.playground/Contents.swift @@ -0,0 +1,22 @@ +// Copyright © 2017-2020 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. + +import UIKit +import TrustWalletCore +import SwiftProtobuf + +enum PlaygroundError: Error { + case invalidHexString +} + +func reverseHex(string: String) throws -> String { + guard let data = Data(hexString: string) else { + throw PlaygroundError.invalidHexString + } + return Data(data.reversed()).hexString +} + +try reverseHex(string: "0xdeadbeef") diff --git a/swift/Playground.playground/contents.xcplayground b/swift/Playground.playground/contents.xcplayground new file mode 100644 index 00000000000..89da2d47069 --- /dev/null +++ b/swift/Playground.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From b39e7d79c6e0dcf95e3ec07a43ce84f0a7a9ce0b Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Tue, 7 Jul 2020 13:38:23 +0800 Subject: [PATCH 56/81] codegen typescript coin type and protobuf messages (#1024) * codegen coin type and protobuf messages * move protobufjs to dependencies * add check version script * map step outputs to job outputs * read current script dir * remove .npmrc * npm publish public * add npm version check script * add filter paths * update outputs --- .github/workflows/ts-ci.yml | 72 ++ README.md | 2 + coins.json | 4 +- tools/android-release.sh | 0 typescript/.gitignore | 2 + typescript/codegen/bin/codegen | 50 + typescript/codegen/templates/core_types.ejs | 23 + typescript/package.json | 45 + typescript/src/index.ts | 10 + typescript/tests/index.test.ts | 38 + typescript/tools/check-gpr-version | 17 + typescript/tools/check-npm-version | 13 + typescript/tsconfig.json | 20 + typescript/yarn.lock | 1227 +++++++++++++++++++ 14 files changed, 1521 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ts-ci.yml mode change 100644 => 100755 tools/android-release.sh create mode 100644 typescript/.gitignore create mode 100755 typescript/codegen/bin/codegen create mode 100644 typescript/codegen/templates/core_types.ejs create mode 100644 typescript/package.json create mode 100644 typescript/src/index.ts create mode 100644 typescript/tests/index.test.ts create mode 100755 typescript/tools/check-gpr-version create mode 100755 typescript/tools/check-npm-version create mode 100644 typescript/tsconfig.json create mode 100644 typescript/yarn.lock diff --git a/.github/workflows/ts-ci.yml b/.github/workflows/ts-ci.yml new file mode 100644 index 00000000000..92d56a486f4 --- /dev/null +++ b/.github/workflows/ts-ci.yml @@ -0,0 +1,72 @@ +name: Typescript CI + +on: + push: + branches: [ master ] + paths: + - typescript/** + pull_request: + branches: [ master ] + paths: + - typescript/** + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + outputs: + publish_npm: ${{ steps.version.outputs.publish_npm }} + publish_gpr: ${{ steps.version.outputs.publish_gpr }} + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install + working-directory: typescript + - name: Build and test + run: yarn build && yarn test + working-directory: typescript + - name: Check if needed to publish + id: version + run: | + echo "::set-output name=publish_npm::$(tools/check-npm-version)" + echo "::set-output name=publish_gpr::$(tools/check-gpr-version)" + working-directory: typescript + env: + TOKEN: ${{secrets.GITHUB_TOKEN}} + publish-npm: + needs: build + if: needs.build.outputs.publish_npm == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://registry.npmjs.org/ + - name: Publish + run: | + yarn install && yarn build && npm publish --access public + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-gpr: + needs: build + if: needs.build.outputs.publish_gpr == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://npm.pkg.github.com/ + - name: Publish + run: | + yarn install && yarn build && npm publish --access public + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/README.md b/README.md index 4bec5c85a50..73c09312521 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Trust Wallet Core is a cross-platform library that implements low-level cryptogr ![iOS CI](https://github.com/trustwallet/wallet-core/workflows/iOS%20CI/badge.svg) ![Android CI](https://github.com/trustwallet/wallet-core/workflows/Android%20CI/badge.svg) ![Linux CI](https://github.com/trustwallet/wallet-core/workflows/Linux%20CI/badge.svg) +![Docker CI](https://github.com/trustwallet/wallet-core/workflows/Docker%20CI/badge.svg) +![Typescript CI](https://github.com/trustwallet/wallet-core/workflows/Typescript%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/82e76f6ea4ba4f0d9029e8846c04c093)](https://www.codacy.com/app/hewigovens/wallet-core?utm_source=github.com&utm_medium=referral&utm_content=TrustWallet/wallet-core&utm_campaign=Badge_Grade) ![Codecov](https://codecov.io/gh/TrustWallet/wallet-core/branch/master/graph/badge.svg) diff --git a/coins.json b/coins.json index 233a381ff3f..fc8793b6695 100644 --- a/coins.json +++ b/coins.json @@ -906,10 +906,10 @@ "symbol": "IOTX", "decimals": 18, "blockchain": "IoTeX", - "hrp": "io", "derivationPath": "m/44'/304'/0'/0/0", "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "hrp": "io", "explorer": { "url": "https://iotexscan.io", "txPath": "/action/", @@ -1119,10 +1119,10 @@ "symbol": "ONE", "decimals": 18, "blockchain": "Harmony", - "hrp": "one", "derivationPath": "m/44'/1023'/0'/0/0", "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "hrp": "one", "explorer": { "url": "https://explorer.harmony.one", "txPath": "/#/tx/", diff --git a/tools/android-release.sh b/tools/android-release.sh old mode 100644 new mode 100755 diff --git a/typescript/.gitignore b/typescript/.gitignore new file mode 100644 index 00000000000..1ea6743a064 --- /dev/null +++ b/typescript/.gitignore @@ -0,0 +1,2 @@ +dist/ +src/generated/ diff --git a/typescript/codegen/bin/codegen b/typescript/codegen/bin/codegen new file mode 100755 index 00000000000..65b1df54e15 --- /dev/null +++ b/typescript/codegen/bin/codegen @@ -0,0 +1,50 @@ +#! /usr/bin/env node +const fs = require('fs').promises; +const path = require('path'); +const ejs = require('ejs'); +const prettier = require("prettier"); + +const main = async () => { + const coins = require('../../../coins.json') + coins.forEach((coin) => { + coin.slip44 = coin['derivationPath'].split('/')[2].replace('\'', ''); + }) + await generateCoinType(coins); +}; + +const generateCoinType = async (coins) => { + const methods = [ + { + name: 'id', + returnType: 'string', + body: (coin) => `return '${coin.id}'` + }, + { + name: 'decimals', + returnType: 'number', + body: (coin) => `return ${coin.decimals}` + }, + { + name: 'name', + returnType: 'string', + body: (coin) => `return '${coin.name}'` + }, + { + name: 'derivationPath', + returnType: 'string', + body: (coin) => `return "${coin.derivationPath}"` + }, + { + name: 'symbol', + returnType: 'string', + body: (coin) => `return '${coin.symbol}'` + } + ]; + + const template = await fs.readFile(path.resolve(__dirname, '../templates/core_types.ejs'), 'utf8'); + let data = await ejs.render(template, { coins, methods }); + data = await prettier.format(data, { parser: 'typescript', singleQuote: true, trailingComma: 'es5' }); + await fs.writeFile(path.resolve(__dirname, '../../src/generated/core_types.ts'), data); +}; + +main(); diff --git a/typescript/codegen/templates/core_types.ejs b/typescript/codegen/templates/core_types.ejs new file mode 100644 index 00000000000..4a4cd8a5cbe --- /dev/null +++ b/typescript/codegen/templates/core_types.ejs @@ -0,0 +1,23 @@ +// Copyright © 2017-2020 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. + +export enum CoinType { + <% coins.forEach((coin) => { -%> + <%-coin.id%> = <%-coin.slip44%>, + <% }) %> +} + +export namespace CoinType { + <% methods.forEach((method) => { -%> + export function <%-method.name%>(coin: CoinType): <%-method.returnType%> { + switch (coin) { + <% coins.forEach((coin) => { -%> + case CoinType.<%-coin.id%>: <%-method.body(coin)%>; + <% }) %> + } + } + <% }) %> +} diff --git a/typescript/package.json b/typescript/package.json new file mode 100644 index 00000000000..3cd8a2e6623 --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,45 @@ +{ + "name": "@trustwallet/wallet-core", + "version": "0.0.6", + "description": "wallet core types and protobuf messages", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "mocha -r ts-node/register tests/**/*.test.ts", + "generate": "yarn codegen:coin && yarn codegen:js && yarn codegen:ts", + "codegen:coin": "codegen/bin/codegen", + "codegen:js": "pbjs -t static-module ../src/proto/Ethereum.proto --no-delimited --force-long -o src/generated/core_proto.js", + "codegen:ts": "pbts -o src/generated/core_proto.d.ts src/generated/core_proto.js", + "clean": "rm -rf dist src/generated && mkdir -p dist/generated src/generated", + "build": "yarn clean && yarn generate && cp src/generated/core_proto.* dist/generated && tsc --skipLibCheck" + }, + "repository": { + "type": "git", + "url": "git://github.com/trustwallet/wallet-core.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/trustwallet/wallet-core/issues" + }, + "homepage": "https://github.com/trustwallet/wallet-core#readme", + "files": [ + "dist" + ], + "dependencies": { + "protobufjs": "^6.9.0" + }, + "devDependencies": { + "@types/chai": "^4.2.11", + "@types/mocha": "^7.0.2", + "buffer": "^5.6.0", + "chai": "^4.2.0", + "ejs": "^3.1.3", + "escodegen": "^1.14.3", + "jsdoc": "^3.6.4", + "mocha": "^8.0.1", + "prettier": "^2.0.5", + "ts-node": "^8.10.2", + "typescript": "^3.9.5" + } +} diff --git a/typescript/src/index.ts b/typescript/src/index.ts new file mode 100644 index 00000000000..7fc83ba9b2e --- /dev/null +++ b/typescript/src/index.ts @@ -0,0 +1,10 @@ +// Copyright © 2017-2020 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. + +import { CoinType } from './generated/core_types' +import { TW } from './generated/core_proto' + +export { TW, CoinType } diff --git a/typescript/tests/index.test.ts b/typescript/tests/index.test.ts new file mode 100644 index 00000000000..05a20b17d6b --- /dev/null +++ b/typescript/tests/index.test.ts @@ -0,0 +1,38 @@ +// Copyright © 2017-2020 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. + +import 'mocha' +import { expect } from 'chai' +import { Buffer } from 'buffer' +import { TW, CoinType } from '../dist' + +describe('Wallet Core types tests', () => { + + it('test CoinType.ethereum', () => { + const coin = CoinType.ethereum; + expect(coin).to.equal(60) + expect(CoinType.id(coin)).to.equal('ethereum') + expect(CoinType.name(coin)).to.equal('Ethereum') + expect(CoinType.symbol(coin)).to.equal('ETH') + expect(CoinType.decimals(coin)).to.equal(18) + expect(CoinType.derivationPath(coin)).to.equal(`m/44'/60'/0'/0/0`) + }) + + it('test Ethereum encoding SigningInput', () => { + const input = TW.Ethereum.Proto.SigningInput.create({ + toAddress: '0x3535353535353535353535353535353535353535', + chainId: Buffer.from('01', 'hex'), + nonce: Buffer.from('09', 'hex'), + gasPrice: Buffer.from('04a817c800', 'hex'), + gasLimit: Buffer.from('5208', 'hex'), + amount: Buffer.from('0de0b6b3a7640000', 'hex'), + privateKey: Buffer.from('4646464646464646464646464646464646464646464646464646464646464646', 'hex') + }); + + const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish() + expect(Buffer.from(encoded).toString('hex')).to.equal("0a01011201091a0504a817c800220252082a2a30783335333533353335333533353335333533353335333533353335333533353335333533353335333532080de0b6b3a764000042204646464646464646464646464646464646464646464646464646464646464646") + }) +}) diff --git a/typescript/tools/check-gpr-version b/typescript/tools/check-gpr-version new file mode 100755 index 00000000000..80f83b2e888 --- /dev/null +++ b/typescript/tools/check-gpr-version @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +VERSION=$(cat "$DIR/../package.json" | jq '.version') +GHR_KEY_PATH='.data.repository.packages.edges[0].node.latestVersion.version' +GHR_VERSION=$(curl -X "POST" "https://api.github.com/graphql" \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Content-Type: application/json; charset=utf-8' \ + -d $'{"query": "query { repository(owner: \\"trustwallet\\", name:\\"wallet-core\\") { name packages(names: \\"wallet-core\\", first: 1) { edges { node { name latestVersion { version summary } } } } } } "}' \ + | jq $GHR_KEY_PATH) + +if [[ $VERSION != $GHR_VERSION ]]; then + echo true +else + echo false +fi diff --git a/typescript/tools/check-npm-version b/typescript/tools/check-npm-version new file mode 100755 index 00000000000..bea97300bf1 --- /dev/null +++ b/typescript/tools/check-npm-version @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +VERSION=$(cat "$DIR/../package.json" | jq '.version') +NPM_VERSION=\"$(npm view @trustwallet/wallet-core version)\" + +if [[ $VERSION != $NPM_VERSION ]]; then + echo true +else + echo false +fi diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json new file mode 100644 index 00000000000..808eaf5fc74 --- /dev/null +++ b/typescript/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "strict": true, + "typeRoots": [ + "./node_modules/@types" + ], + "types": [ + "node" + ], + "noImplicitAny": false + }, + "exclude": [ + "node_modules", + "./tests/**/*.ts" + ], +} diff --git a/typescript/yarn.lock b/typescript/yarn.lock new file mode 100644 index 00000000000..d928ddc9736 --- /dev/null +++ b/typescript/yarn.lock @@ -0,0 +1,1227 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/parser@^7.9.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64" + integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + +"@types/chai@^4.2.11": + version "4.2.11" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" + integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== + +"@types/long@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + +"@types/mocha@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" + integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== + +"@types/node@^13.7.0": + version "13.13.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.12.tgz#9c72e865380a7dc99999ea0ef20fc9635b503d20" + integrity sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw== + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array.prototype.map@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.2.tgz#9a4159f416458a23e9483078de1106b2ef68f8ec" + integrity sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.4" + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +binary-extensions@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +catharsis@^0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.11.tgz#d0eb3d2b82b7da7a3ce2efb1a7b00becc6643468" + integrity sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g== + dependencies: + lodash "^4.17.14" + +chai@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" + integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.0" + type-detect "^4.0.5" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +chokidar@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" + optionalDependencies: + fsevents "~2.1.2" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +diff@4.0.2, diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +ejs@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.3.tgz#514d967a8894084d18d3d47bd169a1c0560f093d" + integrity sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg== + dependencies: + jake "^10.6.1" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +entities@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-get-iterator@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" + integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== + dependencies: + es-abstract "^1.17.4" + has-symbols "^1.0.1" + is-arguments "^1.0.4" + is-map "^2.0.1" + is-set "^2.0.1" + is-string "^1.0.5" + isarray "^2.0.5" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +filelist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb" + integrity sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ== + dependencies: + minimatch "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +flat@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" + integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + dependencies: + is-buffer "~2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + +glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.9: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" + integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" + integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== + dependencies: + has-symbols "^1.0.1" + +is-set@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" + integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== + +is-string@^1.0.4, is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +iterate-iterator@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" + integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw== + +iterate-value@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" + integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== + dependencies: + es-get-iterator "^1.0.2" + iterate-iterator "^1.0.1" + +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +js-yaml@3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js2xmlparser@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.1.tgz#670ef71bc5661f089cc90481b99a05a1227ae3bd" + integrity sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw== + dependencies: + xmlcreate "^2.0.3" + +jsdoc@^3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.4.tgz#246b2832a0ea8b37a441b61745509cfe29e174b6" + integrity sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA== + dependencies: + "@babel/parser" "^7.9.4" + bluebird "^3.7.2" + catharsis "^0.8.11" + escape-string-regexp "^2.0.0" + js2xmlparser "^4.0.1" + klaw "^3.0.0" + markdown-it "^10.0.0" + markdown-it-anchor "^5.2.7" + marked "^0.8.2" + mkdirp "^1.0.4" + requizzle "^0.2.3" + strip-json-comments "^3.1.0" + taffydb "2.6.2" + underscore "~1.10.2" + +klaw@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" + integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== + dependencies: + graceful-fs "^4.1.9" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +linkify-it@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" + integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== + dependencies: + uc.micro "^1.0.1" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash@^4.17.14, lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +markdown-it-anchor@^5.2.7: + version "5.3.0" + resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz#d549acd64856a8ecd1bea58365ef385effbac744" + integrity sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA== + +markdown-it@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" + integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== + dependencies: + argparse "^1.0.7" + entities "~2.0.0" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +marked@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355" + integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +minimatch@3.0.4, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mocha@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.0.1.tgz#fe01f0530362df271aa8f99510447bc38b88d8ed" + integrity sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.3.1" + debug "3.2.6" + diff "4.0.2" + escape-string-regexp "1.0.5" + find-up "4.1.0" + glob "7.1.6" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" + minimatch "3.0.4" + ms "2.1.2" + object.assign "4.1.0" + promise.allsettled "1.0.2" + serialize-javascript "3.0.0" + strip-json-comments "3.0.1" + supports-color "7.1.0" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.0.0" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" + +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-inspect@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@4.1.0, object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +pathval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + +picomatch@^2.0.4, picomatch@^2.0.7: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + +promise.allsettled@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.2.tgz#d66f78fbb600e83e863d893e98b3d4376a9c47c9" + integrity sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg== + dependencies: + array.prototype.map "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + iterate-value "^1.0.0" + +protobufjs@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.9.0.tgz#c08b2bf636682598e6fabbf0edb0b1256ff090bd" + integrity sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" "^13.7.0" + long "^4.0.0" + +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== + dependencies: + picomatch "^2.0.7" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requizzle@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded" + integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ== + dependencies: + lodash "^4.17.14" + +serialize-javascript@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.0.0.tgz#492e489a2d77b7b804ad391a5f5d97870952548e" + integrity sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-json-comments@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + +strip-json-comments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== + +supports-color@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +taffydb@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" + integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-node@^8.10.2: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +typescript@^3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +underscore@~1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" + integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workerpool@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.0.tgz#85aad67fa1a2c8ef9386a1b43539900f61d03d58" + integrity sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xmlcreate@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.3.tgz#df9ecd518fd3890ab3548e1b811d040614993497" + integrity sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + +yargs@13.3.2, yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From 88b6e3c065ebb2965c2d45ac470958d9a863f650 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Fri, 10 Jul 2020 11:52:37 +0200 Subject: [PATCH 57/81] Refresh README (#1033) * Refresh README. * Add crypto.com to projects. Co-authored-by: Catenocrypt --- README.md | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 73c09312521..e6b5abe78c3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -Trust Wallet Core is a cross-platform library that implements low-level cryptographic wallet functionality for all supported blockchains. Most of the code is C++ with a set of strict exported C interfaces. The library provides idiomatic interfaces for all supported languages (currently Swift for iOS and Java for Android). +Trust Wallet Core is an open source, cross-platform, mobile-focused library +implementing low-level cryptographic wallet functionality for a high number of blockchains. +It is a core part of the popular [Trust Wallet](https://trustwallet.com), and some other projects. +Most of the code is C++ with a set of strict C interfaces, and idiomatic interfaces for supported languages: +Swift for iOS and Java for Android. ![iOS CI](https://github.com/trustwallet/wallet-core/workflows/iOS%20CI/badge.svg) ![Android CI](https://github.com/trustwallet/wallet-core/workflows/Android%20CI/badge.svg) @@ -15,29 +19,19 @@ Trust Wallet Core is a cross-platform library that implements low-level cryptogr ![Cocoapods](https://img.shields.io/cocoapods/v/TrustWalletCore.svg) ![Cocoapods platforms](https://img.shields.io/cocoapods/p/TrustWalletCore.svg) -## Documentation +# Documentation -For more complete documentation, see [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core). +For comprehensive documentation, see [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core). -## Supported Blockchains +# Supported Blockchains -We support Bitcoin, Ethereum, Binance Chain and 50+ blockchains, you can see the full list [here](docs/coins.md). +Wallet Core supports more than **50** blockchains: Bitcoin, Ethereum, Binance Chain, and most major blockchain platforms. +The full list is [here](docs/coins.md). -## Building +# Building For build instructions, see [developer.trustwallet.com/wallet-core/building](https://developer.trustwallet.com/wallet-core/building). -## WalletConsole Utility - -Our project comes with an interactive command-line utility called _WalletConsole_, for accessing key and address management functionality of the library. It can be started using: - -``` -$ ./build/walletconsole/walletconsole -Type 'help' for list of commands. -> help -``` - -Further details: [developer.trustwallet.com/wallet-core/walletconsole](https://developer.trustwallet.com/wallet-core/walletconsole). # Using from your project @@ -49,10 +43,10 @@ Add this dependency to build.gradle: ```groovy dependencies { - implementation 'com.trustwallet:wallet-core:x.x.x' + implementation 'com.trustwallet:wallet-core:x.y.z' } ``` -[Replace with version](https://github.com/trustwallet/wallet-core/releases) +Replace x.y.z with a [fresh version](https://github.com/trustwallet/wallet-core/releases) ## iOS @@ -62,13 +56,18 @@ We currently support only CocoaPods. Add this line to your Podfile and run `pod pod 'TrustWalletCore' ``` -## Add your project below +# Projects + +Projects using Trust Wallet Core. Add yours too! + +[Trust Wallet](https://trustwallet.com) + +[Coinpaprika](https://coinpaprika.com/) +| [IFWallet](https://www.ifwallet.com/) +| [crypto.com](https://crypto.com) +| [Alice](https://www.alicedapp.com/) +| [FrontierWallet](https://frontierwallet.com/) -- [Trust Wallet](https://trustwallet.com) -- [coinpaprika](https://coinpaprika.com/) -- [IFWallet](https://www.ifwallet.com/) -- [Alice](https://www.alicedapp.com/) -- [FrontierWallet](https://frontierwallet.com/) # Contributing From a89990dd383feb814a15510201846d0c2a9e7d92 Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Sat, 18 Jul 2020 11:33:05 +0200 Subject: [PATCH 58/81] HashTypeForCoin: fix return type to uint32 (#1042) * HashTypeForCoin: fix return type to uint32, invalid enum values did not go through Swift/Kotlin interfaces. * replace BitcoinSigHashType in tests --- .../app/blockchains/bitcoin/TestBitcoinSigning.kt | 4 ++-- include/TrustWalletCore/TWBitcoinScript.h | 2 +- src/Bitcoin/SigHashType.h | 6 +++--- src/interface/TWBitcoinScript.cpp | 2 +- swift/Tests/Blockchains/BitcoinTests.swift | 10 ++++++++-- swift/Tests/Blockchains/BitconCashTests.swift | 2 +- swift/Tests/Blockchains/DecredTests.swift | 2 +- .../GroestlcoinTransactionSignerTests.swift | 6 +++--- swift/Tests/Blockchains/LitecoinTests.swift | 2 +- swift/Tests/Blockchains/ZcashTests.swift | 2 +- tests/Bitcoin/TWBitcoinScriptTests.cpp | 10 +++++----- tests/Bitcoin/TWBitcoinSigningTests.cpp | 14 +++++++------- 12 files changed, 34 insertions(+), 28 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt index 2e94deba595..531d8c20303 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt @@ -23,7 +23,7 @@ class TestBitcoinSigning { fun testSignP2WPKH() { val input = Bitcoin.SigningInput.newBuilder() .setAmount(335_790_000) - .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN).value()) + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN)) .setToAddress("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx") .setChangeAddress("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU") .setByteFee(1) @@ -87,7 +87,7 @@ class TestBitcoinSigning { fun testSignP2PKH() { val input = Bitcoin.SigningInput.newBuilder() .setAmount(55_000) - .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN).value()) + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOIN)) .setToAddress("1GDCMHsTLBkawQXP8dqcZtr8zGgb4XpCug") .setChangeAddress("1CSR6tXqngr1CfwVF23V4bQotttJmzXqpf") .setByteFee(10) diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index 32e766a4438..baa1be7eef5 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -117,6 +117,6 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_ // Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. TW_EXPORT_STATIC_METHOD -enum TWBitcoinSigHashType TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType); +uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType); TW_EXTERN_C_END diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h index 79ae828b120..b41c1bf88dd 100644 --- a/src/Bitcoin/SigHashType.h +++ b/src/Bitcoin/SigHashType.h @@ -16,13 +16,13 @@ namespace TW::Bitcoin { static const uint8_t SigHashMask = 0x1f; // Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. -inline enum TWBitcoinSigHashType hashTypeForCoin(enum TWCoinType coinType) { +inline uint32_t hashTypeForCoin(enum TWCoinType coinType) { // set fork hash type for BCH switch (coinType) { case TWCoinTypeBitcoinCash: - return (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeFork); + return (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork; case TWCoinTypeBitcoinGold: - return (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeForkBTG); + return (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG; default: return TWBitcoinSigHashTypeAll; } diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index 3e8e2241b44..6aff55689f2 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -148,6 +148,6 @@ struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_ return new TWBitcoinScript{ .impl = script }; } -enum TWBitcoinSigHashType TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { +uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType) { return TW::Bitcoin::hashTypeForCoin(coinType); } diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index d599cdc72e4..6ba7dceb8d8 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -15,7 +15,7 @@ class BitcoinTransactionSignerTests: XCTestCase { func testSignP2WSH() throws { // set up input var input = BitcoinSigningInput.with { - $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin).rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) $0.amount = 1000 $0.byteFee = 1 $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" @@ -107,7 +107,7 @@ class BitcoinTransactionSignerTests: XCTestCase { let scriptHash = lockScript.matchPayToScriptHash()! let input = BitcoinSigningInput.with { $0.toAddress = "3NqULUrjZ7NL36YtBGsSVzqr5q1x9CJWwu" - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) $0.coinType = CoinType.bitcoin.rawValue $0.scripts = [ scriptHash.hexString: BitcoinScript.buildPayToWitnessPubkeyHash(hash: pubkey.bitcoinKeyHash).data @@ -121,4 +121,10 @@ class BitcoinTransactionSignerTests: XCTestCase { // https://blockchair.com/bitcoin/transaction/da2a9ce5d71ff7490bc9025e2888ca109b68ec0bd0e7d26195e1783305c00117 XCTAssertEqual(output.encoded.hexString, "01000000000101a3c50b159e3c0e2b1ed0b1a1d95438f76700726d06a41a36eaa4d4c661485f8b00000000171600140a3cca78017f46ac23e463148adb7231aef81956ffffffff01ccab00000000000017a914e7f40472c54fc93078c5129568cf95c27be3b2c287024830450221008dc29a5430facd4078ad93e72517d87b298d7a73b55d2828acab040ccf713ed5022063a13e348655fa7cdcfff084380611629babf165607b529bcc35bf6ddfab1f8101210370386469db8302c3092955724f56bcca9a36f31df82655aa79be46b08744cd1200000000") } + + func testHashTypeForCoin() { + XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoin), TWBitcoinSigHashTypeAll.rawValue) + XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoinCash), 0x41) + XCTAssertEqual(BitcoinScript.hashTypeForCoin(coinType: .bitcoinGold), 0x4f41) + } } diff --git a/swift/Tests/Blockchains/BitconCashTests.swift b/swift/Tests/Blockchains/BitconCashTests.swift index d9110c3aa0b..64b873d3262 100644 --- a/swift/Tests/Blockchains/BitconCashTests.swift +++ b/swift/Tests/Blockchains/BitconCashTests.swift @@ -65,7 +65,7 @@ class BitcoinCashTests: XCTestCase { } let input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue | BitcoinSigHashType.fork.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoinCash) $0.amount = 600 $0.byteFee = 1 $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index 3c73672f7ce..e4bd63e71c4 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -45,7 +45,7 @@ class DecredTests: XCTestCase { } let input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .decred) $0.amount = amount $0.byteFee = 1 $0.toAddress = "Dsesp1V6DZDEtcq2behmBVKdYqKMdkh96hL" diff --git a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift index 6d3cd150e3b..0eb8606931c 100644 --- a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift @@ -14,7 +14,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2WPKH() throws { var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 2500 $0.byteFee = 1 $0.toAddress = "31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P" @@ -67,7 +67,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2PKH() throws { var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 2500 $0.byteFee = 1 $0.toAddress = "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne" @@ -118,7 +118,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2SH_P2WPKH() throws { var input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 5000 $0.byteFee = 1 $0.toAddress = "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM" diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index e8bd938667f..2c84c7acd79 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -108,7 +108,7 @@ class LitecoinTests: XCTestCase { var input = BitcoinSigningInput.with { $0.toAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00" $0.changeAddress = address - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .litecoin) $0.amount = 1200000 $0.coinType = CoinType.litecoin.rawValue $0.byteFee = 1 diff --git a/swift/Tests/Blockchains/ZcashTests.swift b/swift/Tests/Blockchains/ZcashTests.swift index 4ceb069c4c2..d016477f133 100644 --- a/swift/Tests/Blockchains/ZcashTests.swift +++ b/swift/Tests/Blockchains/ZcashTests.swift @@ -51,7 +51,7 @@ class ZcashTests: XCTestCase { } ] let input = BitcoinSigningInput.with { - $0.hashType = BitcoinSigHashType.all.rawValue + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .zcash) $0.amount = 488000 $0.toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS" $0.coinType = CoinType.zcash.rawValue diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/Bitcoin/TWBitcoinScriptTests.cpp index 39cfd28c8d9..6c6f44f4a45 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/Bitcoin/TWBitcoinScriptTests.cpp @@ -204,11 +204,11 @@ TEST(TWBitcoinScript, LockScriptForCashAddress) { } TEST(TWBitcoinSigHashType, HashTypeForCoin) { - EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoin), TWBitcoinSigHashTypeAll); - EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeLitecoin), TWBitcoinSigHashTypeAll); - EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeZcash), TWBitcoinSigHashTypeAll); - EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinCash), (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeFork)); - EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinGold), (enum TWBitcoinSigHashType)((int)TWBitcoinSigHashTypeAll | (int)TWBitcoinSigHashTypeForkBTG)); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoin), (uint32_t)TWBitcoinSigHashTypeAll); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeLitecoin), (uint32_t)TWBitcoinSigHashTypeAll); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeZcash), (uint32_t)TWBitcoinSigHashTypeAll); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinCash), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeFork); + EXPECT_EQ(TWBitcoinScriptHashTypeForCoin(TWCoinTypeBitcoinGold), (uint32_t)TWBitcoinSigHashTypeAll | (uint32_t)TWBitcoinSigHashTypeForkBTG); } TEST(TWBitcoinSigHashType, IsSingle) { diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index 91017c961ab..1ed3bff6931 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -391,7 +391,7 @@ TEST(BitcoinSigning, EncodeP2WSH) { ); } -Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false, bool omitKeys = false) { +Proto::SigningInput buildInputP2WSH(uint32_t hashType, bool omitScript = false, bool omitKeys = false) { Proto::SigningInput input; input.set_hash_type(hashType); input.set_amount(1000); @@ -462,7 +462,7 @@ TEST(BitcoinSigning, SignP2WSH) { TEST(BitcoinSigning, SignP2WSH_HashNone) { // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeNone); + const auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeNone); { // test plan (but do not reuse plan result) @@ -497,7 +497,7 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { TEST(BitcoinSigning, SignP2WSH_HashSingle) { // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeSingle); + const auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeSingle); { // test plan (but do not reuse plan result) @@ -532,7 +532,7 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAnyoneCanPay); + const auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAnyoneCanPay); { // test plan (but do not reuse plan result) @@ -567,7 +567,7 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { } TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, true); + const auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll, true); { // test plan (but do not reuse plan result) @@ -583,7 +583,7 @@ TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { } TEST(BitcoinSigning, SignP2WSH_NegativeMissingKeys) { - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, false, true); + const auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll, false, true); { // test plan (but do not reuse plan result). Plan works even with missing keys. @@ -600,7 +600,7 @@ TEST(BitcoinSigning, SignP2WSH_NegativeMissingKeys) { TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { // Setup input - auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + auto input = buildInputP2WSH((uint32_t)TWBitcoinSigHashTypeAll); auto plan = Bitcoin::TransactionPlan(); input.mutable_plan()->clear_utxos(); From 6c4ef5ca9cf150f49a180e01338da3e56acd0eaf Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 18 Jul 2020 11:36:21 +0200 Subject: [PATCH 59/81] Add Filecoin message changes (#1043) * Add Filecoin message changes * add version field * switch gasLimit type from BigInt to int64_t * Fix iOS test --- src/Filecoin/Entry.cpp | 8 +++-- src/Filecoin/Entry.h | 13 +++++--- src/Filecoin/Transaction.cpp | 18 ++++++++--- src/Filecoin/Transaction.h | 2 +- swift/Tests/Blockchains/FilecoinTests.swift | 2 +- tests/Filecoin/AddressTests.cpp | 2 ++ tests/Filecoin/SignerTests.cpp | 10 +++--- tests/Filecoin/TWAnySignerTests.cpp | 35 ++++++++++++--------- tests/Filecoin/TWCoinTypeTests.cpp | 8 +++-- tests/Filecoin/TransactionTests.cpp | 6 ++-- 10 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/Filecoin/Entry.cpp b/src/Filecoin/Entry.cpp index ef98fcbdb87..39db5385ded 100644 --- a/src/Filecoin/Entry.cpp +++ b/src/Filecoin/Entry.cpp @@ -14,11 +14,13 @@ using namespace std; // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, + const char*) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, + const char*) const { return Address(publicKey).string(); } @@ -26,6 +28,6 @@ void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) con signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } diff --git a/src/Filecoin/Entry.h b/src/Filecoin/Entry.h index 8259953005e..74435732123 100644 --- a/src/Filecoin/Entry.h +++ b/src/Filecoin/Entry.h @@ -11,12 +11,15 @@ namespace TW::Filecoin { /// Entry point for implementation of Filecoin coin. -/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { -public: +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific +/// includes in this file +class Entry : public CoinEntry { + public: virtual std::vector coinTypes() const { return {TWCoinTypeFilecoin}; } - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; + virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, + TW::byte p2sh, const char* hrp) const; + virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, + const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual bool supportsJSONSigning() const { return true; } virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; diff --git a/src/Filecoin/Transaction.cpp b/src/Filecoin/Transaction.cpp index 6168a36da0c..b3f18267920 100644 --- a/src/Filecoin/Transaction.cpp +++ b/src/Filecoin/Transaction.cpp @@ -37,11 +37,19 @@ const Data cidPrefix = { }; Cbor::Encode Transaction::message() const { - return Cbor::Encode::array( - {Cbor::Encode::bytes(to.bytes), Cbor::Encode::bytes(from.bytes), Cbor::Encode::uint(nonce), - Cbor::Encode::bytes(encodeVaruint(value)), Cbor::Encode::bytes(encodeVaruint(gasPrice)), - Cbor::Encode::bytes(encodeVaruint(gasLimit)), Cbor::Encode::uint(0), - Cbor::Encode::bytes(Data())}); + Cbor::Encode cborGasLimit = gasLimit >= 0 ? Cbor::Encode::uint((uint64_t)gasLimit) + : Cbor::Encode::negInt((uint64_t)(-gasLimit - 1)); + return Cbor::Encode::array({ + Cbor::Encode::uint(0), // version + Cbor::Encode::bytes(to.bytes), // to address + Cbor::Encode::bytes(from.bytes), // from address + Cbor::Encode::uint(nonce), // nonce + Cbor::Encode::bytes(encodeVaruint(value)), // value + Cbor::Encode::bytes(encodeVaruint(gasPrice)), // gas price + cborGasLimit, // gas limit + Cbor::Encode::uint(0), // abi.MethodNum (0 => send) + Cbor::Encode::bytes(Data()) // data (empty) + }); } Data Transaction::cid() const { diff --git a/src/Filecoin/Transaction.h b/src/Filecoin/Transaction.h index 8885816ff1e..a2e73a64f66 100644 --- a/src/Filecoin/Transaction.h +++ b/src/Filecoin/Transaction.h @@ -26,7 +26,7 @@ class Transaction { uint256_t value; // Miner fee uint256_t gasPrice; - uint256_t gasLimit; + int64_t gasLimit; // Transaction type; 0 for simple transfers uint64_t method; // Transaction data; empty for simple transfers diff --git a/swift/Tests/Blockchains/FilecoinTests.swift b/swift/Tests/Blockchains/FilecoinTests.swift index 2fdad85dac3..023099a50e1 100644 --- a/swift/Tests/Blockchains/FilecoinTests.swift +++ b/swift/Tests/Blockchains/FilecoinTests.swift @@ -29,7 +29,7 @@ class FilecoinTests: XCTestCase { let output: FilecoinSigningOutput = AnySigner.sign(input: input, coin: .filecoin) - XCTAssertEqual(output.encoded.hexString, "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac351052600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60befa485903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01") + XCTAssertEqual(output.encoded.hexString, "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922176cc900") } } diff --git a/tests/Filecoin/AddressTests.cpp b/tests/Filecoin/AddressTests.cpp index 2eb28ebd9ee..db657eda357 100644 --- a/tests/Filecoin/AddressTests.cpp +++ b/tests/Filecoin/AddressTests.cpp @@ -13,6 +13,8 @@ using namespace TW; using namespace TW::Filecoin; +// clang-format off + struct address_test { std::string encoded; const char* hex; diff --git a/tests/Filecoin/SignerTests.cpp b/tests/Filecoin/SignerTests.cpp index b20ee5b0d92..30886b6fc47 100644 --- a/tests/Filecoin/SignerTests.cpp +++ b/tests/Filecoin/SignerTests.cpp @@ -36,11 +36,11 @@ TEST(FilecoinSigner, Sign) { Data signature = Signer::sign(privateKey, tx); - ASSERT_EQ( - hex(tx.serialize(signature)), - "828855018ac93840e869c06b79b748a3c504b696bb339a7f5501cf01bf485f61435e6770b52615bf455e043a23" - "6101430017704200024200c80040584201477bcf9a71c9e19af77fbd92677230a9612d927877e439eb4aa30643" - "0a4ffae42251194820df211d00075e88e68c9704a880448532ec0092045a1e00e8f1cbb900"); + ASSERT_EQ(hex(tx.serialize(signature)), + "82890055018ac93840e869c06b79b748a3c504b696bb339a7f5501cf01bf485f" + "61435e6770b52615bf455e043a2361014300177042000218c80040584201c95e" + "32222984251892b06b7da692e38003d6dd146da5f8a0bd67590099fa4a0937e0" + "c075310a35a85c000b598e07920090cbc768231219a11e3ef7e9bed11da000"); } } // namespace TW::Filecoin diff --git a/tests/Filecoin/TWAnySignerTests.cpp b/tests/Filecoin/TWAnySignerTests.cpp index 96594f3ea1d..bd063aebc51 100644 --- a/tests/Filecoin/TWAnySignerTests.cpp +++ b/tests/Filecoin/TWAnySignerTests.cpp @@ -42,33 +42,40 @@ TEST(TWAnySignerFilecoin, Sign) { auto outputData = TWAnySignerSign(inputData, TWCoinTypeFilecoin); ASSERT_EQ(hex(TWDataBytes(outputData), TWDataBytes(outputData) + TWDataSize(outputData)), - "0aa4018288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99b" - "b13c577fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac" - "351052600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60" - "befa485903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); + "0aa401" + "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de8" + "6bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e" + "6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040" + "584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa" + "1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922" + "176cc900"); Proto::SigningOutput output; output.ParseFromArray(TWDataBytes(outputData), static_cast(TWDataSize(outputData))); ASSERT_EQ(hex(output.encoded()), - "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c57" - "7fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac351052" - "600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60befa48" - "5903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); + "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de8" + "6bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e" + "6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040" + "584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa" + "1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922" + "176cc900"); TWDataDelete(inputData); TWDataDelete(outputData); } TEST(TWAnySignerFilecoin, SignJSON) { - auto json = STRING(R"({"toAddress":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","nonce":"2","value":"IIasNRBSYAAA","gasPrice":"Ag==","gasLimit":"1000"})"); + auto json = STRING( + R"({"toAddress":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","nonce":"2","value":"IIasNRBSYAAA","gasPrice":"Ag==","gasLimit":"1000"})"); auto key = DATA("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeFilecoin)); ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeFilecoin)); - assertStringsEqual(result, - "8288583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de86bb1d096861ba99bb13c57" - "7fee003e72f51e89f837c45501cf01bf485f61435e6770b52615bf455e043a2361024a002086ac351052" - "600000420002430003e800405842014e896d4fa72a1f39c37a4415629dfbb7ea301b7f001fff60befa48" - "5903f51d824659ba19c5bc969d7206de7afabfc4a0eec2dd34f15e58a064adf4ee9f72e64f01"); + assertStringsEqual(result, "828900583103a33d476e13eb8bde5d21becf2b86dd60642f0297cc6a5b914de8" + "6bb1d096861ba99bb13c577fee003e72f51e89f837c45501cf01bf485f61435e" + "6770b52615bf455e043a2361024a002086ac3510526000004200021903e80040" + "584201d7320c388135b20dea596493156495519db9e3cfcb78ccb18462dde4aa" + "1c507750a79ef014caf717acbe2ce562e5e55a4c990607c32a741f1c073fa922" + "176cc900"); } diff --git a/tests/Filecoin/TWCoinTypeTests.cpp b/tests/Filecoin/TWCoinTypeTests.cpp index c56a4abcf51..750d5d7f199 100644 --- a/tests/Filecoin/TWCoinTypeTests.cpp +++ b/tests/Filecoin/TWCoinTypeTests.cpp @@ -27,8 +27,12 @@ TEST(TWFilecoinCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFilecoin)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFilecoin)); assertStringsEqual(symbol, "FIL"); - assertStringsEqual(txUrl, "https://filscan.io/#/message/detail?cid=bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe"); - assertStringsEqual(accUrl, "https://filscan.io/#/address/detail?address=t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i"); + assertStringsEqual(txUrl, + "https://filscan.io/#/message/" + "detail?cid=bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe"); + assertStringsEqual( + accUrl, + "https://filscan.io/#/address/detail?address=t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i"); assertStringsEqual(id, "filecoin"); assertStringsEqual(name, "Filecoin"); } diff --git a/tests/Filecoin/TransactionTests.cpp b/tests/Filecoin/TransactionTests.cpp index 08224f2a4b7..2c6d42957d4 100644 --- a/tests/Filecoin/TransactionTests.cpp +++ b/tests/Filecoin/TransactionTests.cpp @@ -27,10 +27,10 @@ TEST(FilecoinTransaction, Serialize) { /*gasLimit*/ 50); ASSERT_EQ(hex(tx.message().encoded()), - "8855013d403ac3911e9f806228326fa68619d36a4641d455013d413d4c3fe3d89f99495a48c6046224a7" - "1f0cd71b0000001234567890430003e84200014200320040"); + "890055013d403ac3911e9f806228326fa68619d36a4641d455013d413d4c3fe3" + "d89f99495a48c6046224a71f0cd71b0000001234567890430003e842000118320040"); ASSERT_EQ(hex(tx.cid()), - "0171a0e4022020421f7d6207c9575803d5d96387c399acbf4bdac3026196cb4d423bc8966bb1"); + "0171a0e40220bebd2d8facba996f5e04e7c64e1b0f088c63078263a4587b838ed3573419d743"); } } // namespace TW::Filecoin From ea5787031274783ac5e57ee2ada81eca609bee02 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Sat, 18 Jul 2020 20:40:18 +0800 Subject: [PATCH 60/81] Fix Xcode 12 warnings (#1040) * Fix xcode12 build warnings * include angled bracket * bump SwiftProtobuf to latest * warn quoted include --- codegen/lib/templates/hrp.h.erb | 3 ++- include/TrustWalletCore/TWAES.h | 6 +++--- include/TrustWalletCore/TWAESPaddingMode.h | 2 +- include/TrustWalletCore/TWAccount.h | 6 +++--- include/TrustWalletCore/TWAnyAddress.h | 8 ++++---- include/TrustWalletCore/TWAnySigner.h | 8 ++++---- include/TrustWalletCore/TWBase58.h | 6 +++--- include/TrustWalletCore/TWBitcoinAddress.h | 6 +++--- include/TrustWalletCore/TWBitcoinScript.h | 10 +++++----- include/TrustWalletCore/TWBitcoinSigHashType.h | 2 +- include/TrustWalletCore/TWBlockchain.h | 2 +- include/TrustWalletCore/TWCoinType.h | 16 ++++++++-------- .../TrustWalletCore/TWCoinTypeConfiguration.h | 7 ++++--- include/TrustWalletCore/TWCurve.h | 3 ++- include/TrustWalletCore/TWData.h | 2 +- include/TrustWalletCore/TWEthereumAbiEncoder.h | 6 +++--- .../TrustWalletCore/TWEthereumAbiFunction.h | 6 +++--- .../TWEthereumAbiValueDecoder.h | 6 +++--- .../TWEthereumAbiValueEncoder.h | 6 +++--- include/TrustWalletCore/TWEthereumChainID.h | 3 ++- include/TrustWalletCore/TWFIOAccount.h | 4 ++-- include/TrustWalletCore/TWGroestlcoinAddress.h | 6 +++--- include/TrustWalletCore/TWHDVersion.h | 3 ++- include/TrustWalletCore/TWHDWallet.h | 18 +++++++++--------- include/TrustWalletCore/TWHash.h | 4 ++-- include/TrustWalletCore/TWPrivateKey.h | 8 ++++---- include/TrustWalletCore/TWPublicKey.h | 8 ++++---- include/TrustWalletCore/TWPublicKeyType.h | 3 ++- include/TrustWalletCore/TWPurpose.h | 3 ++- include/TrustWalletCore/TWRippleXAddress.h | 8 ++++---- include/TrustWalletCore/TWSS58AddressType.h | 3 ++- include/TrustWalletCore/TWSegwitAddress.h | 8 ++++---- include/TrustWalletCore/TWStellarMemoType.h | 2 +- include/TrustWalletCore/TWStellarPassphrase.h | 3 ++- include/TrustWalletCore/TWStellarVersionByte.h | 2 +- include/TrustWalletCore/TWStoredKey.h | 12 ++++++------ include/TrustWalletCore/TWString.h | 2 +- src/Base58.cpp | 1 + src/Bitcoin/Script.h | 1 + swift/Podfile | 7 +++++++ swift/Podfile.lock | 10 +++++----- swift/Sources/DerivationPath.swift | 2 +- swift/project.yml | 1 + tools/ios-build-framework | 4 ++-- 44 files changed, 128 insertions(+), 109 deletions(-) diff --git a/codegen/lib/templates/hrp.h.erb b/codegen/lib/templates/hrp.h.erb index 30d399689bd..0974e63cd00 100644 --- a/codegen/lib/templates/hrp.h.erb +++ b/codegen/lib/templates/hrp.h.erb @@ -8,7 +8,8 @@ // #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h index 510d2f32291..bdaae85cbfb 100644 --- a/include/TrustWalletCore/TWAES.h +++ b/include/TrustWalletCore/TWAES.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWAESPaddingMode.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWAESPaddingMode.h b/include/TrustWalletCore/TWAESPaddingMode.h index 9e4713d0ed6..dc5647376ce 100644 --- a/include/TrustWalletCore/TWAESPaddingMode.h +++ b/include/TrustWalletCore/TWAESPaddingMode.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWAccount.h b/include/TrustWalletCore/TWAccount.h index c48d6451ba9..d9f377a7c45 100644 --- a/include/TrustWalletCore/TWAccount.h +++ b/include/TrustWalletCore/TWAccount.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWString.h" -#include "TWCoinType.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWAnyAddress.h b/include/TrustWalletCore/TWAnyAddress.h index 3771b80e762..ace62af5ded 100644 --- a/include/TrustWalletCore/TWAnyAddress.h +++ b/include/TrustWalletCore/TWAnyAddress.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWAnySigner.h b/include/TrustWalletCore/TWAnySigner.h index 2a2ca6be4a3..85514447f7f 100644 --- a/include/TrustWalletCore/TWAnySigner.h +++ b/include/TrustWalletCore/TWAnySigner.h @@ -5,10 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBase58.h b/include/TrustWalletCore/TWBase58.h index 5c6ca64ddac..392c73bf53b 100644 --- a/include/TrustWalletCore/TWBase58.h +++ b/include/TrustWalletCore/TWBase58.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBitcoinAddress.h b/include/TrustWalletCore/TWBitcoinAddress.h index b3af8e29880..0bca579c033 100644 --- a/include/TrustWalletCore/TWBitcoinAddress.h +++ b/include/TrustWalletCore/TWBitcoinAddress.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index baa1be7eef5..3cb75a728bf 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -6,11 +6,11 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWPublicKey.h" -#include "TWCoinType.h" -#include "TWBitcoinSigHashType.h" +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBitcoinSigHashType.h b/include/TrustWalletCore/TWBitcoinSigHashType.h index 33fba1992d2..74fa785d5f7 100644 --- a/include/TrustWalletCore/TWBitcoinSigHashType.h +++ b/include/TrustWalletCore/TWBitcoinSigHashType.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 6a174d8af7c..1710fe5be40 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index c9e45360b74..82339d8e605 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -6,14 +6,14 @@ #pragma once -#include "TWBase.h" -#include "TWBlockchain.h" -#include "TWCurve.h" -#include "TWHDVersion.h" -#include "TWHRP.h" -#include "TWPrivateKey.h" -#include "TWPurpose.h" -#include "TWString.h" +#include +#include +#include +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWCoinTypeConfiguration.h b/include/TrustWalletCore/TWCoinTypeConfiguration.h index 34d93a68835..884e7b59fd4 100644 --- a/include/TrustWalletCore/TWCoinTypeConfiguration.h +++ b/include/TrustWalletCore/TWCoinTypeConfiguration.h @@ -5,9 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWString.h" + +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWCurve.h b/include/TrustWalletCore/TWCurve.h index a23a1c7985d..39ce70ea43b 100644 --- a/include/TrustWalletCore/TWCurve.h +++ b/include/TrustWalletCore/TWCurve.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWData.h b/include/TrustWalletCore/TWData.h index 6ebe32aff78..16d638bd0d4 100644 --- a/include/TrustWalletCore/TWData.h +++ b/include/TrustWalletCore/TWData.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWEthereumAbiEncoder.h b/include/TrustWalletCore/TWEthereumAbiEncoder.h index c59ee109813..75e9f3826a1 100644 --- a/include/TrustWalletCore/TWEthereumAbiEncoder.h +++ b/include/TrustWalletCore/TWEthereumAbiEncoder.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWString.h" -#include "TWData.h" +#include +#include +#include // Wrapper class for Ethereum ABI encoding & decoding. Also builder for Function objects. // See also TWEthereumAbiFunction. diff --git a/include/TrustWalletCore/TWEthereumAbiFunction.h b/include/TrustWalletCore/TWEthereumAbiFunction.h index 2d8b43f1f4d..ce930bcce4a 100644 --- a/include/TrustWalletCore/TWEthereumAbiFunction.h +++ b/include/TrustWalletCore/TWEthereumAbiFunction.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWString.h" -#include "TWData.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWEthereumAbiValueDecoder.h b/include/TrustWalletCore/TWEthereumAbiValueDecoder.h index 445a3177ad0..148989a089d 100644 --- a/include/TrustWalletCore/TWEthereumAbiValueDecoder.h +++ b/include/TrustWalletCore/TWEthereumAbiValueDecoder.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWEthereumAbiValueEncoder.h b/include/TrustWalletCore/TWEthereumAbiValueEncoder.h index 0fcc93e91e8..4b18a71a848 100644 --- a/include/TrustWalletCore/TWEthereumAbiValueEncoder.h +++ b/include/TrustWalletCore/TWEthereumAbiValueEncoder.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWEthereumChainID.h b/include/TrustWalletCore/TWEthereumChainID.h index 67bebf95499..573d4c2b413 100644 --- a/include/TrustWalletCore/TWEthereumChainID.h +++ b/include/TrustWalletCore/TWEthereumChainID.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWFIOAccount.h b/include/TrustWalletCore/TWFIOAccount.h index 8012cbf0faf..a3fb9257503 100644 --- a/include/TrustWalletCore/TWFIOAccount.h +++ b/include/TrustWalletCore/TWFIOAccount.h @@ -6,8 +6,8 @@ #pragma once -#include "TWBase.h" -#include "TWString.h" +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWGroestlcoinAddress.h b/include/TrustWalletCore/TWGroestlcoinAddress.h index a32b05dc102..b116f157318 100644 --- a/include/TrustWalletCore/TWGroestlcoinAddress.h +++ b/include/TrustWalletCore/TWGroestlcoinAddress.h @@ -6,9 +6,9 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWHDVersion.h b/include/TrustWalletCore/TWHDVersion.h index 1b47fe14449..383d0636471 100644 --- a/include/TrustWalletCore/TWHDVersion.h +++ b/include/TrustWalletCore/TWHDVersion.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index bdf27f3fba7..ab6ae80be94 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -6,15 +6,15 @@ #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWCurve.h" -#include "TWData.h" -#include "TWHDVersion.h" -#include "TWPrivateKey.h" -#include "TWPublicKey.h" -#include "TWPurpose.h" -#include "TWString.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWHash.h b/include/TrustWalletCore/TWHash.h index 280dc5f9b7f..9f7e3fd1fe9 100644 --- a/include/TrustWalletCore/TWHash.h +++ b/include/TrustWalletCore/TWHash.h @@ -6,8 +6,8 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index 38d41b5ae04..f89844fb034 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWCurve.h" -#include "TWData.h" -#include "TWPublicKey.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPublicKey.h b/include/TrustWalletCore/TWPublicKey.h index 220672d2a1e..29b64ffa206 100644 --- a/include/TrustWalletCore/TWPublicKey.h +++ b/include/TrustWalletCore/TWPublicKey.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWPublicKeyType.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPublicKeyType.h b/include/TrustWalletCore/TWPublicKeyType.h index 0a3bc5c38ce..174a6c3c8a3 100644 --- a/include/TrustWalletCore/TWPublicKeyType.h +++ b/include/TrustWalletCore/TWPublicKeyType.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWPurpose.h b/include/TrustWalletCore/TWPurpose.h index 3227efd89e3..c7dc5ccdb83 100644 --- a/include/TrustWalletCore/TWPurpose.h +++ b/include/TrustWalletCore/TWPurpose.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWRippleXAddress.h b/include/TrustWalletCore/TWRippleXAddress.h index 975d7476250..d475c4b024b 100644 --- a/include/TrustWalletCore/TWRippleXAddress.h +++ b/include/TrustWalletCore/TWRippleXAddress.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWHRP.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWSS58AddressType.h b/include/TrustWalletCore/TWSS58AddressType.h index b76f3c212fe..2ad30d717c1 100644 --- a/include/TrustWalletCore/TWSS58AddressType.h +++ b/include/TrustWalletCore/TWSS58AddressType.h @@ -6,7 +6,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWSegwitAddress.h b/include/TrustWalletCore/TWSegwitAddress.h index e77d2f20996..4133c0a08b0 100644 --- a/include/TrustWalletCore/TWSegwitAddress.h +++ b/include/TrustWalletCore/TWSegwitAddress.h @@ -6,10 +6,10 @@ #pragma once -#include "TWBase.h" -#include "TWData.h" -#include "TWHRP.h" -#include "TWString.h" +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStellarMemoType.h b/include/TrustWalletCore/TWStellarMemoType.h index 45f9c629206..f5abda479ca 100644 --- a/include/TrustWalletCore/TWStellarMemoType.h +++ b/include/TrustWalletCore/TWStellarMemoType.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStellarPassphrase.h b/include/TrustWalletCore/TWStellarPassphrase.h index aca15cdeff3..f6a8ea33214 100644 --- a/include/TrustWalletCore/TWStellarPassphrase.h +++ b/include/TrustWalletCore/TWStellarPassphrase.h @@ -5,7 +5,8 @@ // file LICENSE at the root of the source code distribution tree. #pragma once -#include "TWBase.h" + +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStellarVersionByte.h b/include/TrustWalletCore/TWStellarVersionByte.h index b694c80b6bb..defd6e105b5 100644 --- a/include/TrustWalletCore/TWStellarVersionByte.h +++ b/include/TrustWalletCore/TWStellarVersionByte.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index bb2048d236f..be2cc91ff52 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -6,12 +6,12 @@ #pragma once -#include "TWBase.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWHDWallet.h" -#include "TWPrivateKey.h" -#include "TWString.h" +#include +#include +#include +#include +#include +#include TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWString.h b/include/TrustWalletCore/TWString.h index 2495be45941..dea9441d488 100644 --- a/include/TrustWalletCore/TWString.h +++ b/include/TrustWalletCore/TWString.h @@ -6,7 +6,7 @@ #pragma once -#include "TWBase.h" +#include TW_EXTERN_C_BEGIN diff --git a/src/Base58.cpp b/src/Base58.cpp index 207809114c3..c0e430f50a0 100644 --- a/src/Base58.cpp +++ b/src/Base58.cpp @@ -11,6 +11,7 @@ #include #include +#include using namespace TW; diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index e40c4b733bc..06db939ccce 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -13,6 +13,7 @@ #include #include +#include namespace TW::Bitcoin { diff --git a/swift/Podfile b/swift/Podfile index 8c3a5145bf2..5d60e21b200 100644 --- a/swift/Podfile +++ b/swift/Podfile @@ -11,6 +11,13 @@ target 'TrustWalletCore' do end post_install do |installer| + # Set default deployment target + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + end + end + installer.pods_project.build_configurations.each do |config| config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' end diff --git a/swift/Podfile.lock b/swift/Podfile.lock index ae81306f6a7..c4c6353731c 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -1,6 +1,6 @@ PODS: - - SwiftLint (0.32.0) - - SwiftProtobuf (1.7.0) + - SwiftLint (0.39.2) + - SwiftProtobuf (1.10.2) DEPENDENCIES: - SwiftLint @@ -12,9 +12,9 @@ SPEC REPOS: - SwiftProtobuf SPEC CHECKSUMS: - SwiftLint: 009a898ef2a1c851f45e1b59349bf6ff2ddc990d - SwiftProtobuf: 4fd9645e69b72cbae6ec8da5be0cdd20ca6565dd + SwiftLint: 22ccbbe3b8008684be5955693bab135e0ed6a447 + SwiftProtobuf: bec1ae7d686ff73dd09d79866154f4970b891410 -PODFILE CHECKSUM: 4382612b240ed6d49e9a3b9bbb107c581e72b8ea +PODFILE CHECKSUM: 3a51502ee71f1c313b60afcec49ad1dc8ed279b7 COCOAPODS: 1.9.3 diff --git a/swift/Sources/DerivationPath.swift b/swift/Sources/DerivationPath.swift index 16da7631ee6..bf2f468e8e9 100644 --- a/swift/Sources/DerivationPath.swift +++ b/swift/Sources/DerivationPath.swift @@ -8,7 +8,7 @@ import Foundation /// Represents a hierarchical determinisic derivation path. public struct DerivationPath: Codable, Hashable, CustomStringConvertible { - let indexCount = 5 + var indexCount = 5 /// List of indices in the derivation path. public private(set) var indices = [Index]() diff --git a/swift/project.yml b/swift/project.yml index 120ca294056..0b3f9ec67e9 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -47,6 +47,7 @@ targets: BUILD_LIBRARY_FOR_DISTRIBUTION: true INFOPLIST_FILE: 'Info.plist' CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: true OTHER_CFLAGS: $(inherited) -Wno-comma postCompileScripts: - script: ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml diff --git a/tools/ios-build-framework b/tools/ios-build-framework index e0c3f66babc..811c1b2d8b3 100755 --- a/tools/ios-build-framework +++ b/tools/ios-build-framework @@ -34,7 +34,7 @@ function buildDevices() { function buildSimulators() { echo -e "\nBuilding for iOS Simulator..." - build "platform=iOS Simulator,name=iPhone 11,OS=13.3" "ios-sim" + build "platform=iOS Simulator,name=iPhone 11" "ios-sim" } function buildCatalyst() { @@ -66,8 +66,8 @@ function buildXCFramework() { function main() { init - buildDevices buildSimulators + buildDevices buildCatalyst buildMac buildXCFramework From eddc642f5cb0f98046ee6cfdb553093b3f7a1b8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2020 13:44:17 +0200 Subject: [PATCH 61/81] Bump lodash from 4.17.15 to 4.17.19 in /typescript (#1045) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- typescript/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typescript/yarn.lock b/typescript/yarn.lock index d928ddc9736..605b4f9222d 100644 --- a/typescript/yarn.lock +++ b/typescript/yarn.lock @@ -732,9 +732,9 @@ locate-path@^5.0.0: p-locate "^4.1.0" lodash@^4.17.14, lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@3.0.0: version "3.0.0" From 461a011258c8a7b964ec3077be787c12ad3a7b7f Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Fri, 24 Jul 2020 23:18:36 +0200 Subject: [PATCH 62/81] Eth ABI enocde/decode fix (ParamArray) (#1048) * Correct getSize() of ParamArray, add more checks. * Add encode/decode tests with arrays, with dynamic types. * Fix for Array decode (for dynamic types). * Minor formatting. Co-authored-by: Catenocrypt --- src/Ethereum/ABI/Array.cpp | 55 +++++++++--------------- src/Ethereum/ABI/Array.h | 2 +- src/Ethereum/ABI/Parameters.cpp | 9 +++- src/Ethereum/ABI/Parameters.h | 4 +- tests/Ethereum/AbiTests.cpp | 75 +++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 40 deletions(-) diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp index c23686be3de..3f9f6376ec5 100644 --- a/src/Ethereum/ABI/Array.cpp +++ b/src/Ethereum/ABI/Array.cpp @@ -27,58 +27,41 @@ std::string ParamArray::getFirstType() const { return _params.getParamUnsafe(0)->getType(); } +size_t ParamArray::getSize() const +{ + return 32 + _params.getSize(); +} + void ParamArray::encode(Data& data) const { size_t n = _params.getCount(); ValueEncoder::encodeUInt256(uint256_t(n), data); - - size_t headSize = 0; - for (auto i = 0; i < n; ++i) { - auto p = _params.getParamUnsafe(i); - if (p->isDynamic()) { - headSize += 32; - } else { - headSize += p->getSize(); - } - } - - size_t dynamicOffset = 0; - for (auto i = 0; i < n; ++i) { - auto p = _params.getParamUnsafe(i); - if (p->isDynamic()) { - ValueEncoder::encodeUInt256(uint256_t(headSize + dynamicOffset), data); - dynamicOffset += p->getSize(); - } else { - p->encode(data); - } - } - - for (auto i = 0; i < n; ++i) { - auto p = _params.getParamUnsafe(i); - if (p->isDynamic()) { - p->encode(data); - } - } + _params.encode(data); } bool ParamArray::decode(const Data& encoded, size_t& offset_inout) { size_t origOffset = offset_inout; // read length uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { return false; } + if (!ABI::decode(encoded, len256, offset_inout)) { + return false; + } // check if length is in the size_t range size_t len = static_cast(len256); - if (len256 != static_cast(len)) { return false; } - // read values + if (len256 != static_cast(len)) { + return false; + } + // check number of values auto n = _params.getCount(); if (n != len) { // Element number mismatch: the proto has to have exact same number of values as in the encoded form // Note: this could be handles in a smarter way, and create more elements as needed return false; } - for (auto i = 0; i < n; ++i) { - if (!_params.getParamUnsafe(i)->decode(encoded, offset_inout)) { return false; } - } + + // read values + auto res = _params.decode(encoded, offset_inout); + // padding offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return true; -} \ No newline at end of file + return res; +} diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h index 362b10ff402..4adc96a05ad 100644 --- a/src/Ethereum/ABI/Array.h +++ b/src/Ethereum/ABI/Array.h @@ -29,7 +29,7 @@ class ParamArray: public ParamCollection std::string getFirstType() const; std::shared_ptr getParam(int paramIndex) { return _params.getParamUnsafe(paramIndex); } virtual std::string getType() const { return getFirstType() + "[]"; } - virtual size_t getSize() const { return _params.getSize(); } + virtual size_t getSize() const; virtual bool isDynamic() const { return true; } virtual size_t getCount() const { return _params.getCount(); } virtual void encode(Data& data) const; diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp index 7591e9fc39a..7372b34832e 100644 --- a/src/Ethereum/ABI/Parameters.cpp +++ b/src/Ethereum/ABI/Parameters.cpp @@ -62,11 +62,16 @@ std::string ParamSet::getType() const { } size_t ParamSet::getSize() const { + // 2-pass encoding size_t s = 0; - for(auto p: _params) { + for (auto p: _params) { + if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { + // offset used + s += 32; + } s += p->getSize(); } - return 32 + ValueEncoder::paddedTo32(s); + return ValueEncoder::paddedTo32(s); } size_t ParamSet::getHeadSize() const { diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h index c6622ee90f6..5bac50ea89f 100644 --- a/src/Ethereum/ABI/Parameters.h +++ b/src/Ethereum/ABI/Parameters.h @@ -36,9 +36,11 @@ class ParamSet { /// Return the function type signature, of the form "baz(int32,uint256)" std::string getType() const; size_t getSize() const; - size_t getHeadSize() const; virtual void encode(Data& data) const; virtual bool decode(const Data& encoded, size_t& offset_inout); + +private: + size_t getHeadSize() const; }; /// Collection of different parameters, dynamic length, "(,,...)". diff --git a/tests/Ethereum/AbiTests.cpp b/tests/Ethereum/AbiTests.cpp index 280425e57c0..5cccbfd9f91 100644 --- a/tests/Ethereum/AbiTests.cpp +++ b/tests/Ethereum/AbiTests.cpp @@ -361,6 +361,8 @@ TEST(EthereumAbi, ParamString) { "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" "48656c6c6f20576f726c64210000000000000000000000000000000000000000", hex(encoded)); + EXPECT_EQ(3 * 32, encoded.size()); + EXPECT_EQ(3 * 32, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); EXPECT_EQ(helloStr, param.getVal()); @@ -417,10 +419,14 @@ TEST(EthereumAbi, ParamByteArray) { auto param = ParamByteArray(data10); Data encoded; param.encode(encoded); + EXPECT_EQ(2 * 32, encoded.size()); + EXPECT_EQ(2 * 32, param.getSize()); EXPECT_EQ( "000000000000000000000000000000000000000000000000000000000000000a" "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); + EXPECT_EQ(2 * 32, encoded.size()); + EXPECT_EQ(2 * 32, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); EXPECT_EQ(data10, param.getVal()); @@ -441,6 +447,8 @@ TEST(EthereumAbi, ParamByteArrayFix) { auto param = ParamByteArrayFix(10, data10); Data encoded; param.encode(encoded); + EXPECT_EQ(32, encoded.size()); + EXPECT_EQ(32, param.getSize()); EXPECT_EQ( "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); @@ -472,12 +480,16 @@ TEST(EthereumAbi, ParamArrayByte) { param.addParam(std::make_shared(51)); Data encoded; param.encode(encoded); + EXPECT_EQ(4 * 32, encoded.size()); + EXPECT_EQ(4 * 32, param.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000003" "0000000000000000000000000000000000000000000000000000000000000031" "0000000000000000000000000000000000000000000000000000000000000032" "0000000000000000000000000000000000000000000000000000000000000033", hex(encoded)); + EXPECT_EQ(4 * 32, encoded.size()); + EXPECT_EQ(4 * 32, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); EXPECT_EQ(3, param.getVal().size()); @@ -504,11 +516,15 @@ TEST(EthereumAbi, ParamArrayAddress) { param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); Data encoded; param.encode(encoded); + EXPECT_EQ(3 * 32, encoded.size()); + EXPECT_EQ(3 * 32, param.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000002" "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3", hex(encoded)); + EXPECT_EQ(3 * 32, encoded.size()); + EXPECT_EQ(3 * 32, param.getSize()); size_t offset = 0; EXPECT_TRUE(param.decode(encoded, offset)); EXPECT_EQ(2, param.getVal().size()); @@ -518,6 +534,19 @@ TEST(EthereumAbi, ParamArrayAddress) { } } +TEST(EthereumAbi, ParamArrayOfByteArray) { + auto param = ParamArray(); + param.addParam(std::make_shared(parse_hex("1011"))); + param.addParam(std::make_shared(parse_hex("102222"))); + param.addParam(std::make_shared(parse_hex("10333333"))); + EXPECT_EQ(3, param.getVal().size()); + + EXPECT_EQ("bytes[]", param.getType()); + EXPECT_TRUE(param.isDynamic()); + EXPECT_EQ((1 + 3 + 3 * 2) * 32, param.getSize()); + EXPECT_EQ(3, param.getCount()); +} + ///// Direct encode & decode TEST(EthereumAbi, EncodeVectorByte10) { @@ -538,6 +567,28 @@ TEST(EthereumAbi, EncodeVectorByte) { "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); } +TEST(EthereumAbi, EncodeArrayByte) { + auto p = ParamArray(std::vector>{ + std::make_shared(parse_hex("1011")), + std::make_shared(parse_hex("102222")) + }); + EXPECT_EQ("bytes[]", p.getType()); + Data encoded; + p.encode(encoded); + EXPECT_EQ( + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000080" + "0000000000000000000000000000000000000000000000000000000000000002" + "1011000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000003" + "1022220000000000000000000000000000000000000000000000000000000000", + hex(encoded) + ); + EXPECT_EQ((1 + 2 + 2 * 2) * 32, encoded.size()); + EXPECT_EQ((1 + 2 + 2 * 2) * 32, p.getSize()); +} + TEST(EthereumAbi, DecodeUInt) { Data encoded = parse_hex("000000000000000000000000000000000000000000000000000000000000002a"); size_t offset = 0; @@ -646,6 +697,28 @@ TEST(EthereumAbi, DecodeByteArray10) { EXPECT_EQ(32, offset); } +TEST(EthereumAbi, DecodeArrayOfByteArray) { + Data encoded = parse_hex( + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000080" + "0000000000000000000000000000000000000000000000000000000000000002" + "1011000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000003" + "1022200000000000000000000000000000000000000000000000000000000000" + ); + size_t offset = 0; + Data decoded; + auto param = ParamArray(); + param.addParam(std::make_shared(Data())); + param.addParam(std::make_shared(Data())); + bool res = param.decode(encoded, offset); + EXPECT_TRUE(res); + EXPECT_EQ(2, param.getCount()); + EXPECT_EQ(7 * 32, offset); + EXPECT_EQ(2, param.getVal().size()); +} + ///// Parameters encode & decode TEST(EthereumAbi, EncodeParamsSimple) { @@ -658,6 +731,7 @@ TEST(EthereumAbi, EncodeParamsSimple) { p.encode(encoded); EXPECT_EQ(3 * 32, encoded.size()); + EXPECT_EQ(3 * 32, p.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000010" "0000000000000000000000000000000000000000000000000000000000000011" @@ -682,6 +756,7 @@ TEST(EthereumAbi, EncodeParamsMixed) { p.encode(encoded); EXPECT_EQ(13 * 32, encoded.size()); + EXPECT_EQ(13 * 32, p.getSize()); EXPECT_EQ( "0000000000000000000000000000000000000000000000000000000000000045" "00000000000000000000000000000000000000000000000000000000000000a0" From c4401deccd3c6778de692a258eeb086e6960b3fb Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Sat, 25 Jul 2020 09:16:12 +0800 Subject: [PATCH 63/81] [Ethereum] Support decoding contract data / abi (#1044) * 1. simplify Ethereum ABI C interface 2. add abi json to support * Add ContractCall.h/cpp * Add ParamFactory and more tests * Add swift test * Add android / c interface test * bump build gradle to latest * handle uintN/intN/bytesN * Fix codacy warning and add int type test * Expand size check. * Correct getSize() of ParamArray, add more checks. * Add encode/decode tests with arrays, with dynamic types. * Fix for Array decode (for dynamic types). * Update multicall test json result --- .../blockchains/ethereum/TestEthereumAbi.kt | 42 +++++ .../ethereum/TestEthereumAbiDecoder.kt | 28 --- ...mAbiEncoder.kt => TestEthereumAbiValue.kt} | 30 +++- android/build.gradle | 2 +- include/TrustWalletCore/TWEthereumAbi.h | 34 ++++ .../TrustWalletCore/TWEthereumAbiEncoder.h | 41 ----- .../TrustWalletCore/TWEthereumAbiFunction.h | 88 ++++----- ...AbiValueEncoder.h => TWEthereumAbiValue.h} | 28 +-- .../TWEthereumAbiValueDecoder.h | 22 --- src/Ethereum/ABI.h | 1 + src/Ethereum/ABI/Array.cpp | 15 +- src/Ethereum/ABI/Bytes.cpp | 26 ++- src/Ethereum/ABI/ParamFactory.cpp | 86 +++++++++ src/Ethereum/ABI/ParamFactory.h | 25 +++ src/Ethereum/ABI/Parameters.cpp | 46 +++-- src/Ethereum/ContractCall.cpp | 110 ++++++++++++ src/Ethereum/ContractCall.h | 16 ++ ...hereumAbiEncoder.cpp => TWEthereumAbi.cpp} | 46 ++--- src/interface/TWEthereumAbiFunction.cpp | 2 +- ...alueEncoder.cpp => TWEthereumAbiValue.cpp} | 27 +-- src/interface/TWEthereumAbiValueDecoder.cpp | 19 -- swift/Tests/Blockchains/Data | 1 + .../Tests/Blockchains/EthereumAbiTests.swift | 32 +++- tests/Ethereum/AbiTests.cpp | 62 ++++++- tests/Ethereum/ContractCallTests.cpp | 169 ++++++++++++++++++ tests/Ethereum/Data/custom.json | 20 +++ tests/Ethereum/Data/ens.json | 53 ++++++ tests/Ethereum/Data/erc20.json | 50 ++++++ tests/Ethereum/Data/erc721.json | 78 ++++++++ tests/Ethereum/Data/kyber_proxy.json | 88 +++++++++ tests/Ethereum/Data/uniswap_router_v2.json | 168 +++++++++++++++++ ...ncoderTests.cpp => TWEthereumAbiTests.cpp} | 64 ++++--- .../TWEthereumAbiValueDecoderTests.cpp | 6 +- .../TWEthereumAbiValueEncodeTests.cpp | 40 ++--- 34 files changed, 1280 insertions(+), 285 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt delete mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt rename android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/{TestEthereumAbiEncoder.kt => TestEthereumAbiValue.kt} (73%) create mode 100644 include/TrustWalletCore/TWEthereumAbi.h delete mode 100644 include/TrustWalletCore/TWEthereumAbiEncoder.h rename include/TrustWalletCore/{TWEthereumAbiValueEncoder.h => TWEthereumAbiValue.h} (53%) delete mode 100644 include/TrustWalletCore/TWEthereumAbiValueDecoder.h create mode 100644 src/Ethereum/ABI/ParamFactory.cpp create mode 100644 src/Ethereum/ABI/ParamFactory.h create mode 100644 src/Ethereum/ContractCall.cpp create mode 100644 src/Ethereum/ContractCall.h rename src/interface/{TWEthereumAbiEncoder.cpp => TWEthereumAbi.cpp} (51%) rename src/interface/{TWEthereumAbiValueEncoder.cpp => TWEthereumAbiValue.cpp} (64%) delete mode 100644 src/interface/TWEthereumAbiValueDecoder.cpp create mode 120000 swift/Tests/Blockchains/Data create mode 100644 tests/Ethereum/ContractCallTests.cpp create mode 100644 tests/Ethereum/Data/custom.json create mode 100644 tests/Ethereum/Data/ens.json create mode 100644 tests/Ethereum/Data/erc20.json create mode 100644 tests/Ethereum/Data/erc721.json create mode 100644 tests/Ethereum/Data/kyber_proxy.json create mode 100644 tests/Ethereum/Data/uniswap_router_v2.json rename tests/Ethereum/{TWEthereumAbiEncoderTests.cpp => TWEthereumAbiTests.cpp} (83%) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt new file mode 100644 index 00000000000..5461b7e6ed0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt @@ -0,0 +1,42 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.EthereumAbi + +class TestEthereumAbiDecoder { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumAbiDecodeApprove() { + val call = "095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed0000000000000000000000000000000000000000000000000000000000000001".toHexByteArray() + val abi = """ + { + "095ea7b3": { + "constant": false, + "inputs": [{ + "name": "_spender", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + } + """.trimIndent() + + val decoded = EthereumAbi.decodeCall(call, abi) + val expected = """{"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"},{"name":"_value","type":"uint256","value":"1"}]}""" + + assertEquals(decoded, expected) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt deleted file mode 100644 index 23b5488ef0b..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiDecoder.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.trustwallet.core.app.blockchains.ethereum - -import com.trustwallet.core.app.utils.toHexByteArray -import org.junit.Assert.assertEquals -import org.junit.Test -import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.EthereumAbiValueDecoder - -class TestEthereumAbiDecoder { - - init { - System.loadLibrary("TrustWalletCore") - } - - @Test - fun testEthereumAbiDecoder() { - val expected = "1234567890987654321" - val inputs = listOf( - "112210f4b16c1cb1", - "000000000000000000000000000000000000000000000000112210f4b16c1cb1", - "000000000000000000000000000000000000000000000000112210f4b16c1cb10000000000000000000000000000000000000000000000000000000000000000" - ) - for (input in inputs) { - val data = Numeric.hexStringToByteArray(input) - assertEquals(expected, EthereumAbiValueDecoder.decodeUInt256(data)) - } - } -} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiEncoder.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt similarity index 73% rename from android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiEncoder.kt rename to android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt index 994229a6f73..aae6fc451ef 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiEncoder.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt @@ -5,8 +5,8 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.EthereumAbiFunction import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.EthereumAbiEncoder -import wallet.core.jni.EthereumAbiValueEncoder +import wallet.core.jni.EthereumAbi +import wallet.core.jni.EthereumAbiValue class TestEthereumAbiEncoder { @@ -16,12 +16,12 @@ class TestEthereumAbiEncoder { @Test fun testEthereumAbiEncoderFuncSimple1() { - val function = EthereumAbiEncoder.buildFunction("sam") + val function = EthereumAbiFunction("sam") assertEquals(0, function.addParamBool(true, false)) assertEquals("sam(bool)", function.type) - val encoded = EthereumAbiEncoder.encode(function) + val encoded = EthereumAbi.encode(function) assertEquals("0xa35856da" + "0000000000000000000000000000000000000000000000000000000000000001", Numeric.toHexString(encoded)); @@ -29,7 +29,7 @@ class TestEthereumAbiEncoder { @Test fun testEthereumAbiEncoderEncodeFuncCase1() { - val function = EthereumAbiEncoder.buildFunction("sam") + val function = EthereumAbiFunction("sam") // add params assertEquals(0, function.addParamBytes("0x64617665".toHexByteArray(), false)) assertEquals(1, function.addParamBool(true, false)) @@ -41,7 +41,7 @@ class TestEthereumAbiEncoder { // check signature assertEquals("sam(bytes,bool,uint256[])", function.type) // encode - val encoded = EthereumAbiEncoder.encode(function) + val encoded = EthereumAbi.encode(function) assertEquals("0xa5643bf2" + "0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000001" + @@ -57,7 +57,7 @@ class TestEthereumAbiEncoder { assertEquals(0, function.getParamUInt64(0, true)) // decode output val encodedOutput = "0000000000000000000000000000000000000000000000000000000000000045".toHexByteArray() - val decodeRes = EthereumAbiEncoder.decodeOutput(function, encodedOutput) + val decodeRes = EthereumAbi.decodeOutput(function, encodedOutput) assertEquals(true, decodeRes) // new output value assertEquals(0x45, function.getParamUInt64(0, true)) @@ -65,7 +65,21 @@ class TestEthereumAbiEncoder { @Test fun testEthereumAbiValueEncodeInt32() { - val data = EthereumAbiValueEncoder.encodeInt32(69) + val data = EthereumAbiValue.encodeInt32(69) assertEquals(Numeric.toHexString(data), "0x0000000000000000000000000000000000000000000000000000000000000045") } + + @Test + fun testEthereumAbiValueDecodeUInt256() { + val expected = "1234567890987654321" + val inputs = listOf( + "112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb1", + "000000000000000000000000000000000000000000000000112210f4b16c1cb10000000000000000000000000000000000000000000000000000000000000000" + ) + for (input in inputs) { + val data = Numeric.hexStringToByteArray(input) + assertEquals(expected, EthereumAbiValue.decodeUInt256(data)) + } + } } diff --git a/android/build.gradle b/android/build.gradle index 4f90d08d99d..f27ea5cf3ad 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5' diff --git a/include/TrustWalletCore/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h new file mode 100644 index 00000000000..47cd9d5370d --- /dev/null +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include +#include +#include + +// Wrapper class for Ethereum ABI encoding & decoding. + +TW_EXTERN_C_BEGIN + +struct TWEthereumAbiFunction; + +TW_EXPORT_CLASS +struct TWEthereumAbi; + +/// Encode function to Eth ABI binary +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull fn); + +/// Decode function output from Eth ABI binary, fill output parameters +TW_EXPORT_STATIC_METHOD +bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull encoded); + +/// Decode function call data to human readable json format, according to input abi json +TW_EXPORT_STATIC_METHOD +TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _Nonnull abi); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiEncoder.h b/include/TrustWalletCore/TWEthereumAbiEncoder.h deleted file mode 100644 index 75e9f3826a1..00000000000 --- a/include/TrustWalletCore/TWEthereumAbiEncoder.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2020 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. - -#pragma once - -#include -#include -#include - -// Wrapper class for Ethereum ABI encoding & decoding. Also builder for Function objects. -// See also TWEthereumAbiFunction. - -TW_EXTERN_C_BEGIN - -struct TWEthereumAbiFunction; - -TW_EXPORT_CLASS -struct TWEthereumAbiEncoder; - -/// Creates a function object, with the given name and empty parameter list. It must be deleted at the end. -/// Note: Create name is reserved for own-class creation in the codegen toolchain -TW_EXPORT_STATIC_METHOD -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiEncoderBuildFunction(TWString *_Nonnull name); - -/// Deletes a function object created with a 'TWEthereumAbiEncoderBuildFunction' method. -/// Note: func is a reserved keyword in codegenerated swift. -TW_EXPORT_STATIC_METHOD -void TWEthereumAbiEncoderDeleteFunction(struct TWEthereumAbiFunction *_Nonnull func_in); - -/// Encode function to Eth ABI binary -TW_EXPORT_STATIC_METHOD -TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnull func_in); - -/// Decode function output from Eth ABI binary, fill output parameters -TW_EXPORT_STATIC_METHOD -bool TWEthereumAbiEncoderDecodeOutput(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull encoded); - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiFunction.h b/include/TrustWalletCore/TWEthereumAbiFunction.h index ce930bcce4a..0f9466dbcb6 100644 --- a/include/TrustWalletCore/TWEthereumAbiFunction.h +++ b/include/TrustWalletCore/TWEthereumAbiFunction.h @@ -17,172 +17,172 @@ struct TWEthereumAbiFunction; /// Creates a function object, with the given name and empty parameter list. It must be deleted at the end. TW_EXPORT_STATIC_METHOD -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name); +struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name); /// Deletes a function object created with a 'TWEthereumAbiFunctionCreateWithString' method. TW_EXPORT_METHOD -void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull func_in); +void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull fn); /// Return the function type signature, of the form "baz(int32,uint256)" TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull func_in); +TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull fn); /// Methods for adding parameters of the given type (input or output). /// For output parameters (isOutput=true) a value has to be specified, although usually not needd. /// Returns the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, uint8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, uint8_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, uint16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, uint16_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, uint32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, uint32_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, uint64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, uint64_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int8_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int16_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int32_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int64_t val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, bool val, bool isOutput); +int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull fn, bool val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull func_in, TWString *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull fn, TWString *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, size_t count_in, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, size_t size, TWData *_Nonnull val, bool isOutput); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull func_in, bool isOutput); +int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull fn, bool isOutput); /// Methods for accessing the value of an output or input parameter, of different types. TW_EXPORT_METHOD -uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput); +TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); /// Methods for adding a parameter of the given type to a top-level input parameter array. Returns the index of the parameter (0-based). /// Note that nested ParamArrays are not possible through this API, could be done by using index paths like "1/0" TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint8_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint8_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint16_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint16_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint32_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint32_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint64_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint64_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int8_t val); +int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int8_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int16_t val); +int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int16_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int32_t val); +int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int32_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int64_t val); +int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int64_t val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, bool val); +int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, bool val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWString *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWString *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, size_t count_in, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, size_t size, TWData *_Nonnull val); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiValueEncoder.h b/include/TrustWalletCore/TWEthereumAbiValue.h similarity index 53% rename from include/TrustWalletCore/TWEthereumAbiValueEncoder.h rename to include/TrustWalletCore/TWEthereumAbiValue.h index 4b18a71a848..17cab074c09 100644 --- a/include/TrustWalletCore/TWEthereumAbiValueEncoder.h +++ b/include/TrustWalletCore/TWEthereumAbiValue.h @@ -13,43 +13,47 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS -struct TWEthereumAbiValueEncoder; +struct TWEthereumAbiValue; /// Returned data must be deleted (hint: use WRAPD() macro). -/// Encode a type according to EIP712, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise. -/// See: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md +/// Encode a type according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise. TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBool(bool value); +TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value); TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt32(int32_t value); +TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value); TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt32(uint32_t value); +TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value); /// Encode an int256. Input value is represented as a 32-byte value TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt256(TWData* _Nonnull value); +TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value); /// Encode an uint256. Input value is represented as a 32-byte binary value TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt256(TWData* _Nonnull value); +TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value); /// Encode the 20 bytes of an address TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeAddress(TWData* _Nonnull value); +TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value); /// Encode a string by encoding its hash TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeString(TWString* _Nonnull value); +TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value); /// Encode a number of bytes, up to 32 bytes, padded on the right. Longer arrays are truncated. TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytes(TWData* _Nonnull value); +TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value); /// Encode a dynamic number of bytes by encoding its hash TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytesDyn(TWData* _Nonnull value); +TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value); + + +/// Decodes input data (bytes longer than 32 will be truncated) as uint256 +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumAbiValueDecodeUInt256(TWData* _Nonnull input); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiValueDecoder.h b/include/TrustWalletCore/TWEthereumAbiValueDecoder.h deleted file mode 100644 index 148989a089d..00000000000 --- a/include/TrustWalletCore/TWEthereumAbiValueDecoder.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2017-2020 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. - -#pragma once - -#include -#include -#include - -TW_EXTERN_C_BEGIN - -TW_EXPORT_CLASS -struct TWEthereumAbiValueDecoder; - -/// Decodes input data (bytes longer than 32 will be truncated) as uint256 -TW_EXPORT_STATIC_METHOD -TWString* _Nonnull TWEthereumAbiValueDecoderDecodeUInt256(TWData* _Nonnull input); - -TW_EXTERN_C_END diff --git a/src/Ethereum/ABI.h b/src/Ethereum/ABI.h index 2217e9096ad..e00285c5a21 100644 --- a/src/Ethereum/ABI.h +++ b/src/Ethereum/ABI.h @@ -13,3 +13,4 @@ #include "ABI/Bytes.h" #include "ABI/ParamAddress.h" #include "ABI/Function.h" +#include "ABI/ParamFactory.h" diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp index 3f9f6376ec5..5357965f894 100644 --- a/src/Ethereum/ABI/Array.cpp +++ b/src/Ethereum/ABI/Array.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Array.h" +#include "ParamFactory.h" #include "ValueEncoder.h" #include @@ -47,16 +48,22 @@ bool ParamArray::decode(const Data& encoded, size_t& offset_inout) { } // check if length is in the size_t range size_t len = static_cast(len256); - if (len256 != static_cast(len)) { + if (len256 != uint256_t(len)) { return false; } // check number of values auto n = _params.getCount(); - if (n != len) { - // Element number mismatch: the proto has to have exact same number of values as in the encoded form - // Note: this could be handles in a smarter way, and create more elements as needed + if (n == 0 || n > len) { + // Encoded length is less than params count, unsafe to continue decoding return false; } + if (n < len) { + // pad with first type + auto first = _params.getParamUnsafe(0); + for (size_t i = 0; i < len - n; i++) { + _params.addParam(ParamFactory::make(first->getType())); + } + } // read values auto res = _params.decode(encoded, offset_inout); diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp index 289cc66dd06..5fb23381ef5 100644 --- a/src/Ethereum/ABI/Bytes.cpp +++ b/src/Ethereum/ABI/Bytes.cpp @@ -23,12 +23,18 @@ bool ParamByteArray::decodeBytes(const Data& encoded, Data& decoded, size_t& off size_t origOffset = offset_inout; // read len uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { return false; } + if (!ABI::decode(encoded, len256, offset_inout)) { + return false; + } // check if length is in the size_t range size_t len = static_cast(len256); - if (len256 != static_cast(len)) { return false; } + if (len256 != uint256_t(len)) { + return false; + } // check if there is enough data - if (encoded.size() < offset_inout + len) { return false; } + if (encoded.size() < offset_inout + len) { + return false; + } // read data decoded = Data(encoded.begin() + offset_inout, encoded.begin() + offset_inout + len); offset_inout += len; @@ -44,14 +50,18 @@ void ParamByteArrayFix::encode(Data& data) const { append(data, Data(padding)); } -bool ParamByteArrayFix::decodeBytesFix(const Data& encoded, size_t n, Data& decoded, size_t& offset_inout) { +bool ParamByteArrayFix::decodeBytesFix(const Data& encoded, size_t n, Data& decoded, + size_t& offset_inout) { size_t origOffset = offset_inout; if (encoded.size() < offset_inout + n) { // not enough data return false; } - if (decoded.size() < n) { append(decoded, Data(n - decoded.size())); } - std::copy(encoded.begin() + offset_inout, encoded.begin() + (offset_inout + n), decoded.begin()); + if (decoded.size() < n) { + append(decoded, Data(n - decoded.size())); + } + std::copy(encoded.begin() + offset_inout, encoded.begin() + (offset_inout + n), + decoded.begin()); offset_inout += n; // padding offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); @@ -65,7 +75,9 @@ void ParamString::encodeString(const std::string& decoded, Data& data) { bool ParamString::decodeString(const Data& encoded, std::string& decoded, size_t& offset_inout) { Data decodedData; - if (!ParamByteArray::decodeBytes(encoded, decodedData, offset_inout)) { return false; } + if (!ParamByteArray::decodeBytes(encoded, decodedData, offset_inout)) { + return false; + } decoded = std::string(decodedData.begin(), decodedData.end()); return true; } diff --git a/src/Ethereum/ABI/ParamFactory.cpp b/src/Ethereum/ABI/ParamFactory.cpp new file mode 100644 index 00000000000..c19c0e934f1 --- /dev/null +++ b/src/Ethereum/ABI/ParamFactory.cpp @@ -0,0 +1,86 @@ +// Copyright © 2017-2020 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. + +#include "ParamFactory.h" +#include "HexCoding.h" + +#include + +using namespace std; +using namespace boost::algorithm; + +namespace TW::Ethereum::ABI { + +static int parseBitSize(const std::string& type) { + if (type.size() == 0){ + return 256; + } + int size = stoi(type); + if (size < 8 || size > 256 || 256 % size != 0) { + throw invalid_argument("invalid bit size"); + } + return size; +} + +static std::shared_ptr makeUInt(const std::string& type) { + auto bits = parseBitSize(type); + return make_shared(bits); +} + +static std::shared_ptr makeInt(const std::string& type) { + auto bits = parseBitSize(type); + return make_shared(bits); +} + +std::shared_ptr ParamFactory::make(const std::string& type) { + shared_ptr param; + if (type == "address") { + param = make_shared(); + } else if (starts_with(type, "uint")) { + param = makeUInt(type.substr(4, type.size() - 1)); + } else if (starts_with(type, "int")) { + param = makeInt(type.substr(3, type.size() - 1)); + } else if (type == "bool") { + param = make_shared(); + } else if (type == "bytes") { + param = make_shared(); + } else if (starts_with(type, "bytes")) { + auto bits = stoi(type.substr(5, type.size() - 1)); + param = make_shared(bits); + } else if (type == "string") { + param = make_shared(); + } + return param; +} + +std::string ParamFactory::getValue(const std::shared_ptr& param, const std::string& type) { + std::string result = ""; + if (type == "address") { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getData()); + } else if (starts_with(type, "uint")) { + auto value = dynamic_pointer_cast(param); + result = boost::lexical_cast(value->getVal()); + } else if (starts_with(type, "int")) { + auto value = dynamic_pointer_cast(param); + result = boost::lexical_cast(value->getVal()); + } else if (type == "bool") { + auto value = dynamic_pointer_cast(param); + result = value->getVal() ? "true" : "false"; + } else if (type == "bytes") { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getVal()); + } else if (starts_with(type, "bytes")) { + auto value = dynamic_pointer_cast(param); + result = hexEncoded(value->getVal()); + } else if (type == "string") { + auto value = dynamic_pointer_cast(param); + result = value->getVal(); + } + return result; +} + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamFactory.h b/src/Ethereum/ABI/ParamFactory.h new file mode 100644 index 00000000000..5db745ae5cb --- /dev/null +++ b/src/Ethereum/ABI/ParamFactory.h @@ -0,0 +1,25 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "Array.h" +#include "Bytes.h" +#include "ParamAddress.h" + +#include + +namespace TW::Ethereum::ABI { + +/// Factory creates concrete ParamBase class from string type. +class ParamFactory +{ +public: + static std::shared_ptr make(const std::string& type); + static std::string getValue(const std::shared_ptr& param, const std::string& type); +}; + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp index 7372b34832e..627570b8fc8 100644 --- a/src/Ethereum/ABI/Parameters.cpp +++ b/src/Ethereum/ABI/Parameters.cpp @@ -7,25 +7,29 @@ #include "Parameters.h" #include "ValueEncoder.h" -#include #include +#include using namespace TW::Ethereum::ABI; ParamSet::~ParamSet() { - _params.clear(); + _params.clear(); } /// Returns the index of the parameter int ParamSet::addParam(const std::shared_ptr& param) { assert(param.get() != nullptr); - if (param.get() == nullptr) { return -1; } + if (param.get() == nullptr) { + return -1; + } _params.push_back(param); return static_cast(_params.size() - 1); } void ParamSet::addParams(const std::vector>& params) { - for (auto p: params) { addParam(p); } + for (auto p : params) { + addParam(p); + } } bool ParamSet::getParam(int paramIndex, std::shared_ptr& param_out) const { @@ -52,8 +56,10 @@ std::shared_ptr ParamSet::getParamUnsafe(int paramIndex) const { std::string ParamSet::getType() const { std::string t = "("; int cnt = 0; - for(auto p: _params) { - if (cnt > 0) t += ","; + for (auto p : _params) { + if (cnt > 0) { + t += ","; + } t += p->getType(); ++cnt; } @@ -76,7 +82,7 @@ size_t ParamSet::getSize() const { size_t ParamSet::getHeadSize() const { size_t s = 0; - for(auto p: _params) { + for (auto p : _params) { if (p->isDynamic()) { s += 32; } else { @@ -92,7 +98,7 @@ void ParamSet::encode(Data& data) const { size_t dynamicOffset = 0; // pass 1: small values or indices - for(auto p: _params) { + for (auto p : _params) { if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { // include only offset ValueEncoder::encodeUInt256(uint256_t(headSize + dynamicOffset), data); @@ -101,32 +107,38 @@ void ParamSet::encode(Data& data) const { // encode small data p->encode(data); } - } + } // pass 2: dynamic values - for(auto p: _params) { + for (auto p : _params) { if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { // encode large data p->encode(data); } - } + } } bool ParamSet::decode(const Data& encoded, size_t& offset_inout) { // pass 1: small values - for(auto p: _params) { + for (auto p : _params) { if (p->isDynamic()) { uint256_t index; - if (!ABI::decode(encoded, index, offset_inout)) { return false; } + if (!ABI::decode(encoded, index, offset_inout)) { + return false; + } // index is read but not used } else { - if (!p->decode(encoded, offset_inout)) { return false; } + if (!p->decode(encoded, offset_inout)) { + return false; + } } } - // pass2: large values - for(auto p: _params) { + // pass2: large values + for (auto p : _params) { if (p->isDynamic()) { - if (!p->decode(encoded, offset_inout)) { return false; } + if (!p->decode(encoded, offset_inout)) { + return false; + } } } return true; diff --git a/src/Ethereum/ContractCall.cpp b/src/Ethereum/ContractCall.cpp new file mode 100644 index 00000000000..ee87292c395 --- /dev/null +++ b/src/Ethereum/ContractCall.cpp @@ -0,0 +1,110 @@ +// Copyright © 2017-2020 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. + +#include "ContractCall.h" +#include "ABI.h" +#include "HexCoding.h" +#include "uint256.h" +#include +#include + +using namespace std; +using json = nlohmann::json; + +namespace TW::Ethereum::ABI { + +static void fillArray(Function& func, const string& type) { + auto param = make_shared(); + auto baseType = string(type.begin(), type.end() - 2); + auto value = ParamFactory::make(baseType); + param->addParam(value); + func.addParam(param, false); +} + +static void fill(Function& func, const string& type) { + if (boost::algorithm::ends_with(type, "[]")) { + fillArray(func, type); + } else { + auto param = ParamFactory::make(type); + func.addParam(param, false); + } +} + +static vector getArrayValue(Function& func, const string& type, int idx) { + auto baseType = string(type.begin(), type.end() - 2); + shared_ptr param; + func.getInParam(idx, param); + auto array = dynamic_pointer_cast(param); + auto count = array->getCount(); + auto result = vector(); + for (int i = 0; i < count; i++) { + result.push_back(ParamFactory::getValue(array->getParam(i), baseType)); + } + return result; +} + +static string getValue(Function& func, const string& type, int idx) { + shared_ptr param; + func.getInParam(idx, param); + return ParamFactory::getValue(param, type); +} + +static json buildInputs(Function& func, const json& registry) { + auto inputs = json::array(); + for (int i = 0; i < registry["inputs"].size(); i++) { + auto info = registry["inputs"][i]; + auto type = info["type"]; + auto input = json{ + {"name", info["name"]}, + {"type", type} + }; + if (boost::algorithm::ends_with(type.get(), "[]")) { + input["value"] = json(getArrayValue(func, type, i)); + } else if (type == "bool") { + input["value"] = getValue(func, type, i) == "true" ? json(true) : json(false); + } else { + input["value"] = getValue(func, type, i); + } + inputs.push_back(input); + } + return inputs; +} + +optional decodeCall(const Data& call, const json& abi) { + // check bytes length + if (call.size() <= 4) { + return {}; + } + + auto methodId = hex(Data(call.begin(), call.begin() + 4)); + + if (abi.find(methodId) == abi.end()) { + return {}; + } + + // build Function with types + const auto registry = abi[methodId]; + auto func = Function(registry["name"]); + for (auto& input : registry["inputs"]) { + fill(func, input["type"]); + } + + // decode inputs + size_t offset = 0; + auto success = func.decodeInput(call, offset); + if (!success) { + return {}; + } + + // build output json + auto decoded = json{ + {"function", func.getType()}, + {"inputs", buildInputs(func, registry)}, + }; + return decoded.dump(); +} + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ContractCall.h b/src/Ethereum/ContractCall.h new file mode 100644 index 00000000000..ca7b5c44718 --- /dev/null +++ b/src/Ethereum/ContractCall.h @@ -0,0 +1,16 @@ +// Copyright © 2017-2020 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. + +#pragma once + +#include "Data.h" +#include +#include +#include + +namespace TW::Ethereum::ABI { + std::optional decodeCall(const Data& call, const nlohmann::json& abi); +} // namespace TW::Ethereum::ABI diff --git a/src/interface/TWEthereumAbiEncoder.cpp b/src/interface/TWEthereumAbi.cpp similarity index 51% rename from src/interface/TWEthereumAbiEncoder.cpp rename to src/interface/TWEthereumAbi.cpp index 976dfe46075..a6208fe4193 100644 --- a/src/interface/TWEthereumAbiEncoder.cpp +++ b/src/interface/TWEthereumAbi.cpp @@ -4,40 +4,29 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include +#include #include -#include "Ethereum/ABI.h" #include "Data.h" +#include "Ethereum/ABI.h" +#include "Ethereum/ContractCall.h" #include "HexCoding.h" -#include "../uint256.h" +#include "uint256.h" +#include #include #include -#include using namespace TW; using namespace TW::Ethereum; using namespace TW::Ethereum::ABI; /// Wrapper for C interface, empty as all methods are static -struct TWEthereumAbiEncoder { - //TW::Ethereum::ABI::Encoder impl; +struct TWEthereumAbi { + // TW::Ethereum::ABI::Encoder impl; }; -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiEncoderBuildFunction(TWString *_Nonnull name) { - auto func = Function(TWStringUTF8Bytes(name)); - return new TWEthereumAbiFunction{ func }; -} - -void TWEthereumAbiEncoderDeleteFunction(struct TWEthereumAbiFunction *_Nonnull func_in) { - assert(func_in != nullptr); - delete func_in; -} - -///// Encode & decode - -TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnull func_in) { +TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull func_in) { assert(func_in != nullptr); Function& function = func_in->impl; @@ -46,7 +35,8 @@ TWData*_Nonnull TWEthereumAbiEncoderEncode(struct TWEthereumAbiFunction *_Nonnul return TWDataCreateWithData(&encoded); } -bool TWEthereumAbiEncoderDecodeOutput(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull encoded) { +bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull func_in, + TWData* _Nonnull encoded) { assert(func_in != nullptr); Function& function = func_in->impl; assert(encoded != nullptr); @@ -55,3 +45,19 @@ bool TWEthereumAbiEncoderDecodeOutput(struct TWEthereumAbiFunction *_Nonnull fun size_t offset = 0; return function.decodeOutput(encData, offset); } + +TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* _Nonnull abiString) { + const Data& call = *(reinterpret_cast(callData)); + const auto& jsonString = *reinterpret_cast(abiString); + try { + auto abi = nlohmann::json::parse(jsonString); + auto string = decodeCall(call, abi); + if (!string.has_value()) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(string->c_str()); + } + catch(...) { + return nullptr; + } +} diff --git a/src/interface/TWEthereumAbiFunction.cpp b/src/interface/TWEthereumAbiFunction.cpp index e185e8ae9e7..93b451c2a51 100644 --- a/src/interface/TWEthereumAbiFunction.cpp +++ b/src/interface/TWEthereumAbiFunction.cpp @@ -19,7 +19,7 @@ using namespace TW; using namespace TW::Ethereum; using namespace TW::Ethereum::ABI; -struct TWEthereumAbiFunction *_Nullable TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name) { +struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name) { auto func = Function(TWStringUTF8Bytes(name)); return new TWEthereumAbiFunction{ func }; } diff --git a/src/interface/TWEthereumAbiValueEncoder.cpp b/src/interface/TWEthereumAbiValue.cpp similarity index 64% rename from src/interface/TWEthereumAbiValueEncoder.cpp rename to src/interface/TWEthereumAbiValue.cpp index 473826cdab6..e9732657c14 100644 --- a/src/interface/TWEthereumAbiValueEncoder.cpp +++ b/src/interface/TWEthereumAbiValue.cpp @@ -4,9 +4,10 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include +#include #include +#include #include #include @@ -15,58 +16,64 @@ using namespace TW::Ethereum; using namespace TW; -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBool(bool value) { +TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value) { Data data; ABI::ValueEncoder::encodeBool(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt32(int32_t value) { +TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value) { Data data; ABI::ValueEncoder::encodeInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt32(uint32_t value) { +TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value) { Data data; ABI::ValueEncoder::encodeUInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeInt256(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value) { Data data; int256_t value256 = static_cast(TW::load(*reinterpret_cast(value))); ABI::ValueEncoder::encodeInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeUInt256(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value) { Data data; uint256_t value256 = TW::load(*reinterpret_cast(value)); ABI::ValueEncoder::encodeUInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeAddress(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value) { Data data; ABI::ValueEncoder::encodeAddress(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeString(TWString* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value) { Data data; ABI::ValueEncoder::encodeString(TWStringUTF8Bytes(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytes(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value) { Data data; ABI::ValueEncoder::encodeBytes(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } -TWData* _Nonnull TWEthereumAbiValueEncoderEncodeBytesDyn(TWData* _Nonnull value) { +TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value) { Data data; ABI::ValueEncoder::encodeBytesDyn(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } + +TWString* _Nonnull TWEthereumAbiValueDecodeUInt256(TWData* _Nonnull input) { + auto data = TW::data(TWDataBytes(input), TWDataSize(input)); + auto decoded = Ethereum::ABI::ValueDecoder::decodeUInt256(data); + return TWStringCreateWithUTF8Bytes(TW::toString(decoded).c_str()); +} diff --git a/src/interface/TWEthereumAbiValueDecoder.cpp b/src/interface/TWEthereumAbiValueDecoder.cpp deleted file mode 100644 index d16df43b9a5..00000000000 --- a/src/interface/TWEthereumAbiValueDecoder.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2017-2020 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. - -#include - -#include -#include - -using namespace TW::Ethereum; -using namespace TW; - -TWString* _Nonnull TWEthereumAbiValueDecoderDecodeUInt256(TWData* _Nonnull input) { - auto data = TW::data(TWDataBytes(input), TWDataSize(input)); - auto decoded = Ethereum::ABI::ValueDecoder::decodeUInt256(data); - return TWStringCreateWithUTF8Bytes(TW::toString(decoded).c_str()); -} diff --git a/swift/Tests/Blockchains/Data b/swift/Tests/Blockchains/Data new file mode 120000 index 00000000000..f50c5d874d3 --- /dev/null +++ b/swift/Tests/Blockchains/Data @@ -0,0 +1 @@ +../../../tests/Ethereum/Data \ No newline at end of file diff --git a/swift/Tests/Blockchains/EthereumAbiTests.swift b/swift/Tests/Blockchains/EthereumAbiTests.swift index decc52126f9..71ff407a414 100644 --- a/swift/Tests/Blockchains/EthereumAbiTests.swift +++ b/swift/Tests/Blockchains/EthereumAbiTests.swift @@ -9,7 +9,7 @@ import TrustWalletCore class EthereumAbiTests: XCTestCase { func testAbiEncoder() { - let function = EthereumAbiEncoder.buildFunction(name: "sam")! + let function = EthereumAbiFunction(name: "sam") // add params XCTAssertEqual(0, function.addParamBytes(val: Data(hexString: "0x64617665")!, isOutput: false)) XCTAssertEqual(1, function.addParamBool(val: true, isOutput: false)) @@ -24,20 +24,20 @@ class EthereumAbiTests: XCTestCase { XCTAssertEqual(function.getType(), "sam(bytes,bool,uint256[])") // encode into binrary - XCTAssertEqual(EthereumAbiEncoder.encode(func_in: function).hexString, "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + XCTAssertEqual(EthereumAbi.encode(fn: function).hexString, "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") // original output value XCTAssertEqual(0, function.getParamUInt64(idx: 0, isOutput: true)) // decode output from binary let encodedOutput = Data(hexString: "0x0000000000000000000000000000000000000000000000000000000000000045")! - let decodeRes = EthereumAbiEncoder.decodeOutput(func_in: function, encoded: encodedOutput) + let decodeRes = EthereumAbi.decodeOutput(fn: function, encoded: encodedOutput) XCTAssertEqual(true, decodeRes) // new output value XCTAssertEqual(0x45, function.getParamUInt64(idx: 0, isOutput: true)) } func testValueEncoder() { - let data2 = EthereumAbiValueEncoder.encodeInt32(value: 69) + let data2 = EthereumAbiValue.encodeInt32(value: 69) XCTAssertEqual(data2.hexString, "0000000000000000000000000000000000000000000000000000000000000045") } @@ -50,7 +50,29 @@ class EthereumAbiTests: XCTestCase { ] for input in inputs { let data = Data(hexString: input)! - XCTAssertEqual(expected, EthereumAbiValueDecoder.decodeUInt256(input: data)) + XCTAssertEqual(expected, EthereumAbiValue.decodeUInt256(input: data)) } } + + func testDecodeApprove() throws { + let data = Data(hexString: "095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed0000000000000000000000000000000000000000000000000000000000000001")! + let url = Bundle(for: EthereumAbiTests.self).url(forResource: "erc20", withExtension: "json")! + let abi = try String(contentsOf: url) + let decoded = EthereumAbi.decodeCall(data: data, abi: abi)! + let expected = """ + { + "function": "approve(address,uint256)", + "inputs": [{ + "name": "_spender", + "type": "address", + "value": "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + }, { + "name": "_value", + "type": "uint256", + "value": "1" + }] + } + """ + XCTAssertJSONEqual(decoded, expected) + } } diff --git a/tests/Ethereum/AbiTests.cpp b/tests/Ethereum/AbiTests.cpp index 5cccbfd9f91..f0bfd49a17c 100644 --- a/tests/Ethereum/AbiTests.cpp +++ b/tests/Ethereum/AbiTests.cpp @@ -12,8 +12,6 @@ using namespace TW; using namespace TW::Ethereum::ABI; -///// Util - ///// Parameter types @@ -547,6 +545,25 @@ TEST(EthereumAbi, ParamArrayOfByteArray) { EXPECT_EQ(3, param.getCount()); } +TEST(EthereumAbi, ParamArrayBytesContract) { + auto param = ParamArray(); + param.addParam(std::make_shared(parse_hex("0xd5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000047331175b23c2f067204b506ca1501c26731c990"))); + param.addParam(std::make_shared(parse_hex("0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000"))); + param.addParam(std::make_shared(parse_hex("0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000"))); + param.addParam(std::make_shared(parse_hex("0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"))); + EXPECT_EQ(4, param.getCount()); + EXPECT_EQ(4, param.getVal().size()); + + EXPECT_EQ("bytes[]", param.getType()); + EXPECT_TRUE(param.isDynamic()); + + Data encoded; + param.encode(encoded); + EXPECT_EQ(896, encoded.size()); + + EXPECT_EQ(896, param.getSize()); +} + ///// Direct encode & decode TEST(EthereumAbi, EncodeVectorByte10) { @@ -1047,3 +1064,44 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { EXPECT_EQ(std::string("Hello, world!"), (std::dynamic_pointer_cast(param))->getVal()); EXPECT_EQ(4 + 9 * 32, offset); } + +TEST(EthereumAbi, DecodeFunctionContractMulticall) { + Data encoded = parse_hex( + "0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000" + "000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000" + "000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000" + "0000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000" + "000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000" + "00000044d5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000" + "0000000000000047331175b23c2f067204b506ca1501c26731c990000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000064304e6a" + "dee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000" + "000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e" + "574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c" + "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000" + "0000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e57" + "4c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00" + "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" + "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" + "000000000000000000000000000000000000000000000000000000000000000000"); + ASSERT_EQ(4 + 928, encoded.size()); + + auto func = Function("multicall", std::vector>{ + std::make_shared(std::vector>{ + std::make_shared(Data()), + std::make_shared(Data()), + std::make_shared(Data()), + std::make_shared(Data()) + }), + }); + EXPECT_EQ("multicall(bytes[])", func.getType()); + + size_t offset = 0; + bool res = func.decodeInput(encoded, offset); + EXPECT_TRUE(res); + EXPECT_EQ(4 + 29 * 32, offset); +} diff --git a/tests/Ethereum/ContractCallTests.cpp b/tests/Ethereum/ContractCallTests.cpp new file mode 100644 index 00000000000..26006faa46d --- /dev/null +++ b/tests/Ethereum/ContractCallTests.cpp @@ -0,0 +1,169 @@ +// Copyright © 2017-2020 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. + +#include "Ethereum/ContractCall.h" +#include "HexCoding.h" + +#include +#include + +using namespace TW; +using namespace TW::Ethereum::ABI; + +extern std::string TESTS_ROOT; + +static nlohmann::json load_json(std::string path) { + std::ifstream stream(path); + nlohmann::json json; + stream >> json; + return json; +} + +TEST(ContractCall, Approval) { + auto path = TESTS_ROOT + "/Ethereum/Data/erc20.json"; + auto abi = load_json(path); + auto call = parse_hex("095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + "0000000000000000000000000000000000000000000000000000000000000001"); + auto decoded = decodeCall(call, abi); + + auto expected = + R"|({"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"},{"name":"_value","type":"uint256","value":"1"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, UniswapSwapTokens) { + auto path = TESTS_ROOT + "/Ethereum/Data/uniswap_router_v2.json"; + auto abi = load_json(path); + // https://etherscan.io/tx/0x57a2414f3cd9ca373b7e663ae67ecf933e40cb77a6e4ed28e4e28b5aa0d8ec63 + auto call = parse_hex( + "0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000" + "00000000000000000000000000000000229f7e501ad62bdb000000000000000000000000000000000000000000" + "00000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f10000" + "00000000000000000000000000000000000000000000000000005f0ed070000000000000000000000000000000" + "00000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac4" + "95271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000" + "000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d32218924" + "6dafa5ebde1f4699f498"); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"swapExactTokensForTokens(uint256,uint256,address[],address,uint256)","inputs":[{"name":"amountIn","type":"uint256","value":"1000000000000000000"},{"name":"amountOutMin","type":"uint256","value":"2494851601099271131"},{"name":"path","type":"address[]","value":["0x6b175474e89094c44da98b954eedeac495271d0f","0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2","0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xe41d2489571d322189246dafa5ebde1f4699f498"]},{"name":"to","type":"address","value":"0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1"},{"name":"deadline","type":"uint256","value":"1594806384"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, KyberTrade) { + auto path = TESTS_ROOT + "/Ethereum/Data/kyber_proxy.json"; + auto abi = load_json(path); + + // https://etherscan.io/tx/0x51ffab782b9a27d754389505d5a50db525c04c68142ce20512d579f10f9e13e4 + auto call = parse_hex( + "ae591d54000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000" + "000000000000000000000000000004a97d605a3b980000000000000000000000000000dac17f958d2ee523a220" + "6206994597c13d831ec70000000000000000000000007755297c6a26d495739206181fe81646dbd0bf39ffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000" + "000000000000000ce32ff7d63c35d189000000000000000000000000440bbd6a888a36de6e2f6a25f65bc4e168" + "74faa9000000000000000000000000000000000000000000000000000000000000000800000000000000000000" + "000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000" + "000000000000000000"); + auto decoded = decodeCall(call, abi); + + auto expected = + R"|({"function":"tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)","inputs":[{"name":"src","type":"address","value":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"},{"name":"srcAmount","type":"uint256","value":"86000000000000000000"},{"name":"dest","type":"address","value":"0xdac17f958d2ee523a2206206994597c13d831ec7"},{"name":"destAddress","type":"address","value":"0x7755297c6a26d495739206181fe81646dbd0bf39"},{"name":"maxDestAmount","type":"uint256","value":"115792089237316195423570985008687907853269984665640564039457584007913129639935"},{"name":"minConversionRate","type":"uint256","value":"237731504554534883721"},{"name":"platformWallet","type":"address","value":"0x440bbd6a888a36de6e2f6a25f65bc4e16874faa9"},{"name":"platformFeeBps","type":"uint256","value":"8"},{"name":"hint","type":"bytes","value":"0x"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, ApprovalForAll) { + auto path = TESTS_ROOT + "/Ethereum/Data/erc721.json"; + auto abi = load_json(path); + + // https://etherscan.io/tx/0xc2744000a107aee4761cf8a638657f91c3003a54e2f1818c37d781be7e48187a + auto call = parse_hex("0xa22cb46500000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b" + "0c0000000000000000000000000000000000000000000000000000000000000001"); + auto decoded = decodeCall(call, abi); + + auto expected = + R"|({"function":"setApprovalForAll(address,bool)","inputs":[{"name":"to","type":"address","value":"0x88341d1a8f672d2780c8dc725902aae72f143b0c"},{"name":"approved","type":"bool","value":true}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, CustomCall) { + auto path = TESTS_ROOT + "/Ethereum/Data/custom.json"; + auto abi = load_json(path); + + auto call = parse_hex("ec37a4a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000067472757374790000000000000000000000000000000000000000000000000000"); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"setName(string,uint256,int32)","inputs":[{"name":"name","type":"string","value":"trusty"},{"name":"age","type":"uint","value":"3"},{"name":"height","type":"int32","value":"100"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, SetResolver) { + auto call = parse_hex("0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c" + "6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"); + auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; + auto abi = load_json(path); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"setResolver(bytes32,address)","inputs":[{"name":"node","type":"bytes32","value":"0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f"},{"name":"resolver","type":"address","value":"0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, RenewENS) { + auto call = parse_hex( + "0xacf1a84100000000000000000000000000000000000000000000000000000000000000400000000000000000" + "000000000000000000000000000000000000000001e18558000000000000000000000000000000000000000000" + "000000000000000000000a68657769676f76656e7300000000000000000000000000000000000000000000"); + auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; + auto abi = load_json(path); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"renew(string,uint256)","inputs":[{"name":"name","type":"string","value":"hewigovens"},{"name":"duration","type":"uint256","value":"31556952"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, Multicall) { + auto call = parse_hex( + "0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000" + "000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000" + "000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000" + "0000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000" + "000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000" + "00000044d5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000" + "0000000000000047331175b23c2f067204b506ca1501c26731c990000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000064304e6a" + "dee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000" + "000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e" + "574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c" + "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000" + "0000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e57" + "4c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00" + "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" + "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" + "000000000000000000000000000000000000000000000000000000000000000000"); + ASSERT_EQ(4 + 928, call.size()); + auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; + auto abi = load_json(path); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"multicall(bytes[])","inputs":[{"name":"data","type":"bytes[]","value":["0xd5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000047331175b23c2f067204b506ca1501c26731c990","0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"]}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, Invalid) { + EXPECT_FALSE(decodeCall(Data(), "{}").has_value()); + EXPECT_FALSE(decodeCall(parse_hex("0xa22cb46500"), "{}").has_value()); +} diff --git a/tests/Ethereum/Data/custom.json b/tests/Ethereum/Data/custom.json new file mode 100644 index 00000000000..711a8c86217 --- /dev/null +++ b/tests/Ethereum/Data/custom.json @@ -0,0 +1,20 @@ +{ + "ec37a4a0": { + "constant": false, + "inputs": [{ + "name": "name", + "type": "string" + }, { + "name": "age", + "type": "uint" + }, { + "name": "height", + "type": "int32" + }], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} \ No newline at end of file diff --git a/tests/Ethereum/Data/ens.json b/tests/Ethereum/Data/ens.json new file mode 100644 index 00000000000..a7c263019c8 --- /dev/null +++ b/tests/Ethereum/Data/ens.json @@ -0,0 +1,53 @@ +{ + "1896f70a": { + "constant": false, + "inputs": [{ + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, { + "internalType": "address", + "name": "resolver", + "type": "address" + }], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "acf1a841": { + "constant": false, + "inputs": [{ + "internalType": "string", + "name": "name", + "type": "string" + }, { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }], + "name": "renew", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + "ac9650d8": { + "constant": false, + "inputs": [{ + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }], + "name": "multicall", + "outputs": [{ + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/erc20.json b/tests/Ethereum/Data/erc20.json new file mode 100644 index 00000000000..b4163266f55 --- /dev/null +++ b/tests/Ethereum/Data/erc20.json @@ -0,0 +1,50 @@ +{ + "095ea7b3": { + "constant": false, + "inputs": [{ + "name": "_spender", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "a9059cbb": { + "constant": false, + "inputs": [{ + "name": "_to", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "23b872dd": { + "constant": false, + "inputs": [{ + "name": "_from", + "type": "address" + }, { + "name": "_to", + "type": "address" + }, { + "name": "_value", + "type": "uint256" + }], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/erc721.json b/tests/Ethereum/Data/erc721.json new file mode 100644 index 00000000000..76e96bf86e7 --- /dev/null +++ b/tests/Ethereum/Data/erc721.json @@ -0,0 +1,78 @@ +{ + "095ea7b3": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "23b872dd": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "from", + "type": "address" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "42842e0e": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "from", + "type": "address" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + "a22cb465": { + "constant": false, + "inputs": [{ + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "bool", + "name": "approved", + "type": "bool" + }], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/kyber_proxy.json b/tests/Ethereum/Data/kyber_proxy.json new file mode 100644 index 00000000000..cf36edd7c7e --- /dev/null +++ b/tests/Ethereum/Data/kyber_proxy.json @@ -0,0 +1,88 @@ +{ + "cb3c28c7": { + "inputs": [{ + "internalType": "contract IERC20", + "name": "src", + "type": "address" + }, { + "internalType": "uint256", + "name": "srcAmount", + "type": "uint256" + }, { + "internalType": "contract IERC20", + "name": "dest", + "type": "address" + }, { + "internalType": "address payable", + "name": "destAddress", + "type": "address" + }, { + "internalType": "uint256", + "name": "maxDestAmount", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "minConversionRate", + "type": "uint256" + }, { + "internalType": "address payable", + "name": "platformWallet", + "type": "address" + }], + "name": "trade", + "outputs": [{ + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "payable", + "type": "function" + }, + "ae591d54": { + "inputs": [{ + "internalType": "contract IERC20", + "name": "src", + "type": "address" + }, { + "internalType": "uint256", + "name": "srcAmount", + "type": "uint256" + }, { + "internalType": "contract IERC20", + "name": "dest", + "type": "address" + }, { + "internalType": "address payable", + "name": "destAddress", + "type": "address" + }, { + "internalType": "uint256", + "name": "maxDestAmount", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "minConversionRate", + "type": "uint256" + }, { + "internalType": "address payable", + "name": "platformWallet", + "type": "address" + }, { + "internalType": "uint256", + "name": "platformFeeBps", + "type": "uint256" + }, { + "internalType": "bytes", + "name": "hint", + "type": "bytes" + }], + "name": "tradeWithHintAndFee", + "outputs": [{ + "internalType": "uint256", + "name": "destAmount", + "type": "uint256" + }], + "stateMutability": "payable", + "type": "function" + } +} diff --git a/tests/Ethereum/Data/uniswap_router_v2.json b/tests/Ethereum/Data/uniswap_router_v2.json new file mode 100644 index 00000000000..3bf37d19b23 --- /dev/null +++ b/tests/Ethereum/Data/uniswap_router_v2.json @@ -0,0 +1,168 @@ +{ + "7ff36ab5": { + "inputs": [{ + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactETHForTokens", + "outputs": [{ + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }], + "stateMutability": "payable", + "type": "function" + }, + "b6f9de95": { + "inputs": [{ + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactETHForTokensSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + "18cbafe5": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForETH", + "outputs": [{ + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }], + "stateMutability": "nonpayable", + "type": "function" + }, + "791ac947": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForETHSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + "38ed1739": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForTokens", + "outputs": [{ + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }], + "stateMutability": "nonpayable", + "type": "function" + }, + "5c11d795": { + "inputs": [{ + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, { + "internalType": "address", + "name": "to", + "type": "address" + }, { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }], + "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +} diff --git a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp b/tests/Ethereum/TWEthereumAbiTests.cpp similarity index 83% rename from tests/Ethereum/TWEthereumAbiEncoderTests.cpp rename to tests/Ethereum/TWEthereumAbiTests.cpp index 2079c9f57c5..cf8b00bbd2f 100644 --- a/tests/Ethereum/TWEthereumAbiEncoderTests.cpp +++ b/tests/Ethereum/TWEthereumAbiTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include +#include #include #include @@ -13,13 +13,14 @@ #include "HexCoding.h" #include "uint256.h" +#include "../interface/TWTestUtilities.h" #include #include namespace TW::Ethereum { TEST(TWEthereumAbi, FuncCreateEmpty) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); TWString* type = TWEthereumAbiFunctionGetType(func); @@ -27,11 +28,11 @@ TEST(TWEthereumAbi, FuncCreateEmpty) { EXPECT_EQ("baz()", type2); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, FuncCreate1) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); auto p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); @@ -47,11 +48,11 @@ TEST(TWEthereumAbi, FuncCreate1) { EXPECT_EQ("baz(uint64)", type2); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, FuncCreate2) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("baz")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("baz")); EXPECT_TRUE(func != nullptr); TWString* p1valStr = TWStringCreateWithUTF8Bytes("0045"); @@ -74,11 +75,11 @@ TEST(TWEthereumAbi, FuncCreate2) { EXPECT_EQ("baz(uint256)", std::string(TWStringUTF8Bytes(type))); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, EncodeFuncCase1) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("sam")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("sam")); EXPECT_TRUE(func != nullptr); EXPECT_EQ(0, TWEthereumAbiFunctionAddParamBytes(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("64617665")), false)); @@ -92,7 +93,7 @@ TEST(TWEthereumAbi, EncodeFuncCase1) { TWString* type = TWEthereumAbiFunctionGetType(func); EXPECT_EQ("sam(bytes,bool,uint256[])", std::string(TWStringUTF8Bytes(type))); - TWData* encoded = TWEthereumAbiEncoderEncode(func); + TWData* encoded = TWEthereumAbiEncode(func); Data enc2 = data(TWDataBytes(encoded), TWDataSize(encoded)); EXPECT_EQ("a5643bf2" "0000000000000000000000000000000000000000000000000000000000000060" @@ -107,11 +108,11 @@ TEST(TWEthereumAbi, EncodeFuncCase1) { hex(enc2)); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, EncodeFuncCase2) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("f")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("f")); EXPECT_TRUE(func != nullptr); EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt256(func, TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0123")), false)); @@ -125,7 +126,7 @@ TEST(TWEthereumAbi, EncodeFuncCase2) { TWString* type = TWEthereumAbiFunctionGetType(func); EXPECT_EQ("f(uint256,uint32[],bytes10,string)", std::string(TWStringUTF8Bytes(type))); - TWData* encoded = TWEthereumAbiEncoderEncode(func); + TWData* encoded = TWEthereumAbiEncode(func); Data enc2 = data(TWDataBytes(encoded), TWDataSize(encoded)); EXPECT_EQ("47b941bf" "0000000000000000000000000000000000000000000000000000000000000123" @@ -140,12 +141,12 @@ TEST(TWEthereumAbi, EncodeFuncCase2) { hex(enc2)); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, EncodeFuncMonster) { // Monster function with all parameters types - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("monster")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("monster")); EXPECT_TRUE(func != nullptr); TWEthereumAbiFunctionAddParamUInt8(func, 1, false); @@ -199,16 +200,16 @@ TEST(TWEthereumAbi, EncodeFuncMonster) { "uint8[],uint16[],uint32[],uint64[],uint168[],uint256[],int8[],int16[],int32[],int64[],int168[],int256[],bool[],string[],address[],bytes[],bytes5[])", std::string(TWStringUTF8Bytes(type))); - TWData* encoded = TWEthereumAbiEncoderEncode(func); + TWData* encoded = TWEthereumAbiEncode(func); Data enc2 = data(TWDataBytes(encoded), TWDataSize(encoded)); EXPECT_EQ(4 + 76 * 32, enc2.size()); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, DecodeOutputFuncCase1) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("readout")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("readout")); EXPECT_TRUE(func != nullptr); TWEthereumAbiFunctionAddParamAddress(func, @@ -221,17 +222,17 @@ TEST(TWEthereumAbi, DecodeOutputFuncCase1) { // decode auto encoded = TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0000000000000000000000000000000000000000000000000000000000000045")); - EXPECT_EQ(true, TWEthereumAbiEncoderDecodeOutput(func, encoded)); + EXPECT_EQ(true, TWEthereumAbiDecodeOutput(func, encoded)); // new output value EXPECT_EQ(0x45, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); } TEST(TWEthereumAbi, GetParamWrongType) { - TWEthereumAbiFunction* func = TWEthereumAbiEncoderBuildFunction(TWStringCreateWithUTF8Bytes("func")); + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(TWStringCreateWithUTF8Bytes("func")); ASSERT_TRUE(func != nullptr); // add parameters EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt8(func, 1, true)); @@ -258,7 +259,28 @@ TEST(TWEthereumAbi, GetParamWrongType) { EXPECT_EQ("", hex(*(static_cast(TWEthereumAbiFunctionGetParamAddress(func, 99, true))))); // delete - TWEthereumAbiEncoderDeleteFunction(func); + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, DecodeCall) { + auto callHex = STRING("c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000"); + auto call = WRAPD(TWDataCreateWithHexString(callHex.get())); + auto abi = STRING(R"|({"c47f0027":{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}})|"); + auto decoded = WRAPS(TWEthereumAbiDecodeCall(call.get(), abi.get())); + auto expected = R"|({"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]})|"; + + assertStringsEqual(decoded, expected); +} + +TEST(TWEthereumAbi, DecodeInvalidCall) { + auto callHex = STRING("c47f002700"); + auto call = WRAPD(TWDataCreateWithHexString(callHex.get())); + + auto decoded1 = TWEthereumAbiDecodeCall(call.get(), STRING(",,").get()); + auto decoded2 = TWEthereumAbiDecodeCall(call.get(), STRING("{}").get()); + + EXPECT_TRUE(decoded1 == nullptr); + EXPECT_TRUE(decoded2 == nullptr); } } // namespace TW::Ethereum diff --git a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp b/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp index fbe2f79bf2f..84075126924 100644 --- a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp +++ b/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include +#include #include "Data.h" #include "HexCoding.h" @@ -14,7 +14,7 @@ using namespace TW; using namespace std; -TEST(TWEthereumAbiValueDecoder, decodeUInt256) { +TEST(TWEthereumAbiValue, decodeUInt256) { const char * expected = "10020405371567"; auto inputs = vector{ "091d0eb3e2af", @@ -24,7 +24,7 @@ TEST(TWEthereumAbiValueDecoder, decodeUInt256) { }; for (auto &input: inputs) { auto data = WRAPD(TWDataCreateWithHexString(STRING(input.c_str()).get())); - auto result = WRAPS(TWEthereumAbiValueDecoderDecodeUInt256(data.get())); + auto result = WRAPS(TWEthereumAbiValueDecodeUInt256(data.get())); assertStringsEqual(result, expected); } } diff --git a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp b/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp index 9b3e7e83cb2..8821f72bcb5 100644 --- a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp +++ b/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include +#include #include "Data.h" #include "HexCoding.h" @@ -19,10 +19,10 @@ const string brownFox = "The quick brown fox jumps over the lazy dog"; const string brownFoxDot = "The quick brown fox jumps over the lazy dog."; const string hello = "hello"; -TEST(TWEthereumAbiValueEncoder, encodeBool) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBool(false)).get())), +TEST(TWEthereumAbiValue, encodeBool) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBool(false)).get())), "0000000000000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBool(true)).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBool(true)).get())), "0000000000000000000000000000000000000000000000000000000000000001"); } @@ -31,41 +31,41 @@ TWData* _Nonnull buildUInt256(const uint256_t& value) { return TWDataCreateWithBytes(data.data(), data.size()); } -TEST(TWEthereumAbiValueEncoder, encodeInt) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt32(69)).get())), +TEST(TWEthereumAbiValue, encodeInt) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt32(69)).get())), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt32(-1)).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt32(-1)).get())), "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeUInt32(69)).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeUInt32(69)).get())), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeUInt256(buildUInt256(uint256_t(69)))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeUInt256(buildUInt256(uint256_t(69)))).get())), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt256(buildUInt256(uint256_t(69)))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt256(buildUInt256(uint256_t(69)))).get())), "0000000000000000000000000000000000000000000000000000000000000045"); // int256(-1) - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeInt256(buildUInt256(~uint256_t(0)))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeInt256(buildUInt256(~uint256_t(0)))).get())), "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); } -TEST(TWEthereumAbiValueEncoder, encodeAddress) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeAddress(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), +TEST(TWEthereumAbiValue, encodeAddress) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeAddress(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), "0000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed"); } -TEST(TWEthereumAbiValueEncoder, encodeString) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeString(TWStringCreateWithUTF8Bytes("trustwallet"))).get())), +TEST(TWEthereumAbiValue, encodeString) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeString(TWStringCreateWithUTF8Bytes("trustwallet"))).get())), "31924c4e2bb082322d1efa718bf67c73ca297b481dac9f76ad35670cff0056a3"); } -TEST(TWEthereumAbiValueEncoder, encodeBytes) { - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("45")))).get())), +TEST(TWEthereumAbiValue, encodeBytes) { + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("45")))).get())), "4500000000000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBytes(TWDataCreateWithHexString(TWStringCreateWithUTF8Bytes("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")))).get())), "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed000000000000000000000000"); } -TEST(TWEthereumAbiValueEncoder, encodeBytesDyn) { +TEST(TWEthereumAbiValue, encodeBytesDyn) { std::string valueStr = "trustwallet"; - EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncoderEncodeBytesDyn(TWDataCreateWithBytes(reinterpret_cast(valueStr.c_str()), valueStr.length()))).get())), + EXPECT_EQ(hex(*reinterpret_cast(WRAPD(TWEthereumAbiValueEncodeBytesDyn(TWDataCreateWithBytes(reinterpret_cast(valueStr.c_str()), valueStr.length()))).get())), "31924c4e2bb082322d1efa718bf67c73ca297b481dac9f76ad35670cff0056a3"); } From c77825bf0a1e9a50ba6f7a37eb24065b7248a504 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Mon, 27 Jul 2020 20:49:25 +0800 Subject: [PATCH 64/81] Add kotlinx.coroutines to test and remove duplicated tests (#1050) --- android/app/build.gradle | 2 + .../blockchains/CoinAddressDerivationTests.kt | 11 ++- .../core/app/blockchains/TestHash.kt | 85 ------------------- .../app/blockchains/TestSegwitAddress.java | 43 ---------- .../core/app/utils/TestSegwitAddress.java | 8 -- src/Decred/Entry.h | 1 - src/Groestlcoin/Entry.h | 1 - src/NEO/Entry.h | 1 - src/Zcash/Entry.h | 1 - 9 files changed, 9 insertions(+), 144 deletions(-) delete mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt delete mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java diff --git a/android/app/build.gradle b/android/app/build.gradle index ba02131f006..644f816b115 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,6 +23,8 @@ android { dependencies { implementation project(':trustwalletcore') + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.0.2' 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 f48de1d0a76..69b763d1c82 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 @@ -5,6 +5,7 @@ import org.junit.Test import wallet.core.jni.HDWallet import wallet.core.jni.CoinType import wallet.core.jni.CoinType.* +import kotlinx.coroutines.* class CoinAddressDerivationTests { @@ -17,10 +18,12 @@ class CoinAddressDerivationTests { val wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", "") for (i in 0 .. 4) { - CoinType.values().forEach { coin -> - val privateKey = wallet.getKeyForCoin(coin) - val address = coin.deriveAddress(privateKey) - runDerivationChecks(coin, address) + GlobalScope.launch { + CoinType.values().forEach { coin -> + val privateKey = wallet.getKeyForCoin(coin) + val address = coin.deriveAddress(privateKey) + runDerivationChecks(coin, address) + } } } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt deleted file mode 100644 index a8b6f138dc6..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHash.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.trustwallet.core.app.blockchains - -import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.Hash -import org.junit.Assert.assertEquals -import org.junit.Test - -class TestHash { - - init { - System.loadLibrary("TrustWalletCore") - } - - @Test - fun testHashKeccak256() { - val bytes = Hash.keccak256("Test keccak-256".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x9aeb50f48121c80b2ff73ad48b5f197d940f748d936d35c992367370c1abfb18") - } - - @Test - fun testSha1() { - val bytes = Hash.sha1("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x5df9954f1ca26eabf18c663cc9258f7f1fd09f9e") - } - - @Test - fun testSha256() { - val bytes = Hash.sha256("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0xf250fc8f40aeea3297c0158ec1bfa07b503805f2a822530bd63c50625bb9376b") - } - - @Test - fun testSha512() { - val bytes = Hash.sha512("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x14ee17baf1fa3b1c5b3d3f232d609bc8e8c22dc1c4c8a81ac3d51468a27cc2431a54726d511f467d3420f37d5fc3694e8001990b706c4cc9239c397b4a7522e9") - } - - @Test - fun testKeccak512() { - val bytes = Hash.keccak512("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x6caadef962497d4ee4769b854b00cc0eb922cbfc1c8d676bc193ae9fc8d09c9c044d9771dfd96dc362db0dec6dba593a870806de283d177a5d07e36a9aa52077") - } - - @Test - fun testSha3_256() { - val bytes = Hash.sha3256("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0xc0f4cbc9992e2085fbe43a73bc1e2938f54babc0ede584d47d9df4e4511c8c62") - } - - @Test - fun testSha3_512() { - val bytes = Hash.sha3512("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x4fda1bee2d0c28e5eaf7fff8cbb48eed946a6aec7090c610d71896059fc942cfef1a56b811aefe31a750cce4f27921032100a7030aa8b347b3720494a1561fb9") - } - - @Test - fun testBlake2b256() { - val bytes = Hash.blake2b("Test hash".toByteArray(), 32) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0xe45cf5595c68cb024ad6ec872ab6b7e88377015712e775f643da6af788b5347f") - } - - @Test - fun testRipemd160() { - val bytes = Hash.ripemd("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, "0x687901a63dc3d0dc884232fbaee0badbda853cfa") - } - - @Test - fun testSha256RIPEMD() { - val bytesExpected = Hash.ripemd(Hash.sha256("Test hash".toByteArray())) - val expHex = Numeric.toHexString(bytesExpected) - val bytes = Hash.sha256RIPEMD("Test hash".toByteArray()) - val hex = Numeric.toHexString(bytes) - assertEquals(hex, expHex) - } -} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java deleted file mode 100644 index 15228b3d554..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestSegwitAddress.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.trustwallet.core.app.blockchains; - -import wallet.core.jni.SegwitAddress; -import wallet.core.jni.HRP; -import wallet.core.jni.PublicKey; -import wallet.core.jni.PublicKeyType; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class TestSegwitAddress { - - static { - System.loadLibrary("TrustWalletCore"); - } - - @Test - public void testFromPublic() { - byte[] data = {0x02, - (byte) 0xf1, (byte) 0xe7, (byte) 0x33, (byte) 0xed, (byte) 0x60, (byte) 0x30, (byte) 0xcc, (byte) 0x56, - (byte) 0x9c, (byte) 0x43, (byte) 0x23, (byte) 0xa3, (byte) 0x4b, (byte) 0x17, (byte) 0xe1, (byte) 0x92, - (byte) 0xd5, (byte) 0x81, (byte) 0x07, (byte) 0xd9, (byte) 0xff, (byte) 0xbc, (byte) 0xe7, (byte) 0x1c, - (byte) 0x84, (byte) 0x20, (byte) 0xb7, (byte) 0x79, (byte) 0xf4, (byte) 0x84, (byte) 0xdb, (byte) 0xa1 - }; - PublicKey publicKey = new PublicKey(data, PublicKeyType.SECP256K1); - SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); - assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); - } - - @Test - public void testBadHrp() { - byte[] data = {0x02, - (byte) 0xf1, (byte) 0xe7, (byte) 0x33, (byte) 0xed, (byte) 0x60, (byte) 0x30, (byte) 0xcc, (byte) 0x56, - (byte) 0x9c, (byte) 0x43, (byte) 0x23, (byte) 0xa3, (byte) 0x4b, (byte) 0x17, (byte) 0xe1, (byte) 0x92, - (byte) 0xd5, (byte) 0x81, (byte) 0x07, (byte) 0xd9, (byte) 0xff, (byte) 0xbc, (byte) 0xe7, (byte) 0x1c, - (byte) 0x84, (byte) 0x20, (byte) 0xb7, (byte) 0x79, (byte) 0xf4, (byte) 0x84, (byte) 0xdb, (byte) 0xa1 - }; - PublicKey publicKey = new PublicKey(data, PublicKeyType.SECP256K1); - SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); - assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); - } -} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java index 3e2345565eb..1289b970d56 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestSegwitAddress.java @@ -21,12 +21,4 @@ public void testFromPublic() { SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); } - - @Test - public void testBadHrp() { - byte[] data = Numeric.INSTANCE.hexStringToByteArray("0x02f1e733ed6030cc569c4323a34b17e192d58107d9ffbce71c8420b779f484dba1"); - PublicKey publicKey = new PublicKey(data, PublicKeyType.SECP256K1); - SegwitAddress address = new SegwitAddress(HRP.BITCOIN, publicKey); - assertEquals(address.description(), "bc1qrq6gs660qewd282en83n6em9s4rlslj3cd2wmg"); - } } diff --git a/src/Decred/Entry.h b/src/Decred/Entry.h index 1ee2fbf40e6..56b67534e2c 100644 --- a/src/Decred/Entry.h +++ b/src/Decred/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual std::vector coinTypes() const { return {TWCoinTypeDecred}; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - // normalizeAddress is not used virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; diff --git a/src/Groestlcoin/Entry.h b/src/Groestlcoin/Entry.h index 07f6fb9a980..a8d4d7f7a9d 100644 --- a/src/Groestlcoin/Entry.h +++ b/src/Groestlcoin/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual std::vector coinTypes() const { return {TWCoinTypeGroestlcoin}; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - // normalizeAddress is not used virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; diff --git a/src/NEO/Entry.h b/src/NEO/Entry.h index 784786b99cc..f381a4c6cab 100644 --- a/src/NEO/Entry.h +++ b/src/NEO/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual std::vector coinTypes() const { return {TWCoinTypeNEO}; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - // normalizeAddress is not used virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; diff --git a/src/Zcash/Entry.h b/src/Zcash/Entry.h index 6b430daac86..424a7b49fe1 100644 --- a/src/Zcash/Entry.h +++ b/src/Zcash/Entry.h @@ -16,7 +16,6 @@ class Entry: public CoinEntry { public: virtual std::vector coinTypes() const { return {TWCoinTypeZcash, TWCoinTypeZelcash}; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - // normalizeAddress is not used virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; From 0450f754730def9075d6fa7d74278c82ac2ae9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 28 Jul 2020 02:20:09 +0300 Subject: [PATCH 65/81] Elrond: update signing procedure (#1053) * Update serialization (for signing) algorithm * Include "chainID" and "version" in transaction * Adjust proto definition * Adjust serialization (re-enable base64) and tests --- .../blockchains/elrond/TestElrondSigner.kt | 10 +++--- src/Elrond/Serialization.cpp | 27 ++++++++-------- src/proto/Elrond.proto | 2 ++ swift/Tests/Blockchains/ElrondTests.swift | 10 +++--- tests/Elrond/SerializationTests.cpp | 20 ++++++++---- tests/Elrond/SignerTests.cpp | 32 +++++++++++-------- tests/Elrond/TWAnySignerTests.cpp | 16 ++++++---- 7 files changed, 68 insertions(+), 49 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt index 160dd9d7765..9b93c7dd59f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt @@ -36,9 +36,11 @@ class TestElrondSigner { .setValue("0") .setSender(aliceBech32) .setReceiver(bobBech32) - .setGasPrice(200000000000000) - .setGasLimit(500000000) + .setGasPrice(1000000000) + .setGasLimit(50000) .setData("foo") + .setChainId("1") + .setVersion(1) .build() val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) @@ -49,9 +51,9 @@ class TestElrondSigner { .build() val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308" + val expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00" assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":0,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"$expectedSignature"}""", output.encoded) + assertEquals("""{"nonce":0,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) } } diff --git a/src/Elrond/Serialization.cpp b/src/Elrond/Serialization.cpp index 193daf5d210..eaccaabf417 100644 --- a/src/Elrond/Serialization.cpp +++ b/src/Elrond/Serialization.cpp @@ -21,7 +21,9 @@ std::map fields_order { {"gasPrice", 5}, {"gasLimit", 6}, {"data", 7}, - {"signature", 8} + {"chainID", 8}, + {"version", 9}, + {"signature", 10} }; struct FieldsSorter { @@ -34,7 +36,7 @@ template using sorted_map = std::map; using sorted_json = nlohmann::basic_json; -string Elrond::serializeTransaction(const Proto::TransactionMessage& message) { +sorted_json preparePayload(const Elrond::Proto::TransactionMessage& message) { sorted_json payload { {"nonce", json(message.nonce())}, {"value", json(message.value())}, @@ -48,20 +50,19 @@ string Elrond::serializeTransaction(const Proto::TransactionMessage& message) { payload["data"] = json(TW::Base64::encode(TW::data(message.data()))); } + payload["chainID"] = json(message.chain_id()); + payload["version"] = json(message.version()); + + return payload; +} + +string Elrond::serializeTransaction(const Proto::TransactionMessage& message) { + sorted_json payload = preparePayload(message); return payload.dump(); } string Elrond::serializeSignedTransaction(const Proto::TransactionMessage& message, string signature) { - sorted_json payload { - {"nonce", json(message.nonce())}, - {"value", json(message.value())}, - {"receiver", json(message.receiver())}, - {"sender", json(message.sender())}, - {"gasPrice", json(message.gas_price())}, - {"gasLimit", json(message.gas_limit())}, - {"data", json(message.data())}, - {"signature", json(signature)}, - }; - + sorted_json payload = preparePayload(message); + payload["signature"] = json(signature); return payload.dump(); } diff --git a/src/proto/Elrond.proto b/src/proto/Elrond.proto index 41b4a0efc06..36cb5aaf2c2 100644 --- a/src/proto/Elrond.proto +++ b/src/proto/Elrond.proto @@ -18,6 +18,8 @@ message TransactionMessage { uint64 gas_price = 5; uint64 gas_limit = 6; string data = 7; + string chain_id = 8; + uint32 version = 9; } // Input data necessary to create a signed transaction. diff --git a/swift/Tests/Blockchains/ElrondTests.swift b/swift/Tests/Blockchains/ElrondTests.swift index c3e3e5b4b8c..e82ad7f0aeb 100644 --- a/swift/Tests/Blockchains/ElrondTests.swift +++ b/swift/Tests/Blockchains/ElrondTests.swift @@ -33,17 +33,19 @@ class ElrondTests: XCTestCase { $0.value = "0" $0.sender = aliceBech32 $0.receiver = bobBech32 - $0.gasPrice = 200000000000000 - $0.gasLimit = 500000000 + $0.gasPrice = 1000000000 + $0.gasLimit = 50000 $0.data = "foo" + $0.chainID = "1" + $0.version = 1 } $0.privateKey = privateKey.data } let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308" - let expectedEncoded = #"{"nonce":0,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"\#(expectedSignature)"}"# + let expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00" + let expectedEncoded = #"{"nonce":0,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# XCTAssertEqual(output.signature, expectedSignature) XCTAssertEqual(output.encoded, expectedEncoded) diff --git a/tests/Elrond/SerializationTests.cpp b/tests/Elrond/SerializationTests.cpp index 80c048bb9da..35110688955 100644 --- a/tests/Elrond/SerializationTests.cpp +++ b/tests/Elrond/SerializationTests.cpp @@ -21,10 +21,12 @@ TEST(ElrondSerialization, SignableString) { message.set_value("43"); message.set_sender("alice"); message.set_receiver("bob"); - message.set_data("foobar"); + message.set_data("foo"); + message.set_chain_id("1"); + message.set_version(1); string jsonString = serializeTransaction(message); - ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"bob","sender":"alice","gasPrice":0,"gasLimit":0,"data":"Zm9vYmFy"})", jsonString); + ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"bob","sender":"alice","gasPrice":0,"gasLimit":0,"data":"Zm9v","chainID":"1","version":1})", jsonString); } TEST(ElrondSerialization, SignableStringWithRealData) { @@ -33,11 +35,13 @@ TEST(ElrondSerialization, SignableStringWithRealData) { message.set_value("100"); message.set_sender(ALICE_BECH32); message.set_receiver(BOB_BECH32); - message.set_gas_price(200000000000000); - message.set_gas_limit(500000000); - message.set_data("for dinner"); + message.set_gas_price(1000000000); + message.set_gas_limit(50000); + message.set_data("foo"); + message.set_chain_id("1"); + message.set_version(1); - string expected = (boost::format(R"({"nonce":15,"value":"100","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"Zm9yIGRpbm5lcg=="})") % BOB_BECH32 % ALICE_BECH32).str(); + string expected = (boost::format(R"({"nonce":15,"value":"100","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1})") % BOB_BECH32 % ALICE_BECH32).str(); string actual = serializeTransaction(message); ASSERT_EQ(expected, actual); } @@ -48,7 +52,9 @@ TEST(ElrondSerialization, SignableStringWithoutData) { message.set_value("43"); message.set_sender("feed"); message.set_receiver("abba"); + message.set_chain_id("1"); + message.set_version(1); string jsonString = serializeTransaction(message); - ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"abba","sender":"feed","gasPrice":0,"gasLimit":0})", jsonString); + ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"abba","sender":"feed","gasPrice":0,"gasLimit":0,"chainID":"1","version":1})", jsonString); } diff --git a/tests/Elrond/SignerTests.cpp b/tests/Elrond/SignerTests.cpp index adda6c83a4d..39a019caf65 100644 --- a/tests/Elrond/SignerTests.cpp +++ b/tests/Elrond/SignerTests.cpp @@ -27,15 +27,17 @@ TEST(ElrondSigner, Sign) { input.mutable_transaction()->set_value("0"); input.mutable_transaction()->set_sender(ALICE_BECH32); input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(200000000000000); - input.mutable_transaction()->set_gas_limit(500000000); + input.mutable_transaction()->set_gas_price(1000000000); + input.mutable_transaction()->set_gas_limit(50000); input.mutable_transaction()->set_data("foo"); + input.mutable_transaction()->set_chain_id("1"); + input.mutable_transaction()->set_version(1); auto output = Signer::sign(input); auto signature = output.signature(); auto encoded = output.encoded(); - auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_EQ(expectedSignature, signature); ASSERT_EQ(expectedEncoded, encoded); @@ -43,12 +45,12 @@ TEST(ElrondSigner, Sign) { TEST(ElrondSigner, SignJSON) { // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000}})") % BOB_BECH32 % ALICE_BECH32).str(); + auto input = (boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str(); auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_EQ(expectedEncoded, encoded); } @@ -62,15 +64,17 @@ TEST(ElrondSigner, SignWithoutData) { input.mutable_transaction()->set_value("0"); input.mutable_transaction()->set_sender(ALICE_BECH32); input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(200000000000000); - input.mutable_transaction()->set_gas_limit(500000000); + input.mutable_transaction()->set_gas_price(1000000000); + input.mutable_transaction()->set_gas_limit(50000); input.mutable_transaction()->set_data(""); + input.mutable_transaction()->set_chain_id("1"); + input.mutable_transaction()->set_version(1); auto output = Signer::sign(input); auto signature = output.signature(); auto encoded = output.encoded(); - auto expectedSignature = "39ab0e18bfce04bf53c9610faa3b9e7cecfca919510a7631e529e9086279b70a4832df32a5d1b8fdceb4e9082f2995da97f9195532c8d611ee749bc312cbf90c"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "3079d37bfbdbe66fbb4c4b186144f9d9ad5b4b08fbcd6083be0688cf1171123109dfdefdbabf91425c757ca109b6db6d674cb9aeebb19a1a51333565abb53109"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_EQ(expectedSignature, signature); ASSERT_EQ(expectedEncoded, encoded); @@ -78,12 +82,12 @@ TEST(ElrondSigner, SignWithoutData) { TEST(ElrondSigner, SignJSONWithoutData) { // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"transaction" : {"value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000}})") % BOB_BECH32 % ALICE_BECH32).str(); + auto input = (boost::format(R"({"transaction" : {"value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str(); auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "39ab0e18bfce04bf53c9610faa3b9e7cecfca919510a7631e529e9086279b70a4832df32a5d1b8fdceb4e9082f2995da97f9195532c8d611ee749bc312cbf90c"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "3079d37bfbdbe66fbb4c4b186144f9d9ad5b4b08fbcd6083be0688cf1171123109dfdefdbabf91425c757ca109b6db6d674cb9aeebb19a1a51333565abb53109"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_EQ(expectedEncoded, encoded); } diff --git a/tests/Elrond/TWAnySignerTests.cpp b/tests/Elrond/TWAnySignerTests.cpp index f9366ab52ba..0124614bf06 100644 --- a/tests/Elrond/TWAnySignerTests.cpp +++ b/tests/Elrond/TWAnySignerTests.cpp @@ -26,17 +26,19 @@ TEST(TWAnySignerElrond, Sign) { input.mutable_transaction()->set_value("0"); input.mutable_transaction()->set_sender(ALICE_BECH32); input.mutable_transaction()->set_receiver(BOB_BECH32); - input.mutable_transaction()->set_gas_price(200000000000000); - input.mutable_transaction()->set_gas_limit(500000000); + input.mutable_transaction()->set_gas_price(1000000000); + input.mutable_transaction()->set_gas_limit(50000); input.mutable_transaction()->set_data("foo"); + input.mutable_transaction()->set_chain_id("1"); + input.mutable_transaction()->set_version(1); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeElrond); auto signature = output.signature(); auto encoded = output.encoded(); - auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_EQ(expectedSignature, signature); ASSERT_EQ(expectedEncoded, encoded); @@ -44,11 +46,11 @@ TEST(TWAnySignerElrond, Sign) { TEST(TWAnySignerElrond, SignJSON) { // Shuffle some fields, assume arbitrary order in the input - auto input = STRING((boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000}})") % BOB_BECH32 % ALICE_BECH32).str().c_str()); + auto input = STRING((boost::format(R"({"transaction" : {"data":"foo","value":"0","nonce":0,"receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainId":"1","version":1}})") % BOB_BECH32 % ALICE_BECH32).str().c_str()); auto privateKey = DATA(ALICE_SEED_HEX); auto encoded = WRAPS(TWAnySignerSignJSON(input.get(), privateKey.get(), TWCoinTypeElrond)); - auto expectedSignature = "b88ad2fe98a7316ea432a0a76c18cd87200fe75f27a8f053ea6532b40317dbec5136c5463aef132ae951b7e60d45d921caaa5903e70821dcda98f237d4ec4308"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":200000000000000,"gasLimit":500000000,"data":"foo","signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); + auto expectedSignature = "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"; + auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeElrond)); assertStringsEqual(encoded, expectedEncoded.c_str()); From cf8f918cf17a22e62f1080e28c70fb7983be9170 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Fri, 31 Jul 2020 11:39:50 +0800 Subject: [PATCH 66/81] change find_package for ubuntu (#1057) --- protobuf-plugin/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobuf-plugin/CMakeLists.txt b/protobuf-plugin/CMakeLists.txt index bc4bcb291f1..7375fc0efda 100644 --- a/protobuf-plugin/CMakeLists.txt +++ b/protobuf-plugin/CMakeLists.txt @@ -13,7 +13,7 @@ endif() include_directories(${PREFIX}/include) link_directories(${PREFIX}/lib) -find_library(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) +find_package(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) From d87969dfec7f56a77443c21994578a3a8e03844a Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Mon, 3 Aug 2020 16:55:43 +0800 Subject: [PATCH 67/81] [Polkadot] Update DOT decimals/path/tests (#1052) * Update DOT decimals/path/tests * Update cpp staking tests, android/swift test * Add mainnet bond dot tests * test kusama preimage --- .../blockchains/CoinAddressDerivationTests.kt | 4 +- .../blockchains/kusama/TestKusamaSigner.kt | 30 +-- .../polkadot/TestPolkadotSigner.kt | 54 +++++ coins.json | 6 +- src/Polkadot/Extrinsic.cpp | 70 +++++-- src/Polkadot/Extrinsic.h | 5 +- src/Polkadot/ScaleCodec.h | 8 +- src/proto/Polkadot.proto | 16 +- swift/Tests/Blockchains/KusamaTests.swift | 56 +---- swift/Tests/Blockchains/PolkadotTests.swift | 30 +++ swift/Tests/CoinAddressDerivationTests.swift | 4 +- swift/project.yml | 2 +- tests/Kusama/SignerTests.cpp | 197 ++---------------- tests/Kusama/TWAnySignerTests.cpp | 14 +- tests/Polkadot/ScaleCodecTests.cpp | 4 +- tests/Polkadot/SignerTests.cpp | 195 +++++++++++++++++ tests/Polkadot/TWCoinTypeTests.cpp | 10 +- 17 files changed, 418 insertions(+), 287 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt create mode 100644 tests/Polkadot/SignerTests.cpp 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 69b763d1c82..64c1ac17353 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 @@ -81,8 +81,8 @@ class CoinAddressDerivationTests { SOLANA -> assertEquals("2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m", address) TON -> assertEquals("EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo", address) ALGORAND -> assertEquals("JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ", address) - KUSAMA -> assertEquals("DE2jNrgosggXWJXfYDmRgy1q8XKkbtzSxj2uWAy5fbBfZwT", address) - POLKADOT -> assertEquals("1b97X8xTpFKMDzJpxiVhdYMNvekBDSfvGFf4DutxFkUjqfR", address) + KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) + POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) CARDANO -> assertEquals("addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk", address) NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt index 552d7e30173..d7e1a571fb8 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt @@ -11,43 +11,43 @@ import com.trustwallet.core.app.utils.toHexBytesInByteString import junit.framework.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner -import wallet.core.jni.CoinType.POLKADOT +import wallet.core.jni.CoinType.KUSAMA import wallet.core.jni.proto.Polkadot import wallet.core.jni.proto.Polkadot.SigningOutput -class TestPolkadotSigner { +class TestKusamaSigner { init { System.loadLibrary("TrustWalletCore") } @Test - fun PolkadotTransactionSigning() { - val key = "0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115".toHexBytesInByteString() + fun KusamaTransactionSigning() { + val key = "0x8cdc538e96f460da9d639afc5c226f477ce98684d77fb31e88db74c1f1dd86b2".toHexBytesInByteString() val hash = "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe".toHexBytesInByteString() val call = Polkadot.Balance.Transfer.newBuilder().apply { - toAddress = "FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP" - value = "3039".toHexBytesInByteString() - }.build() + toAddress = "CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY" + value = "0x02540be400".toHexBytesInByteString() + } - val signingInput = Polkadot.SigningInput.newBuilder().apply { + val input = Polkadot.SigningInput.newBuilder().apply { genesisHash = hash blockHash = hash - nonce = 0 - specVersion = 1031 + nonce = 1 + specVersion = 2019 network = Polkadot.Network.KUSAMA - extrinsicVersion = 4 + transactionVersion = 2 privateKey = key balanceCall = Polkadot.Balance.newBuilder().apply { - transfer = call + transfer = call.build() }.build() - }.build() + } - val output = AnySigner.sign(signingInput, POLKADOT, SigningOutput.parser()) + val output = AnySigner.sign(input.build(), KUSAMA, SigningOutput.parser()) val encoded = Numeric.toHexString(output.encoded.toByteArray()) - val expected = "0x2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0" + val expected = "0x350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402" assertEquals(encoded, expected) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt new file mode 100644 index 00000000000..f3b3b32edbe --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt @@ -0,0 +1,54 @@ +// Copyright © 2017-2020 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. + +package com.trustwallet.core.app.blockchains.polkadot + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytesInByteString +import junit.framework.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.POLKADOT +import wallet.core.jni.proto.Polkadot +import wallet.core.jni.proto.Polkadot.SigningOutput + +class TestPolkadotSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun PolkadotTransactionSigning() { + val key = "0x37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62".toHexBytesInByteString() + val hash = "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3".toHexBytesInByteString() + + val call = Polkadot.Staking.Bond.newBuilder().apply { + controller = "14CeRumfZBNBVux9GgaiR5qw9E8RndNsiFWvhcHs76HEPjbP" + value = "0x02540be400".toHexBytesInByteString() + rewardDestination = Polkadot.RewardDestination.STAKED + } + + val input = Polkadot.SigningInput.newBuilder().apply { + genesisHash = hash + blockHash = hash + nonce = 0 + specVersion = 17 + network = Polkadot.Network.POLKADOT + transactionVersion = 3 + privateKey = key + stakingCall = Polkadot.Staking.newBuilder().apply { + bond = call.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLKADOT, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + val expected = "0x3902848d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa00a559867d1304cc95bac7cfe5d1b2fd49aed9f43c25c7d29b9b01c1238fa1f6ffef34b9650e42325de41e20fd502af7b074c67a9ec858bd9a1ba6d4212e3e0d0f00000007008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0700e40b540200" + assertEquals(encoded, expected) + } +} diff --git a/coins.json b/coins.json index fc8793b6695..d02faac69de 100644 --- a/coins.json +++ b/coins.json @@ -1227,7 +1227,7 @@ "symbol": "KSM", "decimals": 12, "blockchain": "Polkadot", - "derivationPath": "m/44'/434'/0'", + "derivationPath": "m/44'/434'/0'/0'/0'", "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { @@ -1248,9 +1248,9 @@ "id": "polkadot", "name": "Polkadot", "symbol": "DOT", - "decimals": 15, + "decimals": 10, "blockchain": "Polkadot", - "derivationPath": "m/44'/354'/0'", + "derivationPath": "m/44'/354'/0'/0'/0'", "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { diff --git a/src/Polkadot/Extrinsic.cpp b/src/Polkadot/Extrinsic.cpp index 9aae2d2e0a4..c05dea2e6ed 100644 --- a/src/Polkadot/Extrinsic.cpp +++ b/src/Polkadot/Extrinsic.cpp @@ -5,12 +5,52 @@ // file LICENSE at the root of the source code distribution tree. #include "Extrinsic.h" +#include +#include using namespace TW; using namespace TW::Polkadot; static constexpr uint8_t signedBit = 0x80; static constexpr uint8_t sigTypeEd25519 = 0x00; +static constexpr uint8_t extrinsicFormat = 4; + +static const std::string balanceTransfer = "Balances.transfer"; +static const std::string stakingBond = "Staking.bond"; +static const std::string stakingBondExtra = "Staking.bond_extra"; +static const std::string stakingUnbond = "Staking.unbond"; +static const std::string stakingWithdrawUnbond = "Staking.withdraw_unbonded"; +static const std::string stakingNominate = "Staking.nominate"; +static const std::string stakingChill = "Staking.chill"; + +static std::map polkadotCallIndices = { + {balanceTransfer, Data{0x05, 0x00}}, + {stakingBond, Data{0x07, 0x00}}, + {stakingBondExtra, Data{0x07, 0x01}}, + {stakingUnbond, Data{0x07, 0x02}}, + {stakingWithdrawUnbond, Data{0x07, 0x03}}, + {stakingNominate, Data{0x07, 0x05}}, + {stakingChill, Data{0x07, 0x06}}, +}; + +static std::map kusamaCallIndices = { + {balanceTransfer, Data{0x04, 0x00}}, + {stakingBond, Data{0x06, 0x00}}, + {stakingBondExtra, Data{0x06, 0x01}}, + {stakingUnbond, Data{0x06, 0x02}}, + {stakingWithdrawUnbond, Data{0x06, 0x03}}, + {stakingNominate, Data{0x06, 0x05}}, + {stakingChill, Data{0x06, 0x06}}, +}; + +static Data getCallIndex(TWSS58AddressType network, const std::string& key) { + switch (network) { + case TWSS58AddressTypePolkadot: + return polkadotCallIndices[key]; + case TWSS58AddressTypeKusama: + return kusamaCallIndices[key]; + } +} Data Extrinsic::encodeEraNonceTip() const { Data data; @@ -24,14 +64,15 @@ Data Extrinsic::encodeEraNonceTip() const { } Data Extrinsic::encodeCall(const Proto::SigningInput& input) { - // call index from MetadataV10 + // call index from MetadataV11 Data data; + auto network = TWSS58AddressType(input.network()); if (input.has_balance_call()) { auto transfer = input.balance_call().transfer(); - auto address = SS58Address(transfer.to_address(), byte(input.network())); + auto address = SS58Address(transfer.to_address(), network); auto value = load(transfer.value()); // call index - append(data, {0x04, 0x00}); + append(data, getCallIndex(network, balanceTransfer)); // destination append(data, encodeAddress(address)); // value @@ -39,11 +80,11 @@ Data Extrinsic::encodeCall(const Proto::SigningInput& input) { } else if (input.has_staking_call()) { auto staking = input.staking_call(); if (staking.has_bond()) { - auto address = SS58Address(staking.bond().validator(), byte(input.network())); + auto address = SS58Address(staking.bond().controller(), byte(input.network())); auto value = load(staking.bond().value()); auto reward = byte(staking.bond().reward_destination()); // call index - append(data, {0x06, 0x00}); + append(data, getCallIndex(network, stakingBond)); // validator append(data, encodeAddress(address)); // value @@ -53,30 +94,33 @@ Data Extrinsic::encodeCall(const Proto::SigningInput& input) { } else if (staking.has_bond_extra()) { auto value = load(staking.unbond().value()); // call index - append(data, {0x06, 0x01}); + append(data, getCallIndex(network, stakingBondExtra)); // value append(data, encodeCompact(value)); } else if (staking.has_unbond()) { auto value = load(staking.unbond().value()); // call index - append(data, {0x06, 0x02}); + append(data, getCallIndex(network, stakingUnbond)); // value append(data, encodeCompact(value)); } else if (staking.has_withdraw_unbonded()) { + auto spans = staking.withdraw_unbonded().slashing_spans(); // call index - append(data, {0x06, 0x03}); + append(data, getCallIndex(network, stakingWithdrawUnbond)); + // num_slashing_spans + encode32LE(spans, data); } else if (staking.has_nominate()) { std::vector addresses; for (auto& n : staking.nominate().nominators()) { - addresses.push_back(SS58Address(n, byte(input.network()))); + addresses.push_back(SS58Address(n, network)); } // call index - append(data, {0x06, 0x05}); + append(data, getCallIndex(network, stakingNominate)); // nominators append(data, encodeAddresses(addresses)); } else if (staking.has_chill()) { // call index - append(data, {0x06, 0x06}); + append(data, getCallIndex(network, stakingChill)); } } return data; @@ -90,6 +134,8 @@ Data Extrinsic::encodePayload() const { append(data, encodeEraNonceTip()); // specVersion encode32LE(specVersion, data); + // transactionVersion + encode32LE(version, data); // genesis hash append(data, genesisHash); // block hash @@ -100,7 +146,7 @@ Data Extrinsic::encodePayload() const { Data Extrinsic::encodeSignature(const PublicKey& signer, const Data& signature) const { Data data; // version header - append(data, Data{ static_cast(version | signedBit)}); + append(data, Data{extrinsicFormat | signedBit}); // signer public key append(data, encodeAddress(signer)); // signature type diff --git a/src/Polkadot/Extrinsic.h b/src/Polkadot/Extrinsic.h index 3e25a39bc6e..2d3c759f8aa 100644 --- a/src/Polkadot/Extrinsic.h +++ b/src/Polkadot/Extrinsic.h @@ -14,6 +14,7 @@ namespace TW::Polkadot { +// ExtrinsicV4 class Extrinsic { public: Data blockHash; @@ -21,7 +22,7 @@ class Extrinsic { uint64_t nonce; // Runtime spec version uint32_t specVersion; - // Extrinsic version + // transaction version uint32_t version; // balances::TakeFees uint256_t tip; @@ -35,7 +36,7 @@ class Extrinsic { , genesisHash(input.genesis_hash().begin(), input.genesis_hash().end()) , nonce(input.nonce()) , specVersion(input.spec_version()) - , version(input.extrinsic_version()) + , version(input.transaction_version()) , tip(load(input.tip())) { if (input.has_era()) { era = encodeEra(input.era().phase(), input.era().period()); diff --git a/src/Polkadot/ScaleCodec.h b/src/Polkadot/ScaleCodec.h index abe79f63cf1..c9fea779452 100644 --- a/src/Polkadot/ScaleCodec.h +++ b/src/Polkadot/ScaleCodec.h @@ -99,13 +99,13 @@ inline Data encodeVector(std::vector& vec) { } inline Data encodeAddress(const PublicKey& key) { - auto data = Data{0xff}; + auto data = Data{}; append(data, Data(key.bytes.begin(), key.bytes.end())); return data; } inline Data encodeAddress(const SS58Address& address) { - auto data = Data{0xff}; + auto data = Data{}; // first byte is network append(data, Data(address.bytes.begin() + 1, address.bytes.end())); return data; @@ -121,8 +121,8 @@ inline Data encodeAddresses(const std::vector& addresses) { inline Data encodeEra(const uint64_t block, const uint64_t period) { // MortalEra(phase, period) - // See decodeMortalObject at https://github.com/polkadot-js/api/blob/master/packages/types/src/primitive/Extrinsic/ExtrinsicEra.ts#L74 - // See toU8a at https://github.com/polkadot-js/api/blob/master/packages/types/src/primitive/Extrinsic/ExtrinsicEra.ts#L141 + // See decodeMortalObject at https://github.com/polkadot-js/api/blob/master/packages/types/src/extrinsic/ExtrinsicEra.ts#L87 + // See toU8a at https://github.com/polkadot-js/api/blob/master/packages/types/src/extrinsic/ExtrinsicEra.ts#L167 uint64_t calPeriod = uint64_t(pow(2, ceil(log2(double(period))))); calPeriod = std::min(std::max(calPeriod, uint64_t(4)), uint64_t(1) << 16); uint64_t phase = block % calPeriod; diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index 0c51868b490..1710c32068c 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -31,7 +31,7 @@ message Balance { message Staking { message Bond { - string validator = 1; + string controller = 1; bytes value = 2; RewardDestination reward_destination = 3; } @@ -44,7 +44,9 @@ message Staking { bytes value = 1; } - message WithdrawUnbonded {} + message WithdrawUnbonded { + int32 slashing_spans = 1; + } message Nominate { repeated string nominators = 1; @@ -68,11 +70,11 @@ message SigningInput { bytes genesis_hash = 2; uint64 nonce = 3; uint32 spec_version = 4; - bytes tip = 5; // big integer - Era era = 6; // empty means Immortal - bytes private_key = 7; - Network network = 8; - uint32 extrinsic_version = 9; + uint32 transaction_version = 5; + bytes tip = 6; // big integer + Era era = 7; // empty means Immortal + bytes private_key = 8; + Network network = 9; oneof message_oneof { Balance balance_call = 10; diff --git a/swift/Tests/Blockchains/KusamaTests.swift b/swift/Tests/Blockchains/KusamaTests.swift index c089c409980..bb8ff23df6b 100644 --- a/swift/Tests/Blockchains/KusamaTests.swift +++ b/swift/Tests/Blockchains/KusamaTests.swift @@ -32,59 +32,19 @@ class KusamaTests: XCTestCase { XCTAssertEqual(address.data.hexString, pubkey.data.hexString) } - func testStorageKey() { - func generateStorageKey(module: String, function: String, publicKey: Data) -> Data { - var data = Data() - data.append(Hash.twoXXHash64Concat(data: module.data(using: .utf8)!)) - data.append(Hash.twoXXHash64Concat(data: function.data(using: .utf8)!)) - data.append(Hash.blake2b(data: publicKey, size: 32)) - return data - } - - let address = AnyAddress(string: "HKtMPUSoTC8Hts2uqcQVzPAuPRpecBt4XJ5Q1AT1GM3tp2r", coin: .kusama)! - let key = generateStorageKey(module: "Balances", function: "FreeBalance", publicKey: address.data) - XCTAssertEqual(key.hexString, "c2261276cc9d1f8598ea4b6a74b15c2f6482b9ade7bc6657aaca787ba1add3b4c801483fa04e8fd48dc5c5675891cfaab709696db6de3184d95d26a1c894f1f8") - } - func testSigningTransfer() { - let key = PrivateKey(data: Data(hexString: "0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")!)! - let address = CoinType.kusama.deriveAddress(privateKey: key) - - XCTAssertEqual(address.description, "FfmSiZNJP72xtSaXiP2iUhBwWeMEvmjPrxY2ViVkWaeChDC") - - let genesisHash = Data(hexString: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe")! - let input = PolkadotSigningInput.with { - $0.genesisHash = genesisHash - $0.blockHash = genesisHash - $0.nonce = 0 - $0.specVersion = 1031 - $0.balanceCall = PolkadotBalance.with { - $0.transfer = PolkadotBalance.Transfer.with { - $0.toAddress = "FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP" - $0.value = Data(hexString: "3039")! // 12345 - } - } - $0.network = .kusama - $0.extrinsicVersion = 4 - $0.privateKey = key.data - } - let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .kusama) - - XCTAssertEqual(output.encoded.hexString, "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") - } - - func testSigningTransfer2() { - // https://kusama.subscan.io/extrinsic/0x20cfbba19817e4b7a61e718d269de47e7067a24860fa978c2a8ead4c96a827c4 - // 1p test wallet + // https://kusama.subscan.io/extrinsic/0x9211b8f6500c78f4771d18289c6187ec59c2b1fb28e8324ee32a1f9a3303be7e + // real key in 1p test let wallet = HDWallet.test - let key = wallet.getKeyForCoin(coin: .kusama) + let key = wallet.getKey(derivationPath: "m/44'/434'/0'") + print(key.data.hexString) let genesisHash = Data(hexString: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe")! let input = PolkadotSigningInput.with { $0.genesisHash = genesisHash $0.blockHash = genesisHash - $0.nonce = 0 - $0.specVersion = 1031 + $0.nonce = 1 + $0.specVersion = 2019 $0.balanceCall = PolkadotBalance.with { $0.transfer = PolkadotBalance.Transfer.with { $0.toAddress = "CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY" @@ -93,11 +53,11 @@ class KusamaTests: XCTestCase { } } $0.network = .kusama - $0.extrinsicVersion = 4 + $0.transactionVersion = 2 $0.privateKey = key.data } let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .kusama) - XCTAssertEqual(output.encoded.hexString, "3d0284fff41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae70043e0fe7497f1d11ca6635b7860ef9551d395172b18af22c16e375326326f524cd32ffafb3e1e73112f016a8028c50eebb608df29523751a11147e36a49f2d40a0000000400ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402") + XCTAssertEqual(output.encoded.hexString, "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402") } } diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index 6068414f23e..85f34a8ae29 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -31,4 +31,34 @@ class PolkadotTests: XCTestCase { XCTAssertEqual(address.description, addressFromString.description) XCTAssertEqual(address.data, pubkey.data) } + + func testSigningBond() { + // https://polkadot.subscan.io/extrinsic/0x5ec2ec6633b4b6993d9cf889ef42c457a99676244dc361a9ae17935d331dc39a + // real key in 1p test + let wallet = HDWallet.test + let key = wallet.getKey(derivationPath: "m/44'/354'/0'") + print(key.data.hexString) + + let address = CoinType.polkadot.deriveAddress(privateKey: key) + print(address) + let genesisHash = Data(hexString: "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")! + let input = PolkadotSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = genesisHash + $0.nonce = 0 + $0.specVersion = 17 + $0.network = .polkadot + $0.transactionVersion = 3 + $0.privateKey = key.data + $0.stakingCall.bond = PolkadotStaking.Bond.with { + $0.controller = address + $0.rewardDestination = .staked + // 0.01 + $0.value = Data(hexString: "0x02540be400")! + } + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + XCTAssertEqual(output.encoded.hexString, "3902848d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa00a559867d1304cc95bac7cfe5d1b2fd49aed9f43c25c7d29b9b01c1238fa1f6ffef34b9650e42325de41e20fd502af7b074c67a9ec858bd9a1ba6d4212e3e0d0f00000007008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0700e40b540200") + } } diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index fcdec76a55b..978bc0c8b31 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -176,10 +176,10 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) case .kusama: - let expectedResult = "DE2jNrgosggXWJXfYDmRgy1q8XKkbtzSxj2uWAy5fbBfZwT" + let expectedResult = "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) case .polkadot: - let expectedResult = "1b97X8xTpFKMDzJpxiVhdYMNvekBDSfvGFf4DutxFkUjqfR" + let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) case .kava: let expectedResult = "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" diff --git a/swift/project.yml b/swift/project.yml index 0b3f9ec67e9..3a47df9d91e 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -47,7 +47,7 @@ targets: BUILD_LIBRARY_FOR_DISTRIBUTION: true INFOPLIST_FILE: 'Info.plist' CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION: YES_ERROR - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: true + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: $(inherited) true OTHER_CFLAGS: $(inherited) -Wno-comma postCompileScripts: - script: ${PODS_ROOT}/SwiftLint/swiftlint --config ../.swiftlint.yml diff --git a/tests/Kusama/SignerTests.cpp b/tests/Kusama/SignerTests.cpp index d0f52b2bfaf..a4f3c72b3fa 100644 --- a/tests/Kusama/SignerTests.cpp +++ b/tests/Kusama/SignerTests.cpp @@ -5,205 +5,48 @@ // file LICENSE at the root of the source code distribution tree. #include "Polkadot/Signer.h" +#include "Polkadot/Extrinsic.h" +#include "SS58Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "PublicKey.h" #include "proto/Polkadot.pb.h" #include "uint256.h" +#include #include -using namespace TW; -using namespace TW::Polkadot; -TEST(PolkadotSigner, SignTransfer) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); +namespace TW::Polkadot { + extern PrivateKey privateKey; + extern PublicKey toPublicKey; + auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto balanceCall = input.mutable_balance_call(); - auto &transfer = *balanceCall->mutable_transfer(); - auto value = store(uint256_t(12345)); - transfer.set_to_address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); - transfer.set_value(value.data(), value.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -TEST(PolkadotSigner, SignTransferEra) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto blockHash = parse_hex("0xee17a6af2ea3f8c230e184e6821f7dfd56b60b9ca46b06e713acd9c8c834a5ee"); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); +TEST(PolkadotSigner, SignTransferKSM) { + auto blockHash = parse_hex("4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypeKusama); auto input = Proto::SigningInput(); input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_genesis_hash(genesisHashKSM.data(), genesisHashKSM.size()); input.set_nonce(0); - input.set_spec_version(1031); + input.set_spec_version(2019); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto &era = *input.mutable_era(); - era.set_phase(429119); - era.set_period(8); + input.set_transaction_version(2); auto balanceCall = input.mutable_balance_call(); auto &transfer = *balanceCall->mutable_transfer(); auto value = store(uint256_t(12345)); - transfer.set_to_address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); + transfer.set_to_address(toAddress.string()); transfer.set_value(value.data(), value.size()); + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); auto output = Signer::sign(input); - ASSERT_EQ(hex(output.encoded()), "310284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee004898684cbde8f04384e381636326f5a7334dfa9617e66771eedb2b8cb1d3b9415531305ad70e4e38accd06ef0fbe5963d928b59979f1807500170981af335306720000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -TEST(PolkadotSigner, SignNominate) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &nominate = *stakingCall->mutable_nominate(); - nominate.add_nominators("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); - nominate.add_nominators("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "ad0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00855fe8ba83a200c7b363fe7c9f315cc5578e25745a4131584713c3642644ed43ae503a2fe10bcc6a4d5eb6ebc96d5f17e4035f3658e8d6de8998f68a66a3df01000000060508ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); -} - -TEST(PolkadotSigner, SignNominate2) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &nominate = *stakingCall->mutable_nominate(); - // payload size larger than 256, will be hashed - nominate.add_nominators("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); - nominate.add_nominators("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); - nominate.add_nominators("HJ1dGPxVr13KHGiCTGQfZjZMKGc8J52CsHEjcEXNMDyeGxf"); - nominate.add_nominators("F4LocUbsPrcC8xVap4wiTgDakzn3xFyXneuYDHRaHxnb6dH"); - nominate.add_nominators("DriCrAgdVV57NeQm5bWn5KQpVndVnXnm55BjRpe6qzZ5ktJ"); - nominate.add_nominators("GiBnzCGFofhmAvsUv9FUShUb8YJYYwWex3ThQNkbDDNprS6"); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "bd0484ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee006d071e9e29e9108f07b9127a6a71f5e2b44b8c30452878aeb4d0fe12f7f0c43bca5e7eca7e199e1287ab26422903069ce14afead5fb3878f9f036578c369a80e000000060518ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72ffd0bd030f88d88e9746e7e684a210d0e1b8b7db04d6a3dad1da047e7200c21e10ff6dd7ab69a1bccd2dbb782487f801509bf3aed97db9492b99f0d4e7ad08896e1cff38bc883d8fb7ae97eb3a7862cff1252172f4a901264c84b7feee0a900a64f77dffb6f0e5386adc413a67e628050d4ca4fc8ee2f170ed959465feff17bd88b7cf3c"); + ASSERT_EQ(hex(preimage), "04008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0000000e307000002000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); } -TEST(PolkadotSigner, SignChill) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "a10184ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee004c4801e3edba279dbb6e017f4c5fc9fc263a51a5d1e2f59e44e0f709541522d3c3cdd3d7ed1225913901e177ac4161df846632ddb7bad1f5b504612fa1ee620b0000000606"); -} - -TEST(PolkadotSigner, SignWithdraw) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto __attribute__((unused)) &withdraw = *stakingCall->mutable_withdraw_unbonded(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "a10184ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00c595893548338fa3f90f1cf1e9b650f357925e29d26148ae732d6307fd630d56639448786841b36d7ff13023a633891c47d2cf0e79f03a26105f369a69f8c2010000000603"); -} - -TEST(PolkadotSigner, SignUnbond) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &unbond = *stakingCall->mutable_unbond(); - - auto value = store(uint256_t(123456)); - unbond.set_value(value.data(), value.size()); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "b10184ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee005e29b1763a6287af1daa0487097850040038b8517df8cd47c57d8f9e8cb203872ff9f755c7b69ddefee98a0ad0dfbbcde757f33e4cba67331b323f8816af100a000000060202890700"); -} - -TEST(PolkadotSigner, SignBond) { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - auto input = Proto::SigningInput(); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); - - auto stakingCall = input.mutable_staking_call(); - auto &bond = *stakingCall->mutable_bond(); - - auto value = store(uint256_t(123456)); - - bond.set_validator("FfmSiZNJP72xtSaXiP2iUhBwWeMEvmjPrxY2ViVkWaeChDC"); - bond.set_value(value.data(), value.size()); - bond.set_reward_destination(Proto::RewardDestination::STAKED); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "390284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee006589694e6696e2173c0c4a1a0353267410546fb8f0970d037a6d079719c0e440a1af26bafae266fb383b41936682d74b68bb4018f7565f27fb7a6646a5c7cb0d0000000600ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0289070000"); -} +} // namespace diff --git a/tests/Kusama/TWAnySignerTests.cpp b/tests/Kusama/TWAnySignerTests.cpp index 726e860a289..5e4a0584079 100644 --- a/tests/Kusama/TWAnySignerTests.cpp +++ b/tests/Kusama/TWAnySignerTests.cpp @@ -16,26 +16,26 @@ using namespace TW; using namespace TW::Polkadot; TEST(TWAnySignerKusama, Sign) { - auto key = parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"); + auto key = parse_hex("0x8cdc538e96f460da9d639afc5c226f477ce98684d77fb31e88db74c1f1dd86b2"); auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); Proto::SigningInput input; input.set_block_hash(genesisHash.data(), genesisHash.size()); input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(0); - input.set_spec_version(1031); + input.set_nonce(1); + input.set_spec_version(2019); input.set_private_key(key.data(), key.size()); input.set_network(Proto::Network::KUSAMA); - input.set_extrinsic_version(4); + input.set_transaction_version(2); auto balanceCall = input.mutable_balance_call(); auto &transfer = *balanceCall->mutable_transfer(); - auto value = store(uint256_t(12345)); - transfer.set_to_address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); + auto value = store(uint256_t(10000000000)); + transfer.set_to_address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); transfer.set_value(value.data(), value.size()); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeKusama); - ASSERT_EQ(hex(output.encoded()), "2d0284ff88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0034a113577b56545c45e18969471eebe11ed434f3b2f06e2e3dc8dc137ba804caf60757787ebdeb298327e2f29d68c5520965405ef5582db0445c06e1c11a8a0e0000000400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); + ASSERT_EQ(hex(output.encoded()), "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402"); } diff --git a/tests/Polkadot/ScaleCodecTests.cpp b/tests/Polkadot/ScaleCodecTests.cpp index e5636cbaa01..13231d58465 100644 --- a/tests/Polkadot/ScaleCodecTests.cpp +++ b/tests/Polkadot/ScaleCodecTests.cpp @@ -56,7 +56,7 @@ TEST(PolkadotCodec, EncodeAddress) { auto address = Kusama::Address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP"); auto encoded = encodeAddress(address); - ASSERT_EQ(hex(encoded), "ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"); + ASSERT_EQ(hex(encoded), "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"); } TEST(PolkadotCodec, EncodeVectorAddresses) { @@ -65,7 +65,7 @@ TEST(PolkadotCodec, EncodeVectorAddresses) { Kusama::Address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY") }; auto encoded = encodeAddresses(addresses); - ASSERT_EQ(hex(encoded), "08ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48ff0e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); + ASSERT_EQ(hex(encoded), "088eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); } TEST(PolkadotCodec, EncodeEra) { diff --git a/tests/Polkadot/SignerTests.cpp b/tests/Polkadot/SignerTests.cpp new file mode 100644 index 00000000000..a43156321de --- /dev/null +++ b/tests/Polkadot/SignerTests.cpp @@ -0,0 +1,195 @@ +// Copyright © 2017-2020 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. + +#include "Polkadot/Signer.h" +#include "Polkadot/Extrinsic.h" +#include "SS58Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" + +#include +#include + + +namespace TW::Polkadot { + auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); + auto toPublicKey = PublicKey(parse_hex("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519); + auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + +TEST(PolkadotSigner, SignTransferDOT) { + + auto blockHash = parse_hex("0x343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypePolkadot); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto &era = *input.mutable_era(); + era.set_phase(927699); + era.set_period(8); + + auto balanceCall = input.mutable_balance_call(); + auto &transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(12345)); + transfer.set_to_address(toAddress.string()); + transfer.set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(preimage), "05008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c032000000110000000300000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + ASSERT_EQ(hex(output.encoded()), "29028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee003d91a06263956d8ce3ce5c55455baefff299d9cb2bb3f76866b6828ee4083770b6c03b05d7b6eb510ac78d047002c1fe5c6ee4b37c9c5a8b09ea07677f12e50d3200000005008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); +} + +TEST(PolkadotSigner, SignNominate) { + auto blockHash = parse_hex("52bc855411f95698cb987dc9af93f719f2e06e87bfa7b75a83d58baafd08bca7"); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto &nominate = *stakingCall->mutable_nominate(); + + nominate.add_nominators("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + nominate.add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "a1028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0038c999e3878beb7a927ff63483390f6e237120612678d5cc9c40c27028250d94c9a894cf8befe8444ff4b69a0b64c2220aa7c30f886f54e0589f73257f59ed0f0000000705082c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f18127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a"); +} + +TEST(PolkadotSigner, SignNominate2) { + auto blockHash = parse_hex("d22a6b2e3e61325050718bd04a14da9efca1f41c9f0a525c375d36106e25af68"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto &nominate = *stakingCall->mutable_nominate(); + // payload size larger than 256, will be hashed + nominate.add_nominators("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + nominate.add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + nominate.add_nominators("1WG3jyNqniQMRZGQUc7QD2kVLT8hkRPGMSqAb5XYQM1UDxN"); + nominate.add_nominators("16QFrtU6kDdBjxY8qEKz5EEfuDkHxqG8pix3wSGKQzRcuWHo"); + nominate.add_nominators("14ShUZUYUR35RBZW6uVVt1zXDxmSQddkeDdXf1JkMA6P721N"); + nominate.add_nominators("15MUBwP6dyVw5CXF9PjSSv7SdXQuDSwjX86v1kBodCSWVR7c"); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "a1048488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00135bbc68b67fffadaf7e98b6402c4fc60382765f543225083a024b0e0ff8071d4ec4ddd67a65828113cc76f3208765608be010d2fcfdcd47e8fe342872704c000000000705182c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f18127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a1650c532ed1a8641e8922aa24ade0ff411d03edd9ed1c6b7fe42f1a801cee37ceee9d5d071a418b51c02b456d5f5cefd6231041ad59b0e8379c59c11ba4a2439984e16482c99cfad1436111e321a86d87d0fac203bf64538f888e45d793b5413c08d5de7a5d97bea2c7ddf516d0635bddc43f326ae2f80e2595b49d4a08c4619"); +} + +TEST(PolkadotSigner, SignChill) { + auto blockHash = parse_hex("1d4a1ecc8b1c37bf0ba5d3e0bf14ec5402fbb035eeaf6d8042c07ca5f8c57429"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "9d018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0088b5e1cd93ba74b82e329f95e1b22660385970182172b2ae280801fdd1ee5652cf7bf319e5e176ccc299dd8eb1e7fccb0ea7717efaf4aacd7640789dd09c1e070000000706"); +} + +TEST(PolkadotSigner, SignWithdraw) { + auto blockHash = parse_hex("7b4d1d1e2573eabcc90a3e96058eb0d8d21d7a0b636e8030d152d9179a345dda"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto &withdraw = *stakingCall->mutable_withdraw_unbonded(); + withdraw.set_slashing_spans(10); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "ad018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee002e49bf0dec9bef01dd3bd25419e2147dc983613d0860108f889f9ff2d062c5e3267e309e2dbc35dd2fc2b877b57d86a5f12cbeb8217485be32be3c34d2507d0e00000007030a000000"); +} + +TEST(PolkadotSigner, SignUnbond) { + auto blockHash = parse_hex("84c9509904b5b2b3ff0a35fc7f033db9306e6bf0700c32d44e049578bfd33ba1"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto &unbond = *stakingCall->mutable_unbond(); + + auto value = store(uint256_t(123456)); + unbond.set_value(value.data(), value.size()); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "ad018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00cababd023dd35e6639b5a55fb35e5863142e15637d2942c7de58ab966a6d81c5794e9a2fa53b1da7c5f9ed272e52f865fb9ccf1ccf5881bbee7af357101bf90c000000070202890700"); +} + +TEST(PolkadotSigner, SignBond) { + auto blockHash = parse_hex("94f4552ef9412a85d8728717f09128bb21548a2e5eec2e7e5cefbe72af17aad7"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(Proto::Network::POLKADOT); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto &bond = *stakingCall->mutable_bond(); + + auto value = store(uint256_t(123456)); + + bond.set_controller("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + bond.set_value(value.data(), value.size()); + bond.set_reward_destination(Proto::RewardDestination::STAKED); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "31028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0096fde35692962591e8f41782c5bbe4c3c01022bcfc140a0b11f00502997817263fcaf476477e0db6abbc1d3c9a231aed8ba0ee6d5105db08ac2a3dd68d3c870000000007002c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f180289070000"); +} + +} // namespace diff --git a/tests/Polkadot/TWCoinTypeTests.cpp b/tests/Polkadot/TWCoinTypeTests.cpp index 43b87491c5f..1799dc2b26f 100644 --- a/tests/Polkadot/TWCoinTypeTests.cpp +++ b/tests/Polkadot/TWCoinTypeTests.cpp @@ -16,20 +16,20 @@ TEST(TWPolkadotCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolkadot)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolkadot, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePolkadot, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePolkadot)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePolkadot)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolkadot), 15); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolkadot), 10); ASSERT_EQ(TWBlockchainPolkadot, TWCoinTypeBlockchain(TWCoinTypePolkadot)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePolkadot)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePolkadot)); assertStringsEqual(symbol, "DOT"); - assertStringsEqual(txUrl, "https://polkadot.subscan.io/extrinsic/t123"); - assertStringsEqual(accUrl, "https://polkadot.subscan.io/account/a12"); + assertStringsEqual(txUrl, "https://polkadot.subscan.io/extrinsic/0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4"); + assertStringsEqual(accUrl, "https://polkadot.subscan.io/account/13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2"); assertStringsEqual(id, "polkadot"); assertStringsEqual(name, "Polkadot"); } From cd3f7bcb7221cbc312fd7ecc75d8dcda28eb665f Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Wed, 5 Aug 2020 02:12:52 +0200 Subject: [PATCH 68/81] Add locking to setting up dispatchers. (#1062) * Add locking to setting up dispatchers. * Multi-threaded test for Coins initialization. * Add thread-safety to CoinInfoData initialization. --- codegen/lib/templates/CoinInfoData.cpp.erb | 16 ++++++++-- src/Coin.cpp | 10 ++++++- tests/CoinAddressDerivationTests.cpp | 34 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index dcda64a0663..ef449df6fb1 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.erb @@ -11,11 +11,14 @@ #include #include +#include +#include using namespace TW; /// Map with info about known coins static std::map coins; +static std::mutex coinsMutex; static const CoinInfo defaultsForMissing = { "?", @@ -43,13 +46,22 @@ void fillMap(); // forward /// Get coin from map, if missing returns defaults (not to have contains-check in each accessor method) const CoinInfo& getCoinInfo(TWCoinType coin) { - if (coins.size() == 0) { fillMap(); } - if (coins.find(coin) == coins.end()) { return defaultsForMissing; } + if (coins.size() == 0) { + fillMap(); + } + assert(coins.size() > 0); + if (coins.find(coin) == coins.end()) { + return defaultsForMissing; + } return coins.at(coin); } /// Fill the map. Simple static initializer did not work out due to nondeterministic static initialization order void fillMap() { + std::lock_guard guard(coinsMutex); + if (coins.size() > 0) { + return; + } coins = std::map { <% coins.each do |coin| -%> { diff --git a/src/Coin.cpp b/src/Coin.cpp index 2df5a0e5537..3d370443db4 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -12,6 +12,7 @@ #include #include +#include // Includes for entry points for coin implementations #include "Aeternity/Entry.h" @@ -59,6 +60,7 @@ using namespace std; // Map with coin entry dispatchers, key is coin type map dispatchMap = {}; +mutex dispatchMapMutex; // List of supported coint types set coinTypes = {}; @@ -104,6 +106,11 @@ void setupDispatchers() { new Elrond::Entry(), }; // end_of_coin_entries_marker_do_not_modify + lock_guard guard(dispatchMapMutex); + if (dispatchMap.size() > 0) { + // already set up, skip + return; + } dispatchMap.clear(); coinTypes.clear(); for (auto d : dispatchers) { @@ -117,7 +124,6 @@ void setupDispatchers() { }; } } - return; // Note: dispatchers are created at first use, and never freed } @@ -125,6 +131,8 @@ inline void setupDispatchersIfNeeded() { if (dispatchMap.size() == 0) { setupDispatchers(); } + assert(dispatchMap.size() > 0); + // it is set up by this time, and will not get modified } CoinEntry* coinDispatcher(TWCoinType coinType) { diff --git a/tests/CoinAddressDerivationTests.cpp b/tests/CoinAddressDerivationTests.cpp index 1bf597c6d88..0efb09a78d4 100644 --- a/tests/CoinAddressDerivationTests.cpp +++ b/tests/CoinAddressDerivationTests.cpp @@ -9,6 +9,9 @@ #include +#include +#include + namespace TW { TEST(Coin, DeriveAddress) { @@ -74,4 +77,35 @@ TEST(Coin, DeriveAddress) { EXPECT_EQ(TW::deriveAddress(TWCoinTypeElrond, privateKey), "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); } +int countThreadReady = 0; +std::mutex countThreadReadyMutex; + +void useCoinFromThread() { + const int tryCount = 20; + for (int i = 0; i < tryCount; ++i) { + // perform some operations + const auto coinTypes = TW::getCoinTypes(); + TW::validateAddress(TWCoinTypeEthereum, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + } + countThreadReadyMutex.lock(); + ++countThreadReady; + countThreadReadyMutex.unlock(); +} + +TEST(Coin, InitMultithread) { + const int numThread = 20; + countThreadReady = 0; + std::thread thread[numThread]; + // execute in threads + for (int i = 0; i < numThread; ++i) { + thread[i] = std::thread(useCoinFromThread); + } + // wait for completion + for (int i = 0; i < numThread; ++i) { + thread[i].join(); + } + // check that all completed OK + ASSERT_EQ(countThreadReady, numThread); +} + } // namespace TW From fe98234dd230554ee62f73ccc1203884af0b7e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 10 Aug 2020 16:19:09 +0300 Subject: [PATCH 69/81] Changed the token symbol from ERD to eGLD (#1067) --- coins.json | 2 +- docs/coins.md | 2 +- tests/Elrond/TWCoinTypeTests.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coins.json b/coins.json index d02faac69de..77670f56b25 100644 --- a/coins.json +++ b/coins.json @@ -1362,7 +1362,7 @@ { "id": "elrond", "name": "Elrond", - "symbol": "ERD", + "symbol": "eGLD", "decimals": 18, "blockchain": "ElrondNetwork", "derivationPath": "m/44'/508'/0'/0'/0'", diff --git a/docs/coins.md b/docs/coins.md index 1c19c8c2c3c..39221d07e8a 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -45,7 +45,7 @@ This list is generated from [./coins.json](../coins.json) | 494 | BandChain | BAND | | | | 500 | Theta | THETA | | | | 501 | Solana | SOL | | | -| 508 | Elrond | ERD | | | +| 508 | Elrond | eGLD | | | | 714 | Binance | BNB | | | | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | diff --git a/tests/Elrond/TWCoinTypeTests.cpp b/tests/Elrond/TWCoinTypeTests.cpp index 9e73e615721..fa8238e79f4 100644 --- a/tests/Elrond/TWCoinTypeTests.cpp +++ b/tests/Elrond/TWCoinTypeTests.cpp @@ -26,7 +26,7 @@ TEST(TWElrondCoinType, TWCoinType) { ASSERT_EQ(TWBlockchainElrondNetwork, TWCoinTypeBlockchain(TWCoinTypeElrond)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeElrond)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeElrond)); - assertStringsEqual(symbol, "ERD"); + assertStringsEqual(symbol, "eGLD"); assertStringsEqual(txUrl, "https://explorer.elrond.com/transactions/1fc9785cb8bea0129a16cf7bddc97630c176a556ea566f0e72923c882b5cb3c8"); assertStringsEqual(accUrl, "https://explorer.elrond.com/address/erd12yne790km8ezwetkz7m3hmqy9utdc6vdkgsunfzpwguec6v04p2qtk9uqj"); assertStringsEqual(id, "elrond"); From 8d3100f61e36d1e928ed1dea60ff7554bba0db16 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Wed, 12 Aug 2020 10:07:51 +0800 Subject: [PATCH 70/81] Add Binance SmartChain (#1064) * Add binance smart chain defines * Fix swift tests * Fix android tests * Add bc/bsc account tests * add signer tests * rename to coinId and add swift key store test * add cross chain transfer / staking messages * Add swift tests and bva prefix * add hrp validator constant --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../core/app/blockchains/TestHDWallet.kt | 4 +- .../TestBinanceSmartChainAddress.kt | 31 + codegen/bin/coins | 7 +- codegen/bin/cointests | 5 - codegen/bin/newcoin | 13 +- codegen/lib/coin_test_gen.rb | 4 - codegen/lib/templates/CoinInfoData.cpp.erb | 2 + codegen/lib/templates/coins.md.erb | 4 +- coins.json | 2780 +++++++++-------- docs/coins.md | 1 + include/TrustWalletCore/TWAccount.h | 2 +- include/TrustWalletCore/TWCoinType.h | 5 + include/TrustWalletCore/TWEthereumChainID.h | 1 + include/TrustWalletCore/TWHDWallet.h | 4 +- include/TrustWalletCore/TWStoredKey.h | 2 +- src/Binance/Address.cpp | 13 + src/Binance/Address.h | 3 +- src/Binance/Serialization.cpp | 87 +- src/Binance/Serialization.h | 11 +- src/Binance/Signer.cpp | 16 + src/Coin.cpp | 4 + src/Coin.h | 3 + src/DerivationPath.h | 8 +- src/Ethereum/Entry.h | 1 + src/HDWallet.cpp | 12 +- src/HDWallet.h | 6 +- src/Keystore/Account.cpp | 21 +- src/Keystore/Account.h | 7 +- src/Keystore/StoredKey.cpp | 58 +- src/Keystore/StoredKey.h | 2 +- src/interface/TWAccount.cpp | 6 +- src/interface/TWAnyAddress.cpp | 1 + src/interface/TWCoinType.cpp | 4 + src/interface/TWHDWallet.cpp | 17 +- src/interface/TWStoredKey.cpp | 4 +- src/proto/Binance.proto | 33 + swift/Sources/DerivationPath.swift | 10 +- .../Extensions/HDWallet+Extension.swift | 16 - .../Tests/Blockchains/BinanceChainTests.swift | 52 +- .../Blockchains/BinanceSmartChainTests.swift | 20 + swift/Tests/Blockchains/BitconCashTests.swift | 14 +- swift/Tests/Blockchains/DashTests.swift | 12 +- swift/Tests/Blockchains/DecredTests.swift | 6 +- swift/Tests/Blockchains/DogeTests.swift | 6 +- .../Tests/Blockchains/GroestlcoinTests.swift | 12 +- swift/Tests/Blockchains/KusamaTests.swift | 2 +- swift/Tests/Blockchains/LitecoinTests.swift | 36 +- swift/Tests/Blockchains/MonacoinTests.swift | 36 +- swift/Tests/Blockchains/PolkadotTests.swift | 2 +- swift/Tests/Blockchains/QtumTests.swift | 25 +- swift/Tests/Blockchains/ZcashTests.swift | 6 +- swift/Tests/CoinAddressDerivationTests.swift | 125 +- swift/Tests/DerivationPathTests.swift | 4 +- swift/Tests/HDWalletTests.swift | 40 +- swift/Tests/Keystore/Data/bnb_wallet.json | 33 + swift/Tests/Keystore/KeyStoreTests.swift | 78 +- tests/Aeternity/TWAeternityAddressTests.cpp | 15 +- tests/Binance/SignerTests.cpp | 355 ++- tests/BinanceSmartChain/SignerTests.cpp | 91 + tests/BinanceSmartChain/TWAnyAddressTests.cpp | 29 + tests/BinanceSmartChain/TWCoinTypeTests.cpp | 34 + tests/BitcoinCash/TWBitcoinCashTests.cpp | 4 +- tests/BitcoinGold/TWBitcoinGoldTests.cpp | 4 +- tests/Cardano/AddressTests.cpp | 18 +- tests/Decred/AddressTests.cpp | 2 +- tests/Decred/TWDecredTests.cpp | 2 +- tests/DerivationPathTests.cpp | 2 +- tests/Groestlcoin/AddressTests.cpp | 2 +- tests/Groestlcoin/TWGroestlcoinTests.cpp | 4 +- tests/HDWalletTests.cpp | 20 +- tests/Keystore/StoredKeyTests.cpp | 40 +- tests/Litecoin/TWLitecoinTests.cpp | 4 +- tests/Monacoin/TWMonacoinAddressTests.cpp | 4 +- tests/Qtum/TWQtumAddressTests.cpp | 8 +- tests/Solana/TWSolanaAddressTests.cpp | 2 +- tests/TON/TWTONAddressTests.cpp | 2 +- tests/Viacoin/TWViacoinAddressTests.cpp | 8 +- tests/Waves/AddressTests.cpp | 4 +- tests/Zcash/TWZcashAddressTests.cpp | 8 +- tests/Zcoin/TWZCoinAddressTests.cpp | 4 +- tests/Zelcash/TWZelcashAddressTests.cpp | 8 +- tests/interface/TWHDWalletTests.cpp | 37 +- tests/interface/TWStoredKeyTests.cpp | 1 + walletconsole/lib/Address.cpp | 12 +- walletconsole/lib/Address.h | 2 +- walletconsole/lib/CommandExecutor.cpp | 2 +- walletconsole/lib/Keys.cpp | 4 +- 88 files changed, 2615 insertions(+), 1835 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt delete mode 100644 swift/Sources/Extensions/HDWallet+Extension.swift create mode 100644 swift/Tests/Blockchains/BinanceSmartChainTests.swift create mode 100644 swift/Tests/Keystore/Data/bnb_wallet.json create mode 100644 tests/BinanceSmartChain/SignerTests.cpp create mode 100644 tests/BinanceSmartChain/TWAnyAddressTests.cpp create mode 100644 tests/BinanceSmartChain/TWCoinTypeTests.cpp 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 64c1ac17353..69c1eb45923 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 @@ -89,5 +89,6 @@ class CoinAddressDerivationTests { FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) BANDCHAIN -> assertEquals("band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r", address) + BINANCESMARTCHAIN -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt index 5c6ddfa4240..77cbc83a95f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestHDWallet.kt @@ -82,8 +82,8 @@ class TestHDWallet { @Test fun testPublicKeyFromX() { val xpub = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj" - val xpubAddr2 = HDWallet.getPublicKeyFromExtended(xpub, "m/44'/145'/0'/0/2") - val xpubAddr9 = HDWallet.getPublicKeyFromExtended(xpub, "m/44'/145'/0'/0/9") + val xpubAddr2 = HDWallet.getPublicKeyFromExtended(xpub, CoinType.BITCOINCASH,"m/44'/145'/0'/0/2") + val xpubAddr9 = HDWallet.getPublicKeyFromExtended(xpub, CoinType.BITCOINCASH,"m/44'/145'/0'/0/9") assertEquals( Numeric.toHexString(xpubAddr2.data()), diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt new file mode 100644 index 00000000000..80db80b893e --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt @@ -0,0 +1,31 @@ +// Copyright © 2017-2020 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. + +package com.trustwallet.core.app.blockchains.binancesmartchain + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestBinanceSmartChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.BINANCESMARTCHAIN) + val expected = AnyAddress("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", CoinType.BINANCESMARTCHAIN) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/codegen/bin/coins b/codegen/bin/coins index 428469a8c24..be1528d0e21 100755 --- a/codegen/bin/coins +++ b/codegen/bin/coins @@ -12,10 +12,6 @@ def self.format_name(n) formatted end -def self.coin_type(path) - path.split('/')[2].chomp("'") -end - def self.coin_img(coin) "" end @@ -29,8 +25,7 @@ def self.explorer_account_url(c) end json_string = File.read('coins.json') -coins = JSON.parse(json_string).sort_by { |x| x['name'] } -coins_md = coins.sort_by { |x| Integer(coin_type(x['derivationPath'])) } +coins = JSON.parse(json_string).sort_by { |x| x['coinId'] } erbs = [ {'template' => 'CoinInfoData.cpp.erb', 'folder' => 'src/Generated', 'file' => 'CoinInfoData.cpp'}, diff --git a/codegen/bin/cointests b/codegen/bin/cointests index 037285b56bf..e6e0a765ede 100755 --- a/codegen/bin/cointests +++ b/codegen/bin/cointests @@ -36,10 +36,6 @@ def self.display_name(coin) name end -def self.coin_type(path) - path.split('/')[2].chomp("'") -end - # Explorer urls def self.explorer_tx_url(c) path = c['explorer']['url'].to_s + c['explorer']['txPath'].to_s @@ -64,7 +60,6 @@ end json_string = File.read('coins.json') coins = JSON.parse(json_string).sort_by { |x| x['name'] } -coins_md = coins.sort_by { |x| Integer(coin_type(x['derivationPath'])) } erbs = [ {'template' => 'TWCoinTypeTests.cpp.erb', 'folder' => 'tests/interface', 'file' => 'TWCoinTypeTests.cpp'} diff --git a/codegen/bin/newcoin b/codegen/bin/newcoin index 8356e537275..6bbcde6ef18 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -14,15 +14,10 @@ require 'entity_decl' require 'code_generator' require 'coin_test_gen' -def self.coin_type(path) - path.split('/')[2].chomp("'") -end - # Transforms a coin name to a C++ name def self.format_name(coin) formatted = coin['name'] - #formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') + formatted = formatted.gsub(/\s/, '') formatted end @@ -49,7 +44,7 @@ end def self.insert_coin_type(coin) target_file = "include/TrustWalletCore/TWCoinType.h" - target_line = " TWCoinType#{coin['name']} = #{coin_type(coin['derivationPath'])},\n" + target_line = " TWCoinType#{format_name(coin)} = #{coin['coinId']},\n" if insert_target_line(target_file, target_line, "};\n") insert_blockchain_type(coin) end @@ -64,9 +59,9 @@ end def insert_coin_entry(coin) target_file = "src/Coin.cpp" - target_line = "#include \"#{coin['name']}/Entry.h\"\n" + target_line = "#include \"#{format_name(coin)}/Entry.h\"\n" insert_target_line(target_file, target_line, "// end_of_coin_includes_marker_do_not_modify\n") - target_line = " new #{coin['name']}::Entry(),\n" + target_line = " new #{format_name(coin)}::Entry(),\n" insert_target_line(target_file, target_line, " }; // end_of_coin_entries_marker_do_not_modify\n") end diff --git a/codegen/lib/coin_test_gen.rb b/codegen/lib/coin_test_gen.rb index f8bebd779c0..f03a2869d77 100755 --- a/codegen/lib/coin_test_gen.rb +++ b/codegen/lib/coin_test_gen.rb @@ -34,10 +34,6 @@ def display_name(coin) name end - def self.coin_type(path) - path.split('/')[2].chomp("'") - end - # Explorer urls def explorer_tx_url(c) path = c['explorer']['url'].to_s + c['explorer']['txPath'].to_s diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index ef449df6fb1..e53010ed12d 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.erb @@ -40,6 +40,7 @@ static const CoinInfo defaultsForMissing = { 2, "", "", + 0, }; void fillMap(); // forward @@ -85,6 +86,7 @@ void fillMap() { <%= coin['decimals'] %>, "<%= explorer_tx_url(coin) %>", "<%= explorer_account_url(coin) %>", + <% if coin['slip44'].nil? -%><%= coin['coinId'] %><% else -%><%= coin['slip44'] %><% end -%>, } }, <% end -%> diff --git a/codegen/lib/templates/coins.md.erb b/codegen/lib/templates/coins.md.erb index b7f5bcc8e22..cfb218f3849 100644 --- a/codegen/lib/templates/coins.md.erb +++ b/codegen/lib/templates/coins.md.erb @@ -4,6 +4,6 @@ This list is generated from [./coins.json](../coins.json) | Index | Name | Symbol | Logo | URL | | ------- | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -<% coins_md.each do |coin| -%> -| <%= coin_type(coin['derivationPath']).ljust(7, " ") %> | <%= coin['name'].ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | +<% coins.each do |coin| -%> +| <%= coin['coinId'].to_s.ljust(7, " ") %> | <%= coin['name'].ljust(16, " ") %> | <%= coin['symbol'].ljust(6, " ") %> | <%= coin_img(coin['id']).ljust(123) %> | <%= "<#{coin['info']['url']}>".ljust(29, " ") %> | <% end -%> diff --git a/coins.json b/coins.json index 77670f56b25..7f73ae1b4f2 100644 --- a/coins.json +++ b/coins.json @@ -1,1408 +1,1494 @@ -[{ - "id": "nebulas", - "name": "Nebulas", - "symbol": "NAS", - "decimals": 18, - "blockchain": "Nebulas", - "derivationPath": "m/44'/2718'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer.nebulas.io", - "txPath": "/#/tx/", - "accountPath": "/#/address/" - }, - "info": { - "url": "https://nebulas.io", - "client": "https://github.com/nebulasio/go-nebulas", - "clientPublic": "https://mainnet.nebulas.io", - "clientDocs": "https://wiki.nebulas.io/en/latest/dapp-development/rpc/rpc.html" - } +[ + { + "id": "bitcoin", + "name": "Bitcoin", + "coinId": 0, + "symbol": "BTC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/0'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/bitcoin/transaction/", + "accountPath": "/bitcoin/address/", + "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", + "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" }, - { - "id": "ethereum", - "name": "Ethereum", - "symbol": "ETH", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/60'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://etherscan.io", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f", - "sampleAccount": "0x5bb497e8d9fe26e92dd1be01e32076c8e024d167" - }, - "info": { - "url": "https://ethereum.org", - "client": "https://github.com/ethereum/go-ethereum", - "clientPublic": "https://mainnet.infura.io", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://bitcoin.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "litecoin", + "name": "Litecoin", + "coinId": 2, + "symbol": "LTC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/2'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 48, + "p2shPrefix": 50, + "hrp": "ltc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/litecoin/transaction/", + "accountPath": "/litecoin/address/" }, - { - "id": "bitcoin", - "name": "Bitcoin", - "symbol": "BTC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/0'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 0, - "p2shPrefix": 5, - "hrp": "bc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/bitcoin/transaction/", - "accountPath": "/bitcoin/address/", - "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", - "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" - }, - "info": { - "url": "https://bitcoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://litecoin.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "doge", + "name": "Dogecoin", + "coinId": 3, + "symbol": "DOGE", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/3'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 22, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "dgub", + "xprv": "dgpv", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/dogecoin/transaction/", + "accountPath": "/dogecoin/address/" }, - { - "id": "bitcoincash", - "name": "Bitcoin Cash", - "symbol": "BCH", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/145'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 0, - "p2shPrefix": 5, - "hrp": "bitcoincash", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/bitcoin-cash/transaction/", - "accountPath": "/bitcoin-cash/address/" - }, - "info": { - "url": "https://bitcoincash.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://dogecoin.com", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "dash", + "name": "Dash", + "coinId": 5, + "symbol": "DASH", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/5'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 76, + "p2shPrefix": 16, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/dash/transaction/", + "accountPath": "/dash/address/" }, - { - "id": "bitcoingold", - "name": "Bitcoin Gold", - "symbol": "BTG", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/156'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 38, - "p2shPrefix": 23, - "hrp": "btg", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://explorer.bitcoingold.org/insight", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://bitcoingold.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://dash.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "viacoin", + "name": "Viacoin", + "coinId": 14, + "symbol": "VIA", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/14'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 71, + "p2shPrefix": 33, + "hrp": "via", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://explorer.viacoin.org", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "callisto", - "name": "Callisto", - "symbol": "CLO", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/820'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer2.callisto.network", - "txPath": "/tx/", - "accountPath": "/addr/" - }, - "info": { - "url": "https://callisto.network", - "client": "https://github.com/EthereumCommonwealth/go-callisto", - "clientPublic": "https://clo-geth.0xinfra.com", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://viacoin.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "groestlcoin", + "name": "Groestlcoin", + "coinId": 17, + "symbol": "GRS", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/17'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 36, + "p2shPrefix": 5, + "hrp": "grs", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "groestl512d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/groestlcoin/transaction/", + "accountPath": "/groestlcoin/address/" }, - { - "id": "dash", - "name": "Dash", - "symbol": "DASH", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/5'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 76, - "p2shPrefix": 16, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/dash/transaction/", - "accountPath": "/dash/address/" - }, - "info": { - "url": "https://dash.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://www.groestlcoin.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "digibyte", + "name": "DigiByte", + "coinId": 20, + "symbol": "DGB", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/20'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 30, + "p2shPrefix": 63, + "hrp": "dgb", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://digiexplorer.info", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "decred", - "name": "Decred", - "symbol": "DCR", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/42'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 7, - "p2pkhPrefix": 63, - "p2shPrefix": 26, - "publicKeyHasher": "blake256ripemd", - "base58Hasher": "blake256d", - "xpub": "dpub", - "xprv": "dprv", - "explorer": { - "url": "https://dcrdata.decred.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://decred.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://www.digibyte.io", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "monacoin", + "name": "Monacoin", + "coinId": 22, + "symbol": "MONA", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/22'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 50, + "p2shPrefix": 55, + "hrp": "mona", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://blockbook.electrum-mona.org", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "digibyte", - "name": "DigiByte", - "symbol": "DGB", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/20'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 30, - "p2shPrefix": 63, - "hrp": "dgb", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://digiexplorer.info", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://www.digibyte.io", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://monacoin.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "decred", + "name": "Decred", + "coinId": 42, + "symbol": "DCR", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/42'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 7, + "p2pkhPrefix": 63, + "p2shPrefix": 26, + "publicKeyHasher": "blake256ripemd", + "base58Hasher": "blake256d", + "xpub": "dpub", + "xprv": "dprv", + "explorer": { + "url": "https://dcrdata.decred.org", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "doge", - "name": "Dogecoin", - "symbol": "DOGE", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/3'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 30, - "p2shPrefix": 22, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "dgub", - "xprv": "dgpv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/dogecoin/transaction/", - "accountPath": "/dogecoin/address/" - }, - "info": { - "url": "https://dogecoin.com", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://decred.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "ethereum", + "name": "Ethereum", + "coinId": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/60'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://etherscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f", + "sampleAccount": "0x5bb497e8d9fe26e92dd1be01e32076c8e024d167" }, - { - "id": "classic", - "name": "Ethereum Classic", - "symbol": "ETC", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/61'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://blockscout.com/etc/mainnet", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://ethereumclassic.org", - "client": "https://github.com/ethereumclassic/go-ethereum", - "clientPublic": "https://www.ethercluster.com/etc", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://ethereum.org", + "client": "https://github.com/ethereum/go-ethereum", + "clientPublic": "https://mainnet.infura.io", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "classic", + "name": "Ethereum Classic", + "coinId": 61, + "symbol": "ETC", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/61'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://blockscout.com/etc/mainnet", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "gochain", - "name": "GoChain", - "symbol": "GO", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/6060'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer.gochain.io", - "txPath": "/tx/", - "accountPath": "/addr/" - }, - "info": { - "url": "https://gochain.io", - "client": "https://github.com/gochain-io/gochain", - "clientPublic": "https://rpc.gochain.io", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://ethereumclassic.org", + "client": "https://github.com/ethereumclassic/go-ethereum", + "clientPublic": "https://www.ethercluster.com/etc", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "icon", + "name": "ICON", + "coinId": 74, + "symbol": "ICX", + "decimals": 18, + "blockchain": "Icon", + "derivationPath": "m/44'/74'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://tracker.icon.foundation", + "txPath": "/transaction/", + "accountPath": "/address/" }, - { - "id": "groestlcoin", - "name": "Groestlcoin", - "symbol": "GRS", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/17'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 36, - "p2shPrefix": 5, - "hrp": "grs", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "groestl512d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/groestlcoin/transaction/", - "accountPath": "/groestlcoin/address/" - }, - "info": { - "url": "https://www.groestlcoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://icon.foundation", + "client": "https://github.com/icon-project/icon-rpc-server", + "clientPublic": "http://ctz.icxstation.com:9000/api/v3", + "clientDocs": "https://www.icondev.io/docs/icon-json-rpc-v3" + } + }, + { + "id": "cosmos", + "name": "Cosmos", + "coinId": 118, + "symbol": "ATOM", + "decimals": 6, + "blockchain": "Cosmos", + "derivationPath": "m/44'/118'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "cosmos", + "explorer": { + "url": "https://www.mintscan.io", + "txPath": "/txs/", + "accountPath": "/account/" }, - { - "id": "icon", - "name": "ICON", - "symbol": "ICX", - "decimals": 18, - "blockchain": "Icon", - "derivationPath": "m/44'/74'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://tracker.icon.foundation", - "txPath": "/transaction/", - "accountPath": "/address/" - }, - "info": { - "url": "https://icon.foundation", - "client": "https://github.com/icon-project/icon-rpc-server", - "clientPublic": "http://ctz.icxstation.com:9000/api/v3", - "clientDocs": "https://www.icondev.io/docs/icon-json-rpc-v3" - } + "info": { + "url": "https://cosmos.network", + "client": "https://github.com/cosmos/cosmos-sdk", + "clientPublic": "https://stargate.cosmos.network", + "clientDocs": "https://cosmos.network/rpc" + } + }, + { + "id": "zcash", + "name": "Zcash", + "coinId": 133, + "symbol": "ZEC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/133'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 28, + "p2pkhPrefix": 184, + "p2shPrefix": 189, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://blockchair.com/zcash", + "txPath": "/transaction/", + "accountPath": "/address/" }, - { - "id": "litecoin", - "name": "Litecoin", - "symbol": "LTC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/2'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 48, - "p2shPrefix": 50, - "hrp": "ltc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://blockchair.com", - "txPath": "/litecoin/transaction/", - "accountPath": "/litecoin/address/" - }, - "info": { - "url": "https://litecoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://z.cash", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "zcoin", + "name": "Zcoin", + "coinId": 136, + "symbol": "XZC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/136'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 82, + "p2shPrefix": 7, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://explorer.zcoin.io", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "ontology", - "name": "Ontology", - "symbol": "ONT", - "decimals": 0, - "blockchain": "Ontology", - "derivationPath": "m/44'/1024'/0'/0/0", - "curve": "nist256p1", - "publicKeyType": "nist256p1", - "explorer": { - "url": "https://explorer.ont.io", - "txPath": "/transaction/", - "accountPath": "/address/" - }, - "info": { - "url": "https://ont.io", - "client": "https://github.com/ontio/ontology", - "clientPublic": "http://dappnode1.ont.io:20336", - "clientDocs": "https://github.com/ontio/ontology/blob/master/docs/specifications/rpc_api.md" - } + "info": { + "url": "https://zcoin.io", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "ripple", + "name": "XRP", + "coinId": 144, + "symbol": "XRP", + "decimals": 6, + "blockchain": "Ripple", + "derivationPath": "m/44'/144'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://bithomp.com", + "txPath": "/explorer/", + "accountPath": "/explorer/", + "sampleTx": "E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054", + "sampleAccount": "rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU" }, - { - "id": "viacoin", - "name": "Viacoin", - "symbol": "VIA", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/84'/14'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 71, - "p2shPrefix": 33, - "hrp": "via", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "zpub", - "xprv": "zprv", - "explorer": { - "url": "https://explorer.viacoin.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://viacoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://ripple.com/xrp", + "client": "https://github.com/ripple/rippled", + "clientPublic": "https://s2.ripple.com:51234", + "clientDocs": "https://xrpl.org/rippled-api.html" + } + }, + { + "id": "bitcoincash", + "name": "Bitcoin Cash", + "coinId": 145, + "symbol": "BCH", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/145'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bitcoincash", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/bitcoin-cash/transaction/", + "accountPath": "/bitcoin-cash/address/" }, - { - "id": "poa", - "name": "POA Network", - "symbol": "POA", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/178'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://blockscout.com", - "txPath": "/poa/core/tx/", - "accountPath": "/poa/core/address/" - }, - "info": { - "url": "https://poa.network", - "client": "https://github.com/poanetwork/parity-ethereum", - "clientPublic": "https://core.poa.network", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://bitcoincash.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "stellar", + "name": "Stellar", + "coinId": 148, + "symbol": "XLM", + "decimals": 7, + "blockchain": "Stellar", + "derivationPath": "m/44'/148'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://blockchair.com/stellar", + "txPath": "/transaction/", + "accountPath": "/account/" }, - { - "id": "thundertoken", - "name": "Thunder Token", - "symbol": "TT", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/1001'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://scan.thundercore.com", - "txPath": "/transactions/", - "accountPath": "/address/" - }, - "info": { - "url": "https://thundercore.com", - "client": "https://github.com/thundercore/pala", - "clientPublic": "https://mainnet-rpc.thundercore.com", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://stellar.org", + "client": "https://github.com/stellar/go", + "clientPublic": "https://horizon.stellar.org", + "clientDocs": "https://www.stellar.org/developers/horizon/reference" + } + }, + { + "id": "bitcoingold", + "name": "Bitcoin Gold", + "coinId": 156, + "symbol": "BTG", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/84'/156'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 38, + "p2shPrefix": 23, + "hrp": "btg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "zpub", + "xprv": "zprv", + "explorer": { + "url": "https://explorer.bitcoingold.org/insight", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "tomochain", - "name": "TomoChain", - "symbol": "TOMO", - "decimals": 18, - "blockchain": "Ethereum", - "derivationPath": "m/44'/889'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://scan.tomochain.com", - "txPath": "/txs/", - "accountPath": "/address/" - }, - "info": { - "url": "https://tomochain.com", - "client": "https://github.com/tomochain/tomochain", - "clientPublic": "https://rpc.tomochain.com", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://bitcoingold.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "nano", + "name": "Nano", + "coinId": 165, + "symbol": "NANO", + "decimals": 30, + "blockchain": "Nano", + "derivationPath": "m/44'/165'/0'", + "curve": "ed25519Blake2bNano", + "publicKeyType": "ed25519Blake2b", + "url": "https://nano.org", + "explorer": { + "url": "https://nanocrawler.cc", + "txPath": "/explorer/block/", + "accountPath": "/explorer/account/", + "sampleTx": "C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F", + "sampleAccount": "nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf" }, - { - "id": "tron", - "name": "Tron", - "symbol": "TRX", - "decimals": 6, - "blockchain": "Tron", - "derivationPath": "m/44'/195'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://tronscan.org", - "txPath": "/#/transaction/", - "accountPath": "/#/address/" - }, - "info": { - "url": "https://tron.network", - "client": "https://github.com/tronprotocol/java-tron", - "clientPublic": "https://api.trongrid.io", - "clientDocs": "https://developers.tron.network/docs/tron-wallet-rpc-api" - } + "info": { + "url": "https://nano.org", + "client": "https://github.com/nanocurrency/nano-node", + "clientPublic": "", + "clientDocs": "https://docs.nano.org/commands/rpc-protocol/" + } + }, + { + "id": "ravencoin", + "name": "Ravencoin", + "coinId": 175, + "symbol": "RVN", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/175'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 60, + "p2shPrefix": 122, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://ravencoin.network", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "vechain", - "name": "VeChain", - "symbol": "VET", - "decimals": 18, - "blockchain": "Vechain", - "derivationPath": "m/44'/818'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://insight.vecha.in", - "txPath": "/#/main/txs/", - "accountPath": "/#/main/accounts/" - }, - "info": { - "url": "https://vechain.org", - "client": "https://github.com/vechain/thor", - "clientPublic": "", - "clientDocs": "https://doc.vechainworld.io/docs" - } + "info": { + "url": "https://ravencoin.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "poa", + "name": "POA Network", + "coinId": 178, + "symbol": "POA", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/178'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://blockscout.com", + "txPath": "/poa/core/tx/", + "accountPath": "/poa/core/address/" }, - { - "id": "wanchain", - "name": "Wanchain", - "symbol": "WAN", - "decimals": 18, - "blockchain": "Wanchain", - "derivationPath": "m/44'/5718350'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://www.wanscan.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", - "sampleAccount": "0x69b492d57BB777E97AA7044d0575228434E2e8b1" - }, - "info": { - "url": "https://wanchain.org", - "client": "https://github.com/wanchain/go-wanchain", - "clientPublic": "", - "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" - } + "info": { + "url": "https://poa.network", + "client": "https://github.com/poanetwork/parity-ethereum", + "clientPublic": "https://core.poa.network", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "eos", + "name": "EOS", + "coinId": 194, + "symbol": "EOS", + "decimals": 4, + "blockchain": "EOS", + "derivationPath": "m/44'/194'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://bloks.io", + "txPath": "/transaction/", + "accountPath": "/account/" }, - { - "id": "zcoin", - "name": "Zcoin", - "symbol": "XZC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/136'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 82, - "p2shPrefix": 7, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://explorer.zcoin.io", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://zcoin.io", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "http://eos.io", + "client": "https://github.com/eosio/eos", + "clientPublic": "", + "clientDocs": "https://developers.eos.io/eosio-nodeos/reference" + } + }, + { + "id": "tron", + "name": "Tron", + "coinId": 195, + "symbol": "TRX", + "decimals": 6, + "blockchain": "Tron", + "derivationPath": "m/44'/195'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://tronscan.org", + "txPath": "/#/transaction/", + "accountPath": "/#/address/" }, - { - "id": "zcash", - "name": "Zcash", - "symbol": "ZEC", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/133'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 28, - "p2pkhPrefix": 184, - "p2shPrefix": 189, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockchair.com/zcash", - "txPath": "/transaction/", - "accountPath": "/address/" - }, - "info": { - "url": "https://z.cash", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://tron.network", + "client": "https://github.com/tronprotocol/java-tron", + "clientPublic": "https://api.trongrid.io", + "clientDocs": "https://developers.tron.network/docs/tron-wallet-rpc-api" + } + }, + { + "id": "fio", + "name": "FIO", + "coinId": 235, + "symbol": "FIO", + "decimals": 9, + "blockchain": "FIO", + "derivationPath": "m/44'/235'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "url": "https://fioprotocol.io/", + "explorer": { + "url": "https://explorer.fioprotocol.io", + "txPath": "/transaction/", + "accountPath": "/account/" }, - { - "id": "binance", - "name": "Binance", - "displayName": "BNB", - "symbol": "BNB", - "decimals": 8, - "blockchain": "Binance", - "derivationPath": "m/44'/714'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "bnb", - "explorer": { - "url": "https://explorer.binance.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB", - "sampleAccount": "bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz" - }, - "info": { - "url": "https://binance.org", - "client": "https://github.com/binance-chain/node-binary", - "clientPublic": "https://dex.binance.org", - "clientDocs": "https://docs.binance.org/api-reference/dex-api/paths.html" - } + "info": { + "url": "https://fioprotocol.io", + "client": "https://github.com/fioprotocol/fio", + "clientPublic": "https://mainnet.fioprotocol.io", + "clientDocs": "https://developers.fioprotocol.io" + } + }, + { + "id": "nimiq", + "name": "Nimiq", + "coinId": 242, + "symbol": "NIM", + "decimals": 5, + "blockchain": "Nimiq", + "derivationPath": "m/44'/242'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://nimiq.watch", + "txPath": "/#", + "accountPath": "/#" + }, + "info": { + "url": "https://nimiq.com", + "client": "https://github.com/nimiq/core-rs", + "clientPublic": "", + "clientDocs": "https://github.com/nimiq/core-js/wiki/JSON-RPC-API" + } + }, + { + "id": "algorand", + "name": "Algorand", + "coinId": 283, + "symbol": "ALGO", + "decimals": 6, + "blockchain": "Algorand", + "derivationPath": "m/44'/283'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://algoexplorer.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A", + "sampleAccount": "J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM" }, - { - "id": "ripple", - "name": "XRP", - "symbol": "XRP", - "decimals": 6, - "blockchain": "Ripple", - "derivationPath": "m/44'/144'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "explorer": { - "url": "https://bithomp.com", - "txPath": "/explorer/", - "accountPath": "/explorer/", - "sampleTx": "E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054", - "sampleAccount": "rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU" - }, - "info": { - "url": "https://ripple.com/xrp", - "client": "https://github.com/ripple/rippled", - "clientPublic": "https://s2.ripple.com:51234", - "clientDocs": "https://xrpl.org/rippled-api.html" - } + "info": { + "url": "https://www.algorand.com/", + "client": "https://github.com/algorand/go-algorand", + "clientPublic": "https://indexer.algorand.network", + "clientDocs": "https://developer.algorand.org/docs/algod-rest-paths" + } + }, + { + "id": "iotex", + "name": "IoTeX", + "coinId": 304, + "symbol": "IOTX", + "decimals": 18, + "blockchain": "IoTeX", + "derivationPath": "m/44'/304'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "io", + "explorer": { + "url": "https://iotexscan.io", + "txPath": "/action/", + "accountPath": "/address/" }, - { - "id": "tezos", - "name": "Tezos", - "symbol": "XTZ", - "decimals": 6, - "blockchain": "Tezos", - "derivationPath": "m/44'/1729'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://tezos.id", - "txPath": "/", - "accountPath": "/" - }, - "info": { - "url": "https://tezos.com", - "client": "https://gitlab.com/tezos/tezos", - "clientPublic": "https://rpc.tulip.tools/mainnet", - "clientDocs": "https://tezos.gitlab.io/tezos/api/rpc.html" - } + "info": { + "url": "https://iotex.io", + "client": "https://github.com/iotexproject/iotex-core", + "clientPublic": "", + "clientDocs": "https://docs.iotex.io/#api" + } + }, + { + "id": "zilliqa", + "name": "Zilliqa", + "coinId": 313, + "symbol": "ZIL", + "decimals": 12, + "blockchain": "Zilliqa", + "derivationPath": "m/44'/313'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "zil", + "explorer": { + "url": "https://viewblock.io", + "txPath": "/zilliqa/tx/", + "accountPath": "/zilliqa/address/" + }, + "info": { + "url": "https://zilliqa.com", + "client": "https://github.com/Zilliqa/Zilliqa", + "clientPublic": "https://api.zilliqa.com", + "clientDocs": "https://apidocs.zilliqa.com" + } + }, + { + "id": "terra", + "name": "Terra", + "coinId": 330, + "symbol": "LUNA", + "decimals": 6, + "blockchain": "Cosmos", + "derivationPath": "m/44'/330'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "terra", + "explorer": { + "url": "https://terra.stake.id", + "txPath": "/#/tx/", + "accountPath": "/#/address/" }, - { - "id": "nimiq", - "name": "Nimiq", - "symbol": "NIM", - "decimals": 5, - "blockchain": "Nimiq", - "derivationPath": "m/44'/242'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://nimiq.watch", - "txPath": "/#", - "accountPath": "/#" - }, - "info": { - "url": "https://nimiq.com", - "client": "https://github.com/nimiq/core-rs", - "clientPublic": "", - "clientDocs": "https://github.com/nimiq/core-js/wiki/JSON-RPC-API" - } + "info": { + "url": "https://terra.money", + "client": "https://github.com/terra-project/core", + "clientPublic": "https://rpc.terra.dev", + "clientDocs": "https://docs.terra.money" + } + }, + { + "id": "polkadot", + "name": "Polkadot", + "coinId": 354, + "symbol": "DOT", + "decimals": 10, + "blockchain": "Polkadot", + "derivationPath": "m/44'/354'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://polkadot.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/" }, - { - "id": "stellar", - "name": "Stellar", - "symbol": "XLM", - "decimals": 7, - "blockchain": "Stellar", - "derivationPath": "m/44'/148'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://blockchair.com/stellar", - "txPath": "/transaction/", - "accountPath": "/account/" - }, - "info": { - "url": "https://stellar.org", - "client": "https://github.com/stellar/go", - "clientPublic": "https://horizon.stellar.org", - "clientDocs": "https://www.stellar.org/developers/horizon/reference" - } + "info": { + "url": "https://polkadot.network/", + "client": "https://github.com/paritytech/polkadot", + "clientPublic": "", + "clientDocs": "https://polkadot.js.org/api/substrate/rpc.html" + } + }, + { + "id": "ton", + "name": "TON", + "coinId": 396, + "symbol": "GRAM", + "decimals": 9, + "blockchain": "TON", + "derivationPath": "m/44'/396'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://test.ton.org", + "txPath": "/testnet/transaction?hash=", + "accountPath": "/testnet/account?account=" }, - { - "id": "aion", - "name": "Aion", - "symbol": "AION", - "decimals": 18, - "blockchain": "Aion", - "derivationPath": "m/44'/425'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://mainnet.aion.network", - "txPath": "/#/transaction/", - "accountPath": "/#/account/" - }, - "info": { - "url": "https://aion.network", - "client": "https://github.com/aionnetwork/aion", - "clientPublic": "", - "clientDocs": "https://github.com/aionnetwork/aion/wiki/JSON-RPC-API-Docs" - } + "info": { + "url": "https://test.ton.org", + "client": "https://github.com/ton-blockchain/ton", + "clientPublic": "", + "clientDocs": "https://test.ton.org/" + } + }, + { + "id": "near", + "name": "NEAR", + "coinId": 397, + "symbol": "NEAR", + "decimals": 18, + "blockchain": "NEAR", + "derivationPath": "m/44'/397'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://explorer.nearprotocol.com", + "txPath": "/transactions/", + "accountPath": "/accounts/" }, - { - "id": "cosmos", - "name": "Cosmos", - "symbol": "ATOM", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/118'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "cosmos", - "explorer": { - "url": "https://www.mintscan.io", - "txPath": "/txs/", - "accountPath": "/account/" - }, - "info": { - "url": "https://cosmos.network", - "client": "https://github.com/cosmos/cosmos-sdk", - "clientPublic": "https://stargate.cosmos.network", - "clientDocs": "https://cosmos.network/rpc" - } + "info": { + "url": "https://nearprotocol.com", + "client": "https://github.com/nearprotocol/nearcore", + "clientPublic": "https://rpc.nearprotocol.com", + "clientDocs": "https://docs.nearprotocol.com" + } + }, + { + "id": "aion", + "name": "Aion", + "coinId": 425, + "symbol": "AION", + "decimals": 18, + "blockchain": "Aion", + "derivationPath": "m/44'/425'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://mainnet.aion.network", + "txPath": "/#/transaction/", + "accountPath": "/#/account/" }, - { - "id": "kin", - "name": "Kin", - "symbol": "KIN", - "decimals": 5, - "blockchain": "Stellar", - "derivationPath": "m/44'/2017'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://www.kin.org", - "txPath": "/blockchainInfoPage/?&dataType=public&header=Transaction&id=", - "accountPath": "/blockchainAccount/?&dataType=public&header=accountID&id=" - }, - "info": { - "url": "https://www.kin.org", - "client": "https://github.com/kinecosystem/go", - "clientPublic": "https://horizon.kinfederation.com", - "clientDocs": "https://www.stellar.org/developers/horizon/reference" - } + "info": { + "url": "https://aion.network", + "client": "https://github.com/aionnetwork/aion", + "clientPublic": "", + "clientDocs": "https://github.com/aionnetwork/aion/wiki/JSON-RPC-API-Docs" + } + }, + { + "id": "kusama", + "name": "Kusama", + "coinId": 434, + "symbol": "KSM", + "decimals": 12, + "blockchain": "Polkadot", + "derivationPath": "m/44'/434'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://kusama.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd", + "sampleAccount": "DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk" }, - { - "id": "nuls", - "name": "NULS", - "symbol": "NULS", - "decimals": 8, - "blockchain": "NULS", - "derivationPath": "m/44'/8964'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "explorer": { - "url": "https://nulscan.io", - "txPath": "/transaction/info?hash=", - "accountPath": "/address/info?address=" - }, - "info": { - "url": "https://nuls.io", - "client": "https://github.com/nuls-io/nuls-v2", - "clientPublic": "https://public1.nuls.io/", - "clientDocs": "https://docs.nuls.io/" - } + "info": { + "url": "https://kusama.network", + "client": "https://github.com/paritytech/polkadot", + "clientPublic": "wss://kusama-rpc.polkadot.io/", + "clientDocs": "https://polkadot.js.org/api/substrate/rpc.html" + } + }, + { + "id": "aeternity", + "name": "Aeternity", + "coinId": 457, + "symbol": "AE", + "decimals": 18, + "blockchain": "Aeternity", + "derivationPath": "m/44'/457'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://explorer.aepps.com", + "txPath": "/transactions/", + "accountPath": "/account/transactions/" }, - { - "id": "theta", - "name": "Theta", - "symbol": "THETA", - "decimals": 18, - "blockchain": "Theta", - "derivationPath": "m/44'/500'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://explorer.thetatoken.org", - "txPath": "/txs/", - "accountPath": "/account/" - }, - "info": { - "url": "https://www.thetatoken.org", - "client": "https://github.com/thetatoken/theta-protocol-ledger", - "clientPublic": "", - "clientDocs": "https://github.com/thetatoken/theta-mainnet-integration-guide/blob/master/docs/api.md#api-reference" - } + "info": { + "url": "https://aeternity.com", + "client": "https://github.com/aeternity/aeternity", + "clientPublic": "https://sdk-mainnet.aepps.com", + "clientDocs": "http://aeternity.com/api-docs/" + } + }, + { + "id": "kava", + "name": "Kava", + "coinId": 459, + "symbol": "KAVA", + "decimals": 6, + "blockchain": "Cosmos", + "derivationPath": "m/44'/459'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "kava", + "explorer": { + "url": "https://kava.mintscan.io", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", + "sampleAccount": "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" }, - { - "id": "qtum", - "name": "Qtum", - "symbol": "QTUM", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/2301'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 58, - "p2shPrefix": 50, - "hrp": "qc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://qtum.info", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://qtum.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://kava.io", + "client": "https://github.com/kava-labs/kava", + "clientPublic": "https://data.kava.io", + "clientDocs": "https://rpc.kava.io" + } + }, + { + "id": "filecoin", + "name": "Filecoin", + "coinId": 461, + "symbol": "FIL", + "decimals": 18, + "blockchain": "Filecoin", + "derivationPath": "m/44'/461'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://filscan.io", + "txPath": "/#/message/detail?cid=", + "accountPath": "/#/address/detail?address=", + "sampleTx": "bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe", + "sampleAccount": "t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i" }, - { - "id": "eos", - "name": "EOS", - "symbol": "EOS", - "decimals": 4, - "blockchain": "EOS", - "derivationPath": "m/44'/194'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "explorer": { - "url": "https://bloks.io", - "txPath": "/transaction/", - "accountPath": "/account/" - }, - "info": { - "url": "http://eos.io", - "client": "https://github.com/eosio/eos", - "clientPublic": "", - "clientDocs": "https://developers.eos.io/eosio-nodeos/reference" - } + "info": { + "url": "https://filecoin.io/", + "client": "https://github.com/filecoin-project/lotus", + "clientPublic": "", + "clientDocs": "https://docs.lotu.sh" + } + }, + { + "id": "band", + "name": "BandChain", + "symbol": "BAND", + "coinId": 494, + "decimals": 6, + "blockchain": "Cosmos", + "derivationPath": "m/44'/494'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "band", + "explorer": { + "url": "https://scan-wenchang-testnet2.bandchain.org/", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173", + "sampleAccount": "band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp" }, - { - "id": "nano", - "name": "Nano", - "symbol": "NANO", - "decimals": 30, - "blockchain": "Nano", - "derivationPath": "m/44'/165'/0'", - "curve": "ed25519Blake2bNano", - "publicKeyType": "ed25519Blake2b", - "url": "https://nano.org", - "explorer": { - "url": "https://nanocrawler.cc", - "txPath": "/explorer/block/", - "accountPath": "/explorer/account/", - "sampleTx": "C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F", - "sampleAccount": "nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf" - }, - "info": { - "url": "https://nano.org", - "client": "https://github.com/nanocurrency/nano-node", - "clientPublic": "", - "clientDocs": "https://docs.nano.org/commands/rpc-protocol/" - } + "info": { + "url": "https://bandprotocol.com/", + "client": "https://github.com/bandprotocol/bandchain", + "clientPublic": "https://api-wt2-lb.bandchain.org", + "clientDocs": "https://docs.bandchain.org/" + } + }, + { + "id": "theta", + "name": "Theta", + "coinId": 500, + "symbol": "THETA", + "decimals": 18, + "blockchain": "Theta", + "derivationPath": "m/44'/500'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer.thetatoken.org", + "txPath": "/txs/", + "accountPath": "/account/" }, - { - "id": "iotex", - "name": "IoTeX", - "symbol": "IOTX", - "decimals": 18, - "blockchain": "IoTeX", - "derivationPath": "m/44'/304'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "hrp": "io", - "explorer": { - "url": "https://iotexscan.io", - "txPath": "/action/", - "accountPath": "/address/" - }, - "info": { - "url": "https://iotex.io", - "client": "https://github.com/iotexproject/iotex-core", - "clientPublic": "", - "clientDocs": "https://docs.iotex.io/#api" - } + "info": { + "url": "https://www.thetatoken.org", + "client": "https://github.com/thetatoken/theta-protocol-ledger", + "clientPublic": "", + "clientDocs": "https://github.com/thetatoken/theta-mainnet-integration-guide/blob/master/docs/api.md#api-reference" + } + }, + { + "id": "solana", + "name": "Solana", + "coinId": 501, + "symbol": "SOL", + "decimals": 9, + "blockchain": "Solana", + "derivationPath": "m/44'/501'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://explorer.solana.com", + "txPath": "/transactions/", + "accountPath": "/accounts/" }, - { - "id": "zilliqa", - "name": "Zilliqa", - "symbol": "ZIL", - "decimals": 12, - "blockchain": "Zilliqa", - "derivationPath": "m/44'/313'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "zil", - "explorer": { - "url": "https://viewblock.io", - "txPath": "/zilliqa/tx/", - "accountPath": "/zilliqa/address/" - }, - "info": { - "url": "https://zilliqa.com", - "client": "https://github.com/Zilliqa/Zilliqa", - "clientPublic": "https://api.zilliqa.com", - "clientDocs": "https://apidocs.zilliqa.com" - } + "info": { + "url": "https://solana.com", + "client": "https://github.com/solana-labs/solana", + "clientPublic": "https://api.mainnet-beta.solana.com", + "clientDocs": "https://docs.solana.com" + } + }, + { + "id": "elrond", + "name": "Elrond", + "coinId": 508, + "symbol": "eGLD", + "decimals": 18, + "blockchain": "ElrondNetwork", + "derivationPath": "m/44'/508'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "hrp": "erd", + "explorer": { + "url": "https://explorer.elrond.com", + "txPath": "/transactions/", + "accountPath": "/address/" }, - { - "id": "zelcash", - "name": "Zelcash", - "symbol": "ZEL", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/19167'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 28, - "p2pkhPrefix": 184, - "p2shPrefix": 189, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://explorer.zel.cash", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://zel.cash", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "https://blockbook.zel.cash", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://elrond.com/", + "client": "https://github.com/ElrondNetwork/elrond-go", + "clientPublic": "https://api.elrond.com", + "clientDocs": "https://docs.elrond.com" + } + }, + { + "id": "binance", + "name": "Binance", + "displayName": "BNB", + "coinId": 714, + "symbol": "BNB", + "decimals": 8, + "blockchain": "Binance", + "derivationPath": "m/44'/714'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "bnb", + "explorer": { + "url": "https://explorer.binance.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB", + "sampleAccount": "bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz" }, - { - "id": "ravencoin", - "name": "Ravencoin", - "symbol": "RVN", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/175'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 60, - "p2shPrefix": 122, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://ravencoin.network", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://ravencoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://binance.org", + "client": "https://github.com/binance-chain/node-binary", + "clientPublic": "https://dex.binance.org", + "clientDocs": "https://docs.binance.org/api-reference/dex-api/paths.html" + } + }, + { + "id": "vechain", + "name": "VeChain", + "coinId": 818, + "symbol": "VET", + "decimals": 18, + "blockchain": "Vechain", + "derivationPath": "m/44'/818'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://insight.vecha.in", + "txPath": "/#/main/txs/", + "accountPath": "/#/main/accounts/" }, - { - "id": "waves", - "name": "Waves", - "symbol": "WAVES", - "decimals": 8, - "blockchain": "Waves", - "derivationPath": "m/44'/5741564'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "curve25519", - "explorer": { - "url": "https://wavesexplorer.com", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://wavesplatform.com", - "client": "https://github.com/wavesplatform/Waves", - "clientPublic": "https://nodes.wavesnodes.com", - "clientDocs": "https://nodes.wavesnodes.com/api-docs/index.html" - } + "info": { + "url": "https://vechain.org", + "client": "https://github.com/vechain/thor", + "clientPublic": "", + "clientDocs": "https://doc.vechainworld.io/docs" + } + }, + { + "id": "callisto", + "name": "Callisto", + "coinId": 820, + "symbol": "CLO", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/820'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer2.callisto.network", + "txPath": "/tx/", + "accountPath": "/addr/" }, - { - "id": "aeternity", - "name": "Aeternity", - "symbol": "AE", - "decimals": 18, - "blockchain": "Aeternity", - "derivationPath": "m/44'/457'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://explorer.aepps.com", - "txPath": "/transactions/", - "accountPath": "/account/transactions/" - }, - "info": { - "url": "https://aeternity.com", - "client": "https://github.com/aeternity/aeternity", - "clientPublic": "https://sdk-mainnet.aepps.com", - "clientDocs": "http://aeternity.com/api-docs/" - } + "info": { + "url": "https://callisto.network", + "client": "https://github.com/EthereumCommonwealth/go-callisto", + "clientPublic": "https://clo-geth.0xinfra.com", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "neo", + "name": "NEO", + "coinId": 888, + "symbol": "NEO", + "decimals": 8, + "blockchain": "NEO", + "derivationPath": "m/44'/888'/0'/0/0", + "curve": "nist256p1", + "publicKeyType": "nist256p1", + "explorer": { + "url": "https://neoscan.io", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53", + "sampleAccount": "AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb" }, - { - "id": "terra", - "name": "Terra", - "symbol": "LUNA", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/330'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "terra", - "explorer": { - "url": "https://terra.stake.id", - "txPath": "/#/tx/", - "accountPath": "/#/address/" - }, - "info": { - "url": "https://terra.money", - "client": "https://github.com/terra-project/core", - "clientPublic": "https://rpc.terra.dev", - "clientDocs": "https://docs.terra.money" - } + "info": { + "url": "https://neo.org", + "client": "https://github.com/neo-project/neo", + "clientPublic": "http://seed1.ngd.network:10332", + "clientDocs": "https://neo.org/eco" + } + }, + { + "id": "tomochain", + "name": "TomoChain", + "coinId": 889, + "symbol": "TOMO", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/889'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://scan.tomochain.com", + "txPath": "/txs/", + "accountPath": "/address/" }, - { - "id": "monacoin", - "name": "Monacoin", - "symbol": "MONA", - "decimals": 8, - "blockchain": "Bitcoin", - "derivationPath": "m/44'/22'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 50, - "p2shPrefix": 55, - "hrp": "mona", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", - "xpub": "xpub", - "xprv": "xprv", - "explorer": { - "url": "https://blockbook.electrum-mona.org", - "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://monacoin.org", - "client": "https://github.com/trezor/blockbook", - "clientPublic": "", - "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "info": { + "url": "https://tomochain.com", + "client": "https://github.com/tomochain/tomochain", + "clientPublic": "https://rpc.tomochain.com", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "thundertoken", + "name": "Thunder Token", + "coinId": 1001, + "symbol": "TT", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/1001'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://scan.thundercore.com", + "txPath": "/transactions/", + "accountPath": "/address/" }, - { - "id": "fio", - "name": "FIO", - "symbol": "FIO", - "decimals": 9, - "blockchain": "FIO", - "derivationPath": "m/44'/235'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "url": "https://fioprotocol.io/", - "explorer": { - "url": "https://explorer.fioprotocol.io", - "txPath": "/transaction/", - "accountPath": "/account/" - }, - "info": { - "url": "https://fioprotocol.io", - "client": "https://github.com/fioprotocol/fio", - "clientPublic": "https://mainnet.fioprotocol.io", - "clientDocs": "https://developers.fioprotocol.io" - } + "info": { + "url": "https://thundercore.com", + "client": "https://github.com/thundercore/pala", + "clientPublic": "https://mainnet-rpc.thundercore.com", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "harmony", + "name": "Harmony", + "coinId": 1023, + "symbol": "ONE", + "decimals": 18, + "blockchain": "Harmony", + "derivationPath": "m/44'/1023'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "one", + "explorer": { + "url": "https://explorer.harmony.one", + "txPath": "/#/tx/", + "accountPath": "/#/address/" }, - { - "id": "harmony", - "name": "Harmony", - "symbol": "ONE", - "decimals": 18, - "blockchain": "Harmony", - "derivationPath": "m/44'/1023'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "hrp": "one", - "explorer": { - "url": "https://explorer.harmony.one", - "txPath": "/#/tx/", - "accountPath": "/#/address/" - }, - "info": { - "url": "https://harmony.one", - "client": "https://github.com/harmony-one/go-sdk", - "clientPublic": "", - "clientDocs": "https://docs.harmony.one/home/harmony-networks/harmony-network-overview/mainnet" - } + "info": { + "url": "https://harmony.one", + "client": "https://github.com/harmony-one/go-sdk", + "clientPublic": "", + "clientDocs": "https://docs.harmony.one/home/harmony-networks/harmony-network-overview/mainnet" + } + }, + { + "id": "ontology", + "name": "Ontology", + "coinId": 1024, + "symbol": "ONT", + "decimals": 0, + "blockchain": "Ontology", + "derivationPath": "m/44'/1024'/0'/0/0", + "curve": "nist256p1", + "publicKeyType": "nist256p1", + "explorer": { + "url": "https://explorer.ont.io", + "txPath": "/transaction/", + "accountPath": "/address/" }, - { - "id": "solana", - "name": "Solana", - "symbol": "SOL", - "decimals": 9, - "blockchain": "Solana", - "derivationPath": "m/44'/501'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://explorer.solana.com", - "txPath": "/transactions/", - "accountPath": "/accounts/" - }, - "info": { - "url": "https://solana.com", - "client": "https://github.com/solana-labs/solana", - "clientPublic": "https://api.mainnet-beta.solana.com", - "clientDocs": "https://docs.solana.com" - } + "info": { + "url": "https://ont.io", + "client": "https://github.com/ontio/ontology", + "clientPublic": "http://dappnode1.ont.io:20336", + "clientDocs": "https://github.com/ontio/ontology/blob/master/docs/specifications/rpc_api.md" + } + }, + { + "id": "tezos", + "name": "Tezos", + "coinId": 1729, + "symbol": "XTZ", + "decimals": 6, + "blockchain": "Tezos", + "derivationPath": "m/44'/1729'/0'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://tezos.id", + "txPath": "/", + "accountPath": "/" }, - { - "id": "near", - "name": "NEAR", - "symbol": "NEAR", - "decimals": 18, - "blockchain": "NEAR", - "derivationPath": "m/44'/397'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://explorer.nearprotocol.com", - "txPath": "/transactions/", - "accountPath": "/accounts/" - }, - "info": { - "url": "https://nearprotocol.com", - "client": "https://github.com/nearprotocol/nearcore", - "clientPublic": "https://rpc.nearprotocol.com", - "clientDocs": "https://docs.nearprotocol.com" - } + "info": { + "url": "https://tezos.com", + "client": "https://gitlab.com/tezos/tezos", + "clientPublic": "https://rpc.tulip.tools/mainnet", + "clientDocs": "https://tezos.gitlab.io/tezos/api/rpc.html" + } + }, + { + "id": "cardano", + "name": "Cardano", + "coinId": 1815, + "symbol": "ADA", + "decimals": 6, + "blockchain": "Cardano", + "derivationPath": "m/1852'/1815'/0'/0/0", + "curve": "ed25519Extended", + "publicKeyType": "ed25519Extended", + "hrp": "addr", + "explorer": { + "url": "https://shelleyexplorer.cardano.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", + "sampleAccount": "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j" }, - { - "id": "algorand", - "name": "Algorand", - "symbol": "ALGO", - "decimals": 6, - "blockchain": "Algorand", - "derivationPath": "m/44'/283'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://algoexplorer.io", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A", - "sampleAccount": "J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM" - }, - "info": { - "url": "https://www.algorand.com/", - "client": "https://github.com/algorand/go-algorand", - "clientPublic": "https://indexer.algorand.network", - "clientDocs": "https://developer.algorand.org/docs/algod-rest-paths" - } + "info": { + "url": "https://www.cardano.org", + "client": "https://github.com/input-output-hk/cardano-sl", + "clientPublic": "", + "clientDocs": "https://cardanodocs.com/introduction/" + } + }, + { + "id": "kin", + "name": "Kin", + "coinId": 2017, + "symbol": "KIN", + "decimals": 5, + "blockchain": "Stellar", + "derivationPath": "m/44'/2017'/0'", + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://www.kin.org", + "txPath": "/blockchainInfoPage/?&dataType=public&header=Transaction&id=", + "accountPath": "/blockchainAccount/?&dataType=public&header=accountID&id=" }, - { - "id": "ton", - "name": "TON", - "symbol": "GRAM", - "decimals": 9, - "blockchain": "TON", - "derivationPath": "m/44'/396'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://test.ton.org", - "txPath": "/testnet/transaction?hash=", - "accountPath": "/testnet/account?account=" - }, - "info": { - "url": "https://test.ton.org", - "client": "https://github.com/ton-blockchain/ton", - "clientPublic": "", - "clientDocs": "https://test.ton.org/" - } + "info": { + "url": "https://www.kin.org", + "client": "https://github.com/kinecosystem/go", + "clientPublic": "https://horizon.kinfederation.com", + "clientDocs": "https://www.stellar.org/developers/horizon/reference" + } + }, + { + "id": "qtum", + "name": "Qtum", + "coinId": 2301, + "symbol": "QTUM", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/2301'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 58, + "p2shPrefix": 50, + "hrp": "qc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://qtum.info", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "kusama", - "name": "Kusama", - "symbol": "KSM", - "decimals": 12, - "blockchain": "Polkadot", - "derivationPath": "m/44'/434'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://kusama.subscan.io", - "txPath": "/extrinsic/", - "accountPath": "/account/", - "sampleTx": "0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd", - "sampleAccount": "DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk" - }, - "info": { - "url": "https://kusama.network", - "client": "https://github.com/paritytech/polkadot", - "clientPublic": "wss://kusama-rpc.polkadot.io/", - "clientDocs": "https://polkadot.js.org/api/substrate/rpc.html" - } + "info": { + "url": "https://qtum.org", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "nebulas", + "name": "Nebulas", + "coinId": 2718, + "symbol": "NAS", + "decimals": 18, + "blockchain": "Nebulas", + "derivationPath": "m/44'/2718'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer.nebulas.io", + "txPath": "/#/tx/", + "accountPath": "/#/address/" }, - { - "id": "polkadot", - "name": "Polkadot", - "symbol": "DOT", - "decimals": 10, - "blockchain": "Polkadot", - "derivationPath": "m/44'/354'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "explorer": { - "url": "https://polkadot.subscan.io", - "txPath": "/extrinsic/", - "accountPath": "/account/" - }, - "info": { - "url": "https://polkadot.network/", - "client": "https://github.com/paritytech/polkadot", - "clientPublic": "", - "clientDocs": "https://polkadot.js.org/api/substrate/rpc.html" - } + "info": { + "url": "https://nebulas.io", + "client": "https://github.com/nebulasio/go-nebulas", + "clientPublic": "https://mainnet.nebulas.io", + "clientDocs": "https://wiki.nebulas.io/en/latest/dapp-development/rpc/rpc.html" + } + }, + { + "id": "gochain", + "name": "GoChain", + "coinId": 6060, + "symbol": "GO", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/6060'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer.gochain.io", + "txPath": "/tx/", + "accountPath": "/addr/" }, - { - "id": "kava", - "name": "Kava", - "symbol": "KAVA", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/459'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "kava", - "explorer": { - "url": "https://kava.mintscan.io", - "txPath": "/txs/", - "accountPath": "/account/", - "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", - "sampleAccount": "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" - }, - "info": { - "url": "https://kava.io", - "client": "https://github.com/kava-labs/kava", - "clientPublic": "https://data.kava.io", - "clientDocs": "https://rpc.kava.io" - } + "info": { + "url": "https://gochain.io", + "client": "https://github.com/gochain-io/gochain", + "clientPublic": "https://rpc.gochain.io", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "nuls", + "name": "NULS", + "coinId": 8964, + "symbol": "NULS", + "decimals": 8, + "blockchain": "NULS", + "derivationPath": "m/44'/8964'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://nulscan.io", + "txPath": "/transaction/info?hash=", + "accountPath": "/address/info?address=" }, - { - "id": "cardano", - "name": "Cardano", - "symbol": "ADA", - "decimals": 6, - "blockchain": "Cardano", - "derivationPath": "m/1852'/1815'/0'/0/0", - "curve": "ed25519Extended", - "publicKeyType": "ed25519Extended", - "hrp": "addr", - "explorer": { - "url": "https://shelleyexplorer.cardano.org", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", - "sampleAccount": "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j" - }, - "info": { - "url": "https://www.cardano.org", - "client": "https://github.com/input-output-hk/cardano-sl", - "clientPublic": "", - "clientDocs": "https://cardanodocs.com/introduction/" - } + "info": { + "url": "https://nuls.io", + "client": "https://github.com/nuls-io/nuls-v2", + "clientPublic": "https://public1.nuls.io/", + "clientDocs": "https://docs.nuls.io/" + } + }, + { + "id": "zelcash", + "name": "Zelcash", + "coinId": 19167, + "symbol": "ZEL", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/19167'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 28, + "p2pkhPrefix": 184, + "p2shPrefix": 189, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": { + "url": "https://explorer.zel.cash", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "neo", - "name": "NEO", - "symbol": "NEO", - "decimals": 8, - "blockchain": "NEO", - "derivationPath": "m/44'/888'/0'/0/0", - "curve": "nist256p1", - "publicKeyType": "nist256p1", - "explorer": { - "url": "https://neoscan.io", - "txPath": "/transaction/", - "accountPath": "/address/", - "sampleTx": "e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53", - "sampleAccount": "AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb" - }, - "info": { - "url": "https://neo.org", - "client": "https://github.com/neo-project/neo", - "clientPublic": "http://seed1.ngd.network:10332", - "clientDocs": "https://neo.org/eco" - } + "info": { + "url": "https://zel.cash", + "client": "https://github.com/trezor/blockbook", + "clientPublic": "https://blockbook.zel.cash", + "clientDocs": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "wanchain", + "name": "Wanchain", + "coinId": 5718350, + "symbol": "WAN", + "decimals": 18, + "blockchain": "Wanchain", + "derivationPath": "m/44'/5718350'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://www.wanscan.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", + "sampleAccount": "0x69b492d57BB777E97AA7044d0575228434E2e8b1" }, - { - "id": "filecoin", - "name": "Filecoin", - "symbol": "FIL", - "decimals": 18, - "blockchain": "Filecoin", - "derivationPath": "m/44'/461'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "explorer": { - "url": "https://filscan.io", - "txPath": "/#/message/detail?cid=", - "accountPath": "/#/address/detail?address=", - "sampleTx": "bafy2bzacecbm3ofxjjzcl2rg32ninphza34mm3ijr55zjsamwfqmz4ib63mqe", - "sampleAccount": "t1nbb73vhk5dtmnsgeaetbo76daepqjtrfoccn74i" - }, - "info": { - "url": "https://filecoin.io/", - "client": "https://github.com/filecoin-project/lotus", - "clientPublic": "", - "clientDocs": "https://docs.lotu.sh" - } + "info": { + "url": "https://wanchain.org", + "client": "https://github.com/wanchain/go-wanchain", + "clientPublic": "", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" + } + }, + { + "id": "waves", + "name": "Waves", + "coinId": 5741564, + "symbol": "WAVES", + "decimals": 8, + "blockchain": "Waves", + "derivationPath": "m/44'/5741564'/0'/0'/0'", + "curve": "ed25519", + "publicKeyType": "curve25519", + "explorer": { + "url": "https://wavesexplorer.com", + "txPath": "/tx/", + "accountPath": "/address/" }, - { - "id": "elrond", - "name": "Elrond", - "symbol": "eGLD", - "decimals": 18, - "blockchain": "ElrondNetwork", - "derivationPath": "m/44'/508'/0'/0'/0'", - "curve": "ed25519", - "publicKeyType": "ed25519", - "hrp": "erd", - "explorer": { - "url": "https://explorer.elrond.com", - "txPath": "/transactions/", - "accountPath": "/address/" - }, - "info": { - "url": "https://elrond.com/", - "client": "https://github.com/ElrondNetwork/elrond-go", - "clientPublic": "https://api.elrond.com", - "clientDocs": "https://docs.elrond.com" - } + "info": { + "url": "https://wavesplatform.com", + "client": "https://github.com/wavesplatform/Waves", + "clientPublic": "https://nodes.wavesnodes.com", + "clientDocs": "https://nodes.wavesnodes.com/api-docs/index.html" + } + }, + { + "id": "binance-smart", + "name": "Binance SmartChain", + "coinId": 10000714, + "slip44": 714, + "symbol": "BNB", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/714'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer.binance.org/smart-testnet", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", + "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" }, - { - "id": "band", - "name": "BandChain", - "symbol": "BAND", - "decimals": 6, - "blockchain": "Cosmos", - "derivationPath": "m/44'/494'/0'/0/0", - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "band", - "explorer": { - "url": "https://scan-wenchang-testnet2.bandchain.org/", - "txPath": "/tx/", - "accountPath": "/account/", - "sampleTx": "473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173", - "sampleAccount": "band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp" - }, - "info": { - "url": "https://bandprotocol.com/", - "client": "https://github.com/bandprotocol/bandchain", - "clientPublic": "https://api-wt2-lb.bandchain.org", - "clientDocs": "https://docs.bandchain.org/" - } + "info": { + "url": "https://www.binance.org/en/smartChain", + "client": "https://github.com/binance-chain/bsc", + "clientPublic": "https://data-seed-prebsc-1-s1.binance.org:8545", + "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" } + } ] diff --git a/docs/coins.md b/docs/coins.md index 39221d07e8a..29f62adc27d 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -64,3 +64,4 @@ This list is generated from [./coins.json](../coins.json) | 19167 | Zelcash | ZEL | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | +| 10000714 | Binance SmartChain | BNB | | | diff --git a/include/TrustWalletCore/TWAccount.h b/include/TrustWalletCore/TWAccount.h index d9f377a7c45..f2056f2b44c 100644 --- a/include/TrustWalletCore/TWAccount.h +++ b/include/TrustWalletCore/TWAccount.h @@ -17,7 +17,7 @@ TW_EXPORT_CLASS struct TWAccount; TW_EXPORT_STATIC_METHOD -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey); +struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey); TW_EXPORT_METHOD void TWAccountDelete(struct TWAccount *_Nonnull account); diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 82339d8e605..44aaaaaa122 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -82,6 +82,7 @@ enum TWCoinType { TWCoinTypeFilecoin = 461, TWCoinTypeElrond = 508, TWCoinTypeBandChain = 494, + TWCoinTypeBinanceSmartChain = 10000714, }; /// Returns the blockchain for a coin type. @@ -138,4 +139,8 @@ uint8_t TWCoinTypeP2shPrefix(enum TWCoinType coin); TW_EXPORT_PROPERTY uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin); +/// Static prefix for this coin type +TW_EXPORT_PROPERTY +uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin); + TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumChainID.h b/include/TrustWalletCore/TWEthereumChainID.h index 573d4c2b413..2b9c925b274 100644 --- a/include/TrustWalletCore/TWEthereumChainID.h +++ b/include/TrustWalletCore/TWEthereumChainID.h @@ -21,6 +21,7 @@ enum TWEthereumChainID { TWEthereumChainIDVeChain = 74, TWEthereumChainIDThunderToken = 108, TWEthereumChainIDTomoChain = 88, + TWEthereumChainIDBinanceSmartChain = 97, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index ab6ae80be94..cd2c291a6df 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -63,7 +63,7 @@ TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *_Nonnull walle /// Generates the private key for the specified derivation path. TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, TWString *_Nonnull derivationPath); +struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, TWString *_Nonnull derivationPath); /// Generates the private key for the specified BIP44 path. /// @@ -81,6 +81,6 @@ TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *_Nonnull wa /// Computes the public key from an exteded public key representation. TW_EXPORT_STATIC_METHOD -struct TWPublicKey *_Nullable TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, TWString *_Nonnull derivationPath); +struct TWPublicKey *_Nullable TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, enum TWCoinType coin, TWString *_Nonnull derivationPath); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index be2cc91ff52..ec72fef83d9 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -72,7 +72,7 @@ void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCo /// Adds a new account. TW_EXPORT_METHOD -void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey); +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey); /// Saves the key to a file. TW_EXPORT_METHOD diff --git a/src/Binance/Address.cpp b/src/Binance/Address.cpp index 311c05306e8..481f2eda9ca 100644 --- a/src/Binance/Address.cpp +++ b/src/Binance/Address.cpp @@ -8,8 +8,21 @@ #include "Address.h" #include +#include using namespace TW::Binance; const std::string Address::hrp = HRP_BINANCE; +const std::string Address::hrpValidator = "bva"; +bool Address::isValid(const std::string& addr) { + std::vector hrps = {hrp, hrpValidator, "bnbp", "bvap", "bca", "bcap"}; + bool result = false; + for (auto& hrp : hrps) { + result = Bech32Address::isValid(addr, hrp); + if (result) { + break; + } + } + return result; +} diff --git a/src/Binance/Address.h b/src/Binance/Address.h index 198540e0962..2fb7c1fab0d 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -16,8 +16,9 @@ namespace TW::Binance { class Address: public Bech32Address { public: static const std::string hrp; // HRP_BINANCE + static const std::string hrpValidator; // HRP_BINANCE - static bool isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } + static bool isValid(const std::string& addr); Address() : Bech32Address(hrp) {} diff --git a/src/Binance/Serialization.cpp b/src/Binance/Serialization.cpp index 9cf72597680..a6493e68d83 100644 --- a/src/Binance/Serialization.cpp +++ b/src/Binance/Serialization.cpp @@ -7,20 +7,27 @@ #include "Serialization.h" #include "Address.h" +#include "Bech32Address.h" +#include "Ethereum/Address.h" #include "../HexCoding.h" using namespace TW; using namespace TW::Binance; +using namespace google::protobuf; using json = nlohmann::json; static inline std::string addressString(const std::string& bytes) { - auto data = std::vector(bytes.begin(), bytes.end()); - auto address = Address(data); - return address.string(); + auto data = Data(bytes.begin(), bytes.end()); + return Address(data).string(); } -json Binance::signatureJSON(const Binance::Proto::SigningInput& input) { +static inline std::string validatorAddress(const std::string& bytes) { + auto data = Data(bytes.begin(), bytes.end()); + return Bech32Address(Address::hrpValidator, data).string(); +} + +json Binance::signatureJSON(const Proto::SigningInput& input) { json j; j["account_number"] = std::to_string(input.account_number()); j["chain_id"] = input.chain_id(); @@ -32,7 +39,7 @@ json Binance::signatureJSON(const Binance::Proto::SigningInput& input) { return j; } -json Binance::orderJSON(const Binance::Proto::SigningInput& input) { +json Binance::orderJSON(const Proto::SigningInput& input) { json j; if (input.has_trade_order()) { j["id"] = input.trade_order().id(); @@ -80,40 +87,78 @@ json Binance::orderJSON(const Binance::Proto::SigningInput& input) { } else if (input.has_refundhtlt_order()) { j["from"] = addressString(input.refundhtlt_order().from()); j["swap_id"] = hex(input.refundhtlt_order().swap_id()); + } else if (input.has_transfer_out_order()) { + auto to = input.transfer_out_order().to(); + auto addr = Ethereum::Address(Data(to.begin(), to.end())); + j["from"] = addressString(input.transfer_out_order().from()); + j["to"] = addr.string(); + j["amount"] = tokenJSON(input.transfer_out_order().amount()); + j["expire_time"] = input.transfer_out_order().expire_time(); + } else if (input.has_side_delegate_order()) { + j["type"] = "cosmos-sdk/MsgSideChainDelegate"; + j["value"] = { + {"delegator_addr", addressString(input.side_delegate_order().delegator_addr())}, + {"validator_addr",validatorAddress(input.side_delegate_order().validator_addr())}, + {"delegation", tokenJSON(input.side_delegate_order().delegation(), true)}, + {"side_chain_id", input.side_delegate_order().chain_id()}, + }; + } else if (input.has_side_redelegate_order()) { + j["type"] = "cosmos-sdk/MsgSideChainRedelegate"; + j["value"] = { + {"delegator_addr", addressString(input.side_redelegate_order().delegator_addr())}, + {"validator_src_addr", validatorAddress(input.side_redelegate_order().validator_src_addr())}, + {"validator_dst_addr", validatorAddress(input.side_redelegate_order().validator_dst_addr())}, + {"amount", tokenJSON(input.side_redelegate_order().amount(), true)}, + {"side_chain_id", input.side_redelegate_order().chain_id()}, + }; + } else if (input.has_side_undelegate_order()) { + j["type"] = "cosmos-sdk/MsgSideChainUndelegate"; + j["value"] = { + {"delegator_addr", addressString(input.side_undelegate_order().delegator_addr())}, + {"validator_addr", validatorAddress(input.side_undelegate_order().validator_addr())}, + {"amount", tokenJSON(input.side_undelegate_order().amount(), true)}, + {"side_chain_id", input.side_undelegate_order().chain_id()}, + }; } return j; } -json Binance::inputsJSON(const Binance::Proto::SendOrder& order) { +json Binance::inputsJSON(const Proto::SendOrder& order) { json j = json::array(); for (auto& input : order.inputs()) { - json sj; - sj["address"] = addressString(input.address()); - sj["coins"] = tokensJSON(input.coins()); - j.push_back(sj); + j.push_back({ + {"address", addressString(input.address())}, + {"coins", tokensJSON(input.coins())} + }); } return j; } -json Binance::outputsJSON(const Binance::Proto::SendOrder& order) { +json Binance::outputsJSON(const Proto::SendOrder& order) { json j = json::array(); for (auto& output : order.outputs()) { - json sj; - sj["address"] = addressString(output.address()); - sj["coins"] = tokensJSON(output.coins()); - j.push_back(sj); + j.push_back({ + {"address", addressString(output.address())}, + {"coins", tokensJSON(output.coins())} + }); + } + return j; +} + +json Binance::tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount) { + json j = { {"denom", token.denom()} }; + if (stringAmount) { + j["amount"] = std::to_string(token.amount()); + } else { + j["amount"] = token.amount(); } return j; } -json Binance::tokensJSON( - const ::google::protobuf::RepeatedPtrField& tokens) { +json Binance::tokensJSON(const RepeatedPtrField& tokens) { json j = json::array(); for (auto& token : tokens) { - json sj; - sj["denom"] = token.denom(); - sj["amount"] = token.amount(); - j.push_back(sj); + j.push_back(tokenJSON(token)); } return j; } diff --git a/src/Binance/Serialization.h b/src/Binance/Serialization.h index 5baf29dec85..9b06682fbec 100644 --- a/src/Binance/Serialization.h +++ b/src/Binance/Serialization.h @@ -11,10 +11,11 @@ namespace TW::Binance { -nlohmann::json signatureJSON(const Binance::Proto::SigningInput& input); -nlohmann::json orderJSON(const Binance::Proto::SigningInput& input); -nlohmann::json inputsJSON(const Binance::Proto::SendOrder& order); -nlohmann::json outputsJSON(const Binance::Proto::SendOrder& order); -nlohmann::json tokensJSON(const ::google::protobuf::RepeatedPtrField& tokens); +nlohmann::json signatureJSON(const Proto::SigningInput& input); +nlohmann::json orderJSON(const Proto::SigningInput& input); +nlohmann::json inputsJSON(const Proto::SendOrder& order); +nlohmann::json outputsJSON(const Proto::SendOrder& order); +nlohmann::json tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount = false); +nlohmann::json tokensJSON(const ::google::protobuf::RepeatedPtrField& tokens); } // namespace TW::Binance diff --git a/src/Binance/Signer.cpp b/src/Binance/Signer.cpp index d5eddf7cd3e..a847cb24a8c 100644 --- a/src/Binance/Signer.cpp +++ b/src/Binance/Signer.cpp @@ -35,6 +35,10 @@ static const auto tokenMintOrderPrefix = Data{0x46, 0x7E, 0x08, 0x29}; static const auto tokenBurnOrderPrefix = Data{0x7E, 0xD2, 0xD2, 0xA0}; static const auto tokenFreezeOrderPrefix = Data{0xE7, 0x74, 0xB3, 0x2D}; static const auto tokenUnfreezeOrderPrefix = Data{0x65, 0x15, 0xFF, 0x0D}; +static const auto transferOutOrderPrefix = Data{0x80, 0x08, 0x19, 0xC0}; +static const auto sideDelegateOrderPrefix = Data{0xE3, 0xA0, 0x7F, 0xD2}; +static const auto sideRedelegateOrderPrefix = Data{0xE3, 0xCE, 0xD3, 0x64}; +static const auto sideUndelegateOrderPrefix = Data{0x51, 0x4F, 0x7E, 0x0E}; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); @@ -120,6 +124,18 @@ Data Signer::encodeOrder() const { } else if (input.has_refundhtlt_order()) { data = input.refundhtlt_order().SerializeAsString(); prefix = refundHTLTOrderPrefix; + } else if (input.has_transfer_out_order()) { + data = input.transfer_out_order().SerializeAsString(); + prefix = transferOutOrderPrefix; + } else if (input.has_side_delegate_order()) { + data = input.side_delegate_order().SerializeAsString(); + prefix = sideDelegateOrderPrefix; + } else if (input.has_side_redelegate_order()) { + data = input.side_redelegate_order().SerializeAsString(); + prefix = sideRedelegateOrderPrefix; + } else if (input.has_side_undelegate_order()) { + data = input.side_undelegate_order().SerializeAsString(); + prefix = sideUndelegateOrderPrefix; } else { return {}; } diff --git a/src/Coin.cpp b/src/Coin.cpp index 3d370443db4..448b21edc02 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -278,6 +278,10 @@ Hash::Hasher TW::base58Hasher(TWCoinType coin) { return getCoinInfo(coin).base58Hasher; } +uint32_t TW::slip44Id(TWCoinType coin) { + return getCoinInfo(coin).slip44; +} + TWString *_Nullable TWCoinTypeConfigurationGetSymbol(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).symbol); } diff --git a/src/Coin.h b/src/Coin.h index 12b4012a096..d48516a8203 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -80,6 +80,8 @@ enum TWHRP hrp(TWCoinType coin); // Note: use output parameter to avoid unneeded copies void anyCoinSign(TWCoinType coinType, const Data& dataIn, Data& dataOut); +uint32_t slip44Id(TWCoinType coin); + std::string anySignJSON(TWCoinType coinType, const std::string& json, const Data& key); bool supportsJSONSigning(TWCoinType coinType); @@ -110,6 +112,7 @@ struct CoinInfo { int decimals; const char* explorerTransactionUrl; const char* explorerAccountUrl; + uint32_t slip44; }; } // namespace TW diff --git a/src/DerivationPath.h b/src/DerivationPath.h index 0955d24cc37..be6b6602d07 100644 --- a/src/DerivationPath.h +++ b/src/DerivationPath.h @@ -55,12 +55,12 @@ struct DerivationPath { indices[0] = DerivationPathIndex(v, /* hardened: */ true); } - TWCoinType coin() const { + uint32_t coin() const { if (indices.size() <= 1) { return TWCoinTypeBitcoin; } - return static_cast(indices[1].value); + return indices[1].value; } - void setCoin(TWCoinType v) { + void setCoin(uint32_t v) { if (indices.size() <= 1) { return; } indices[1] = DerivationPathIndex(v, /* hardened: */ true); } @@ -100,7 +100,7 @@ struct DerivationPath { explicit DerivationPath(std::vector indices) : indices(std::move(indices)) {} /// Creates a `DerivationPath` by BIP44 components. - DerivationPath(TWPurpose purpose, TWCoinType coin, uint32_t account, uint32_t change, + DerivationPath(TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, uint32_t address) : indices(std::vector(5)) { setPurpose(purpose); diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index 91134ae54e1..b7fe34a5da8 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -23,6 +23,7 @@ class Entry: public CoinEntry { TWCoinTypePOANetwork, TWCoinTypeThunderToken, TWCoinTypeTomoChain, + TWCoinTypeBinanceSmartChain, }; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 8f9461e0a6d..dc4f7beb803 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -87,8 +87,8 @@ PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { return PrivateKey(data); } -PrivateKey HDWallet::getKey(const DerivationPath& derivationPath) const { - const auto curve = TWCoinTypeCurve(derivationPath.coin()); +PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { + const auto curve = TWCoinTypeCurve(coin); const auto privateKeyType = getPrivateKeyType(curve); auto node = getNode(*this, curve, derivationPath); switch (privateKeyType) { @@ -110,7 +110,7 @@ PrivateKey HDWallet::getKey(const DerivationPath& derivationPath) const { std::string HDWallet::deriveAddress(TWCoinType coin) const { const auto derivationPath = TW::derivationPath(coin); - return TW::deriveAddress(coin, getKey(derivationPath)); + return TW::deriveAddress(coin, getKey(coin, derivationPath)); } std::string HDWallet::getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { @@ -140,8 +140,7 @@ std::string HDWallet::getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, T return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); } -std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, const DerivationPath& path) { - const auto coin = path.coin(); +std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { const auto curve = TW::curve(coin); const auto hasher = TW::base58Hasher(coin); @@ -166,8 +165,7 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string& e return {}; } -std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, const DerivationPath& path) { - const auto coin = path.coin(); +std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { const auto curve = TW::curve(coin); const auto hasher = TW::base58Hasher(coin); diff --git a/src/HDWallet.h b/src/HDWallet.h index 784dcbd0c34..ce43a762d70 100644 --- a/src/HDWallet.h +++ b/src/HDWallet.h @@ -71,7 +71,7 @@ class HDWallet { PrivateKey getMasterKeyExtension(TWCurve curve) const; /// Returns the private key at the given derivation path. - PrivateKey getKey(const DerivationPath& derivationPath) const; + PrivateKey getKey(const TWCoinType coin, const DerivationPath& derivationPath) const; /// Derives the address for a coin. std::string deriveAddress(TWCoinType coin) const; @@ -83,10 +83,10 @@ class HDWallet { std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const; /// Computes the public key from an exteded public key representation. - static std::optional getPublicKeyFromExtended(const std::string& extended, const DerivationPath& path); + static std::optional getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); /// Computes the private key from an exteded private key representation. - static std::optional getPrivateKeyFromExtended(const std::string& extended, const DerivationPath& path); + static std::optional getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); public: // Private key type (later could be moved out of HDWallet) diff --git a/src/Keystore/Account.cpp b/src/Keystore/Account.cpp index 5a08b7b94bf..c27b7dbfca7 100644 --- a/src/Keystore/Account.cpp +++ b/src/Keystore/Account.cpp @@ -14,12 +14,13 @@ using namespace TW; using namespace TW::Keystore; namespace CodingKeys { -static const auto address = "address"; -static const auto derivationPath = "derivationPath"; -static const auto extendedPublicKey = "extendedPublicKey"; -static const auto indices = "indices"; -static const auto value = "value"; -static const auto hardened = "hardened"; + static const auto address = "address"; + static const auto derivationPath = "derivationPath"; + static const auto extendedPublicKey = "extendedPublicKey"; + static const auto indices = "indices"; + static const auto value = "value"; + static const auto hardened = "hardened"; + static const auto coin = "coin"; } // namespace CodingKeys Account::Account(const nlohmann::json& json) { @@ -33,6 +34,13 @@ Account::Account(const nlohmann::json& json) { derivationPath = DerivationPath(json[CodingKeys::derivationPath].get()); } + if (json.find(CodingKeys::coin) == json.end()) { + // legacy format, get coin from derivation path + coin = TWCoinType(uint32_t(derivationPath.indices[1].value)); + } else { + coin = TWCoinType(json[CodingKeys::coin].get()); + } + if (json.count(CodingKeys::address) != 0 && json[CodingKeys::address].is_string()) { address = json[CodingKeys::address].get(); } else { @@ -49,6 +57,7 @@ nlohmann::json Account::json() const { nlohmann::json j; j[CodingKeys::address] = address; j[CodingKeys::derivationPath] = derivationPath.string(); + j[CodingKeys::coin] = coin; if (!extendedPublicKey.empty()) { j[CodingKeys::extendedPublicKey] = extendedPublicKey; } diff --git a/src/Keystore/Account.h b/src/Keystore/Account.h index bf9b455b9ea..fe5c137fe9a 100644 --- a/src/Keystore/Account.h +++ b/src/Keystore/Account.h @@ -26,13 +26,14 @@ class Account { std::string extendedPublicKey; /// Coin this account is for. - TWCoinType coin() const { return derivationPath.coin(); } + TWCoinType coin; Account() = default; - Account(std::string address, DerivationPath derivationPath, std::string extendedPublicKey = "") + Account(std::string address, TWCoinType coin, DerivationPath derivationPath, std::string extendedPublicKey = "") : address(std::move(address)) , derivationPath(std::move(derivationPath)) - , extendedPublicKey(std::move(extendedPublicKey)) {} + , extendedPublicKey(std::move(extendedPublicKey)) + , coin(coin) {} /// Initializes `Account` with a JSON object. Account(const nlohmann::json& json); diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index bba73ee4907..6c0032b6752 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -51,9 +51,9 @@ StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name const auto wallet = HDWallet(mnemonic, ""); const auto derivationPath = TW::derivationPath(coin); - const auto address = TW::deriveAddress(coin, wallet.getKey(derivationPath)); + const auto address = TW::deriveAddress(coin, wallet.getKey(coin, derivationPath)); const auto extendedKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TW::xpubVersion(coin)); - key.accounts.emplace_back(address, derivationPath, extendedKey); + key.accounts.emplace_back(address, coin, derivationPath, extendedKey); return key; } @@ -73,7 +73,7 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na const auto derivationPath = TW::derivationPath(coin); const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); - key.accounts.emplace_back(address, derivationPath); + key.accounts.emplace_back(address, coin, derivationPath); return key; } @@ -95,7 +95,7 @@ const HDWallet StoredKey::wallet(const Data& password) const { const Account* StoredKey::account(TWCoinType coin) const { for (auto& account : accounts) { - if (account.coin() == coin) { + if (account.coin == coin) { return &account; } } @@ -109,7 +109,7 @@ const Account* StoredKey::account(TWCoinType coin, const HDWallet* wallet) { assert(wallet != nullptr); for (auto& account : accounts) { - if (account.coin() == coin) { + if (account.coin == coin) { if (account.address.empty()) { account.address = wallet->deriveAddress(coin); } @@ -123,28 +123,26 @@ const Account* StoredKey::account(TWCoinType coin, const HDWallet* wallet) { const auto version = TW::xpubVersion(coin); const auto extendedPublicKey = wallet->getExtendedPublicKey(derivationPath.purpose(), coin, version); - accounts.emplace_back(address, derivationPath, extendedPublicKey); + accounts.emplace_back(address, coin, derivationPath, extendedPublicKey); return &accounts.back(); } -void StoredKey::addAccount(const std::string& address, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) { - accounts.emplace_back(address, derivationPath, extetndedPublicKey); +void StoredKey::addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey) { + accounts.emplace_back(address, coin, derivationPath, extetndedPublicKey); } void StoredKey::removeAccount(TWCoinType coin) { accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool { - return account.coin() == coin; - } - ), accounts.end()); + return account.coin == coin; + }), accounts.end()); } - const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { switch (type) { case StoredKeyType::mnemonicPhrase: { const auto wallet = this->wallet(password); const auto account = *this->account(coin, &wallet); - return wallet.getKey(account.derivationPath); + return wallet.getKey(coin, account.derivationPath); } case StoredKeyType::privateKey: return PrivateKey(payload.decrypt(password)); @@ -156,12 +154,12 @@ void StoredKey::fixAddresses(const Data& password) { case StoredKeyType::mnemonicPhrase: { const auto wallet = this->wallet(password); for (auto& account : accounts) { - if (!account.address.empty() && TW::validateAddress(account.coin(), account.address)) { + if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) { continue; } const auto& derivationPath = account.derivationPath; - const auto key = wallet.getKey(derivationPath); - account.address = TW::deriveAddress(derivationPath.coin(), key); + const auto key = wallet.getKey(account.coin, derivationPath); + account.address = TW::deriveAddress(account.coin, key); } } break; @@ -169,10 +167,10 @@ void StoredKey::fixAddresses(const Data& password) { case StoredKeyType::privateKey: { auto key = PrivateKey(payload.decrypt(password)); for (auto& account : accounts) { - if (!account.address.empty() && TW::validateAddress(account.coin(), account.address)) { + if (!account.address.empty() && TW::validateAddress(account.coin, account.address)) { continue; } - account.address = TW::deriveAddress(account.coin(), key); + account.address = TW::deriveAddress(account.coin, key); } } break; @@ -191,23 +189,23 @@ StoredKey StoredKey::createWithJson(const nlohmann::json& json) { } namespace CodingKeys { -static const auto address = "address"; -static const auto type = "type"; -static const auto name = "name"; -static const auto id = "id"; -static const auto crypto = "crypto"; -static const auto activeAccounts = "activeAccounts"; -static const auto version = "version"; -static const auto coin = "coin"; + static const auto address = "address"; + static const auto type = "type"; + static const auto name = "name"; + static const auto id = "id"; + static const auto crypto = "crypto"; + static const auto activeAccounts = "activeAccounts"; + static const auto version = "version"; + static const auto coin = "coin"; } // namespace CodingKeys namespace UppercaseCodingKeys { -static const auto crypto = "Crypto"; + static const auto crypto = "Crypto"; } // namespace UppercaseCodingKeys namespace TypeString { -static const auto privateKey = "private-key"; -static const auto mnemonic = "mnemonic"; + static const auto privateKey = "private-key"; + static const auto mnemonic = "mnemonic"; } // namespace TypeString void StoredKey::loadJson(const nlohmann::json& json) { @@ -248,7 +246,7 @@ void StoredKey::loadJson(const nlohmann::json& json) { coin = json[CodingKeys::coin].get(); } auto address = json[CodingKeys::address].get(); - accounts.emplace_back(address, DerivationPath(TWPurposeBIP44, coin, 0, 0, 0)); + accounts.emplace_back(address, coin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0)); } } diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 475271e9283..4ec24ad2211 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -77,7 +77,7 @@ class StoredKey { const Account* account(TWCoinType coin) const; /// Add an account - void addAccount(const std::string& address, const DerivationPath& derivationPath, const std::string& extetndedPublicKey); + void addAccount(const std::string& address, TWCoinType coin, const DerivationPath& derivationPath, const std::string& extetndedPublicKey); /// Remove the account for a specific coin void removeAccount(TWCoinType coin); diff --git a/src/interface/TWAccount.cpp b/src/interface/TWAccount.cpp index 302874725fd..cbe49c3f321 100644 --- a/src/interface/TWAccount.cpp +++ b/src/interface/TWAccount.cpp @@ -10,12 +10,12 @@ using namespace TW; -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey) { +struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, TWString *_Nonnull derivationPath, TWString *_Nonnull extendedPublicKey) { auto& addressString = *reinterpret_cast(address); auto& derivationPathString = *reinterpret_cast(derivationPath); auto& extendedPublicKeyString = *reinterpret_cast(extendedPublicKey); const auto dp = DerivationPath(derivationPathString); - return new TWAccount{ Keystore::Account(addressString, dp, extendedPublicKeyString) }; + return new TWAccount{ Keystore::Account(addressString, coin, dp, extendedPublicKeyString) }; } void TWAccountDelete(struct TWAccount *_Nonnull account) { @@ -35,5 +35,5 @@ TWString *_Nonnull TWAccountExtendedPublicKey(struct TWAccount *_Nonnull account } enum TWCoinType TWAccountCoin(struct TWAccount *_Nonnull account) { - return account->impl.coin(); + return account->impl.coin; } diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index 93cac16bfc1..5768adf92e6 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -143,6 +143,7 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { case TWCoinTypeTheta: case TWCoinTypeWanchain: case TWCoinTypeAion: + case TWCoinTypeBinanceSmartChain: data = parse_hex(string); break; diff --git a/src/interface/TWCoinType.cpp b/src/interface/TWCoinType.cpp index c5035c8b6df..a99ecee7c75 100644 --- a/src/interface/TWCoinType.cpp +++ b/src/interface/TWCoinType.cpp @@ -64,3 +64,7 @@ uint8_t TWCoinTypeP2shPrefix(enum TWCoinType coin) { uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin) { return TW::staticPrefix(coin); } + +uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin) { + return TW::slip44Id(coin); +} diff --git a/src/interface/TWHDWallet.cpp b/src/interface/TWHDWallet.cpp index eff92fa74a8..b8a3846a055 100644 --- a/src/interface/TWHDWallet.cpp +++ b/src/interface/TWHDWallet.cpp @@ -46,24 +46,25 @@ struct TWPrivateKey *_Nonnull TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull struct TWPrivateKey *_Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet *wallet, TWCoinType coin) { auto derivationPath = TW::derivationPath(coin); - return new TWPrivateKey{ wallet->impl.getKey(derivationPath) }; + return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; } TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *wallet, TWCoinType coin) { auto derivationPath = TW::derivationPath(coin); - PrivateKey privateKey = wallet->impl.getKey(derivationPath); + PrivateKey privateKey = wallet->impl.getKey(coin, derivationPath); std::string address = deriveAddress(coin, privateKey); return TWStringCreateWithUTF8Bytes(address.c_str()); } -struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, TWString *_Nonnull derivationPath) { +struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, TWString *_Nonnull derivationPath) { auto& s = *reinterpret_cast(derivationPath); - return new TWPrivateKey{ wallet->impl.getKey( TW::DerivationPath(s)) }; + const auto path = DerivationPath(s); + return new TWPrivateKey{ wallet->impl.getKey(coin, path) }; } struct TWPrivateKey *_Nonnull TWHDWalletGetKeyBIP44(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address) { - const auto derivationPath = DerivationPath(TW::purpose(coin), coin, account, change, address); - return new TWPrivateKey{ wallet->impl.getKey(derivationPath) }; + const auto derivationPath = DerivationPath(TW::purpose(coin), TW::slip44Id(coin), account, change, address); + return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; } TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWHDVersion version) { @@ -74,9 +75,9 @@ TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *wallet, TWP return new std::string(wallet->impl.getExtendedPublicKey(purpose, coin, version)); } -TWPublicKey *TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, TWString *_Nonnull derivationPath) { +TWPublicKey *TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, enum TWCoinType coin, TWString *_Nonnull derivationPath) { const auto derivationPathObject = DerivationPath(*reinterpret_cast(derivationPath)); - auto publicKey = HDWallet::getPublicKeyFromExtended(*reinterpret_cast(extended), derivationPathObject); + auto publicKey = HDWallet::getPublicKeyFromExtended(*reinterpret_cast(extended), coin, derivationPathObject); if (!publicKey) { return nullptr; } diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index 28dd3d992c6..3d2ed4c7284 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -107,11 +107,11 @@ void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCo key->impl.removeAccount(coin); } -void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey) { +void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull extetndedPublicKey) { const auto& addressString = *reinterpret_cast(address); const auto& extetndedPublicKeyString = *reinterpret_cast(extetndedPublicKey); const auto dp = TW::DerivationPath(*reinterpret_cast(derivationPath)); - key->impl.addAccount(addressString, dp, extetndedPublicKeyString); + key->impl.addAccount(addressString, coin, dp, extetndedPublicKeyString); } bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) { diff --git a/src/proto/Binance.proto b/src/proto/Binance.proto index 37d2467175f..595892b034d 100644 --- a/src/proto/Binance.proto +++ b/src/proto/Binance.proto @@ -132,6 +132,35 @@ message RefundHTLTOrder { bytes swap_id = 2; } +message TransferOut { + bytes from = 1; + bytes to = 2; + SendOrder.Token amount = 3; + int64 expire_time = 4; +} + +message SideChainDelegate { + bytes delegator_addr = 1; + bytes validator_addr = 2; + SendOrder.Token delegation = 3; + string chain_id = 4; +} + +message SideChainRedelegate { + bytes delegator_addr = 1; + bytes validator_src_addr = 2; + bytes validator_dst_addr = 3; + SendOrder.Token amount = 4; + string chain_id = 5; +} + +message SideChainUndelegate { + bytes delegator_addr = 1; + bytes validator_addr = 2; + SendOrder.Token amount = 3; + string chain_id = 4; +} + // Input data necessary to create a signed order. message SigningInput { string chain_id = 1; @@ -154,6 +183,10 @@ message SigningInput { TokenIssueOrder issue_order = 17; TokenMintOrder mint_order = 18; TokenBurnOrder burn_order = 19; + TransferOut transfer_out_order = 20; + SideChainDelegate side_delegate_order = 21; + SideChainRedelegate side_redelegate_order = 22; + SideChainUndelegate side_undelegate_order = 23; } } diff --git a/swift/Sources/DerivationPath.swift b/swift/Sources/DerivationPath.swift index bf2f468e8e9..c9f694bb6b4 100644 --- a/swift/Sources/DerivationPath.swift +++ b/swift/Sources/DerivationPath.swift @@ -24,12 +24,12 @@ public struct DerivationPath: Codable, Hashable, CustomStringConvertible { } /// Coin type distinguishes between main net, test net, and forks. - public var coinType: CoinType { + public var coinType: UInt32 { get { - return CoinType(rawValue: indices[1].value)! + return indices[1].value } set { - indices[1] = Index(newValue.rawValue, hardened: true) + indices[1] = Index(newValue, hardened: true) } } @@ -69,10 +69,10 @@ public struct DerivationPath: Codable, Hashable, CustomStringConvertible { } /// Creates a `DerivationPath` by components. - public init(purpose: Purpose, coinType: CoinType, account: UInt32 = 0, change: UInt32 = 0, address: UInt32 = 0) { + public init(purpose: Purpose, coin: UInt32, account: UInt32 = 0, change: UInt32 = 0, address: UInt32 = 0) { self.indices = [Index](repeating: Index(0), count: indexCount) self.purpose = purpose - self.coinType = coinType + self.coinType = coin self.account = account self.change = change self.address = address diff --git a/swift/Sources/Extensions/HDWallet+Extension.swift b/swift/Sources/Extensions/HDWallet+Extension.swift deleted file mode 100644 index b57b93c1f11..00000000000 --- a/swift/Sources/Extensions/HDWallet+Extension.swift +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2017-2020 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. - -/// A hierarchical deterministic wallet. -public extension HDWallet { - static func derive(from extended: String, at path: DerivationPath) -> PublicKey? { - return HDWallet.getPublicKeyFromExtended(extended: extended, derivationPath: path.description) - } - - func getKey(at path: DerivationPath) -> PrivateKey { - return getKey(derivationPath: path.description) - } -} diff --git a/swift/Tests/Blockchains/BinanceChainTests.swift b/swift/Tests/Blockchains/BinanceChainTests.swift index 4965311d499..339ed8c43bd 100644 --- a/swift/Tests/Blockchains/BinanceChainTests.swift +++ b/swift/Tests/Blockchains/BinanceChainTests.swift @@ -25,7 +25,7 @@ class BinanceChainTests: XCTestCase { XCTAssertEqual("bnb1devga6q804tx9fqrnx0vtu5r36kxgp9tmk4xkm", address.description) } - func testSigning() { + func testSignSendOrder() { let privateKey = PrivateKey(data: Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")!)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) @@ -63,6 +63,56 @@ class BinanceChainTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679") } + func testSignTransferOutOrder() throws { + let key = PrivateKey(data: Data(hexString: "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let input = BinanceSigningInput.with { + $0.chainID = "test-chain" + $0.accountNumber = 15 + $0.sequence = 1 + $0.privateKey = key.data + $0.transferOutOrder = BinanceTransferOut.with { + $0.from = AnyAddress(publicKey: pubkey, coin: .binance).data + $0.to = AnyAddress(string: "0x35552c16704d214347f29Fa77f77DA6d75d7C752", coin: .ethereum)!.data + $0.amount = BinanceSendOrder.Token.with { + $0.denom = "BNB" + $0.amount = 100000000 + } + $0.expireTime = 12345678 + } + } + + let output: BinanceSigningOutput = AnySigner.sign(input: input, coin: .binance) + + // swiftlint:disable:next line_length + XCTAssertEqual(output.encoded.hexString, "b701f0625dee0a41800819c00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121435552c16704d214347f29fa77f77da6d75d7c7521a0a0a03424e421080c2d72f20cec2f105126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712407eda148e1167b1be1271a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95ef3983cad85a29cd14262c22e0180f2001") + } + + func testSignStakeOrder() throws { + let key = PrivateKey(data: Data(hexString: "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let input = BinanceSigningInput.with { + $0.chainID = "test-chain" + $0.accountNumber = 15 + $0.sequence = 1 + $0.privateKey = key.data + $0.sideDelegateOrder = BinanceSideChainDelegate.with { + $0.delegatorAddr = AnyAddress(publicKey: pubkey, coin: .binance).data + $0.validatorAddr = AnyAddress(string: "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", coin: .binance)!.data + $0.delegation = BinanceSendOrder.Token.with { + $0.denom = "BNB" + $0.amount = 200000000 + } + $0.chainID = "chapel" + } + } + + let output: BinanceSigningOutput = AnySigner.sign(input: input, coin: .binance) + + // swiftlint:disable:next line_length + XCTAssertEqual(output.encoded.hexString, "ba01f0625dee0a44e3a07fd20a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112147cc24a1de5245f14a95e457f903bcc8461ac869c1a0a0a03424e42108084af5f220663686170656c126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7124039302c9975fb2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d9d8f46aeb3627a7d7aa901fe186af34c180f2001") + } + func testSignJSON() { let json = """ { diff --git a/swift/Tests/Blockchains/BinanceSmartChainTests.swift b/swift/Tests/Blockchains/BinanceSmartChainTests.swift new file mode 100644 index 00000000000..a4aaa711e77 --- /dev/null +++ b/swift/Tests/Blockchains/BinanceSmartChainTests.swift @@ -0,0 +1,20 @@ +// Copyright © 2017-2020 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. + +import TrustWalletCore +import XCTest + +class BinanceSmartChainTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .binanceSmartChain) + let expected = AnyAddress(string: "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coin: .binanceSmartChain)! + + XCTAssertEqual(address.description, expected.description) + } +} diff --git a/swift/Tests/Blockchains/BitconCashTests.swift b/swift/Tests/Blockchains/BitconCashTests.swift index 64b873d3262..76a2b0828ed 100644 --- a/swift/Tests/Blockchains/BitconCashTests.swift +++ b/swift/Tests/Blockchains/BitconCashTests.swift @@ -23,9 +23,17 @@ class BitcoinCashTests: XCTestCase { let xpub = "xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw" let coin = CoinType.bitcoinCash - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 0, address: 2))! - - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 2).description + )! + + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr2), "bitcoincash:qq4cm0hcc4trsj98v425f4ackdq7h92rsy6zzstrgy") XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr9), "bitcoincash:qqyqupaugd7mycyr87j899u02exc6t2tcg9frrqnve") diff --git a/swift/Tests/Blockchains/DashTests.swift b/swift/Tests/Blockchains/DashTests.swift index 211d491cc93..2af2b70a34c 100644 --- a/swift/Tests/Blockchains/DashTests.swift +++ b/swift/Tests/Blockchains/DashTests.swift @@ -29,8 +29,16 @@ class DashAddressTests: XCTestCase { let xpub = "xpub6DRhaBX1cn2rRtbpphQTSLYcR3ABXzQeEYoT44MjbwTanhw1ePtNcYTZNeHyrJMsMGTbig4iFMSvht7RviohzFxkpjURgHDThygLqbZ1tib" let coin = CoinType.dash - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 1, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: coin.purpose, coinType: coin, account: 0, change: 1, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: coin.purpose, coin: coin.slip44Id, account: 0, change: 1, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: coin, + derivationPath: DerivationPath(purpose: coin.purpose, coin: coin.slip44Id, account: 0, change: 1, address: 9).description + )! XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey: xpubAddr2), "Xh4D3Mv6ikL5iR45bEsCtaR8Ub4jkRLpU2") XCTAssertEqual(coin.deriveAddressFromPublicKey(publicKey:xpubAddr9), "XvwNJsXVBpvAU92xPwU8phT6wKjJVaBMkk") diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index e4bd63e71c4..e590ae3cfdc 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -22,7 +22,11 @@ class DecredTests: XCTestCase { func testDeriveFromDpub() { let dpub = "dpubZFUmm9oh5zmQkR2Tr2AXS4tCkTWg4B27SpCPFkapZrrAqgU1EwgEFgrmi6EnLGXhak86yDHhXPxFAnGU58W5S4e8NCKG1ASUVaxwRqqNdfP" - let pubkey0 = HDWallet.derive(from: dpub, at: DerivationPath(purpose: .bip44, coinType: .decred, account: 0, change: 0, address: 0))! + let pubkey0 = HDWallet.getPublicKeyFromExtended( + extended: dpub, + coin: .decred, + derivationPath: DerivationPath(purpose: .bip44, coin: CoinType.decred.slip44Id, account: 0, change: 0, address: 0).description + )! XCTAssertEqual(AnyAddress(publicKey: pubkey0, coin: .decred).description, "DsksmLD2wDoA8g8QfFvm99ASg8KsZL8eJFd") } diff --git a/swift/Tests/Blockchains/DogeTests.swift b/swift/Tests/Blockchains/DogeTests.swift index 5094206a703..c0a1553ca32 100644 --- a/swift/Tests/Blockchains/DogeTests.swift +++ b/swift/Tests/Blockchains/DogeTests.swift @@ -39,7 +39,11 @@ class DogeTests: XCTestCase { func testDeriveFromDpub() { let dgub = "dgub8rjvUmFc6cqR6NRBEj2FBZCHUDUrykPyv24Vea6bCsPex5PzNFrRtr4KN37XgwuVzzC2MikJRW2Ddcp99Ehsqp2iaU4eerNCJVruKxz6Gci" - let pubkey8 = HDWallet.derive(from: dgub, at: DerivationPath(purpose: .bip44, coinType: coin, account: 0, change: 0, address: 8))! + let pubkey8 = HDWallet.getPublicKeyFromExtended( + extended: dgub, + coin: .dogecoin, + derivationPath: DerivationPath(purpose: .bip44, coin: coin.slip44Id, account: 0, change: 0, address: 8).description + )! let address = BitcoinAddress(publicKey: pubkey8, prefix: coin.p2pkhPrefix)! XCTAssertEqual(address.description, "DLrjRgrVqbbpGrSQUtSYgsiWWMvRz5skQE") diff --git a/swift/Tests/Blockchains/GroestlcoinTests.swift b/swift/Tests/Blockchains/GroestlcoinTests.swift index c06ebf05021..4711baf9127 100644 --- a/swift/Tests/Blockchains/GroestlcoinTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTests.swift @@ -58,8 +58,16 @@ class GroestlcoinTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6qXFnWiY6FdT5BQptrzEhHfm1WpaBTFc6MHzR4KwscXGdt6xCqUtrAEjrHdeEsjaYEwVMgjtTvENQ83yo2fmkYYGjTpJoH7vFWKQJp1bg1X" let groestlcoin = CoinType.groestlcoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: groestlcoin.purpose, coinType: groestlcoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: groestlcoin.purpose, coinType: groestlcoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: groestlcoin, + derivationPath: DerivationPath(purpose: groestlcoin.purpose, coin: groestlcoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: groestlcoin, + derivationPath: DerivationPath(purpose: groestlcoin.purpose, coin: groestlcoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .groestlcoin, publicKey: zpubAddr4).description, "grs1quwq6ml2r8rc25tue5ltfa6uc4pdzhtzul3c0rk") XCTAssertEqual(SegwitAddress(hrp: .groestlcoin, publicKey: zpubAddr11).description, "grs1ql0a7czm8wrj253h78dm2h5j2k89zwpy2qjq0q9") diff --git a/swift/Tests/Blockchains/KusamaTests.swift b/swift/Tests/Blockchains/KusamaTests.swift index bb8ff23df6b..414903a173f 100644 --- a/swift/Tests/Blockchains/KusamaTests.swift +++ b/swift/Tests/Blockchains/KusamaTests.swift @@ -36,7 +36,7 @@ class KusamaTests: XCTestCase { // https://kusama.subscan.io/extrinsic/0x9211b8f6500c78f4771d18289c6187ec59c2b1fb28e8324ee32a1f9a3303be7e // real key in 1p test let wallet = HDWallet.test - let key = wallet.getKey(derivationPath: "m/44'/434'/0'") + let key = wallet.getKey(coin: .kusama, derivationPath: "m/44'/434'/0'") print(key.data.hexString) let genesisHash = Data(hexString: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe")! diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index 2c84c7acd79..aa2007ece7b 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -62,8 +62,16 @@ class LitecoinTests: XCTestCase { func testDeriveFromLtub() { let xpub = "Ltub2Ye6FtTv7U4zzHDL6iMfcE3cj5BHJjkBXQj1deZEAgSBrHB5oM191hYTF8BC34r7vRDGng59yfP6FH4m3nttc3TLDg944G8QK7d5NnygCRu" let litecoin = CoinType.litecoin - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: litecoin, account: 0, change: 0, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: litecoin, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip44, coin: litecoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip44, coin: litecoin.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.litecoin.p2pkhPrefix)!.description, "LdJvSS8gcRSN1WbSEj6srV8dKzGcybHGKt") XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.litecoin.p2pkhPrefix)!.description, "Laj4byUKgW3wuou4G3XCAPWqzVc3SdEpQk") @@ -73,8 +81,16 @@ class LitecoinTests: XCTestCase { let ypub = "Mtub2sZjeBCxVccvybLHSD1i3Aw38QvCTDadaPyXbSkRRX1RQm3mxtfsbQU5M3PdCSP4xAFHCceEQ3FmQF69Du2wbcmebt3CaWAGALBSe8c4Gvw" let litecoin = CoinType.litecoin - let ypubAddr3 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: litecoin, account: 0, change: 0, address: 3))! - let ypubAddr10 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: litecoin, account: 0, change: 0, address: 10))! + let ypubAddr3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypubAddr10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 10).description + )! XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr3, prefix: CoinType.litecoin.p2shPrefix).description, "MVr2vvjyaTzmfX3LFZcg5KZ7Cc36pgAWcy") XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr10, prefix: CoinType.litecoin.p2shPrefix).description, "MTgkF6T5h92QDmpFsBk4fJeYt3dx5ERQtD") @@ -83,8 +99,16 @@ class LitecoinTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs" let litecoin = CoinType.litecoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: litecoin.purpose, coinType: litecoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: litecoin.purpose, coinType: litecoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: litecoin, + derivationPath: DerivationPath(purpose: .bip49, coin: litecoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .litecoin, publicKey: zpubAddr4).description, "ltc1qcgnevr9rp7aazy62m4gen0tfzlssa52axwytt6") XCTAssertEqual(SegwitAddress(hrp: .litecoin, publicKey: zpubAddr11).description, "ltc1qy072y8968nzp6mz3j292h8lp72d678fcmms6vl") diff --git a/swift/Tests/Blockchains/MonacoinTests.swift b/swift/Tests/Blockchains/MonacoinTests.swift index fa5a18c59bf..9b02e80f2b2 100644 --- a/swift/Tests/Blockchains/MonacoinTests.swift +++ b/swift/Tests/Blockchains/MonacoinTests.swift @@ -62,8 +62,16 @@ class MonacoinTests: XCTestCase { func testDeriveFromXpub() { let xpub = "xpub6CYWFE1BgTCW2vtbDm1RRT81i3hBkQrXCfGs5hYp211fpgLZV5xCEwXMWPAL3LgaBA9koXpLZSUo7rTyJ8q1JwqKhvzVpdzBKRGyyGb31KF" let monacoin = CoinType.monacoin - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: monacoin, account: 0, change: 0, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: monacoin, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip44, coin: monacoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip44, coin: monacoin.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.monacoin.p2pkhPrefix)!.description, "MCoYzbqdsMYTBbjr7rd2zJsSF32QMgZCSj") XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.monacoin.p2pkhPrefix)!.description, "MAtduu1Fvtv1Frx6vbg5tZDZwirCA3y8qq") @@ -73,8 +81,16 @@ class MonacoinTests: XCTestCase { let ypub = "ypub6YKchgn8hmHJ9a1c2wy1ydge6ez5AcWBVSwURTnC93yj6MT1tCUN3qvuZZPsA1CwZVh5qEGhMWhDZEK43jQqWtHBzME91ws9KD6WU9n8Nau" let monacoin = CoinType.monacoin - let ypubAddr3 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: monacoin, account: 0, change: 0, address: 3))! - let ypubAddr10 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: monacoin, account: 0, change: 0, address: 10))! + let ypubAddr3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip49, coin: monacoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypubAddr10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip49, coin: monacoin.slip44Id, account: 0, change: 0, address: 10).description + )! XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr3, prefix: CoinType.monacoin.p2shPrefix).description, "PRAnwctxh9UWFdjCcrQy2Ym1SxMgcjTpRx") XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr10, prefix: CoinType.monacoin.p2shPrefix).description, "PNA4qYzxsVfFXQ3bBSfMhVqumZHAJVZAaQ") @@ -83,8 +99,16 @@ class MonacoinTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6rPmNCEpXnLtvBTZyCWnJDr6QVyBaELfVX6kQeAXtZEAFLRCzWEBc2V35UHUQKJh1SpSNCtAtCx8KhRg5AWFnKrMCsxX4J2Zee21FQ5YS4n" let monacoin = CoinType.monacoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: monacoin.purpose, coinType: monacoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: monacoin.purpose, coinType: monacoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip84, coin: monacoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: monacoin, + derivationPath: DerivationPath(purpose: .bip84, coin: monacoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .monacoin, publicKey: zpubAddr4).description, "mona1qkrylcw85ulyrar9wt35huvpu6hlqwfg2gxf523") XCTAssertEqual(SegwitAddress(hrp: .monacoin, publicKey: zpubAddr11).description, "mona1qulanqvye6gmsf03m0cahr8dwtmj8gy53y8rc6n") diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index 85f34a8ae29..fcb0137c32a 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -36,7 +36,7 @@ class PolkadotTests: XCTestCase { // https://polkadot.subscan.io/extrinsic/0x5ec2ec6633b4b6993d9cf889ef42c457a99676244dc361a9ae17935d331dc39a // real key in 1p test let wallet = HDWallet.test - let key = wallet.getKey(derivationPath: "m/44'/354'/0'") + let key = wallet.getKey(coin: .polkadot, derivationPath: "m/44'/354'/0'") print(key.data.hexString) let address = CoinType.polkadot.deriveAddress(privateKey: key) diff --git a/swift/Tests/Blockchains/QtumTests.swift b/swift/Tests/Blockchains/QtumTests.swift index 230de9d1efd..090a7963f16 100644 --- a/swift/Tests/Blockchains/QtumTests.swift +++ b/swift/Tests/Blockchains/QtumTests.swift @@ -55,8 +55,17 @@ class QtumTests: XCTestCase { func testDeriveFromXpub() { let xpub = "xpub6CAkJZPecMDxRXEXZpDwyxcQ6CGie8GdovuJhsGwc2gFbLxdGr1PyqBXmsL7aYds1wfY2rB3YMVZiEE3CB3Lkj6KGoq1rEJ1wuaGkMDBf1m" let qtum = CoinType.qtum - let xpubAddr2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: qtum.purpose, coinType: qtum, account: 0, change: 0, address: 2))! - let xpubAddr9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: qtum.purpose, coinType: qtum, account: 0, change: 0, address: 9))! + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: qtum, + derivationPath: DerivationPath(purpose: qtum.purpose, coin: qtum.slip44Id, account: 0, change: 0, address: 2).description + )! + + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: qtum, + derivationPath: DerivationPath(purpose: qtum.purpose, coin: qtum.slip44Id, account: 0, change: 0, address: 9).description + )! XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.qtum.p2pkhPrefix)!.description, "QStYeAAfiYKxsABzY9yugHDpm5bsynYPqc") XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.qtum.p2pkhPrefix)!.description, "QfbKFChfhx1s4VXS9BzaVJgyKw5a1hnFg4") @@ -65,8 +74,16 @@ class QtumTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6rJJqJZcpaC7DrdsYiprLfUfvtaf11ZZWmrmYeWMkdZTx6tgfQLiBZuisraogskwBRLMGWfXoCyWRrXSypwPdNV2UWJXm5bDVQvBXvrzz9d" let qtum = CoinType.qtum - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: .bip84, coinType: qtum, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: .bip84, coinType: qtum, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: qtum, + derivationPath: DerivationPath(purpose: .bip84, coin: qtum.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: qtum, + derivationPath: DerivationPath(purpose: .bip84, coin: qtum.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(SegwitAddress(hrp: .qtum, publicKey: zpubAddr4).description, "qc1q3cvjmc2cgjkz9y58waj3r9ccchmrmrdzq03783") XCTAssertEqual(SegwitAddress(hrp: .qtum, publicKey: zpubAddr11).description, "qc1qrlk0ajg6khu2unsdppggs3pgpxxvdeymky58af") diff --git a/swift/Tests/Blockchains/ZcashTests.swift b/swift/Tests/Blockchains/ZcashTests.swift index d016477f133..dca6f59f969 100644 --- a/swift/Tests/Blockchains/ZcashTests.swift +++ b/swift/Tests/Blockchains/ZcashTests.swift @@ -34,7 +34,11 @@ class ZcashTests: XCTestCase { func testDeriveFromXpub() { let xpub = "xpub6C7HhMqpir3KBA6ammv5B58RT3XFTJqoZFoj3J56dz9XwehZ2puSH38ERtnz7HaXGxaZP8AHT4M2bSRHpBXUZrbsJ2xg3xs53DGKYCqj8mr" - let pubkey = HDWallet.derive(from: xpub, at: DerivationPath(purpose: zcash.purpose, coinType: zcash))! + let pubkey = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: .zcash, + derivationPath: DerivationPath(purpose: zcash.purpose, coin: zcash.slip44Id).description + )! let address = AnyAddress(publicKey: pubkey, coin: .zcash) XCTAssertEqual(address.description, "t1TKCtCETHPrAdA6eY1fdhhnTkTmb371oPt") diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 978bc0c8b31..d748db05880 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -21,184 +21,187 @@ class CoinAddressDerivationTests: XCTestCase { switch coin { case .aion: let expectedResult = "0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .binance: let expectedResult = "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cosmos: let expectedResult = "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoin: let expectedResult = "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoinCash: let expectedResult = "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoinGold: let expectedResult = "btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .callisto: let expectedResult = "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .dash: let expectedResult = "XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .dogecoin: let expectedResult = "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .digiByte: let expectedResult = "dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ethereum: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ethereumClassic: let expectedResult = "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .goChain: let expectedResult = "0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .icon: let expectedResult = "hx18b380b53c23dc4ee9f6666bc20d1be02f3fe106" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .litecoin: let expectedResult = "ltc1qhd8fxxp2dx3vsmpac43z6ev0kllm4n53t5sk0u" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ontology: let expectedResult = "AHKTnybvnWo3TeY8uvNXekvYxMrXogUjeT" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .poanetwork: let expectedResult = "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .xrp: let expectedResult = "rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tezos: let expectedResult = "tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .thunderToken: let expectedResult = "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tomoChain: let expectedResult = "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tron: let expectedResult = "TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .veChain: let expectedResult = "0x1a553275dF34195eAf23942CB7328AcF9d48c160" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .wanchain: let expectedResult = "0xd5CA90B928279fe5d06144136A25dEd90127Ac15" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zcash: let expectedResult = "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zcoin: let expectedResult = "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nimiq: let expectedResult = "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .stellar: let expectedResult = "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nano: let expectedResult = "nano_39gsbcishxn3n7wd17ono4otq5wazwzusqgqigztx73wbrh5jwbdbshfnumc" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .near: let expectedResult = "NEAR6Y66fCzeKqWiwxoPox5oGeDN9VhNCu7CEQ9M86iniqoN9vg2X" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nebulas: let expectedResult = "n1ZVgEidtdseYv9ogmGz69Cz4mbqmHYSNqJ" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .kin: let expectedResult = "GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .decred: let expectedResult = "DsidJiDGceqHTyqiejABy1ZQ3FX4SiWZkYG" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .theta: let expectedResult = "0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .groestlcoin: let expectedResult = "grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .viacoin: let expectedResult = "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .qtum: let expectedResult = "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .eos: let expectedResult = "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ioTeX: let expectedResult = "io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zilliqa: let expectedResult = "zil1mk6pqphhkmaguhalq6n3cq0h38ltcehg0rfmv6" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .zelcash: let expectedResult = "t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ravencoin: let expectedResult = "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .waves: let expectedResult = "3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .aeternity: let expectedResult = "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .terra: let expectedResult = "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .monacoin: let expectedResult = "M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .fio: let expectedResult = "FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .harmony: let expectedResult = "one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .solana: let expectedResult = "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ton: let expectedResult = "EQAmXWk7P7avw96EViZULpA85Lz6Si3MeWG-vFXmbEjpL-fo" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .algorand: let expectedResult = "JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nuls: let expectedResult = "NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .kusama: let expectedResult = "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .polkadot: let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .kava: let expectedResult = "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bandChain: let expectedResult = "band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .cardano: let expectedResult = "addr1snpa4z7ntyfszv7ckquprdw75w4qjqh0qmya9jtkpxxlzxghlqyvv7l0yjamh8fxraw06p3ua8sj2g2gv98v4849s43t9g2999kquuu5egnprk" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .neo: let expectedResult = "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .filecoin: let expectedResult = "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .elrond: let expectedResult = "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" - AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .binanceSmartChain: + let expectedResult = "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } @@ -206,7 +209,7 @@ class CoinAddressDerivationTests: XCTestCase { } } - private func AssetCoinDerivation(_ coin: CoinType, _ expected: String, _ derivedAddress: String, _ address: AnyAddress?) { + private func assertCoinDerivation(_ coin: CoinType, _ expected: String, _ derivedAddress: String, _ address: AnyAddress?) { XCTAssertNotNil(address, "\(coin) is not implemented CoinType.address(string: String)") XCTAssertEqual(expected, derivedAddress, "\(coin) failed to match address") XCTAssertEqual(expected, address?.description, "\(coin) is not implemented CoinType.address(string: String)") diff --git a/swift/Tests/DerivationPathTests.swift b/swift/Tests/DerivationPathTests.swift index 417b617b9af..693a4174a49 100644 --- a/swift/Tests/DerivationPathTests.swift +++ b/swift/Tests/DerivationPathTests.swift @@ -9,7 +9,7 @@ import XCTest class DerivationPathTests: XCTestCase { func testInitWithIndices() { - let path = DerivationPath(purpose: .bip44, coinType: .ethereum, account: 0, change: 0, address: 0) + let path = DerivationPath(purpose: .bip44, coin: CoinType.ethereum.slip44Id, account: 0, change: 0, address: 0) XCTAssertEqual(path.indices[0], DerivationPath.Index(44, hardened: true)) XCTAssertEqual(path.indices[1], DerivationPath.Index(60, hardened: true)) XCTAssertEqual(path.indices[2], DerivationPath.Index(0, hardened: true)) @@ -28,7 +28,7 @@ class DerivationPathTests: XCTestCase { XCTAssertEqual(path?.indices[4], DerivationPath.Index(0, hardened: false)) XCTAssertEqual(path?.purpose, Purpose(rawValue: 44)!) - XCTAssertEqual(path?.coinType, CoinType(rawValue: 60)!) + XCTAssertEqual(path?.coinType, 60) XCTAssertEqual(path?.account, 0) XCTAssertEqual(path?.change, 0) XCTAssertEqual(path?.address, 0) diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 7375c1f2b79..c0f9df78586 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -290,8 +290,16 @@ class HDWalletTests: XCTestCase { let xpub = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj" let bitcoin = CoinType.bitcoinCash - let xpub2 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: bitcoin, account: 0, change: 0, address: 2))! - let xpub9 = HDWallet.derive(from: xpub, at: DerivationPath(purpose: .bip44, coinType: bitcoin, account: 0, change: 0, address: 9))! + let xpub2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip44, coin: bitcoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpub9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip44, coin: bitcoin.slip44Id, account: 0, change: 0, address: 9).description + )! let xpubAddr2 = BitcoinAddress(publicKey: xpub2, prefix: CoinType.bitcoin.p2pkhPrefix)! let xpubAddr9 = BitcoinAddress(publicKey: xpub9, prefix: CoinType.bitcoin.p2pkhPrefix)! @@ -304,8 +312,16 @@ class HDWalletTests: XCTestCase { let ypub = "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP" let bitcoin = CoinType.bitcoin - let ypub3 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: bitcoin, account: 0, change: 0, address: 3))! - let ypub10 = HDWallet.derive(from: ypub, at: DerivationPath(purpose: .bip49, coinType: bitcoin, account: 0, change: 0, address: 10))! + let ypub3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip49, coin: bitcoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypub10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip49, coin: bitcoin.slip44Id, account: 0, change: 0, address: 10).description + )! let ypubAddr3 = BitcoinAddress.compatibleAddress(publicKey: ypub3, prefix: CoinType.bitcoin.p2shPrefix) let ypubAddr10 = BitcoinAddress.compatibleAddress(publicKey: ypub10, prefix: CoinType.bitcoin.p2shPrefix) @@ -316,8 +332,16 @@ class HDWalletTests: XCTestCase { func testDeriveFromZPub() { let zpub = "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs" let bitcoin = CoinType.bitcoin - let zpubAddr4 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: bitcoin.purpose, coinType: bitcoin, account: 0, change: 0, address: 4))! - let zpubAddr11 = HDWallet.derive(from: zpub, at: DerivationPath(purpose: bitcoin.purpose, coinType: bitcoin, account: 0, change: 0, address: 11))! + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip84, coin: bitcoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: bitcoin, + derivationPath: DerivationPath(purpose: .bip84, coin: bitcoin.slip44Id, account: 0, change: 0, address: 11).description + )! XCTAssertEqual(bitcoin.deriveAddressFromPublicKey(publicKey: zpubAddr4).description, "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n") XCTAssertEqual(bitcoin.deriveAddressFromPublicKey(publicKey: zpubAddr11).description, "bc1qxr4fjkvnxjqphuyaw5a08za9g6qqh65t8qwgum") @@ -327,8 +351,8 @@ class HDWalletTests: XCTestCase { let zpub = "zpub6qeA5j9oSq8tZaYEBTp1X61ZSjeen6HbiUBSG4KLPD8d65Pi7eSMPNuxCqgbLdtnim2hgnJEzmE6jhFoJXtJdRxRKRdNFQBJ6iidx9BHGyk" let bitcoin = CoinType.bitcoin - let path = DerivationPath(purpose: bitcoin.purpose, coinType: bitcoin, account: 0, change: 0, address: 0) - let pubkey = HDWallet.derive(from: zpub, at: path)! + let path = DerivationPath(purpose: bitcoin.purpose, coin: bitcoin.slip44Id, account: 0, change: 0, address: 0) + let pubkey = HDWallet.getPublicKeyFromExtended(extended: zpub, coin: bitcoin, derivationPath: path.description)! let address = bitcoin.deriveAddressFromPublicKey(publicKey: pubkey) XCTAssertEqual(pubkey.data.hexString, "039fdd3652495d01b6a363f8db8b3adce09f83ea5c43ff872ad0a39192340256b0") diff --git a/swift/Tests/Keystore/Data/bnb_wallet.json b/swift/Tests/Keystore/Data/bnb_wallet.json new file mode 100644 index 00000000000..b38f95886be --- /dev/null +++ b/swift/Tests/Keystore/Data/bnb_wallet.json @@ -0,0 +1,33 @@ +{ + "activeAccounts": [{ + "address": "bc1q4zehq85jqx9zzgzvzn9t64yjy66nunn3vehuv6", + "derivationPath": "m/84'/0'/0'/0/0", + "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q" + }, { + "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", + "derivationPath": "m/44'/60'/0'/0/0" + }, { + "address": "bnb1njuczq3hgvupu2vnczrjz7rc8x4uxlmhjyq95z", + "derivationPath": "m/44'/714'/0'/0/0" + }], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "cfeacebdf0d0c57cbbe6260094cdf3a9" + }, + "ciphertext": "60358be4204c0d9c723775159bcadd63a51f0c06fce4024294d8ed4c19acb85cba3ca769dc3521fb572a06f8986d8bbc5736d6900e3e215f9bc112acffa470b178a621922041300bd7", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "14198d7e5f2afbfde2b00539d0c9abaec99e708dd4a2242448c57248e3e07c77" + }, + "mac": "90b65f299a9ac59f50d24c6f80f4cdcffe6500c86687df716a15d79461992085" + }, + "id": "3c937d42-443d-4acf-9311-2d9dfa857e1c", + "name": "", + "type": "mnemonic", + "version": 3 +} diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index f4e56822dd6..99222984433 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -21,6 +21,10 @@ extension KeyStore { var bitcoinWallet: Wallet { return wallets.first(where: { $0.identifier == "btc_missing_address.json"})! } + + var bnbWallet: Wallet { + return wallets.first(where: { $0.identifier == "bnb_wallet.json"})! + } } class KeyStoreTests: XCTestCase { @@ -62,22 +66,30 @@ class KeyStoreTests: XCTestCase { try? fileManager.removeItem(at: watchesDestination) try? fileManager.copyItem(at: watchesURL, to: watchesDestination) + + let bnbWalletURL = Bundle(for: type(of: self)).url(forResource: "bnb_wallet", withExtension: "json")! + let bnbWalletDestination = keyDirectory.appendingPathComponent("bnb_wallet.json") + + try? fileManager.removeItem(at: bnbWalletDestination) + try? fileManager.copyItem(at: bnbWalletURL, to: bnbWalletDestination) } func testLoadKeyStore() { let keyStore = try! KeyStore(keyDirectory: keyDirectory) - XCTAssertEqual(keyStore.wallets.count, 3) + XCTAssertEqual(keyStore.wallets.count, 4) XCTAssertEqual(keyStore.watches.count, 1) } func testCreateHDWallet() throws { - let coins = [CoinType.ethereum] + let coins = [CoinType.ethereum, .binance, .binanceSmartChain] let keyStore = try KeyStore(keyDirectory: keyDirectory) let newWallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) - XCTAssertEqual(newWallet.accounts.count, 1) - XCTAssertEqual(keyStore.wallets.count, 4) + XCTAssertEqual(newWallet.accounts.count, 3) + XCTAssertEqual(keyStore.wallets.count, 5) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .ethereum)) + XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binance)) + XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binanceSmartChain)) } func testUpdateKey() throws { @@ -196,6 +208,64 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(keyStore.hdWallet) } + func testImportJSON() throws { + + let expected = """ + { + "activeAccounts": [{ + "address": "bc1q4zehq85jqx9zzgzvzn9t64yjy66nunn3vehuv6", + "coin": 0, + "derivationPath": "m/84'/0'/0'/0/0", + "extendedPublicKey": "zpub6qMRMrwcEYaqjf8wSpNqtBfUee6MqpQjrZNKfj5a48EUFUx2yUmfkDJMdHwWvkg8SjdS3ua6dy9ofMrzrytTfdyy2pXg344yFwm2Ta9cm6Q" + }, { + "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", + "coin": 60, + "derivationPath": "m/44'/60'/0'/0/0" + }, { + "address": "bnb1njuczq3hgvupu2vnczrjz7rc8x4uxlmhjyq95z", + "coin": 714, + "derivationPath": "m/44'/714'/0'/0/0" + }, { + "address": "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC", + "coin": 10000714, + "derivationPath": "m/44'/714'/0'/0/0" + }], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "cfeacebdf0d0c57cbbe6260094cdf3a9" + }, + "ciphertext": "60358be4204c0d9c723775159bcadd63a51f0c06fce4024294d8ed4c19acb85cba3ca769dc3521fb572a06f8986d8bbc5736d6900e3e215f9bc112acffa470b178a621922041300bd7", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "14198d7e5f2afbfde2b00539d0c9abaec99e708dd4a2242448c57248e3e07c77" + }, + "mac": "90b65f299a9ac59f50d24c6f80f4cdcffe6500c86687df716a15d79461992085" + }, + "id": "3c937d42-443d-4acf-9311-2d9dfa857e1c", + "name": "", + "type": "mnemonic", + "version": 3 + } + """ + + let password = "e28ddf66cec05c1fc09939a00628b230459202b2493fccac288038ef37815723" + let keyStore = try KeyStore(keyDirectory: keyDirectory) + _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.binanceSmartChain], password: password) + + let account = keyStore.bnbWallet.accounts[3] + XCTAssertEqual(keyStore.bnbWallet.accounts.count, 4) + XCTAssertEqual(account.coin, CoinType.binanceSmartChain) + XCTAssertEqual(account.address, "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC") + + let saved = try String(contentsOf: keyStore.bnbWallet.keyURL) + XCTAssertJSONEqual(saved, expected) + } + func testExportMnemonic() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum]) diff --git a/tests/Aeternity/TWAeternityAddressTests.cpp b/tests/Aeternity/TWAeternityAddressTests.cpp index e5047e44ed5..f936454d907 100644 --- a/tests/Aeternity/TWAeternityAddressTests.cpp +++ b/tests/Aeternity/TWAeternityAddressTests.cpp @@ -12,18 +12,17 @@ #include TEST(TWAeternityAddress, HDWallet) { - auto mnemonic = "shoot island position soft burden budget tooth cruel issue economy destroy above"; + auto mnemonic = + "shoot island position soft burden budget tooth cruel issue economy destroy above"; auto passphrase = ""; - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING(mnemonic).get(), - STRING(passphrase).get() - )); + auto wallet = WRAP( + TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeDerivationPath(TWCoinTypeAeternity)); + auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeAeternity, TWCoinTypeDerivationPath(TWCoinTypeAeternity)); auto publicKey = TWPrivateKeyGetPublicKeyEd25519(privateKey); - auto address = TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeAeternity); - auto addressStr = WRAPS(TWAnyAddressDescription(address)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeAeternity)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); assertStringsEqual(addressStr, "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN"); } diff --git a/tests/Binance/SignerTests.cpp b/tests/Binance/SignerTests.cpp index 1094f714d5e..35c0d8d8979 100644 --- a/tests/Binance/SignerTests.cpp +++ b/tests/Binance/SignerTests.cpp @@ -4,13 +4,14 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "Bech32Address.h" +#include "Binance/Address.h" +#include "Binance/Signer.h" #include "Coin.h" +#include "Ethereum/Address.h" #include "HDWallet.h" #include "HexCoding.h" #include "proto/Binance.pb.h" -#include "Binance/Address.h" -#include "Binance/Signer.h" -#include "proto/Binance.pb.h" #include @@ -43,7 +44,9 @@ TEST(BinanceSigner, Sign) { auto signer = Binance::Signer(std::move(input)); auto signature = signer.sign(); - ASSERT_EQ(hex(signature.begin(), signature.end()), "9123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d6997f5f939ef834ea61d596a314237c48e560da9e17b5a"); + ASSERT_EQ(hex(signature.begin(), signature.end()), + "9123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d69" + "97f5f939ef834ea61d596a314237c48e560da9e17b5a"); } TEST(BinanceSigner, Build) { @@ -125,7 +128,9 @@ TEST(BinanceSigner, BuildSend) { auto signer = Binance::Signer(std::move(signingInput)); auto signature = signer.sign(); - ASSERT_EQ(hex(signature.begin(), signature.end()), "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aadc4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f"); + ASSERT_EQ(hex(signature.begin(), signature.end()), + "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aad" + "c4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f"); auto result = signer.build(); @@ -151,11 +156,11 @@ TEST(BinanceSigner, BuildSend2) { const auto derivationPath = TW::derivationPath(TWCoinTypeBinance); const auto fromWallet = HDWallet("swift slam quote sail high remain mandate sample now stamp title among fiscal captain joy puppy ghost arrow attract ozone situate install gain mean", ""); - const auto fromPrivateKey = fromWallet.getKey(derivationPath); + const auto fromPrivateKey = fromWallet.getKey(TWCoinTypeBinance, derivationPath); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const auto toWallet = HDWallet( "bottom quick strong ranch section decide pepper broken oven demand coin run jacket curious business achieve mule bamboo remain vote kid rigid bench rubber", ""); - const auto toPrivateKey = toWallet.getKey(derivationPath); + const auto toPrivateKey = toWallet.getKey(TWCoinTypeBinance, derivationPath); const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto signingInput = Proto::SigningInput(); @@ -164,21 +169,21 @@ TEST(BinanceSigner, BuildSend2) { signingInput.set_sequence(1); signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - auto token = Proto::SendOrder::Token(); + auto token = Proto::SendOrder::Token(); token.set_denom("BNB"); token.set_amount(100000000000000); - auto input = Proto::SendOrder::Input(); + auto input = Proto::SendOrder::Input(); auto fromKeyHash = Binance::Address(fromPublicKey).getKeyHash(); input.set_address(fromKeyHash.data(), fromKeyHash.size()); *input.add_coins() = token; - auto output = Proto::SendOrder::Output(); + auto output = Proto::SendOrder::Output(); auto toKeyHash = Binance::Address(toPublicKey).getKeyHash(); output.set_address(toKeyHash.data(), toKeyHash.size()); *output.add_coins() = token; - auto sendOrder = Proto::SendOrder(); + auto sendOrder = Proto::SendOrder(); *sendOrder.add_inputs() = input; *sendOrder.add_outputs() = output; @@ -202,11 +207,13 @@ TEST(BinanceSigner, BuildSend2) { } TEST(BinanceSigner, BuildHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - const auto toPrivateKey = PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); + const auto toPrivateKey = + PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto toAddr = Binance::Address(toPublicKey).getKeyHash(); @@ -221,9 +228,9 @@ TEST(BinanceSigner, BuildHTLT) { token.set_amount(100000000); auto randomNumberHash = - parse_hex("e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"); + parse_hex("e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"); - auto &htltOrder = *signingInput.mutable_htlt_order(); + auto& htltOrder = *signingInput.mutable_htlt_order(); htltOrder.set_from(fromAddr.data(), fromAddr.size()); htltOrder.set_to(toAddr.data(), toAddr.size()); htltOrder.set_recipient_other_chain(""); @@ -246,7 +253,8 @@ TEST(BinanceSigner, BuildHTLT) { } TEST(BinanceSigner, BuildDepositHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); + const auto fromPrivateKey = + PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -262,7 +270,7 @@ TEST(BinanceSigner, BuildDepositHTLT) { auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - auto &depositHTLTOrder = *signingInput.mutable_deposithtlt_order(); + auto& depositHTLTOrder = *signingInput.mutable_deposithtlt_order(); depositHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); depositHTLTOrder.set_swap_id(swapID.data(), swapID.size()); *depositHTLTOrder.add_amount() = token; @@ -277,7 +285,8 @@ TEST(BinanceSigner, BuildDepositHTLT) { } TEST(BinanceSigner, BuildClaimHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -288,26 +297,27 @@ TEST(BinanceSigner, BuildClaimHTLT) { signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); auto randomNumber = - parse_hex("bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"); + parse_hex("bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"); auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - auto &claimHTLTOrder = *signingInput.mutable_claimhtlt_order(); + auto& claimHTLTOrder = *signingInput.mutable_claimhtlt_order(); claimHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); claimHTLTOrder.set_swap_id(swapID.data(), swapID.size()); claimHTLTOrder.set_random_number(randomNumber.data(), randomNumber.size()); const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ( - hex(data.begin(), data.end()), - "d401f0625dee0a5ec16653000a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741844d35" - "eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e51a20bda6933c7757d0ca428aa01fb9d0935a231f87bf" - "2deeb9b409cea3f2d580a2cc126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561a" - "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7" - "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001"); + hex(data.begin(), data.end()), + "d401f0625dee0a5ec16653000a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741844d35" + "eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e51a20bda6933c7757d0ca428aa01fb9d0935a231f87bf" + "2deeb9b409cea3f2d580a2cc126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561a" + "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7" + "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001"); } TEST(BinanceSigner, BuildRefundHTLT) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -319,7 +329,7 @@ TEST(BinanceSigner, BuildRefundHTLT) { auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - auto &refundHTLTOrder = *signingInput.mutable_refundhtlt_order(); + auto& refundHTLTOrder = *signingInput.mutable_refundhtlt_order(); refundHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); refundHTLTOrder.set_swap_id(swapID.data(), swapID.size()); @@ -333,7 +343,8 @@ TEST(BinanceSigner, BuildRefundHTLT) { } TEST(BinanceSigner, BuildIssueOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -352,29 +363,20 @@ TEST(BinanceSigner, BuildIssueOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "b601f0625dee0a40" - "17efab80" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f" - "4e657742696e616e6365546f6b656e" - "1a0b" - "4e4e422d3333385f424e42" - "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001" - ); - - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("b601f0625dee0a4017efab800a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f4e657742696e616e6365546f6b656e1a0b4e4e422d3333385f424e42208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "b601f0625dee0a40" + "17efab80" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f" + "4e657742696e616e6365546f6b656e" + "1a0b" + "4e4e422d3333385f424e42" + "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac" + "993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c9" + "5aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); } TEST(BinanceSigner, BuildMintOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -391,27 +393,18 @@ TEST(BinanceSigner, BuildMintOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "467e0829" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001" - ); - - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2b467e08290a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "a101f0625dee0a2b" + "467e0829" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" + "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); } TEST(BinanceSigner, BuildBurnOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -428,47 +421,18 @@ TEST(BinanceSigner, BuildBurnOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "7ed2d2a0" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001" - ); - - /* - Matching binance chain sdk code: - - // encode - priv := "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d" - keyManager, err := NewPrivateKeyManager(priv) - assert.NoError(t, err) - fromAddr, err := ctypes.AccAddressFromBech32("bnb1prrujx8kkukrcrppklggadhuvegfnx8phwey70") - assert.NoError(t, err) - - burnMsg := msg.NewTokenBurnMsg(fromAddr, "NNB-338_BNB", 1000000) - signMsg := tx.StdSignMsg{ - ChainID: "test-chain", - AccountNumber: 15, - Sequence: 1, - Memo: "", - Msgs: []msg.Msg{burnMsg}, - Source: 0, - } - rawSignResult, err := keyManager.Sign(signMsg) - fmt.Printf("%x\n", rawSignResult) - - // decode back - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2b7ed2d2a00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "a101f0625dee0a2b" + "7ed2d2a0" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" + "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); } TEST(BinanceSigner, BuildFreezeOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -485,27 +449,18 @@ TEST(BinanceSigner, BuildFreezeOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "e774b32d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" - ); - - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2be774b32d0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ + "a101f0625dee0a2b" + "e774b32d" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" + "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); } TEST(BinanceSigner, BuildUnfreezeOrder) { - const auto fromPrivateKey = PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); @@ -522,23 +477,147 @@ TEST(BinanceSigner, BuildUnfreezeOrder) { const auto data = Binance::Signer(std::move(signingInput)).build(); ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "6515ff0d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" + "a101f0625dee0a2b" + "6515ff0d" + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" + "4e4e422d3333385f424e42" + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" + "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" + "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); +} + +TEST(BinanceSigner, BuildTransferOutOrder) { + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); + + const auto toAddr = Ethereum::Address("0x35552c16704d214347f29Fa77f77DA6d75d7C752"); + const auto toBytes = Data(toAddr.bytes.begin(), toAddr.bytes.end()); + + auto input = Proto::SigningInput(); + input.set_chain_id("test-chain"); + input.set_account_number(15); + input.set_sequence(1); + input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); + + auto& order = *input.mutable_transfer_out_order(); + order.set_from(fromAddr.data(), fromAddr.size()); + order.set_to(toBytes.data(), toBytes.size()); + order.set_expire_time(12345678); + + auto& token = *order.mutable_amount(); + token.set_denom("BNB"); + token.set_amount(100000000); + + const auto data = Binance::Signer(std::move(input)).build(); + ASSERT_EQ(hex(data.begin(), data.end()), + "b701f0625dee0a41800819c00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121435552c16704d" + "214347f29fa77f77da6d75d7c7521a0a0a03424e421080c2d72f20cec2f105126e0a26eb5ae9872103a9" + "a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712407eda148e1167b1be12" + "71a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95" + "ef3983cad85a29cd14262c22e0180f2001"); +} + +TEST(BinanceSigner, BuildSideChainDelegate) { + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); + auto validator = Bech32Address(""); + Bech32Address::decode("bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", validator, "bva"); + + auto input = Proto::SigningInput(); + input.set_chain_id("test-chain"); + input.set_account_number(15); + input.set_sequence(1); + input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); + + auto& order = *input.mutable_side_delegate_order(); + order.set_delegator_addr(fromAddr.data(), fromAddr.size()); + order.set_validator_addr(validator.getKeyHash().data(), validator.getKeyHash().size()); + order.set_chain_id("chapel"); + + auto& token = *order.mutable_delegation(); + token.set_denom("BNB"); + token.set_amount(200000000); + + const auto data = Binance::Signer(std::move(input)).build(); + ASSERT_EQ(hex(data.begin(), data.end()), + "ba01f0625dee0a44e3a07fd20a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112147cc24a1de524" + "5f14a95e457f903bcc8461ac869c1a0a0a03424e42108084af5f220663686170656c126e0a26eb5ae987" + "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7124039302c9975fb" + "2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d" + "9d8f46aeb3627a7d7aa901fe186af34c180f2001"); +} + +TEST(BinanceSigner, BuildSideChainRedelegate) { + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); + auto validator1 = Bech32Address(""); + auto validator2 = Bech32Address(""); + Bech32Address::decode("bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw", validator1, "bva"); + Bech32Address::decode("bva1p7s26ervsmv3w83k5696glautc9sm5rchz5f5e", validator2, "bva"); + + auto input = Proto::SigningInput(); + input.set_chain_id("test-chain"); + input.set_account_number(15); + input.set_sequence(1); + input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); + + auto& order = *input.mutable_side_redelegate_order(); + order.set_delegator_addr(fromAddr.data(), fromAddr.size()); + order.set_validator_src_addr(validator1.getKeyHash().data(), validator1.getKeyHash().size()); + order.set_validator_dst_addr(validator2.getKeyHash().data(), validator2.getKeyHash().size()); + order.set_chain_id("chapel"); + + auto& token = *order.mutable_amount(); + token.set_denom("BNB"); + token.set_amount(100000000); + + const auto data = Binance::Signer(std::move(input)).build(); + ASSERT_EQ(hex(data.begin(), data.end()), + "d001f0625dee0a5ae3ced3640a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138" + "d51c38c7447227605d2f444a881e1a140fa0ad646c86d9171e36a68ba47fbc5e0b0dd078220a0a03424e" + "421080c2d72f2a0663686170656c126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08" + "af44ea561aac993dbe0f6b6a8fc71240114c6927423e95ecc831ec763b629b3a40db8feeb330528a941f" + "d74843c0d63b4271b23916770d4901988c1f56b20086e5768a12290ebec265e30a80f8f3d88e180f2001" ); +} - /* - Matching binance chain sdk code: - // decode - var parsedTx tx.StdTx - rawMsg1, err := hex.DecodeString("a101f0625dee0a2b6515ff0d0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b4e4e422d3333385f424e4218c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001") - assert.NoError(t, err) - err = tx.Cdc.UnmarshalBinaryLengthPrefixed(rawMsg1, &parsedTx) - assert.NoError(t, err) - fmt.Printf("%v\n", parsedTx) - */ +TEST(BinanceSigner, BuildSideChainUndelegate) { + const auto fromPrivateKey = + PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); + const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); + auto validator = Bech32Address(""); + Bech32Address::decode("bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw", validator, "bva"); + + auto input = Proto::SigningInput(); + input.set_chain_id("test-chain"); + input.set_account_number(15); + input.set_sequence(1); + input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); + + auto& order = *input.mutable_side_undelegate_order(); + order.set_delegator_addr(fromAddr.data(), fromAddr.size()); + order.set_validator_addr(validator.getKeyHash().data(), validator.getKeyHash().size()); + order.set_chain_id("chapel"); + + auto& token = *order.mutable_amount(); + token.set_denom("BNB"); + token.set_amount(100000000); + + const auto data = Binance::Signer(std::move(input)).build(); + ASSERT_EQ(hex(data.begin(), data.end()), + "ba01f0625dee0a44514f7e0e0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138" + "d51c38c7447227605d2f444a881e1a0a0a03424e421080c2d72f220663686170656c126e0a26eb5ae987" + "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240a622b7ca7a28" + "75e5eaa675a5ed976b2ec3b8ca055a2b05e7fb471d328bd04df854789437dd06407e41ebb1e5a345604c" + "93663dfb660e223800636c0b94c2e798180f2001" + ); } } // namespace TW::Binance diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp new file mode 100644 index 00000000000..106f4e146fb --- /dev/null +++ b/tests/BinanceSmartChain/SignerTests.cpp @@ -0,0 +1,91 @@ +// Copyright © 2017-2020 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. + +#include +#include +#include "Ethereum/Signer.h" +#include "Ethereum/Transaction.h" +#include "Ethereum/Address.h" +#include "Ethereum/RLP.h" +#include "Ethereum/ABI.h" +#include "proto/Ethereum.pb.h" +#include "HexCoding.h" +#include "uint256.h" +#include "../interface/TWTestUtilities.h" + +#include + +namespace TW::Binance { + +using namespace TW::Ethereum; +using namespace TW::Ethereum::ABI; + +class SignerExposed : public Signer { +public: + SignerExposed(boost::multiprecision::uint256_t chainID) : Signer(chainID) {} + using Signer::hash; +}; + +TEST(BinanceSmartChain, SignNativeTransfer) { + // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e + + auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); + auto transaction = Transaction( + /* nonce: */ 0, + /* gasPrice: */ 20000000000, + /* gasLimit: */ 21000, + /* to: */ toAddress, + /* amount: */ 10000000000000000, // 0.01 + /* payload: */ {} + ); + + // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note + auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + auto signer = SignerExposed(TWEthereumChainIDBinanceSmartChain); + signer.sign(privateKey, transaction); + + auto encoded = RLP::encode(transaction); + ASSERT_EQ(hex(encoded), "f86c808504a817c8008252089431be00eb1fc8e14a696dbc72f746ec3e95f49683872386f26fc100008081e5a057806b486844c5d0b7b5ce34b289f4e8776aa1fe24a3311cef5053995c51050ca07697aa0695de27da817625df0e7e4c64b0ab22d9df30aec92299a7b380be8db7"); +} + +TEST(BinanceSmartChain, SignTokenTransfer) { + auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); + auto func = Function("transfer", std::vector>{ + std::make_shared(toAddress), + std::make_shared(uint256_t(10000000000000000)) + }); + Data payloadFunction; + func.encode(payloadFunction); + EXPECT_EQ(hex(payloadFunction), "a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc10000"); + + auto input = Proto::SigningInput(); + auto chainId = store(uint256_t(TWEthereumChainIDBinanceSmartChain)); + auto nonce = store(uint256_t(30)); + auto gasPrice = store(uint256_t(20000000000)); + auto gasLimit = store(uint256_t(1000000)); + auto tokenContractAddress = "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"; + auto dummyAmount = store(uint256_t(0)); + // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note + auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(tokenContractAddress); + input.set_payload(payloadFunction.data(), payloadFunction.size()); + input.set_amount(dummyAmount.data(), dummyAmount.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeBinanceSmartChain); + + EXPECT_EQ(hex(output.encoded()), expected); +} + +} // namespace TW::Binance diff --git a/tests/BinanceSmartChain/TWAnyAddressTests.cpp b/tests/BinanceSmartChain/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..3cbf9fefebd --- /dev/null +++ b/tests/BinanceSmartChain/TWAnyAddressTests.cpp @@ -0,0 +1,29 @@ +// Copyright © 2017-2020 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. + +#include +#include "HexCoding.h" + +#include "../interface/TWTestUtilities.h" +#include + +using namespace TW; + +TEST(TWBinanceSmartChain, Address) { + + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06").get())); + auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false); + auto string = "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"; + + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeBinanceSmartChain)); + auto expected = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(string).get(), TWCoinTypeBinanceSmartChain)); + + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + auto expectedString = WRAPS(TWAnyAddressDescription(expected.get())); + + assertStringsEqual(addressString, string); + assertStringsEqual(expectedString, string); +} diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/BinanceSmartChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0a2c477fc51 --- /dev/null +++ b/tests/BinanceSmartChain/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// Copyright © 2017-2020 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. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "../interface/TWTestUtilities.h" +#include +#include + + +TEST(TWBinanceSmartChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBinanceSmartChain)); + auto txId = TWStringCreateWithUTF8Bytes("0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBinanceSmartChain, txId)); + auto accId = TWStringCreateWithUTF8Bytes("0x35552c16704d214347f29Fa77f77DA6d75d7C752"); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBinanceSmartChain, accId)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBinanceSmartChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBinanceSmartChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBinanceSmartChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeBinanceSmartChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBinanceSmartChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBinanceSmartChain)); + assertStringsEqual(symbol, "BNB"); + assertStringsEqual(txUrl, "https://explorer.binance.org/smart-testnet/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); + assertStringsEqual(accUrl, "https://explorer.binance.org/smart-testnet/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); + assertStringsEqual(id, "binance-smart"); + assertStringsEqual(name, "Binance SmartChain"); +} diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/BitcoinCash/TWBitcoinCashTests.cpp index 84999b1b628..30dfb7a4850 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/BitcoinCash/TWBitcoinCashTests.cpp @@ -88,8 +88,8 @@ TEST(BitcoinCash, ExtendedKeys) { TEST(BitcoinCash, DeriveFromXPub) { auto xpub = STRING("xpub6CEHLxCHR9sNtpcxtaTPLNxvnY9SQtbcFdov22riJ7jmhxmLFvXAoLbjHSzwXwNNuxC1jUP6tsHzFV9rhW9YKELfmR9pJaKFaM8C3zMPgjw"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/9").get()); auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2, TWCoinTypeBitcoinCash)); auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp index 67e69db3791..93bb9c230e1 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -51,8 +51,8 @@ TEST(BitcoinGoldKey, ExtendedKeys) { TEST(BitcoinGoldKey, DeriveFromZPub) { auto zpub = STRING("zpub6rAU34KLySeCp1KDjD3hayCBXg49uH3G8iDzPovtGWD3eabbfAWSRMsjVyfuRfCCquiKTD6YV42nHUBtwh2TbVPvWqxrGuyEvHN17c3XUXw"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/156'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/156'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoinGold, STRING("m/84'/156'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoinGold, STRING("m/84'/156'/0'/0/9").get()); auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey2, TWCoinTypeBitcoinGold)); auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); diff --git a/tests/Cardano/AddressTests.cpp b/tests/Cardano/AddressTests.cpp index ee84f5c66a9..a7e828aea14 100644 --- a/tests/Cardano/AddressTests.cpp +++ b/tests/Cardano/AddressTests.cpp @@ -93,37 +93,37 @@ TEST(CardanoAddress, MnemonicToAddressV2) { EXPECT_EQ("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", addr); } { - PrivateKey privKey0 = wallet.getKey(DerivationPath("m/44'/1815'/0'/0/0")); + PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/0")); PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Extended); auto addr0 = AddressV2(pubKey0); EXPECT_EQ("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W", addr0.string()); } { - PrivateKey privKey1 = wallet.getKey(DerivationPath("m/44'/1815'/0'/0/1")); + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/1")); PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); auto addr1 = AddressV2(pubKey1); EXPECT_EQ("Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV", addr1.string()); } { - PrivateKey privKey1 = wallet.getKey(DerivationPath("m/44'/1815'/0'/0/2")); + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/2")); PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); auto addr1 = AddressV2(pubKey1); EXPECT_EQ("Ae2tdPwUPEZ8LAVy21zj4BF97iWxKCmPv12W6a18zLX3V7rZDFFVgqUBkKw", addr1.string()); } { - PrivateKey privKey0 = wallet.getKey(DerivationPath("m/1852'/1815'/0'/0/0")); + PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Extended); auto addr0 = AddressV3(pubKey0); EXPECT_EQ("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x", addr0.string()); } { - PrivateKey privKey1 = wallet.getKey(DerivationPath("m/1852'/1815'/0'/0/1")); + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/1")); PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); auto addr1 = AddressV3(pubKey1); EXPECT_EQ("addr1sjkw630aatyg273m9cpgezvs2unf6xrtw0z7udhguh7ednkhf9p0jduldrg5qsnaz99e3sl4f8t56w0hs0zhql9lacr63mx693ppjw2r5nwehs", addr1.string()); } { - PrivateKey privKey1 = wallet.getKey(DerivationPath("m/1852'/1815'/0'/0/2")); + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/2")); PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); auto addr1 = AddressV3(pubKey1); EXPECT_EQ("addr1sng939f9el5mnsj4l30kk2f02ea63rwhny5pa69masam4xtsmp5naq6lks0p7pzkn35z7juyd7hhk3zc8p9dc736pu4nzhyy6fusxapa9v5h5c", addr1.string()); @@ -151,7 +151,7 @@ TEST(CardanoAddress, MnemonicToAddressV2) { // V2 Tested against AdaLite auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; auto wallet = HDWallet(mnemonicPlay1, ""); - PrivateKey privateKey = wallet.getKey(DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); string addr = AddressV2(publicKey).string(); EXPECT_EQ("Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h", addr); @@ -160,7 +160,7 @@ TEST(CardanoAddress, MnemonicToAddressV2) { // V2 Tested against AdaLite auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; auto wallet = HDWallet(mnemonicPlay2, ""); - PrivateKey privateKey = wallet.getKey(DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); string addr = AddressV2(publicKey).string(); EXPECT_EQ("Ae2tdPwUPEZLtJx7LA2XZ3zzwonH9x9ieX3dMzaTBD3TfXuKczjMSjTecr1", addr); @@ -170,7 +170,7 @@ TEST(CardanoAddress, MnemonicToAddressV2) { // In AdaLite V1 addr0 is DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; auto wallet = HDWallet(mnemonicALDemo, ""); - PrivateKey privateKey = wallet.getKey(DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); string addr = AddressV2(publicKey).string(); EXPECT_EQ("Ae2tdPwUPEZJbLcD8iLgN7hVGvq66WdR4zocntRekSP97Ds3MvCfmEDjJYu", addr); diff --git a/tests/Decred/AddressTests.cpp b/tests/Decred/AddressTests.cpp index fc73e0cb011..49f627dabe9 100644 --- a/tests/Decred/AddressTests.cpp +++ b/tests/Decred/AddressTests.cpp @@ -42,6 +42,6 @@ TEST(DecredAddress, Derive) { const auto mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; const auto wallet = HDWallet(mnemonic, ""); const auto path = TW::derivationPath(TWCoinTypeDecred); - const auto address = TW::deriveAddress(TWCoinTypeDecred, wallet.getKey(path)); + const auto address = TW::deriveAddress(TWCoinTypeDecred, wallet.getKey(TWCoinTypeDecred, path)); ASSERT_EQ(address, "DsVMHD5D86dpRnt2GPZvv4bYUJZg6B9Pzqa"); } diff --git a/tests/Decred/TWDecredTests.cpp b/tests/Decred/TWDecredTests.cpp index 39e3f7b22f4..037211ad2d8 100644 --- a/tests/Decred/TWDecredTests.cpp +++ b/tests/Decred/TWDecredTests.cpp @@ -28,7 +28,7 @@ TEST(Decred, ExtendedKeys) { TEST(Decred, DerivePubkeyFromDpub) { auto dpub = STRING("dpubZFUmm9oh5zmQkR2Tr2AXS4tCkTWg4B27SpCPFkapZrrAqgU1EwgEFgrmi6EnLGXhak86yDHhXPxFAnGU58W5S4e8NCKG1ASUVaxwRqqNdfP"); - auto pubKey0 = TWHDWalletGetPublicKeyFromExtended(dpub.get(), STRING("m/44'/42'/0'/0/0").get()); + auto pubKey0 = TWHDWalletGetPublicKeyFromExtended(dpub.get(), TWCoinTypeDecred, STRING("m/44'/42'/0'/0/0").get()); auto address0 = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeDecred, pubKey0)); assertStringsEqual(address0, "DsksmLD2wDoA8g8QfFvm99ASg8KsZL8eJFd"); diff --git a/tests/DerivationPathTests.cpp b/tests/DerivationPathTests.cpp index 04dfd5de255..4b2269ad864 100644 --- a/tests/DerivationPathTests.cpp +++ b/tests/DerivationPathTests.cpp @@ -11,7 +11,7 @@ namespace TW { TEST(DerivationPath, InitWithIndices) { - const auto path = DerivationPath(TWPurposeBIP44, TWCoinTypeEthereum, 0, 0, 0); + const auto path = DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeEthereum), 0, 0, 0); ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */true)); ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */true)); ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */true)); diff --git a/tests/Groestlcoin/AddressTests.cpp b/tests/Groestlcoin/AddressTests.cpp index 9f0089fb5da..d3910b6c382 100644 --- a/tests/Groestlcoin/AddressTests.cpp +++ b/tests/Groestlcoin/AddressTests.cpp @@ -40,6 +40,6 @@ TEST(GroestlcoinAddress, Derive) { const auto mnemonic = "all all all all all all all all all all all all"; const auto wallet = HDWallet(mnemonic, ""); const auto path = TW::derivationPath(TWCoinTypeGroestlcoin); - const auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(path)); + const auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); ASSERT_EQ(address, "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); } diff --git a/tests/Groestlcoin/TWGroestlcoinTests.cpp b/tests/Groestlcoin/TWGroestlcoinTests.cpp index 474293a5d3f..f39ea0cfb6f 100644 --- a/tests/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/Groestlcoin/TWGroestlcoinTests.cpp @@ -77,8 +77,8 @@ TEST(Groestlcoin, ExtendedKeys) { TEST(Groestlcoin, DeriveFromZpub) { auto zpub = STRING("zpub6qXFnWiY6FdT5BQptrzEhHfm1WpaBTFc6MHzR4KwscXGdt6xCqUtrAEjrHdeEsjaYEwVMgjtTvENQ83yo2fmkYYGjTpJoH7vFWKQJp1bg1X"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/17'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/84'/17'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeGroestlcoin, STRING("m/84'/17'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeGroestlcoin, STRING("m/84'/17'/0'/0/11").get()); auto address4 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey4, TWCoinTypeGroestlcoin)); auto address4String = WRAPS(TWAnyAddressDescription(address4.get())); diff --git a/tests/HDWalletTests.cpp b/tests/HDWalletTests.cpp index 07fc895748c..4cc59d363dd 100644 --- a/tests/HDWalletTests.cpp +++ b/tests/HDWalletTests.cpp @@ -20,7 +20,7 @@ namespace TW { TEST(HDWallet, privateKeyFromXPRV) { const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); ASSERT_TRUE(privateKey); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Bitcoin::CashAddress(publicKey); @@ -31,7 +31,7 @@ TEST(HDWallet, privateKeyFromXPRV) { TEST(HDWallet, privateKeyFromXPRV_Invalid) { const std::string xprv = "xprv9y0000"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); ASSERT_FALSE(privateKey); } @@ -39,13 +39,13 @@ TEST(HDWallet, privateKeyFromXPRV_InvalidVersion) { { // Version bytes (first 4) are invalid, 0x00000000 const std::string xprv = "11117pE7xwz2GARukXY8Vj2ge4ozfX4HLgy5ztnJXjr5btzJE8EbtPhZwrcPWAodW2aFeYiXkXjSxJYm5QrnhSKFXDgACcFdMqGns9VLqESCq3"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); ASSERT_FALSE(privateKey); } { // Version bytes (first 4) are invalid, 0xdeadbeef const std::string xprv = "pGoh3VZXR4mTkT4bfqj4paog12KmHkAWkdLY8HNsZagD1ihVccygLr1ioLBhVQsny47uEh5swP3KScFc4JJrazx1Y7xvzmH2y5AseLgVMwomBTg2"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); ASSERT_FALSE(privateKey); } } @@ -53,20 +53,20 @@ TEST(HDWallet, privateKeyFromXPRV_InvalidVersion) { TEST(HDWallet, privateKeyFromExtended_InvalidCurve) { // invalid coin & curve, should fail const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, (TWCoinType)123456, 0, 0, 0)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinType(123456), DerivationPath(TWPurposeBIP44, 123456, 0, 0, 0)); ASSERT_FALSE(privateKey); } TEST(HDWallet, privateKeyFromXPRV_Invalid45) { // 45th byte is not 0 const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbhw2dJ8QexahgVSfkjxU4FgmN4GLGN3Ui8oLqC6433CeyPUNVHHh"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 3)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); ASSERT_FALSE(privateKey); } TEST(HDWallet, privateKeyFromMptv) { const std::string mptv = "Mtpv7SkyM349Svcf1WiRtB5hC91ZZkVsGuv3kz1V7tThGxBFBzBLFnw6LpaSvwpHHuy8dAfMBqpBvaSAHzbffvhj2TwfojQxM7Ppm3CzW67AFL5"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(mptv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoinCash, 0, 0, 4)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(mptv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 4)); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto witness = Data{0x00, 0x14}; @@ -85,7 +85,7 @@ TEST(HDWallet, privateKeyFromMptv) { TEST(HDWallet, privateKeyFromZprv) { const std::string zprv = "zprvAdzGEQ44z4WPLNCRpDaup2RumWxLGgR8PQ9UVsSmJigXsHVDaHK1b6qGM2u9PmxB2Gx264ctAz4yRoN3Xwf1HZmKcn6vmjqwsawF4WqQjfd"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(zprv, DerivationPath(TWPurposeBIP44, TWCoinTypeBitcoin, 0, 0, 5)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(zprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoin), 0, 0, 5)); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Bitcoin::SegwitAddress(publicKey, 0, "bc"); @@ -95,7 +95,7 @@ TEST(HDWallet, privateKeyFromZprv) { TEST(HDWallet, privateKeyFromDGRV) { const std::string dgpv = "dgpv595jAJYGBLanByCJXRzrWBZFVXdNisfuPmKRDquCQcwBbwKbeR21AtkETf4EpjBsfsK3kDZgMqhcuky1B9PrT5nxiEcjghxpUVYviHXuCmc"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(dgpv, DerivationPath(TWPurposeBIP44, TWCoinTypeDogecoin, 0, 0, 1)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(dgpv, TWCoinTypeDogecoin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeDogecoin), 0, 0, 1)); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Bitcoin::Address(publicKey, TW::p2pkhPrefix(TWCoinTypeDogecoin)); @@ -105,7 +105,7 @@ TEST(HDWallet, privateKeyFromDGRV) { TEST(HDWallet, privateKeyFromXPRVForDGB) { const std::string xprvForDgb = "xprv9ynLofyuR3uCqCMJADwzBaPnXB53EVe5oLujvPfdvCxae3NzgEpYjZMgcUeS8EUeYfYVLG61ZgPXm9TZWiwBnLVCgd551vCwpXC19hX3mFJ"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprvForDgb, DerivationPath(TWPurposeBIP44, TWCoinTypeDigiByte, 0, 0, 1)); + auto privateKey = HDWallet::getPrivateKeyFromExtended(xprvForDgb, TWCoinTypeDigiByte, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeDigiByte), 0, 0, 1)); auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Bitcoin::Address(publicKey, TW::p2pkhPrefix(TWCoinTypeDigiByte)); diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp index 5e385323ac0..2f33587300d 100644 --- a/tests/Keystore/StoredKeyTests.cpp +++ b/tests/Keystore/StoredKeyTests.cpp @@ -24,6 +24,8 @@ const auto passwordString = "password"; const auto password = TW::data(string(passwordString)); const auto mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; const TWCoinType coinTypeBc = TWCoinTypeBitcoin; +const TWCoinType coinTypeBnb = TWCoinTypeBinance; +const TWCoinType coinTypeBsc = TWCoinTypeBinanceSmartChain; TEST(StoredKey, CreateWithMnemonic) { auto key = StoredKey::createWithMnemonic("name", password, mnemonic); @@ -65,7 +67,7 @@ TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { const Data& mnemo2Data = key.payload.decrypt(password); EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin(), coinTypeBc); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); } @@ -75,7 +77,7 @@ TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); EXPECT_EQ(key.type, StoredKeyType::privateKey); EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin(), coinTypeBc); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), hex(privateKey)); @@ -112,25 +114,25 @@ TEST(StoredKey, AccountGetCreate) { // not exists, wallet nonnull, create const Account* acc3 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc3 != nullptr); - EXPECT_EQ(acc3->coin(), coinTypeBc); + EXPECT_EQ(acc3->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); // exists const Account* acc4 = key.account(coinTypeBc); EXPECT_TRUE(acc4 != nullptr); - EXPECT_EQ(acc4->coin(), coinTypeBc); + EXPECT_EQ(acc4->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); // exists, wallet nonnull, not create const Account* acc5 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc5 != nullptr); - EXPECT_EQ(acc5->coin(), coinTypeBc); + EXPECT_EQ(acc5->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); // exists, wallet null, not create const Account* acc6 = key.account(coinTypeBc, nullptr); EXPECT_TRUE(acc6 != nullptr); - EXPECT_EQ(acc6->coin(), coinTypeBc); + EXPECT_EQ(acc6->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1); } @@ -138,11 +140,21 @@ TEST(StoredKey, AddRemoveAccount) { auto key = StoredKey::createWithMnemonic("name", password, mnemonic); EXPECT_EQ(key.accounts.size(), 0); - const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); - key.addAccount("bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr", derivationPath, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - EXPECT_EQ(key.accounts.size(), 1); + { + const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); + key.addAccount("bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr", coinTypeBc, derivationPath, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), 1); + } + { + const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); + key.addAccount("bnb1devga6q804tx9fqrnx0vtu5r36kxgp9tmk4xkm", coinTypeBnb, derivationPath, ""); + key.addAccount("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coinTypeBsc, derivationPath, ""); + EXPECT_EQ(key.accounts.size(), 3); + } key.removeAccount(coinTypeBc); + key.removeAccount(coinTypeBnb); + key.removeAccount(coinTypeBsc); EXPECT_EQ(key.accounts.size(), 0); } @@ -177,14 +189,14 @@ TEST(StoredKey, LoadNonexistent) { TEST(StoredKey, LoadLegacyPrivateKey) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-private-key.json"); EXPECT_EQ(key.id, "3051ca7d-3d36-4a4a-acc2-09e9083732b0"); - EXPECT_EQ(key.accounts[0].coin(), TWCoinTypeEthereum); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); EXPECT_EQ(hex(key.payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } TEST(StoredKey, LoadLivepeerKey) { const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/livepeer.json"); EXPECT_EQ(key.id, "70ea3601-ee21-4e94-a7e4-66255a987d22"); - EXPECT_EQ(key.accounts[0].coin(), TWCoinTypeEthereum); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); } @@ -209,10 +221,10 @@ TEST(StoredKey, LoadLegacyMnemonic) { const auto mnemonic = string(reinterpret_cast(data.data())); EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); - EXPECT_EQ(key.accounts[0].coin(), TWCoinTypeEthereum); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); EXPECT_EQ(key.accounts[0].derivationPath.string(), "m/44'/60'/0'/0/0"); EXPECT_EQ(key.accounts[0].address, ""); - EXPECT_EQ(key.accounts[1].coin(), coinTypeBc); + EXPECT_EQ(key.accounts[1].coin, coinTypeBc); EXPECT_EQ(key.accounts[1].derivationPath.string(), "m/84'/0'/0'/0/0"); EXPECT_EQ(key.accounts[1].address, ""); EXPECT_EQ(key.accounts[1].extendedPublicKey, "zpub6r97AegwVxVbJeuDAWP5KQgX5y4Q6KyFUrsFQRn8yzSXrnmpwg1ZKHSWwECR1Kiqgr4h93WN5kdS48KC6hVFniuZHqVFXjULZZkCwurqyPn"); @@ -307,7 +319,7 @@ TEST(StoredKey, RemoveAccount) { EXPECT_EQ(key.accounts.size(), 2); key.removeAccount(TWCoinTypeEthereum); EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin(), coinTypeBc); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); } TEST(StoredKey, MissingAddress) { diff --git a/tests/Litecoin/TWLitecoinTests.cpp b/tests/Litecoin/TWLitecoinTests.cpp index 5bd649dde4c..a66f73ef058 100644 --- a/tests/Litecoin/TWLitecoinTests.cpp +++ b/tests/Litecoin/TWLitecoinTests.cpp @@ -73,8 +73,8 @@ TEST(Litecoin, ExtendedKeys) { TEST(Litecoin, DeriveFromZpub) { auto zpub = STRING("zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeLitecoin, STRING("m/44'/2'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeLitecoin, STRING("m/44'/2'/0'/0/11").get()); auto address4 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey4, TWCoinTypeLitecoin)); auto address4String = WRAPS(TWAnyAddressDescription(address4.get())); diff --git a/tests/Monacoin/TWMonacoinAddressTests.cpp b/tests/Monacoin/TWMonacoinAddressTests.cpp index e2889c2b1d1..b1a922a1d8e 100644 --- a/tests/Monacoin/TWMonacoinAddressTests.cpp +++ b/tests/Monacoin/TWMonacoinAddressTests.cpp @@ -80,8 +80,8 @@ TEST(Monacoin, ExtendedKeys) { TEST(Monacoin, DeriveFromXpub) { auto xpub = STRING("xpub6CYWFE1BgTCW2vtbDm1RRT81i3hBkQrXCfGs5hYp211fpgLZV5xCEwXMWPAL3LgaBA9koXpLZSUo7rTyJ8q1JwqKhvzVpdzBKRGyyGb31KF"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/22'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/22'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeMonacoin, STRING("m/44'/22'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeMonacoin, STRING("m/44'/22'/0'/0/9").get()); auto address2 = TWBitcoinAddressCreateWithPublicKey(pubKey2, TWCoinTypeP2pkhPrefix(TWCoinTypeMonacoin)); auto address2String = WRAPS(TWBitcoinAddressDescription(address2)); diff --git a/tests/Qtum/TWQtumAddressTests.cpp b/tests/Qtum/TWQtumAddressTests.cpp index 66e80f759aa..846613a6729 100644 --- a/tests/Qtum/TWQtumAddressTests.cpp +++ b/tests/Qtum/TWQtumAddressTests.cpp @@ -76,8 +76,8 @@ TEST(Qtum, ExtendedKeys) { TEST(Qtum, DeriveFromXpub) { auto xpub = STRING("xpub6CAkJZPecMDxRXEXZpDwyxcQ6CGie8GdovuJhsGwc2gFbLxdGr1PyqBXmsL7aYds1wfY2rB3YMVZiEE3CB3Lkj6KGoq1rEJ1wuaGkMDBf1m"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/2301'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/2301'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/9").get()); auto address2 = TWBitcoinAddressCreateWithPublicKey(pubKey2, TWCoinTypeP2pkhPrefix(TWCoinTypeQtum)); auto address2String = WRAPS(TWBitcoinAddressDescription(address2)); @@ -91,8 +91,8 @@ TEST(Qtum, DeriveFromXpub) { TEST(Qtum, DeriveFromZpub) { auto zpub = STRING("zpub6rJJqJZcpaC7DrdsYiprLfUfvtaf11ZZWmrmYeWMkdZTx6tgfQLiBZuisraogskwBRLMGWfXoCyWRrXSypwPdNV2UWJXm5bDVQvBXvrzz9d"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2301'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/2301'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeQtum, STRING("m/44'/2301'/0'/0/11").get()); auto address4 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPQtum, pubKey4)); auto address4String = WRAPS(TWSegwitAddressDescription(address4.get())); diff --git a/tests/Solana/TWSolanaAddressTests.cpp b/tests/Solana/TWSolanaAddressTests.cpp index 5af3bfa161f..bb4b600cac9 100644 --- a/tests/Solana/TWSolanaAddressTests.cpp +++ b/tests/Solana/TWSolanaAddressTests.cpp @@ -19,7 +19,7 @@ TEST(TWSolanaAddress, HDWallet) { auto wallet = WRAP( TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeDerivationPath(TWCoinTypeSolana)); + auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeSolana, TWCoinTypeDerivationPath(TWCoinTypeSolana)); auto publicKey = TWPrivateKeyGetPublicKeyEd25519(privateKey); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeSolana)); auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/TON/TWTONAddressTests.cpp b/tests/TON/TWTONAddressTests.cpp index 835eba44b46..0a2e3375833 100644 --- a/tests/TON/TWTONAddressTests.cpp +++ b/tests/TON/TWTONAddressTests.cpp @@ -55,7 +55,7 @@ TEST(TWTONAddress, HDWallet) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeDerivationPath(TWCoinTypeTON)); + auto privateKey = TWHDWalletGetKey(wallet.get(), TWCoinTypeTON, TWCoinTypeDerivationPath(TWCoinTypeTON)); auto publicKey = TWPrivateKeyGetPublicKeyEd25519(privateKey); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeTON)); auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/Viacoin/TWViacoinAddressTests.cpp b/tests/Viacoin/TWViacoinAddressTests.cpp index 38b7ced5585..ad480209687 100644 --- a/tests/Viacoin/TWViacoinAddressTests.cpp +++ b/tests/Viacoin/TWViacoinAddressTests.cpp @@ -74,8 +74,8 @@ TEST(Viacoin, ExtendedKeys) { TEST(Viacoin, DeriveFromXpub) { auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); - auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/14'/0'/0/2").get()); - auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/14'/0'/0/9").get()); + auto pubKey2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/2").get()); + auto pubKey9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/9").get()); auto address2 = TWBitcoinAddressCreateWithPublicKey(pubKey2, TWCoinTypeP2pkhPrefix(TWCoinTypeViacoin)); auto address2String = WRAPS(TWBitcoinAddressDescription(address2)); @@ -89,8 +89,8 @@ TEST(Viacoin, DeriveFromXpub) { TEST(Viacoin, DeriveFromZpub) { auto zpub = STRING("zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs"); - auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/14'/0'/0/4").get()); - auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/14'/0'/0/11").get()); + auto pubKey4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/4").get()); + auto pubKey11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeViacoin, STRING("m/44'/14'/0'/0/11").get()); auto address4 = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPViacoin, pubKey4)); auto address4String = WRAPS(TWSegwitAddressDescription(address4.get())); diff --git a/tests/Waves/AddressTests.cpp b/tests/Waves/AddressTests.cpp index 0ed61aa772e..b5a8afe9232 100644 --- a/tests/Waves/AddressTests.cpp +++ b/tests/Waves/AddressTests.cpp @@ -75,9 +75,9 @@ TEST(WavesAddress, Derive) { "ill special axis adjust pull useful craft peace flee physical"; const auto wallet = HDWallet(mnemonic, ""); const auto address1 = TW::deriveAddress( - TWCoinTypeWaves, wallet.getKey(DerivationPath("m/44'/5741564'/0'/0'/0'"))); + TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/0'"))); const auto address2 = TW::deriveAddress( - TWCoinTypeWaves, wallet.getKey(DerivationPath("m/44'/5741564'/0'/0'/1'"))); + TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/1'"))); ASSERT_EQ(address1, "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); ASSERT_EQ(address2, "3PEXw52bkS9XuLhttWoKyykZjXqEY8zeLxf"); diff --git a/tests/Zcash/TWZcashAddressTests.cpp b/tests/Zcash/TWZcashAddressTests.cpp index 3deb054f8e8..1afd9ae41ab 100644 --- a/tests/Zcash/TWZcashAddressTests.cpp +++ b/tests/Zcash/TWZcashAddressTests.cpp @@ -32,7 +32,7 @@ TEST(TWZcash, DeriveTransparentAddress) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto derivationPath = STRING("m/44'/133'/0'/0/5"); - auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), derivationPath.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeZcash, derivationPath.get())); auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(key.get(), true); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeZcash)); @@ -56,8 +56,8 @@ TEST(TWZcash, ExtendedKeys) { TEST(TWZcash, DerivePubkeyFromXpub) { auto xpub = STRING("xpub6CksSgKBhD9KaLgxLE9LXpSj74b2EB9d1yKvhWxrstk4Md8gmiJb5GwkMeBhpLxVjACMdNbRsAm2GG5ehVuyq42QBYYPAjXjcBxMVmpaaNL"); - auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/133'/0'/0/3").get()); - auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/133'/0'/0/5").get()); + auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcash, STRING("m/44'/133'/0'/0/3").get()); + auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcash, STRING("m/44'/133'/0'/0/5").get()); auto address3 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey3, TWCoinTypeZcash)); auto address3String = WRAPS(TWAnyAddressDescription(address3.get())); @@ -71,7 +71,7 @@ TEST(TWZcash, DerivePubkeyFromXpub) { TEST(TWZcash, DerivePubkeyFromXpub2) { auto xpub = STRING("xpub6C7HhMqpir3KBA6ammv5B58RT3XFTJqoZFoj3J56dz9XwehZ2puSH38ERtnz7HaXGxaZP8AHT4M2bSRHpBXUZrbsJ2xg3xs53DGKYCqj8mr"); - auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/133'/0'/0/0").get()); + auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcash, STRING("m/44'/133'/0'/0/0").get()); auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeZcash, pubKey)); assertStringsEqual(address, "t1TKCtCETHPrAdA6eY1fdhhnTkTmb371oPt"); } diff --git a/tests/Zcoin/TWZCoinAddressTests.cpp b/tests/Zcoin/TWZCoinAddressTests.cpp index 05ddf6a64e6..583e8c41306 100644 --- a/tests/Zcoin/TWZCoinAddressTests.cpp +++ b/tests/Zcoin/TWZCoinAddressTests.cpp @@ -39,8 +39,8 @@ TEST(TWZCoin, ExtendedKeys) { TEST(TWZcoin, DeriveFromXpub) { auto xpub = STRING("xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/136'/0'/0/3").get()); - auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/136'/0'/0/5").get()); + auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/3").get()); + auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZcoin, STRING("m/44'/136'/0'/0/5").get()); auto address3 = TWBitcoinAddressCreateWithPublicKey(pubKey3, TWCoinTypeP2pkhPrefix(TWCoinTypeZcoin)); auto address3String = WRAPS(TWBitcoinAddressDescription(address3)); diff --git a/tests/Zelcash/TWZelcashAddressTests.cpp b/tests/Zelcash/TWZelcashAddressTests.cpp index c61a905c126..570442841e2 100644 --- a/tests/Zelcash/TWZelcashAddressTests.cpp +++ b/tests/Zelcash/TWZelcashAddressTests.cpp @@ -29,7 +29,7 @@ TEST(TWZelcash, DeriveTransparentAddress) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto derivationPath = STRING("m/44'/19167'/0'/0/5"); - auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), derivationPath.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeZelcash, derivationPath.get())); auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(key.get(), true); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeZelcash)); @@ -53,8 +53,8 @@ TEST(TWZelcash, ExtendedKeys) { TEST(TWZelcash, DerivePubkeyFromXpub) { auto xpub = STRING("xpub6DATuScKPEk6YvULrHPff1NKC49nyz5mCZQyxSDEQihq3kfoKDYCznLrsdW4KmXw9TryNfEZ9JSD8tJL9UTC3LnBA54YZL7nqMtJm7Ffnoz"); - auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/19167'/0'/0/3").get()); - auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/19167'/0'/0/5").get()); + auto pubKey3 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZelcash, STRING("m/44'/19167'/0'/0/3").get()); + auto pubKey5 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZelcash, STRING("m/44'/19167'/0'/0/5").get()); auto address3 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey3, TWCoinTypeZelcash)); auto address3String = WRAPS(TWAnyAddressDescription(address3.get())); @@ -68,7 +68,7 @@ TEST(TWZelcash, DerivePubkeyFromXpub) { TEST(TWZelcash, DerivePubkeyFromXpub2) { auto xpub = STRING("xpub6C7HhMqpir3KBA6ammv5B58RT3XFTJqoZFoj3J56dz9XwehZ2puSH38ERtnz7HaXGxaZP8AHT4M2bSRHpBXUZrbsJ2xg3xs53DGKYCqj8mr"); - auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/19167'/0'/0/0").get()); + auto pubKey = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeZelcash, STRING("m/44'/19167'/0'/0/0").get()); auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeZelcash, pubKey)); assertStringsEqual(address, "t1TKCtCETHPrAdA6eY1fdhhnTkTmb371oPt"); } diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 80f2d2254f5..421e4fbfeeb 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -255,6 +255,19 @@ TEST(HDWallet, DeriveElrond) { assertStringsEqual(address, "erd1a6l7q9cfvrgr80xuzm37tapdr4zm3mwrtl6vt8f45df45x7eadfs8ds5vv"); } +TEST(HDWallet, DeriveBinance) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinance)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinanceSmartChain)); + auto keyData = WRAPD(TWPrivateKeyData(key.get())); + auto keyData2 = WRAPD(TWPrivateKeyData(key2.get())); + + auto expected = "ca81b1b0974aa063de2f74c17b9dc364a8208d105659f4f900c121fb170922fe"; + + assertHexEqual(keyData, expected); + assertHexEqual(keyData2, expected); +} + TEST(HDWallet, ExtendedKeys) { auto words = STRING("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"); auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); @@ -283,8 +296,8 @@ TEST(HDWallet, ExtendedKeys) { TEST(HDWallet, PublicKeyFromX) { auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); - auto xpubAddr2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/2").get()); - auto xpubAddr9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/9").get()); + auto xpubAddr2 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/2").get()); + auto xpubAddr9 = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/9").get()); auto data2 = WRAPD(TWPublicKeyData(xpubAddr2)); auto data9 = WRAPD(TWPublicKeyData(xpubAddr9)); @@ -295,14 +308,14 @@ TEST(HDWallet, PublicKeyFromX) { TEST(HDWallet, PublicKeyInvalid) { auto xpub = STRING("xpub0000"); - auto xpubAddr = TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/145'/0'/0/0").get()); + auto xpubAddr = TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeBitcoinCash, STRING("m/44'/145'/0'/0/0").get()); ASSERT_EQ(xpubAddr, nullptr); } TEST(HDWallet, PublicKeyFromY) { auto ypub = STRING("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP"); - auto ypubAddr3 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), STRING("m/44'/0'/0'/0/3").get()); - auto ypubAddr10 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), STRING("m/44'/0'/0'/0/10").get()); + auto ypubAddr3 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/3").get()); + auto ypubAddr10 = TWHDWalletGetPublicKeyFromExtended(ypub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/10").get()); auto data3 = WRAPD(TWPublicKeyData(ypubAddr3)); auto data10 = WRAPD(TWPublicKeyData(ypubAddr10)); @@ -313,8 +326,8 @@ TEST(HDWallet, PublicKeyFromY) { TEST(HDWallet, PublicKeyFromZ) { auto zpub = STRING("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"); - auto zpubAddr4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/0'/0'/0/4").get()); - auto zpubAddr11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), STRING("m/44'/0'/0'/0/11").get()); + auto zpubAddr4 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/4").get()); + auto zpubAddr11 = TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeBitcoin, STRING("m/44'/0'/0'/0/11").get()); auto data4 = WRAPD(TWPublicKeyData(zpubAddr4)); auto data11 = WRAPD(TWPublicKeyData(zpubAddr11)); @@ -329,7 +342,7 @@ TEST(HDWallet, PublicKeyFromZ) { TEST(HDWallet, PublicKeyFromExtended_NIST256p1) { const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); - const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/888'/0'/0/0").get())); // Neo + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeNEO, STRING("m/44'/888'/0'/0/0").get())); // Neo ASSERT_NE(xpubAddr.get(), nullptr); auto data = WRAPD(TWPublicKeyData(xpubAddr.get())); ASSERT_NE(data.get(), nullptr); @@ -339,19 +352,19 @@ TEST(HDWallet, PublicKeyFromExtended_NIST256p1) { TEST(HDWallet, PublicKeyFromExtended_Negative) { const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); { // Ed25519 - const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/501'/0'").get())); // Solana + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeSolana, STRING("m/44'/501'/0'").get())); // Solana EXPECT_EQ(xpubAddr.get(), nullptr); } { // Ed25519Extended - const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/1852'/1815'/0'/0/0").get())); // Cardano + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeCardano, STRING("m/1852'/1815'/0'/0/0").get())); // Cardano EXPECT_EQ(xpubAddr.get(), nullptr); } { // Ed25519Blake2bNano - const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/165'/0'").get())); // Nano + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeNano, STRING("m/44'/165'/0'").get())); // Nano EXPECT_EQ(xpubAddr.get(), nullptr); } { // Curve25519 - const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), STRING("m/44'/5741564'/0'/0'/0'").get())); // Waves + const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeWaves, STRING("m/44'/5741564'/0'/0'/0'").get())); // Waves EXPECT_EQ(xpubAddr.get(), nullptr); } } diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index 6a1c0238419..ac36a9e3c31 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -126,6 +126,7 @@ TEST(TWStoredKey, addressAddRemove) { TWStoredKeyAddAccount(key, WRAPS(TWStringCreateWithUTF8Bytes(addressAdd)).get(), + TWCoinTypeBitcoin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath)).get(), WRAPS(TWStringCreateWithUTF8Bytes(extPubKeyAdd)).get()); EXPECT_EQ(TWStoredKeyAccountCount(key), 1); diff --git a/walletconsole/lib/Address.cpp b/walletconsole/lib/Address.cpp index 09b89ac32b5..7b2ef8a3a0e 100644 --- a/walletconsole/lib/Address.cpp +++ b/walletconsole/lib/Address.cpp @@ -79,17 +79,17 @@ bool Address::addrDefault(const string& coinid, string& res) { return true; } -bool Address::addrDP(const string& coinid, const string& derivPath, string& res) { +bool Address::deriveFromPath(const string& coinid, const string& derivPath, string& res) { + Coin coin; + if (!_coins.findCoin(coinid, coin)) { return false; } + TWCoinType ctype = (TWCoinType)coin.c; + DerivationPath dp(derivPath); // get the private key string mnemo = _keys.getMnemo(); assert(mnemo.length() > 0); // a mnemonic is always set HDWallet wallet(mnemo, ""); - PrivateKey priKey = wallet.getKey(dp); - - Coin coin; - if (!_coins.findCoin(coinid, coin)) { return false; } - TWCoinType ctype = (TWCoinType)coin.c; + PrivateKey priKey = wallet.getKey(ctype, dp); // derive address res = TW::deriveAddress(ctype, priKey); diff --git a/walletconsole/lib/Address.h b/walletconsole/lib/Address.h index d1cc1906085..5e512ab3f68 100644 --- a/walletconsole/lib/Address.h +++ b/walletconsole/lib/Address.h @@ -32,7 +32,7 @@ class Address { /// Derive a default address, using default coin and current mnemonic bool addrDefault(const string& coinid, string& res); /// Derive a new address with the given derivation path - bool addrDP(const string& coinid, const string& derivPath, string& res); + bool deriveFromPath(const string& coinid, const string& derivPath, string& res); }; } // namespace TW::WalletConsole diff --git a/walletconsole/lib/CommandExecutor.cpp b/walletconsole/lib/CommandExecutor.cpp index 20b36d3124f..2f9342066ed 100644 --- a/walletconsole/lib/CommandExecutor.cpp +++ b/walletconsole/lib/CommandExecutor.cpp @@ -131,7 +131,7 @@ bool CommandExecutor::executeOne(const string& cmd, const vector& params if (cmd == "addrpri") { if (!checkMinParams(params, 1)) { return false; } return _address.addrPri(_activeCoin, params[1], res); } if (cmd == "addr") { if (!checkMinParams(params, 1)) { return false; } return _address.addr(_activeCoin, params[1], res); } if (cmd == "addrdefault") { return _address.addrDefault(_activeCoin, res); } - if (cmd == "addrdp") { if (!checkMinParams(params, 1)) { return false; } return _address.addrDP(_activeCoin, params[1], res); } + if (cmd == "addrdp") { if (!checkMinParams(params, 1)) { return false; } return _address.deriveFromPath(_activeCoin, params[1], res); } if (cmd == "toninitmsg") { if (!checkMinParams(params, 1)) { return false; } setCoin("ton", false); return TonCoin::tonInitMsg(params[1], res); } diff --git a/walletconsole/lib/Keys.cpp b/walletconsole/lib/Keys.cpp index cf0ab045fd5..e73a2d2754c 100644 --- a/walletconsole/lib/Keys.cpp +++ b/walletconsole/lib/Keys.cpp @@ -45,7 +45,7 @@ bool Keys::newKey(const string& coinid, string& res) { HDWallet newWallet(256, ""); DerivationPath derivationPath = DerivationPath(coin.derivPath); - PrivateKey key = newWallet.getKey(derivationPath); + PrivateKey key = newWallet.getKey(TWCoinType(coin.c), derivationPath); privateKeyToResult(key, res); return true; } @@ -152,7 +152,7 @@ bool Keys::priDP(const string& coinid, const string& dp, string& res) { _out << "Using derivation path \"" << dp2 << "\" for coin " << coin.name << endl; HDWallet wallet(_currentMnemonic, ""); - PrivateKey priKey = wallet.getKey(dp3); + PrivateKey priKey = wallet.getKey(TWCoinType(coin.c), dp3); privateKeyToResult(priKey, res); return true; From 9ec7bcd4a5c1c6bb1f43bfabdc2a97687d71d61e Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Fri, 14 Aug 2020 17:46:43 +0800 Subject: [PATCH 71/81] [Sample] Add golang signing sample code (#1070) * add go signing sample code * review comments --- samples/go/README.md | 5 +- samples/go/go.mod | 5 + samples/go/go.sum | 20 + samples/go/main.go | 114 ++-- samples/go/protos/bitcoin/Bitcoin.pb.go | 660 ++++++++++++++++++++++++ samples/go/types/twdata.go | 31 ++ samples/go/types/twstring.go | 23 + 7 files changed, 808 insertions(+), 50 deletions(-) create mode 100644 samples/go/go.mod create mode 100644 samples/go/go.sum create mode 100644 samples/go/protos/bitcoin/Bitcoin.pb.go create mode 100644 samples/go/types/twdata.go create mode 100644 samples/go/types/twstring.go diff --git a/samples/go/README.md b/samples/go/README.md index 43b27458a81..d28a5dd0ad6 100644 --- a/samples/go/README.md +++ b/samples/go/README.md @@ -30,7 +30,7 @@ and [Build Instructions](https://developer.trustwallet.com/wallet-core/building) The librabry is already built in this image (Build instructions [here](building.md)) Note: may not be the most recent version. 2. Install go: `apt-get update && apt-get install golang` -(or download from here [go1.13.3](https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). +(or download from here [go1.14.2](https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz), configure `GOROOT` and append `GOROOT/bin` to `PATH`). 3. Go to the **samples/go** folder within wallet core repo: ```shell @@ -44,6 +44,7 @@ cd wallet-core/samples/go ```shell ==> calling wallet core from go -<== mnemonic is valid: true +==> mnemonic is valid: true +==> bitcoin... ``` 6. You might want to copy and run `main` outside of the docker container, make sure you have `libc++1` and `libc++abi1` installed in your host Ubuntu. diff --git a/samples/go/go.mod b/samples/go/go.mod new file mode 100644 index 00000000000..9a564387542 --- /dev/null +++ b/samples/go/go.mod @@ -0,0 +1,5 @@ +module tw + +go 1.14 + +require github.com/golang/protobuf v1.4.2 diff --git a/samples/go/go.sum b/samples/go/go.sum new file mode 100644 index 00000000000..0eeb4918550 --- /dev/null +++ b/samples/go/go.sum @@ -0,0 +1,20 @@ +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= diff --git a/samples/go/main.go b/samples/go/main.go index 38507a3a341..a74b4c0ae17 100644 --- a/samples/go/main.go +++ b/samples/go/main.go @@ -3,67 +3,85 @@ package main // #cgo CFLAGS: -I../../include // #cgo LDFLAGS: -L../../build -L../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm // #include -// #include -// #include // #include // #include +// #include +// #include import "C" -import "fmt" -import "unsafe" -import "encoding/hex" +import ( + "encoding/hex" + "fmt" + "tw/protos/bitcoin" + "tw/types" -// C.TWString -> Go string -func TWStringGoString(s unsafe.Pointer) string { - return C.GoString(C.TWStringUTF8Bytes(s)) -} + "github.com/golang/protobuf/proto" +) -// Go string -> C.TWString -func TWStringCreateWithGoString(s string) unsafe.Pointer { - cStr := C.CString(s) - defer C.free(unsafe.Pointer(cStr)) - str := C.TWStringCreateWithUTF8Bytes(cStr) - return str -} +func main() { + fmt.Println("==> calling wallet core from go") + str := types.TWStringCreateWithGoString("confirm bleak useless tail chalk destroy horn step bulb genuine attract split") + emtpy := types.TWStringCreateWithGoString("") + defer C.TWStringDelete(str) + defer C.TWStringDelete(emtpy) -// C.TWData -> Go byte[] -func TWDataGoBytes(d unsafe.Pointer) []byte { - cBytes := C.TWDataBytes(d) - cSize := C.TWDataSize(d) - return C.GoBytes(unsafe.Pointer(cBytes), C.int(cSize)) -} + fmt.Println("==> mnemonic is valid: ", C.TWHDWalletIsValid(str)) -// Go byte[] -> C.TWData -func TWDataCreateWithGoBytes(d []byte) unsafe.Pointer { - cBytes := C.CBytes(d) - defer C.free(unsafe.Pointer(cBytes)) - data := C.TWDataCreateWithBytes((*C.uchar)(cBytes), C.ulong(len(d))) - return data -} + wallet := C.TWHDWalletCreateWithMnemonic(str, emtpy) + defer C.TWHDWalletDelete(wallet) -func main() { - fmt.Println("==> calling wallet core from go") - str := TWStringCreateWithGoString("confirm bleak useless tail chalk destroy horn step bulb genuine attract split") - emtpy := TWStringCreateWithGoString("") - defer C.TWStringDelete(str) - defer C.TWStringDelete(emtpy) + key := C.TWHDWalletGetKeyForCoin(wallet, C.TWCoinTypeBitcoin) + keyData := C.TWPrivateKeyData(key) + defer C.TWDataDelete(keyData) + + fmt.Println("<== bitcoin private key: ", types.TWDataHexString(keyData)) + + pubKey, _ := hex.DecodeString("0288be7586c41a0498c1f931a0aaf08c15811ee2651a5fe0fa213167dcaba59ae8") + pubKeyData := types.TWDataCreateWithGoBytes(pubKey) + defer C.TWDataDelete(pubKeyData) + fmt.Println("==> bitcoin public key is valid: ", C.TWPublicKeyIsValid(pubKeyData, C.TWPublicKeyTypeSECP256k1)) + + address := C.TWHDWalletGetAddressForCoin(wallet, C.TWCoinTypeBitcoin) + defer C.TWStringDelete(address) + fmt.Println("<== bitcoin address: ", types.TWStringGoString(address)) + + script := C.TWBitcoinScriptLockScriptForAddress(address, C.TWCoinTypeBitcoin) + scriptData := C.TWBitcoinScriptData(script) + defer C.TWBitcoinScriptDelete(script) + defer C.TWDataDelete(scriptData) + fmt.Println("<== bitcoin address lock script: ", types.TWDataHexString(scriptData)) - fmt.Println("<== mnemonic is valid: ", C.TWHDWalletIsValid(str)) + utxoHash, _ := hex.DecodeString("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f") - wallet := C.TWHDWalletCreateWithMnemonic(str, emtpy) - defer C.TWHDWalletDelete(wallet) + utxo := bitcoin.UnspentTransaction{ + OutPoint: &bitcoin.OutPoint{ + Hash: utxoHash, + Index: 0, + Sequence: 4294967295, + }, + Amount: 625000000, + Script: types.TWDataGoBytes(scriptData), + } - key := C.TWHDWalletGetKeyForCoin(wallet, C.TWCoinTypeBitcoin) - keyData := C.TWPrivateKeyData(key) - keyHex := hex.EncodeToString(TWDataGoBytes(keyData)) - fmt.Println("<== bitcoin private key: ", keyHex) + input := bitcoin.SigningInput{ + HashType: 1, // TWBitcoinSigHashTypeAll + Amount: 1000000, + ByteFee: 1, + ToAddress: "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx", + ChangeAddress: "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU", + PrivateKey: [][]byte{types.TWDataGoBytes(keyData)}, + Utxo: []*bitcoin.UnspentTransaction{&utxo}, + CoinType: 0, // TWCoinTypeBitcoin + } - pubKey, _ := hex.DecodeString("0288be7586c41a0498c1f931a0aaf08c15811ee2651a5fe0fa213167dcaba59ae8") - pubKeyData := TWDataCreateWithGoBytes(pubKey) - defer C.TWDataDelete(pubKeyData) + inputBytes, _ := proto.Marshal(&input) + inputData := types.TWDataCreateWithGoBytes(inputBytes) + defer C.TWDataDelete(inputData) - fmt.Println("<== bitcoin public key is valid: ", C.TWPublicKeyIsValid(pubKeyData, C.TWPublicKeyTypeSECP256k1)) + outputData := C.TWAnySignerSign(inputData, C.TWCoinTypeBitcoin) + defer C.TWDataDelete(outputData) - address := C.TWHDWalletGetAddressForCoin(wallet, C.TWCoinTypeBitcoin) - fmt.Println("<== bitcoin address: ", TWStringGoString(address)) + var output bitcoin.SigningOutput + _ = proto.Unmarshal(types.TWDataGoBytes(outputData), &output) + fmt.Println("<== bitcoin signed tx: ", hex.EncodeToString(output.Encoded)) } diff --git a/samples/go/protos/bitcoin/Bitcoin.pb.go b/samples/go/protos/bitcoin/Bitcoin.pb.go new file mode 100644 index 00000000000..ed93c62556a --- /dev/null +++ b/samples/go/protos/bitcoin/Bitcoin.pb.go @@ -0,0 +1,660 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: Bitcoin.proto + +package bitcoin + +import ( + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Transaction struct { + // Transaction data format version. + Version int32 `protobuf:"zigzag32,1,opt,name=version,proto3" json:"version,omitempty"` + // The block number or timestamp at which this transaction is unlocked. + LockTime uint32 `protobuf:"varint,2,opt,name=lockTime,proto3" json:"lockTime,omitempty"` + // A list of 1 or more transaction inputs or sources for coins. + Inputs []*TransactionInput `protobuf:"bytes,3,rep,name=inputs,proto3" json:"inputs,omitempty"` + // A list of 1 or more transaction outputs or destinations for coins + Outputs []*TransactionOutput `protobuf:"bytes,4,rep,name=outputs,proto3" json:"outputs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Transaction) Reset() { *m = Transaction{} } +func (m *Transaction) String() string { return proto.CompactTextString(m) } +func (*Transaction) ProtoMessage() {} +func (*Transaction) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{0} +} + +func (m *Transaction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Transaction.Unmarshal(m, b) +} +func (m *Transaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Transaction.Marshal(b, m, deterministic) +} +func (m *Transaction) XXX_Merge(src proto.Message) { + xxx_messageInfo_Transaction.Merge(m, src) +} +func (m *Transaction) XXX_Size() int { + return xxx_messageInfo_Transaction.Size(m) +} +func (m *Transaction) XXX_DiscardUnknown() { + xxx_messageInfo_Transaction.DiscardUnknown(m) +} + +var xxx_messageInfo_Transaction proto.InternalMessageInfo + +func (m *Transaction) GetVersion() int32 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *Transaction) GetLockTime() uint32 { + if m != nil { + return m.LockTime + } + return 0 +} + +func (m *Transaction) GetInputs() []*TransactionInput { + if m != nil { + return m.Inputs + } + return nil +} + +func (m *Transaction) GetOutputs() []*TransactionOutput { + if m != nil { + return m.Outputs + } + return nil +} + +// Bitcoin transaction input. +type TransactionInput struct { + // Reference to the previous transaction's output. + PreviousOutput *OutPoint `protobuf:"bytes,1,opt,name=previousOutput,proto3" json:"previousOutput,omitempty"` + // Transaction version as defined by the sender. + Sequence uint32 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` + // Computational script for confirming transaction authorization. + Script []byte `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionInput) Reset() { *m = TransactionInput{} } +func (m *TransactionInput) String() string { return proto.CompactTextString(m) } +func (*TransactionInput) ProtoMessage() {} +func (*TransactionInput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{1} +} + +func (m *TransactionInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionInput.Unmarshal(m, b) +} +func (m *TransactionInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionInput.Marshal(b, m, deterministic) +} +func (m *TransactionInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionInput.Merge(m, src) +} +func (m *TransactionInput) XXX_Size() int { + return xxx_messageInfo_TransactionInput.Size(m) +} +func (m *TransactionInput) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionInput.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionInput proto.InternalMessageInfo + +func (m *TransactionInput) GetPreviousOutput() *OutPoint { + if m != nil { + return m.PreviousOutput + } + return nil +} + +func (m *TransactionInput) GetSequence() uint32 { + if m != nil { + return m.Sequence + } + return 0 +} + +func (m *TransactionInput) GetScript() []byte { + if m != nil { + return m.Script + } + return nil +} + +// Bitcoin transaction out-point reference. +type OutPoint struct { + // The hash of the referenced transaction. + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + // The index of the specific output in the transaction. + Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` + // Transaction version as defined by the sender. + Sequence uint32 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OutPoint) Reset() { *m = OutPoint{} } +func (m *OutPoint) String() string { return proto.CompactTextString(m) } +func (*OutPoint) ProtoMessage() {} +func (*OutPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{2} +} + +func (m *OutPoint) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OutPoint.Unmarshal(m, b) +} +func (m *OutPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OutPoint.Marshal(b, m, deterministic) +} +func (m *OutPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_OutPoint.Merge(m, src) +} +func (m *OutPoint) XXX_Size() int { + return xxx_messageInfo_OutPoint.Size(m) +} +func (m *OutPoint) XXX_DiscardUnknown() { + xxx_messageInfo_OutPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_OutPoint proto.InternalMessageInfo + +func (m *OutPoint) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *OutPoint) GetIndex() uint32 { + if m != nil { + return m.Index + } + return 0 +} + +func (m *OutPoint) GetSequence() uint32 { + if m != nil { + return m.Sequence + } + return 0 +} + +// Bitcoin transaction output. +type TransactionOutput struct { + // Transaction amount. + Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` + // Usually contains the public key as a Bitcoin script setting up conditions to claim this output. + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionOutput) Reset() { *m = TransactionOutput{} } +func (m *TransactionOutput) String() string { return proto.CompactTextString(m) } +func (*TransactionOutput) ProtoMessage() {} +func (*TransactionOutput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{3} +} + +func (m *TransactionOutput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionOutput.Unmarshal(m, b) +} +func (m *TransactionOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionOutput.Marshal(b, m, deterministic) +} +func (m *TransactionOutput) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionOutput.Merge(m, src) +} +func (m *TransactionOutput) XXX_Size() int { + return xxx_messageInfo_TransactionOutput.Size(m) +} +func (m *TransactionOutput) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionOutput.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionOutput proto.InternalMessageInfo + +func (m *TransactionOutput) GetValue() int64 { + if m != nil { + return m.Value + } + return 0 +} + +func (m *TransactionOutput) GetScript() []byte { + if m != nil { + return m.Script + } + return nil +} + +type UnspentTransaction struct { + OutPoint *OutPoint `protobuf:"bytes,1,opt,name=out_point,json=outPoint,proto3" json:"out_point,omitempty"` + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UnspentTransaction) Reset() { *m = UnspentTransaction{} } +func (m *UnspentTransaction) String() string { return proto.CompactTextString(m) } +func (*UnspentTransaction) ProtoMessage() {} +func (*UnspentTransaction) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{4} +} + +func (m *UnspentTransaction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UnspentTransaction.Unmarshal(m, b) +} +func (m *UnspentTransaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UnspentTransaction.Marshal(b, m, deterministic) +} +func (m *UnspentTransaction) XXX_Merge(src proto.Message) { + xxx_messageInfo_UnspentTransaction.Merge(m, src) +} +func (m *UnspentTransaction) XXX_Size() int { + return xxx_messageInfo_UnspentTransaction.Size(m) +} +func (m *UnspentTransaction) XXX_DiscardUnknown() { + xxx_messageInfo_UnspentTransaction.DiscardUnknown(m) +} + +var xxx_messageInfo_UnspentTransaction proto.InternalMessageInfo + +func (m *UnspentTransaction) GetOutPoint() *OutPoint { + if m != nil { + return m.OutPoint + } + return nil +} + +func (m *UnspentTransaction) GetScript() []byte { + if m != nil { + return m.Script + } + return nil +} + +func (m *UnspentTransaction) GetAmount() int64 { + if m != nil { + return m.Amount + } + return 0 +} + +// Input data necessary to create a signed transaction. +type SigningInput struct { + // Hash type to use when signing. + HashType uint32 `protobuf:"varint,1,opt,name=hash_type,json=hashType,proto3" json:"hash_type,omitempty"` + // Amount to send. + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + // Transaction fee per byte. + ByteFee int64 `protobuf:"varint,3,opt,name=byte_fee,json=byteFee,proto3" json:"byte_fee,omitempty"` + // Recipient's address. + ToAddress string `protobuf:"bytes,4,opt,name=to_address,json=toAddress,proto3" json:"to_address,omitempty"` + // Change address. + ChangeAddress string `protobuf:"bytes,5,opt,name=change_address,json=changeAddress,proto3" json:"change_address,omitempty"` + // Available private keys. + PrivateKey [][]byte `protobuf:"bytes,10,rep,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + // Available redeem scripts indexed by script hash. + Scripts map[string][]byte `protobuf:"bytes,11,rep,name=scripts,proto3" json:"scripts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Available unspent transaction outputs. + Utxo []*UnspentTransaction `protobuf:"bytes,12,rep,name=utxo,proto3" json:"utxo,omitempty"` + // If sending max amount. + UseMaxAmount bool `protobuf:"varint,13,opt,name=use_max_amount,json=useMaxAmount,proto3" json:"use_max_amount,omitempty"` + // Coin type (forks). + CoinType uint32 `protobuf:"varint,14,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + // Optional transaction plan + Plan *TransactionPlan `protobuf:"bytes,15,opt,name=plan,proto3" json:"plan,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SigningInput) Reset() { *m = SigningInput{} } +func (m *SigningInput) String() string { return proto.CompactTextString(m) } +func (*SigningInput) ProtoMessage() {} +func (*SigningInput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{5} +} + +func (m *SigningInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SigningInput.Unmarshal(m, b) +} +func (m *SigningInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SigningInput.Marshal(b, m, deterministic) +} +func (m *SigningInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_SigningInput.Merge(m, src) +} +func (m *SigningInput) XXX_Size() int { + return xxx_messageInfo_SigningInput.Size(m) +} +func (m *SigningInput) XXX_DiscardUnknown() { + xxx_messageInfo_SigningInput.DiscardUnknown(m) +} + +var xxx_messageInfo_SigningInput proto.InternalMessageInfo + +func (m *SigningInput) GetHashType() uint32 { + if m != nil { + return m.HashType + } + return 0 +} + +func (m *SigningInput) GetAmount() int64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *SigningInput) GetByteFee() int64 { + if m != nil { + return m.ByteFee + } + return 0 +} + +func (m *SigningInput) GetToAddress() string { + if m != nil { + return m.ToAddress + } + return "" +} + +func (m *SigningInput) GetChangeAddress() string { + if m != nil { + return m.ChangeAddress + } + return "" +} + +func (m *SigningInput) GetPrivateKey() [][]byte { + if m != nil { + return m.PrivateKey + } + return nil +} + +func (m *SigningInput) GetScripts() map[string][]byte { + if m != nil { + return m.Scripts + } + return nil +} + +func (m *SigningInput) GetUtxo() []*UnspentTransaction { + if m != nil { + return m.Utxo + } + return nil +} + +func (m *SigningInput) GetUseMaxAmount() bool { + if m != nil { + return m.UseMaxAmount + } + return false +} + +func (m *SigningInput) GetCoinType() uint32 { + if m != nil { + return m.CoinType + } + return 0 +} + +func (m *SigningInput) GetPlan() *TransactionPlan { + if m != nil { + return m.Plan + } + return nil +} + +// Describes a preliminary transaction plan. +type TransactionPlan struct { + // Amount to be received at the other end. + Amount int64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + // Maximum available amount. + AvailableAmount int64 `protobuf:"varint,2,opt,name=available_amount,json=availableAmount,proto3" json:"available_amount,omitempty"` + // Estimated transaction fee. + Fee int64 `protobuf:"varint,3,opt,name=fee,proto3" json:"fee,omitempty"` + // Change. + Change int64 `protobuf:"varint,4,opt,name=change,proto3" json:"change,omitempty"` + // Selected unspent transaction outputs. + Utxos []*UnspentTransaction `protobuf:"bytes,5,rep,name=utxos,proto3" json:"utxos,omitempty"` + // Zcash branch id + BranchId []byte `protobuf:"bytes,6,opt,name=branch_id,json=branchId,proto3" json:"branch_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionPlan) Reset() { *m = TransactionPlan{} } +func (m *TransactionPlan) String() string { return proto.CompactTextString(m) } +func (*TransactionPlan) ProtoMessage() {} +func (*TransactionPlan) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{6} +} + +func (m *TransactionPlan) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionPlan.Unmarshal(m, b) +} +func (m *TransactionPlan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionPlan.Marshal(b, m, deterministic) +} +func (m *TransactionPlan) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionPlan.Merge(m, src) +} +func (m *TransactionPlan) XXX_Size() int { + return xxx_messageInfo_TransactionPlan.Size(m) +} +func (m *TransactionPlan) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionPlan.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionPlan proto.InternalMessageInfo + +func (m *TransactionPlan) GetAmount() int64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *TransactionPlan) GetAvailableAmount() int64 { + if m != nil { + return m.AvailableAmount + } + return 0 +} + +func (m *TransactionPlan) GetFee() int64 { + if m != nil { + return m.Fee + } + return 0 +} + +func (m *TransactionPlan) GetChange() int64 { + if m != nil { + return m.Change + } + return 0 +} + +func (m *TransactionPlan) GetUtxos() []*UnspentTransaction { + if m != nil { + return m.Utxos + } + return nil +} + +func (m *TransactionPlan) GetBranchId() []byte { + if m != nil { + return m.BranchId + } + return nil +} + +// Transaction signing output. +type SigningOutput struct { + // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. + Transaction *Transaction `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"` + // Signed and encoded transaction bytes. + Encoded []byte `protobuf:"bytes,2,opt,name=encoded,proto3" json:"encoded,omitempty"` + // Transaction id + TransactionId string `protobuf:"bytes,3,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + // Optional error message + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SigningOutput) Reset() { *m = SigningOutput{} } +func (m *SigningOutput) String() string { return proto.CompactTextString(m) } +func (*SigningOutput) ProtoMessage() {} +func (*SigningOutput) Descriptor() ([]byte, []int) { + return fileDescriptor_9fbb050c4cb9ab40, []int{7} +} + +func (m *SigningOutput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SigningOutput.Unmarshal(m, b) +} +func (m *SigningOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SigningOutput.Marshal(b, m, deterministic) +} +func (m *SigningOutput) XXX_Merge(src proto.Message) { + xxx_messageInfo_SigningOutput.Merge(m, src) +} +func (m *SigningOutput) XXX_Size() int { + return xxx_messageInfo_SigningOutput.Size(m) +} +func (m *SigningOutput) XXX_DiscardUnknown() { + xxx_messageInfo_SigningOutput.DiscardUnknown(m) +} + +var xxx_messageInfo_SigningOutput proto.InternalMessageInfo + +func (m *SigningOutput) GetTransaction() *Transaction { + if m != nil { + return m.Transaction + } + return nil +} + +func (m *SigningOutput) GetEncoded() []byte { + if m != nil { + return m.Encoded + } + return nil +} + +func (m *SigningOutput) GetTransactionId() string { + if m != nil { + return m.TransactionId + } + return "" +} + +func (m *SigningOutput) GetError() string { + if m != nil { + return m.Error + } + return "" +} + +func init() { + proto.RegisterType((*Transaction)(nil), "TW.Bitcoin.Proto.Transaction") + proto.RegisterType((*TransactionInput)(nil), "TW.Bitcoin.Proto.TransactionInput") + proto.RegisterType((*OutPoint)(nil), "TW.Bitcoin.Proto.OutPoint") + proto.RegisterType((*TransactionOutput)(nil), "TW.Bitcoin.Proto.TransactionOutput") + proto.RegisterType((*UnspentTransaction)(nil), "TW.Bitcoin.Proto.UnspentTransaction") + proto.RegisterType((*SigningInput)(nil), "TW.Bitcoin.Proto.SigningInput") + proto.RegisterMapType((map[string][]byte)(nil), "TW.Bitcoin.Proto.SigningInput.ScriptsEntry") + proto.RegisterType((*TransactionPlan)(nil), "TW.Bitcoin.Proto.TransactionPlan") + proto.RegisterType((*SigningOutput)(nil), "TW.Bitcoin.Proto.SigningOutput") +} + +func init() { proto.RegisterFile("Bitcoin.proto", fileDescriptor_9fbb050c4cb9ab40) } + +var fileDescriptor_9fbb050c4cb9ab40 = []byte{ + // 715 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xdb, 0x6a, 0xdb, 0x4c, + 0x10, 0x46, 0x96, 0xe3, 0xc3, 0xf8, 0x10, 0x67, 0xf9, 0x0f, 0xfa, 0x13, 0xc2, 0xef, 0xaa, 0x29, + 0xb8, 0x14, 0x7c, 0x91, 0x52, 0x1a, 0x0c, 0xa5, 0x24, 0x90, 0x42, 0x28, 0x25, 0x66, 0xe3, 0xd2, + 0x4b, 0xb1, 0x96, 0xb6, 0xf1, 0x36, 0xca, 0xae, 0x2a, 0xad, 0x5c, 0xfb, 0xa2, 0x2f, 0xd0, 0x17, + 0xe9, 0x5b, 0xf4, 0x35, 0xfa, 0x32, 0xbd, 0x28, 0x7b, 0x90, 0x23, 0xdb, 0x34, 0xe4, 0xca, 0xfb, + 0x0d, 0x33, 0xdf, 0xec, 0xf7, 0xcd, 0xac, 0x05, 0x9d, 0x33, 0x26, 0x43, 0xc1, 0xf8, 0x30, 0x49, + 0x85, 0x14, 0xa8, 0x37, 0xf9, 0x30, 0x2c, 0x22, 0x63, 0x15, 0xf1, 0x7f, 0x38, 0xd0, 0x9a, 0xa4, + 0x84, 0x67, 0x24, 0x94, 0x4c, 0x70, 0xe4, 0x41, 0x7d, 0x4e, 0xd3, 0x8c, 0x09, 0xee, 0x39, 0x7d, + 0x67, 0xb0, 0x87, 0x0b, 0x88, 0xf6, 0xa1, 0x11, 0x8b, 0xf0, 0x66, 0xc2, 0x6e, 0xa9, 0x57, 0xe9, + 0x3b, 0x83, 0x0e, 0x5e, 0x61, 0x34, 0x82, 0x1a, 0xe3, 0x49, 0x2e, 0x33, 0xcf, 0xed, 0xbb, 0x83, + 0xd6, 0xb1, 0x3f, 0xdc, 0x6c, 0x34, 0x2c, 0x35, 0xb9, 0x50, 0xa9, 0xd8, 0x56, 0xa0, 0x57, 0x50, + 0x17, 0xb9, 0xd4, 0xc5, 0x55, 0x5d, 0xfc, 0xf8, 0xde, 0xe2, 0x4b, 0x9d, 0x8b, 0x8b, 0x1a, 0xff, + 0x9b, 0x03, 0xbd, 0x4d, 0x6e, 0x74, 0x06, 0xdd, 0x24, 0xa5, 0x73, 0x26, 0xf2, 0xcc, 0xe4, 0x6b, + 0x31, 0xad, 0xe3, 0xfd, 0x6d, 0xea, 0xcb, 0x5c, 0x8e, 0x05, 0xe3, 0x12, 0x6f, 0x54, 0x28, 0xbd, + 0x19, 0xfd, 0x9c, 0x53, 0x1e, 0xae, 0xf4, 0x16, 0x18, 0xfd, 0x03, 0xb5, 0x2c, 0x4c, 0x59, 0x22, + 0x3d, 0xb7, 0xef, 0x0c, 0xda, 0xd8, 0x22, 0x7f, 0x0c, 0x8d, 0x82, 0x0f, 0x21, 0xa8, 0xce, 0x48, + 0x36, 0xd3, 0x9d, 0xdb, 0x58, 0x9f, 0xd1, 0x5f, 0xb0, 0xc3, 0x78, 0x44, 0x17, 0x96, 0xd0, 0x80, + 0xb5, 0x4e, 0xee, 0x7a, 0x27, 0xff, 0x14, 0xf6, 0xb6, 0xc4, 0x2b, 0x9a, 0x39, 0x89, 0x73, 0xaa, + 0xb9, 0x5d, 0x6c, 0x40, 0xe9, 0x52, 0x95, 0xb5, 0x4b, 0x7d, 0x05, 0xf4, 0x9e, 0x67, 0x09, 0xe5, + 0xb2, 0x3c, 0xe8, 0x97, 0xd0, 0x14, 0xb9, 0x0c, 0x12, 0x75, 0xd7, 0x07, 0xb8, 0xd3, 0x10, 0x85, + 0xae, 0x3f, 0xb4, 0x51, 0x71, 0x72, 0x2b, 0x72, 0x6e, 0x3c, 0x71, 0xb1, 0x45, 0xfe, 0x2f, 0x17, + 0xda, 0x57, 0xec, 0x9a, 0x33, 0x7e, 0x6d, 0x86, 0x73, 0x00, 0x4d, 0x65, 0x46, 0x20, 0x97, 0x89, + 0x51, 0xd0, 0xc1, 0x0d, 0x15, 0x98, 0x2c, 0x13, 0x5a, 0x62, 0xa9, 0x94, 0x59, 0xd0, 0x7f, 0xd0, + 0x98, 0x2e, 0x25, 0x0d, 0x3e, 0x52, 0x6a, 0xf9, 0xeb, 0x0a, 0xbf, 0xa1, 0x14, 0x1d, 0x02, 0x48, + 0x11, 0x90, 0x28, 0x4a, 0x69, 0xa6, 0x76, 0xc8, 0x19, 0x34, 0x71, 0x53, 0x8a, 0x53, 0x13, 0x40, + 0x4f, 0xa0, 0x1b, 0xce, 0x08, 0xbf, 0xa6, 0xab, 0x94, 0x1d, 0x9d, 0xd2, 0x31, 0xd1, 0x22, 0xed, + 0x7f, 0x68, 0x25, 0x29, 0x9b, 0x13, 0x49, 0x83, 0x1b, 0xba, 0xf4, 0xa0, 0xef, 0x0e, 0xda, 0x18, + 0x6c, 0xe8, 0x2d, 0x5d, 0xa2, 0x73, 0xa8, 0x1b, 0xa5, 0x99, 0xd7, 0xd2, 0x7b, 0xfa, 0x6c, 0xdb, + 0xae, 0xb2, 0xce, 0xe1, 0x95, 0xc9, 0x3e, 0xe7, 0x32, 0x5d, 0xe2, 0xa2, 0x16, 0x9d, 0x40, 0x35, + 0x97, 0x0b, 0xe1, 0xb5, 0x35, 0xc7, 0xd1, 0x36, 0xc7, 0xf6, 0xac, 0xb0, 0xae, 0x40, 0x47, 0xd0, + 0xcd, 0x33, 0x1a, 0xdc, 0x92, 0x45, 0x60, 0x2d, 0xea, 0xf4, 0x9d, 0x41, 0x03, 0xb7, 0xf3, 0x8c, + 0xbe, 0x23, 0x8b, 0x53, 0x63, 0xd4, 0x01, 0x34, 0x15, 0x99, 0x71, 0xb7, 0x6b, 0xdc, 0x55, 0x01, + 0xed, 0xee, 0x0b, 0xa8, 0x26, 0x31, 0xe1, 0xde, 0xae, 0x9e, 0xf7, 0xa3, 0x7b, 0x1f, 0xda, 0x38, + 0x26, 0x1c, 0xeb, 0xf4, 0xfd, 0x11, 0xb4, 0xcb, 0x62, 0x50, 0x0f, 0x5c, 0xe5, 0x91, 0xa3, 0x7d, + 0x54, 0xc7, 0xbb, 0x8d, 0x34, 0x3b, 0x61, 0xc0, 0xa8, 0x72, 0xe2, 0xf8, 0x3f, 0x1d, 0xd8, 0xdd, + 0x60, 0x2d, 0x0d, 0xd9, 0x59, 0x1b, 0xf2, 0x53, 0xe8, 0x91, 0x39, 0x61, 0x31, 0x99, 0xc6, 0x34, + 0x58, 0x5b, 0x83, 0xdd, 0x55, 0xdc, 0xca, 0xec, 0x81, 0x7b, 0xb7, 0x0a, 0xea, 0xa8, 0x48, 0xcd, + 0x44, 0xf5, 0x0a, 0xb8, 0xd8, 0x22, 0x34, 0x82, 0x1d, 0x65, 0x9f, 0x1a, 0xfb, 0xc3, 0x1d, 0x37, + 0x25, 0xca, 0xcc, 0x69, 0x4a, 0x78, 0x38, 0x0b, 0x58, 0xe4, 0xd5, 0xb4, 0xb4, 0x86, 0x09, 0x5c, + 0x44, 0xfe, 0x77, 0x07, 0x3a, 0x76, 0xe0, 0xf6, 0x5d, 0xbe, 0x86, 0x96, 0xbc, 0x23, 0xb1, 0xaf, + 0xea, 0xf0, 0x5e, 0x97, 0x71, 0xb9, 0x42, 0xfd, 0xfb, 0x52, 0x1e, 0x8a, 0x88, 0x46, 0xd6, 0xc8, + 0x02, 0xaa, 0x2d, 0x2e, 0x25, 0xaa, 0xeb, 0xb8, 0x66, 0x8b, 0x4b, 0xd1, 0x8b, 0x48, 0xcd, 0x81, + 0xa6, 0xa9, 0x48, 0xed, 0x33, 0x30, 0xe0, 0xec, 0x5f, 0xf8, 0xfb, 0x0b, 0x89, 0x63, 0x2a, 0x87, + 0xa1, 0x48, 0xe9, 0xf0, 0x13, 0x67, 0xe6, 0x7b, 0x30, 0xad, 0xe9, 0x9f, 0xe7, 0xbf, 0x03, 0x00, + 0x00, 0xff, 0xff, 0x53, 0xd6, 0xbb, 0x06, 0x27, 0x06, 0x00, 0x00, +} diff --git a/samples/go/types/twdata.go b/samples/go/types/twdata.go new file mode 100644 index 00000000000..f1195643570 --- /dev/null +++ b/samples/go/types/twdata.go @@ -0,0 +1,31 @@ +package types + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #include +import "C" + +import ( + "unsafe" + "encoding/hex" +) + +// C.TWData -> Go byte[] +func TWDataGoBytes(d unsafe.Pointer) []byte { + cBytes := C.TWDataBytes(d) + cSize := C.TWDataSize(d) + return C.GoBytes(unsafe.Pointer(cBytes), C.int(cSize)) +} + +// Go byte[] -> C.TWData +func TWDataCreateWithGoBytes(d []byte) unsafe.Pointer { + cBytes := C.CBytes(d) + defer C.free(unsafe.Pointer(cBytes)) + data := C.TWDataCreateWithBytes((*C.uchar)(cBytes), C.ulong(len(d))) + return data +} + +// C.TWData -> Go hex string +func TWDataHexString(d unsafe.Pointer) string { + return hex.EncodeToString(TWDataGoBytes(d)) +} diff --git a/samples/go/types/twstring.go b/samples/go/types/twstring.go new file mode 100644 index 00000000000..de9fbb5eb2e --- /dev/null +++ b/samples/go/types/twstring.go @@ -0,0 +1,23 @@ +package types + +// #cgo CFLAGS: -I../../../include +// #cgo LDFLAGS: -L../../../build -L../../../build/trezor-crypto -lTrustWalletCore -lprotobuf -lTrezorCrypto -lc++ -lm +// #include +import "C" + +import ( + "unsafe" +) + +// C.TWString -> Go string +func TWStringGoString(s unsafe.Pointer) string { + return C.GoString(C.TWStringUTF8Bytes(s)) +} + +// Go string -> C.TWString +func TWStringCreateWithGoString(s string) unsafe.Pointer { + cStr := C.CString(s) + defer C.free(unsafe.Pointer(cStr)) + str := C.TWStringCreateWithUTF8Bytes(cStr) + return str +} From 53e43e42e129f3d36eb2307e88f165db23635200 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Thu, 20 Aug 2020 20:31:36 +0800 Subject: [PATCH 72/81] expose AnyAddress near public key (#1075) --- src/interface/TWAnyAddress.cpp | 8 ++++++++ swift/Sources/Extensions/AddressProtocol.swift | 2 ++ swift/Tests/Blockchains/DecredTests.swift | 2 +- swift/Tests/Blockchains/EOSTests.swift | 2 -- swift/Tests/Blockchains/KusamaTests.swift | 1 - swift/Tests/Blockchains/NEARTests.swift | 18 ++++++++++++++---- swift/Tests/Blockchains/PolkadotTests.swift | 4 +--- tests/interface/TWAnyAddressTests.cpp | 7 +++++++ 8 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index 5768adf92e6..ca60d7497b9 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -21,6 +21,7 @@ #include "../NEO/Address.h" #include "../Nano/Address.h" #include "../Elrond/Address.h" +#include "../NEAR/Address.h" #include "../Coin.h" #include "../HexCoding.h" @@ -197,6 +198,13 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { break; } + case TWCoinTypeNEAR: { + auto addr = NEAR::Address(string); + // remove last 4 bytes checksum + data = Data(addr.bytes.begin(), addr.bytes.end() - 4); + break; + } + default: break; } return TWDataCreateWithBytes(data.data(), data.size()); diff --git a/swift/Sources/Extensions/AddressProtocol.swift b/swift/Sources/Extensions/AddressProtocol.swift index 1c993e2df09..273e71cea0f 100644 --- a/swift/Sources/Extensions/AddressProtocol.swift +++ b/swift/Sources/Extensions/AddressProtocol.swift @@ -7,3 +7,5 @@ import Foundation public protocol Address: CustomStringConvertible {} + +extension AnyAddress: Equatable {} diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index e590ae3cfdc..77d8bc7d393 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -38,7 +38,7 @@ class DecredTests: XCTestCase { let txHash = Data(Data(hexString: "5015d14dcfd78998cfa13e0325798a74d95bbe75f167a49467303f70dde9bffd")!.reversed()) let utxoAddress = CoinType.decred.deriveAddress(privateKey: key) let script = BitcoinScript.lockScriptForAddress(address: utxoAddress, coin: .decred) - print(txHash.hexString) + let amount = Int64(10_000_000) let utxo = BitcoinUnspentTransaction.with { diff --git a/swift/Tests/Blockchains/EOSTests.swift b/swift/Tests/Blockchains/EOSTests.swift index 259c07e0e7b..2eca9f62e4e 100644 --- a/swift/Tests/Blockchains/EOSTests.swift +++ b/swift/Tests/Blockchains/EOSTests.swift @@ -55,8 +55,6 @@ class EOSTests: XCTestCase { func testSigning() throws { let ouptut: EOSSigningOutput = AnySigner.sign(input: signingInput, coin: .eos) - print(signingInput.privateKey.hexString) - print(try! signingInput.jsonString()) let expectedJSON = """ { diff --git a/swift/Tests/Blockchains/KusamaTests.swift b/swift/Tests/Blockchains/KusamaTests.swift index 414903a173f..a47d097e772 100644 --- a/swift/Tests/Blockchains/KusamaTests.swift +++ b/swift/Tests/Blockchains/KusamaTests.swift @@ -37,7 +37,6 @@ class KusamaTests: XCTestCase { // real key in 1p test let wallet = HDWallet.test let key = wallet.getKey(coin: .kusama, derivationPath: "m/44'/434'/0'") - print(key.data.hexString) let genesisHash = Data(hexString: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe")! let input = PolkadotSigningInput.with { diff --git a/swift/Tests/Blockchains/NEARTests.swift b/swift/Tests/Blockchains/NEARTests.swift index 696b4c31a94..699b8a5e8b8 100644 --- a/swift/Tests/Blockchains/NEARTests.swift +++ b/swift/Tests/Blockchains/NEARTests.swift @@ -7,7 +7,20 @@ import XCTest import TrustWalletCore -class NEARAddressTests: XCTestCase { +class NEARTests: XCTestCase { + + func testAddress() { + let hex = "3b83b07cab54824a59c3d3f2e203a7cd913b7fcdc4439595983e2402c2cf791d" + let string = "NEARTDDWrUMdoC2rA1eU6gNrSU2zyGKdR71TNucTvsQHyfAXjKcJb" + let pubKey = PublicKey(data: Data(hexString: hex)!, type: .ed25519)! + let address = AnyAddress(publicKey: pubKey, coin: .near) + let expected = AnyAddress(string: string, coin: .near)! + + XCTAssertEqual(address, expected) + XCTAssertEqual(address.description, string) + XCTAssertEqual(expected.data.hexString, hex) + } + func testAddressValidation() { let near = CoinType.near for address in [ @@ -18,9 +31,6 @@ class NEARAddressTests: XCTestCase { XCTAssertEqual(near.address(string: address)?.description, address) } } -} - -class NEARSignerTests: XCTestCase { func testSigningTransaction() { // swiftlint:disable:next line_length diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index fcb0137c32a..f64ef1595dd 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -37,10 +37,8 @@ class PolkadotTests: XCTestCase { // real key in 1p test let wallet = HDWallet.test let key = wallet.getKey(coin: .polkadot, derivationPath: "m/44'/354'/0'") - print(key.data.hexString) - let address = CoinType.polkadot.deriveAddress(privateKey: key) - print(address) + let genesisHash = Data(hexString: "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")! let input = PolkadotSigningInput.with { $0.genesisHash = genesisHash diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index dbc77477d5d..ab90a46891c 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -126,4 +126,11 @@ TEST(AnyAddress, Data) { auto pubkey = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(pubkey, "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"); } + // near + { + auto string = STRING("NEARTDDWrUMdoC2rA1eU6gNrSU2zyGKdR71TNucTvsQHyfAXjKcJb"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeNEAR)); + auto pubkey = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(pubkey, "3b83b07cab54824a59c3d3f2e203a7cd913b7fcdc4439595983e2402c2cf791d"); + } } From 27f52cd198da10972a734478790bde94b1afb5b5 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Sat, 22 Aug 2020 14:42:44 +0800 Subject: [PATCH 73/81] near coin define updates (#1076) --- coins.json | 4 ++-- tests/NEAR/TWCoinTypeTests.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coins.json b/coins.json index 7f73ae1b4f2..9593f90eb1b 100644 --- a/coins.json +++ b/coins.json @@ -818,13 +818,13 @@ "name": "NEAR", "coinId": 397, "symbol": "NEAR", - "decimals": 18, + "decimals": 24, "blockchain": "NEAR", "derivationPath": "m/44'/397'/0'", "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { - "url": "https://explorer.nearprotocol.com", + "url": "https://explorer.near.org", "txPath": "/transactions/", "accountPath": "/accounts/" }, diff --git a/tests/NEAR/TWCoinTypeTests.cpp b/tests/NEAR/TWCoinTypeTests.cpp index 2b996be3195..cb8d63d85fa 100644 --- a/tests/NEAR/TWCoinTypeTests.cpp +++ b/tests/NEAR/TWCoinTypeTests.cpp @@ -15,20 +15,20 @@ TEST(TWNEARCoinType, TWCoinType) { auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNEAR)); - auto txId = TWStringCreateWithUTF8Bytes("t123"); + auto txId = TWStringCreateWithUTF8Bytes("FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL"); auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNEAR, txId)); - auto accId = TWStringCreateWithUTF8Bytes("a12"); + auto accId = TWStringCreateWithUTF8Bytes("test-trust.vlad.near"); auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNEAR, accId)); auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNEAR)); auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNEAR)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEAR), 18); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEAR), 24); ASSERT_EQ(TWBlockchainNEAR, TWCoinTypeBlockchain(TWCoinTypeNEAR)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNEAR)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNEAR)); assertStringsEqual(symbol, "NEAR"); - assertStringsEqual(txUrl, "https://explorer.nearprotocol.com/transactions/t123"); - assertStringsEqual(accUrl, "https://explorer.nearprotocol.com/accounts/a12"); + assertStringsEqual(txUrl, "https://explorer.near.org/transactions/FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL"); + assertStringsEqual(accUrl, "https://explorer.near.org/accounts/test-trust.vlad.near"); assertStringsEqual(id, "near"); assertStringsEqual(name, "NEAR"); } From 158bbcaf766fa7941287dcc4451c159ddb01b78c Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Mon, 24 Aug 2020 11:50:48 +0800 Subject: [PATCH 74/81] [Aion] remove hardcoded timestamp (#1078) * remove hardcoded timestamp * change type to unint64 --- src/Aion/Signer.cpp | 1 + src/Aion/Transaction.cpp | 3 +-- src/Aion/Transaction.h | 4 +++- src/proto/Aion.proto | 3 +++ swift/Tests/Blockchains/AionTests.swift | 1 + tests/Aion/SignerTests.cpp | 4 ++-- tests/Aion/TWAnySignerTests.cpp | 1 + tests/Aion/TransactionTests.cpp | 4 ++-- 8 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index d01874a0ad6..a4a3a4adf14 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -23,6 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { /* gasLimit: */ static_cast(load(input.gas_limit())), /* to: */ Address(input.to_address()), /* amount: */ static_cast(load(input.amount())), + /* timestamp */ static_cast(input.timestamp()), /* payload: */ Data(input.payload().begin(), input.payload().end())); Signer::sign(key, transaction); diff --git a/src/Aion/Transaction.cpp b/src/Aion/Transaction.cpp index 19f2bd9c4c0..017dbe96896 100644 --- a/src/Aion/Transaction.cpp +++ b/src/Aion/Transaction.cpp @@ -18,8 +18,7 @@ Data Transaction::encode() const noexcept { append(encoded, Ethereum::RLP::encode(to.bytes)); append(encoded, Ethereum::RLP::encode(amount)); append(encoded, Ethereum::RLP::encode(payload)); - append(encoded, - Ethereum::RLP::encode(uint128_t(155157377101))); // Huge timestamp + append(encoded, Ethereum::RLP::encode(timestamp)); append(encoded, RLP::encodeLong(gasLimit)); append(encoded, RLP::encodeLong(gasPrice)); append(encoded, RLP::encodeLong(uint128_t(1))); // Aion transaction type diff --git a/src/Aion/Transaction.h b/src/Aion/Transaction.h index c87e5bc0256..dc975dcaac9 100644 --- a/src/Aion/Transaction.h +++ b/src/Aion/Transaction.h @@ -22,18 +22,20 @@ class Transaction { uint128_t gasLimit; Address to; uint128_t amount; + uint128_t timestamp; std::vector payload; /// Transaction signature. std::vector signature; Transaction(uint128_t nonce, uint128_t gasPrice, uint128_t gasLimit, Address to, - uint128_t amount, const Data& payload) + uint128_t amount, uint128_t timestamp, const Data& payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) , gasLimit(std::move(gasLimit)) , to(std::move(to)) , amount(std::move(amount)) + , timestamp(std::move(timestamp)) , payload(std::move(payload)) {} public: diff --git a/src/proto/Aion.proto b/src/proto/Aion.proto index 630d1709d30..771e6774171 100644 --- a/src/proto/Aion.proto +++ b/src/proto/Aion.proto @@ -25,6 +25,9 @@ message SigningInput { // Private key. bytes private_key = 7; + + // Timestamp + uint64 timestamp = 8; } // Transaction signing output. diff --git a/swift/Tests/Blockchains/AionTests.swift b/swift/Tests/Blockchains/AionTests.swift index 7b7adef39a9..529395697cb 100644 --- a/swift/Tests/Blockchains/AionTests.swift +++ b/swift/Tests/Blockchains/AionTests.swift @@ -23,6 +23,7 @@ class AionTests: XCTestCase { $0.gasLimit = Data(hexString: "5208")! $0.toAddress = "0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e" $0.amount = Data(hexString: "2710")! + $0.timestamp = 155157377101 $0.privateKey = Data(hexString: "db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")! } diff --git a/tests/Aion/SignerTests.cpp b/tests/Aion/SignerTests.cpp index 04f38e8e39b..b8a7970f248 100644 --- a/tests/Aion/SignerTests.cpp +++ b/tests/Aion/SignerTests.cpp @@ -15,7 +15,7 @@ using namespace TW::Aion; TEST(AionSigner, Sign) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, {}); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); Signer::sign(privateKey, transaction); @@ -28,7 +28,7 @@ TEST(AionSigner, Sign) { TEST(AionSigner, SignWithData) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, parse_hex("41494f4e0000")); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, parse_hex("41494f4e0000")); auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); Signer::sign(privateKey, transaction); diff --git a/tests/Aion/TWAnySignerTests.cpp b/tests/Aion/TWAnySignerTests.cpp index 5abc8d729cd..1f30f761d03 100644 --- a/tests/Aion/TWAnySignerTests.cpp +++ b/tests/Aion/TWAnySignerTests.cpp @@ -28,6 +28,7 @@ TEST(TWAnySignerAion, Sign) { input.set_gas_limit(gasLimit.data(), gasLimit.size()); auto nonce = store(uint256_t(9)); input.set_nonce(nonce.data(), nonce.size()); + input.set_timestamp(155157377101); input.set_private_key(privateKey.data(), privateKey.size()); Proto::SigningOutput output; diff --git a/tests/Aion/TransactionTests.cpp b/tests/Aion/TransactionTests.cpp index f35815c06d8..ad6b8546c2b 100644 --- a/tests/Aion/TransactionTests.cpp +++ b/tests/Aion/TransactionTests.cpp @@ -15,13 +15,13 @@ using namespace TW::Aion; TEST(AionTransaction, Encode) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, {}); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); ASSERT_EQ(hex(transaction.encode()), "f83909a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001"); } TEST(AionTransaction, EncodeWithSignature) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, {}); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); transaction.signature = parse_hex("a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); ASSERT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); } From bcdea5a90d8c336989bb694b6aed7a7a7848e8df Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Tue, 25 Aug 2020 11:16:43 +0200 Subject: [PATCH 75/81] [WIP] Android CI build speedup (#1072) * Android build: limit debug build to x86 abi, to speed up build. * Limit platforms used in :trustwallet build.gradle as well. * Try with ndk.apiFilters only, no apk split. Co-authored-by: Catenocrypt --- android/app/build.gradle | 7 +++++++ android/trustwalletcore/build.gradle | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/android/app/build.gradle b/android/app/build.gradle index 644f816b115..4094afec59a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -18,6 +18,13 @@ android { release { minifyEnabled false } + debug { + minifyEnabled false + // limit platforms built for testing + ndk { + abiFilters 'x86' + } + } } } diff --git a/android/trustwalletcore/build.gradle b/android/trustwalletcore/build.gradle index 6a24810e5e7..ffd218d3aa3 100644 --- a/android/trustwalletcore/build.gradle +++ b/android/trustwalletcore/build.gradle @@ -21,6 +21,13 @@ android { release { minifyEnabled false } + debug { + minifyEnabled false + // limit platforms built for testing + ndk { + abiFilters 'x86' + } + } } sourceSets { From 3e30661d4c6dd4f6b8b0a2b7e30d13609d15a048 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Fri, 28 Aug 2020 17:14:19 +0800 Subject: [PATCH 76/81] Update Binance Smart Chain name and explorer (#1083) * update smart chain name and explorer * use displayName --- coins.json | 5 +++-- docs/coins.md | 2 +- tests/BinanceSmartChain/TWCoinTypeTests.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/coins.json b/coins.json index 9593f90eb1b..d6843379d0c 100644 --- a/coins.json +++ b/coins.json @@ -1467,8 +1467,9 @@ } }, { - "id": "binance-smart", + "id": "bsc", "name": "Binance SmartChain", + "displayName": "BSC", "coinId": 10000714, "slip44": 714, "symbol": "BNB", @@ -1478,7 +1479,7 @@ "curve": "secp256k1", "publicKeyType": "secp256k1Extended", "explorer": { - "url": "https://explorer.binance.org/smart-testnet", + "url": "https://bscscan.com", "txPath": "/tx/", "accountPath": "/address/", "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", diff --git a/docs/coins.md b/docs/coins.md index 29f62adc27d..aeb775b0e01 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -64,4 +64,4 @@ This list is generated from [./coins.json](../coins.json) | 19167 | Zelcash | ZEL | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | -| 10000714 | Binance SmartChain | BNB | | | +| 10000714 | Binance SmartChain | BNB | | | diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/BinanceSmartChain/TWCoinTypeTests.cpp index 0a2c477fc51..40573009343 100644 --- a/tests/BinanceSmartChain/TWCoinTypeTests.cpp +++ b/tests/BinanceSmartChain/TWCoinTypeTests.cpp @@ -27,8 +27,8 @@ TEST(TWBinanceSmartChainCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBinanceSmartChain)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBinanceSmartChain)); assertStringsEqual(symbol, "BNB"); - assertStringsEqual(txUrl, "https://explorer.binance.org/smart-testnet/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); - assertStringsEqual(accUrl, "https://explorer.binance.org/smart-testnet/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); - assertStringsEqual(id, "binance-smart"); - assertStringsEqual(name, "Binance SmartChain"); + assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); + assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); + assertStringsEqual(id, "bsc"); + assertStringsEqual(name, "BSC"); } From ae3d541406770b0ecb0c6f8150d1e7c1628b77a4 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Mon, 31 Aug 2020 18:02:45 +0800 Subject: [PATCH 77/81] Update mainnet chain id (#1084) --- include/TrustWalletCore/TWEthereumChainID.h | 2 +- tests/BinanceSmartChain/SignerTests.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/TrustWalletCore/TWEthereumChainID.h b/include/TrustWalletCore/TWEthereumChainID.h index 2b9c925b274..cc6a8dad2d1 100644 --- a/include/TrustWalletCore/TWEthereumChainID.h +++ b/include/TrustWalletCore/TWEthereumChainID.h @@ -21,7 +21,7 @@ enum TWEthereumChainID { TWEthereumChainIDVeChain = 74, TWEthereumChainIDThunderToken = 108, TWEthereumChainIDTomoChain = 88, - TWEthereumChainIDBinanceSmartChain = 97, + TWEthereumChainIDBinanceSmartChain = 56, }; TW_EXTERN_C_END diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp index 106f4e146fb..0d44e6417ae 100644 --- a/tests/BinanceSmartChain/SignerTests.cpp +++ b/tests/BinanceSmartChain/SignerTests.cpp @@ -44,7 +44,7 @@ TEST(BinanceSmartChain, SignNativeTransfer) { // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - auto signer = SignerExposed(TWEthereumChainIDBinanceSmartChain); + auto signer = SignerExposed(97); signer.sign(privateKey, transaction); auto encoded = RLP::encode(transaction); @@ -62,7 +62,7 @@ TEST(BinanceSmartChain, SignTokenTransfer) { EXPECT_EQ(hex(payloadFunction), "a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc10000"); auto input = Proto::SigningInput(); - auto chainId = store(uint256_t(TWEthereumChainIDBinanceSmartChain)); + auto chainId = store(uint256_t(97)); auto nonce = store(uint256_t(30)); auto gasPrice = store(uint256_t(20000000000)); auto gasLimit = store(uint256_t(1000000)); From 6fb03174761b23ad6b4dde58e6319ac72286d65b Mon Sep 17 00:00:00 2001 From: Philip Arthur Moore Date: Mon, 31 Aug 2020 23:10:34 +0700 Subject: [PATCH 78/81] Frontier Wallet is now Frontier (#1085) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6b5abe78c3..bdc9d0bf701 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Projects using Trust Wallet Core. Add yours too! | [IFWallet](https://www.ifwallet.com/) | [crypto.com](https://crypto.com) | [Alice](https://www.alicedapp.com/) -| [FrontierWallet](https://frontierwallet.com/) +| [Frontier](https://frontier.xyz/) # Contributing From 61c55bfa7f49ec9c916d54aad6b4cd0c32051613 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:43:02 +0800 Subject: [PATCH 79/81] Update bsc display name again (#1086) --- coins.json | 2 +- tests/BinanceSmartChain/TWCoinTypeTests.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coins.json b/coins.json index d6843379d0c..8feb4d1cb18 100644 --- a/coins.json +++ b/coins.json @@ -1469,7 +1469,7 @@ { "id": "bsc", "name": "Binance SmartChain", - "displayName": "BSC", + "displayName": "Smart Chain", "coinId": 10000714, "slip44": 714, "symbol": "BNB", diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/BinanceSmartChain/TWCoinTypeTests.cpp index 40573009343..71dde9b9ba5 100644 --- a/tests/BinanceSmartChain/TWCoinTypeTests.cpp +++ b/tests/BinanceSmartChain/TWCoinTypeTests.cpp @@ -30,5 +30,5 @@ TEST(TWBinanceSmartChainCoinType, TWCoinType) { assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); assertStringsEqual(id, "bsc"); - assertStringsEqual(name, "BSC"); + assertStringsEqual(name, "Smart Chain"); } From a00c16b4f52c1b10a7362749c8f0a9fc7177432a Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Fri, 4 Sep 2020 10:54:31 +0800 Subject: [PATCH 80/81] Set typescript package release tag (#1090) * separate typescript package ci and release * add slip44 to CoinType * generate all proto models * LATEST_TAG might be empty * remove displayName for bsc --- .github/workflows/ts-ci.yml | 47 ------------ .github/workflows/ts-release.yml | 73 +++++++++++++++++++ .../blockchains/CoinAddressDerivationTests.kt | 2 +- .../TestBinanceSmartChainAddress.kt | 4 +- codegen/bin/coins | 2 +- coins.json | 3 +- docs/coins.md | 2 +- include/TrustWalletCore/TWCoinType.h | 2 +- src/Ethereum/Entry.h | 2 +- src/interface/TWAnyAddress.cpp | 2 +- .../Blockchains/BinanceSmartChainTests.swift | 4 +- swift/Tests/CoinAddressDerivationTests.swift | 2 +- swift/Tests/Keystore/KeyStoreTests.swift | 8 +- tests/BinanceSmartChain/SignerTests.cpp | 2 +- tests/BinanceSmartChain/TWAnyAddressTests.cpp | 4 +- tests/BinanceSmartChain/TWCoinTypeTests.cpp | 18 ++--- tests/Keystore/StoredKeyTests.cpp | 2 +- tests/interface/TWHDWalletTests.cpp | 2 +- typescript/codegen/bin/codegen | 9 ++- typescript/codegen/templates/core_types.ejs | 2 +- typescript/package.json | 6 +- typescript/tests/index.test.ts | 17 +++++ typescript/tools/set-tag-version | 9 +++ 23 files changed, 141 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/ts-release.yml create mode 100755 typescript/tools/set-tag-version diff --git a/.github/workflows/ts-ci.yml b/.github/workflows/ts-ci.yml index 92d56a486f4..acc17b1602e 100644 --- a/.github/workflows/ts-ci.yml +++ b/.github/workflows/ts-ci.yml @@ -3,12 +3,8 @@ name: Typescript CI on: push: branches: [ master ] - paths: - - typescript/** pull_request: branches: [ master ] - paths: - - typescript/** jobs: build: @@ -16,9 +12,6 @@ jobs: strategy: matrix: node-version: [14.x] - outputs: - publish_npm: ${{ steps.version.outputs.publish_npm }} - publish_gpr: ${{ steps.version.outputs.publish_gpr }} steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} @@ -30,43 +23,3 @@ jobs: - name: Build and test run: yarn build && yarn test working-directory: typescript - - name: Check if needed to publish - id: version - run: | - echo "::set-output name=publish_npm::$(tools/check-npm-version)" - echo "::set-output name=publish_gpr::$(tools/check-gpr-version)" - working-directory: typescript - env: - TOKEN: ${{secrets.GITHUB_TOKEN}} - publish-npm: - needs: build - if: needs.build.outputs.publish_npm == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 14 - registry-url: https://registry.npmjs.org/ - - name: Publish - run: | - yarn install && yarn build && npm publish --access public - working-directory: typescript - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - publish-gpr: - needs: build - if: needs.build.outputs.publish_gpr == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 14 - registry-url: https://npm.pkg.github.com/ - - name: Publish - run: | - yarn install && yarn build && npm publish --access public - working-directory: typescript - env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/ts-release.yml b/.github/workflows/ts-release.yml new file mode 100644 index 00000000000..7b5af2706c6 --- /dev/null +++ b/.github/workflows/ts-release.yml @@ -0,0 +1,73 @@ +name: Typescript Release + +on: + push: + tags: + - '*' + pull_request: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + outputs: + publish_npm: ${{ steps.version.outputs.publish_npm }} + publish_gpr: ${{ steps.version.outputs.publish_gpr }} + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install + working-directory: typescript + - name: Build and test + run: yarn build && yarn test + working-directory: typescript + - name: Check if needed to publish + id: version + run: | + tools/set-tag-version + echo "::set-output name=publish_npm::$(tools/check-npm-version)" + echo "::set-output name=publish_gpr::$(tools/check-gpr-version)" + working-directory: typescript + env: + TOKEN: ${{secrets.GITHUB_TOKEN}} + publish-npm: + needs: build + if: needs.build.outputs.publish_npm == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://registry.npmjs.org/ + - name: Publish + run: | + tools/set-tag-version + yarn install && yarn build && npm publish --access public + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-gpr: + needs: build + if: needs.build.outputs.publish_gpr == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://npm.pkg.github.com/ + - name: Publish + run: | + tools/set-tag-version + yarn install && yarn build && npm publish --access public + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 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 69c1eb45923..5c69af26d8e 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 @@ -89,6 +89,6 @@ class CoinAddressDerivationTests { FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) BANDCHAIN -> assertEquals("band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r", address) - BINANCESMARTCHAIN -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) + SMARTCHAIN -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt index 80db80b893e..35c0f9e9abd 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt @@ -23,8 +23,8 @@ class TestBinanceSmartChainAddress { val key = PrivateKey("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06".toHexByteArray()) val pubkey = key.getPublicKeySecp256k1(false) - val address = AnyAddress(pubkey, CoinType.BINANCESMARTCHAIN) - val expected = AnyAddress("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", CoinType.BINANCESMARTCHAIN) + val address = AnyAddress(pubkey, CoinType.SMARTCHAIN) + val expected = AnyAddress("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", CoinType.SMARTCHAIN) assertEquals(address.description(), expected.description()) } diff --git a/codegen/bin/coins b/codegen/bin/coins index be1528d0e21..06fe37f07fb 100755 --- a/codegen/bin/coins +++ b/codegen/bin/coins @@ -8,7 +8,7 @@ require 'json' def self.format_name(n) formatted = n formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') + formatted = formatted.gsub(/\s/, '') formatted end diff --git a/coins.json b/coins.json index 8feb4d1cb18..4bac58eec3b 100644 --- a/coins.json +++ b/coins.json @@ -1468,8 +1468,7 @@ }, { "id": "bsc", - "name": "Binance SmartChain", - "displayName": "Smart Chain", + "name": "Smart Chain", "coinId": 10000714, "slip44": 714, "symbol": "BNB", diff --git a/docs/coins.md b/docs/coins.md index aeb775b0e01..80b6fb5653c 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -64,4 +64,4 @@ This list is generated from [./coins.json](../coins.json) | 19167 | Zelcash | ZEL | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | -| 10000714 | Binance SmartChain | BNB | | | +| 10000714 | Smart Chain | BNB | | | diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 44aaaaaa122..2204998326b 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -82,7 +82,7 @@ enum TWCoinType { TWCoinTypeFilecoin = 461, TWCoinTypeElrond = 508, TWCoinTypeBandChain = 494, - TWCoinTypeBinanceSmartChain = 10000714, + TWCoinTypeSmartChain = 10000714, }; /// Returns the blockchain for a coin type. diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index b7fe34a5da8..e89c996de1a 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -23,7 +23,7 @@ class Entry: public CoinEntry { TWCoinTypePOANetwork, TWCoinTypeThunderToken, TWCoinTypeTomoChain, - TWCoinTypeBinanceSmartChain, + TWCoinTypeSmartChain, }; } virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index ca60d7497b9..2d5a0815b59 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -144,7 +144,7 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { case TWCoinTypeTheta: case TWCoinTypeWanchain: case TWCoinTypeAion: - case TWCoinTypeBinanceSmartChain: + case TWCoinTypeSmartChain: data = parse_hex(string); break; diff --git a/swift/Tests/Blockchains/BinanceSmartChainTests.swift b/swift/Tests/Blockchains/BinanceSmartChainTests.swift index a4aaa711e77..a9eed0df2b8 100644 --- a/swift/Tests/Blockchains/BinanceSmartChainTests.swift +++ b/swift/Tests/Blockchains/BinanceSmartChainTests.swift @@ -12,8 +12,8 @@ class BinanceSmartChainTests: XCTestCase { func testAddress() { let key = PrivateKey(data: Data(hexString: "727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06")!)! let pubkey = key.getPublicKeySecp256k1(compressed: false) - let address = AnyAddress(publicKey: pubkey, coin: .binanceSmartChain) - let expected = AnyAddress(string: "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coin: .binanceSmartChain)! + let address = AnyAddress(publicKey: pubkey, coin: .smartChain) + let expected = AnyAddress(string: "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coin: .smartChain)! XCTAssertEqual(address.description, expected.description) } diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index d748db05880..dd1cf2f9f45 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -199,7 +199,7 @@ class CoinAddressDerivationTests: XCTestCase { case .elrond: let expectedResult = "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .binanceSmartChain: + case .smartChain: let expectedResult = "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 99222984433..5754a66a63a 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -81,7 +81,7 @@ class KeyStoreTests: XCTestCase { } func testCreateHDWallet() throws { - let coins = [CoinType.ethereum, .binance, .binanceSmartChain] + let coins = [CoinType.ethereum, .binance, .smartChain] let keyStore = try KeyStore(keyDirectory: keyDirectory) let newWallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) @@ -89,7 +89,7 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(keyStore.wallets.count, 5) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .ethereum)) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binance)) - XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binanceSmartChain)) + XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .smartChain)) } func testUpdateKey() throws { @@ -255,11 +255,11 @@ class KeyStoreTests: XCTestCase { let password = "e28ddf66cec05c1fc09939a00628b230459202b2493fccac288038ef37815723" let keyStore = try KeyStore(keyDirectory: keyDirectory) - _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.binanceSmartChain], password: password) + _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.smartChain], password: password) let account = keyStore.bnbWallet.accounts[3] XCTAssertEqual(keyStore.bnbWallet.accounts.count, 4) - XCTAssertEqual(account.coin, CoinType.binanceSmartChain) + XCTAssertEqual(account.coin, CoinType.smartChain) XCTAssertEqual(account.address, "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC") let saved = try String(contentsOf: keyStore.bnbWallet.keyURL) diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp index 0d44e6417ae..97e19fc6685 100644 --- a/tests/BinanceSmartChain/SignerTests.cpp +++ b/tests/BinanceSmartChain/SignerTests.cpp @@ -83,7 +83,7 @@ TEST(BinanceSmartChain, SignTokenTransfer) { const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeBinanceSmartChain); + ANY_SIGN(input, TWCoinTypeSmartChain); EXPECT_EQ(hex(output.encoded()), expected); } diff --git a/tests/BinanceSmartChain/TWAnyAddressTests.cpp b/tests/BinanceSmartChain/TWAnyAddressTests.cpp index 3cbf9fefebd..4940e520fab 100644 --- a/tests/BinanceSmartChain/TWAnyAddressTests.cpp +++ b/tests/BinanceSmartChain/TWAnyAddressTests.cpp @@ -18,8 +18,8 @@ TEST(TWBinanceSmartChain, Address) { auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false); auto string = "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"; - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeBinanceSmartChain)); - auto expected = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(string).get(), TWCoinTypeBinanceSmartChain)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey, TWCoinTypeSmartChain)); + auto expected = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(string).get(), TWCoinTypeSmartChain)); auto addressString = WRAPS(TWAnyAddressDescription(address.get())); auto expectedString = WRAPS(TWAnyAddressDescription(expected.get())); diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/BinanceSmartChain/TWCoinTypeTests.cpp index 71dde9b9ba5..2b814a189df 100644 --- a/tests/BinanceSmartChain/TWCoinTypeTests.cpp +++ b/tests/BinanceSmartChain/TWCoinTypeTests.cpp @@ -14,18 +14,18 @@ TEST(TWBinanceSmartChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBinanceSmartChain)); + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSmartChain)); auto txId = TWStringCreateWithUTF8Bytes("0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBinanceSmartChain, txId)); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartChain, txId)); auto accId = TWStringCreateWithUTF8Bytes("0x35552c16704d214347f29Fa77f77DA6d75d7C752"); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBinanceSmartChain, accId)); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBinanceSmartChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBinanceSmartChain)); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartChain, accId)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartChain)); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBinanceSmartChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeBinanceSmartChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBinanceSmartChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBinanceSmartChain)); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSmartChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartChain)); assertStringsEqual(symbol, "BNB"); assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp index 2f33587300d..18b4f3c47d8 100644 --- a/tests/Keystore/StoredKeyTests.cpp +++ b/tests/Keystore/StoredKeyTests.cpp @@ -25,7 +25,7 @@ const auto password = TW::data(string(passwordString)); const auto mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; const TWCoinType coinTypeBc = TWCoinTypeBitcoin; const TWCoinType coinTypeBnb = TWCoinTypeBinance; -const TWCoinType coinTypeBsc = TWCoinTypeBinanceSmartChain; +const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; TEST(StoredKey, CreateWithMnemonic) { auto key = StoredKey::createWithMnemonic("name", password, mnemonic); diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 421e4fbfeeb..4450793d9be 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -258,7 +258,7 @@ TEST(HDWallet, DeriveElrond) { TEST(HDWallet, DeriveBinance) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinance)); - auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinanceSmartChain)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); auto keyData2 = WRAPD(TWPrivateKeyData(key2.get())); diff --git a/typescript/codegen/bin/codegen b/typescript/codegen/bin/codegen index 65b1df54e15..bddbe68a570 100755 --- a/typescript/codegen/bin/codegen +++ b/typescript/codegen/bin/codegen @@ -7,7 +7,9 @@ const prettier = require("prettier"); const main = async () => { const coins = require('../../../coins.json') coins.forEach((coin) => { - coin.slip44 = coin['derivationPath'].split('/')[2].replace('\'', ''); + if (!coin['slip44']) { + coin.slip44 = Number(coin['derivationPath'].split('/')[2].replace('\'', '')); + } }) await generateCoinType(coins); }; @@ -38,6 +40,11 @@ const generateCoinType = async (coins) => { name: 'symbol', returnType: 'string', body: (coin) => `return '${coin.symbol}'` + }, + { + name: 'slip44', + returnType: 'number', + body: (coin) => `return ${coin.slip44}` } ]; diff --git a/typescript/codegen/templates/core_types.ejs b/typescript/codegen/templates/core_types.ejs index 4a4cd8a5cbe..96eb09c76ae 100644 --- a/typescript/codegen/templates/core_types.ejs +++ b/typescript/codegen/templates/core_types.ejs @@ -6,7 +6,7 @@ export enum CoinType { <% coins.forEach((coin) => { -%> - <%-coin.id%> = <%-coin.slip44%>, + <%-coin.id%> = <%-coin.coinId%>, <% }) %> } diff --git a/typescript/package.json b/typescript/package.json index 3cd8a2e6623..a04d833bfeb 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@trustwallet/wallet-core", - "version": "0.0.6", + "version": "2.2.9", "description": "wallet core types and protobuf messages", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -8,10 +8,10 @@ "test": "mocha -r ts-node/register tests/**/*.test.ts", "generate": "yarn codegen:coin && yarn codegen:js && yarn codegen:ts", "codegen:coin": "codegen/bin/codegen", - "codegen:js": "pbjs -t static-module ../src/proto/Ethereum.proto --no-delimited --force-long -o src/generated/core_proto.js", + "codegen:js": "pbjs -t static-module '../src/proto/*.proto' --no-delimited --force-long -o src/generated/core_proto.js", "codegen:ts": "pbts -o src/generated/core_proto.d.ts src/generated/core_proto.js", "clean": "rm -rf dist src/generated && mkdir -p dist/generated src/generated", - "build": "yarn clean && yarn generate && cp src/generated/core_proto.* dist/generated && tsc --skipLibCheck" + "build": "yarn clean && yarn generate && cp src/generated/core_proto.d.ts src/generated/core_proto.js dist/generated && tsc --skipLibCheck" }, "repository": { "type": "git", diff --git a/typescript/tests/index.test.ts b/typescript/tests/index.test.ts index 05a20b17d6b..5a56ba6d408 100644 --- a/typescript/tests/index.test.ts +++ b/typescript/tests/index.test.ts @@ -16,11 +16,23 @@ describe('Wallet Core types tests', () => { expect(coin).to.equal(60) expect(CoinType.id(coin)).to.equal('ethereum') expect(CoinType.name(coin)).to.equal('Ethereum') + expect(CoinType.slip44(coin)).to.equal(60) expect(CoinType.symbol(coin)).to.equal('ETH') expect(CoinType.decimals(coin)).to.equal(18) expect(CoinType.derivationPath(coin)).to.equal(`m/44'/60'/0'/0/0`) }) + it('test CoinType.bsc', () => { + const coin = CoinType.bsc; + expect(coin).to.equal(10000714) + expect(CoinType.id(coin)).to.equal('bsc') + expect(CoinType.name(coin)).to.equal('Smart Chain') + expect(CoinType.slip44(coin)).to.equal(714) + expect(CoinType.symbol(coin)).to.equal('BNB') + expect(CoinType.decimals(coin)).to.equal(18) + expect(CoinType.derivationPath(coin)).to.equal(`m/44'/714'/0'/0/0`) + }) + it('test Ethereum encoding SigningInput', () => { const input = TW.Ethereum.Proto.SigningInput.create({ toAddress: '0x3535353535353535353535353535353535353535', @@ -35,4 +47,9 @@ describe('Wallet Core types tests', () => { const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish() expect(Buffer.from(encoded).toString('hex')).to.equal("0a01011201091a0504a817c800220252082a2a30783335333533353335333533353335333533353335333533353335333533353335333533353335333532080de0b6b3a764000042204646464646464646464646464646464646464646464646464646464646464646") }) + + it('test Bitcoin / Bitcoin SigningInput', () => { + expect(TW.Bitcoin.Proto.SigningInput).not.null; + expect(TW.Binance.Proto.SigningInput).not.null; + }) }) diff --git a/typescript/tools/set-tag-version b/typescript/tools/set-tag-version new file mode 100755 index 00000000000..336c57f830e --- /dev/null +++ b/typescript/tools/set-tag-version @@ -0,0 +1,9 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PACKAGE_JSON=$DIR/../package.json +LATEST_TAG=`git describe --long --tags | cut -f 1 -d "-"` +if [[ -n $LATEST_TAG ]]; then + NEW_PACKAGE=$(jq --arg tag "$LATEST_TAG" '.version = $tag' $PACKAGE_JSON) + echo $NEW_PACKAGE | jq . > $PACKAGE_JSON +fi From 7a8d1c1b6d924f2b550154efdfbb1dc6ac961f8c Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Wed, 9 Sep 2020 14:18:32 +0800 Subject: [PATCH 81/81] Change smart chain derivation path (#1101) --- .../blockchains/CoinAddressDerivationTests.kt | 4 +-- .../TestBinanceSmartChainAddress.kt | 0 coins.json | 27 ++++++++++++++++++- docs/coins.md | 2 +- include/TrustWalletCore/TWCoinType.h | 3 ++- src/Ethereum/Entry.h | 1 + src/interface/TWAnyAddress.cpp | 1 + swift/Tests/CoinAddressDerivationTests.swift | 4 +-- swift/Tests/Keystore/KeyStoreTests.swift | 19 ++++++++++--- tests/BinanceSmartChain/TWCoinTypeTests.cpp | 12 ++++++++- tests/Keystore/StoredKeyTests.cpp | 16 ++++++++--- tests/interface/TWHDWalletTests.cpp | 7 ++++- typescript/tests/index.test.ts | 13 ++++++++- 13 files changed, 92 insertions(+), 17 deletions(-) rename android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/{binancesmartchain => smartchain}/TestBinanceSmartChainAddress.kt (100%) 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 5c69af26d8e..64bfcf48ed9 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 @@ -36,7 +36,7 @@ class CoinAddressDerivationTests { CALLISTO -> assertEquals("0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04", address) DASH -> assertEquals("XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT", address) DIGIBYTE -> assertEquals("dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu", address) - ETHEREUM -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + ETHEREUM, SMARTCHAIN -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address) GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address) GROESTLCOIN -> assertEquals("grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j", address) @@ -89,6 +89,6 @@ class CoinAddressDerivationTests { FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) BANDCHAIN -> assertEquals("band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r", address) - SMARTCHAIN -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) + SMARTCHAINLEGACY -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt similarity index 100% rename from android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binancesmartchain/TestBinanceSmartChainAddress.kt rename to android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt diff --git a/coins.json b/coins.json index 4bac58eec3b..2fddde6b0dd 100644 --- a/coins.json +++ b/coins.json @@ -1468,7 +1468,7 @@ }, { "id": "bsc", - "name": "Smart Chain", + "name": "Smart Chain Legacy", "coinId": 10000714, "slip44": 714, "symbol": "BNB", @@ -1490,5 +1490,30 @@ "clientPublic": "https://data-seed-prebsc-1-s1.binance.org:8545", "clientDocs": "https://github.com/ethereum/wiki/wiki/JSON-RPC" } + }, + { + "id": "smartchain", + "name": "Smart Chain", + "coinId": 20000714, + "slip44": 714, + "symbol": "BNB", + "decimals": 18, + "blockchain": "Ethereum", + "derivationPath": "m/44'/60'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://bscscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", + "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" + }, + "info": { + "url": "https://www.binance.org/en/smartChain", + "client": "https://github.com/binance-chain/bsc", + "clientPublic": "https://bsc-dataseed1.binance.org", + "clientDocs": "https://eth.wiki/json-rpc/API" + } } ] diff --git a/docs/coins.md b/docs/coins.md index 80b6fb5653c..5e3b3c4070b 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -64,4 +64,4 @@ This list is generated from [./coins.json](../coins.json) | 19167 | Zelcash | ZEL | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | -| 10000714 | Smart Chain | BNB | | | +| 20000714 | Smart Chain | BNB | | | diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 2204998326b..3c2237f194f 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -82,7 +82,8 @@ enum TWCoinType { TWCoinTypeFilecoin = 461, TWCoinTypeElrond = 508, TWCoinTypeBandChain = 494, - TWCoinTypeSmartChain = 10000714, + TWCoinTypeSmartChainLegacy = 10000714, + TWCoinTypeSmartChain = 20000714, }; /// Returns the blockchain for a coin type. diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index e89c996de1a..e852c119edf 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -23,6 +23,7 @@ class Entry: public CoinEntry { TWCoinTypePOANetwork, TWCoinTypeThunderToken, TWCoinTypeTomoChain, + TWCoinTypeSmartChainLegacy, TWCoinTypeSmartChain, }; } diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index 2d5a0815b59..92a7141c9f7 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -144,6 +144,7 @@ TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { case TWCoinTypeTheta: case TWCoinTypeWanchain: case TWCoinTypeAion: + case TWCoinTypeSmartChainLegacy: case TWCoinTypeSmartChain: data = parse_hex(string); break; diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index dd1cf2f9f45..6f60eceb4d1 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -49,7 +49,7 @@ class CoinAddressDerivationTests: XCTestCase { case .digiByte: let expectedResult = "dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .ethereum: + case .ethereum, .smartChain: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ethereumClassic: @@ -199,7 +199,7 @@ class CoinAddressDerivationTests: XCTestCase { case .elrond: let expectedResult = "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .smartChain: + case .smartChainLegacy: let expectedResult = "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 5754a66a63a..8f65151f9ab 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -229,6 +229,10 @@ class KeyStoreTests: XCTestCase { "address": "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC", "coin": 10000714, "derivationPath": "m/44'/714'/0'/0/0" + }, { + "address": "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea", + "coin": 20000714, + "derivationPath": "m/44'/60'/0'/0/0" }], "crypto": { "cipher": "aes-128-ctr", @@ -255,12 +259,19 @@ class KeyStoreTests: XCTestCase { let password = "e28ddf66cec05c1fc09939a00628b230459202b2493fccac288038ef37815723" let keyStore = try KeyStore(keyDirectory: keyDirectory) - _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.smartChain], password: password) + _ = try keyStore.addAccounts(wallet: keyStore.bnbWallet, coins: [.smartChainLegacy, .smartChain], password: password) + + XCTAssertEqual(keyStore.bnbWallet.accounts.count, 5) + + let accountLegacy = keyStore.bnbWallet.accounts[3] + + XCTAssertEqual(accountLegacy.coin, CoinType.smartChainLegacy) + XCTAssertEqual(accountLegacy.address, "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC") + + let account = keyStore.bnbWallet.accounts[4] - let account = keyStore.bnbWallet.accounts[3] - XCTAssertEqual(keyStore.bnbWallet.accounts.count, 4) XCTAssertEqual(account.coin, CoinType.smartChain) - XCTAssertEqual(account.address, "0x5dEc7A9299360aEb44c83B8F730F2BF5Dd1688bC") + XCTAssertEqual(account.address, "0x33F44330cc4253cCd4ce4224186DB9baCe2190ea") let saved = try String(contentsOf: keyStore.bnbWallet.keyURL) XCTAssertJSONEqual(saved, expected) diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/BinanceSmartChain/TWCoinTypeTests.cpp index 2b814a189df..8ce05e436d6 100644 --- a/tests/BinanceSmartChain/TWCoinTypeTests.cpp +++ b/tests/BinanceSmartChain/TWCoinTypeTests.cpp @@ -26,9 +26,19 @@ TEST(TWBinanceSmartChainCoinType, TWCoinType) { ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartChain)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartChain)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartChain)); + ASSERT_EQ(20000714, TWCoinTypeSmartChain); assertStringsEqual(symbol, "BNB"); assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); - assertStringsEqual(id, "bsc"); + assertStringsEqual(id, "smartchain"); assertStringsEqual(name, "Smart Chain"); } + +TEST(TWBinanceSmartChainLegacyCoinType, TWCoinType) { + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartChainLegacy)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartChainLegacy)); + + ASSERT_EQ(10000714, TWCoinTypeSmartChainLegacy); + assertStringsEqual(id, "bsc"); + assertStringsEqual(name, "Smart Chain Legacy"); +} diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp index 18b4f3c47d8..858eb492df1 100644 --- a/tests/Keystore/StoredKeyTests.cpp +++ b/tests/Keystore/StoredKeyTests.cpp @@ -26,6 +26,8 @@ const auto mnemonic = "team engine square letter hero song dizzy scrub tornado f const TWCoinType coinTypeBc = TWCoinTypeBitcoin; const TWCoinType coinTypeBnb = TWCoinTypeBinance; const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; +const TWCoinType coinTypeEth = TWCoinTypeEthereum; +const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; TEST(StoredKey, CreateWithMnemonic) { auto key = StoredKey::createWithMnemonic("name", password, mnemonic); @@ -142,19 +144,27 @@ TEST(StoredKey, AddRemoveAccount) { { const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); - key.addAccount("bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr", coinTypeBc, derivationPath, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + key.addAccount("bc1qaucw06s3agez8tyyk4zj9kt0q2934e3mcewdpf", coinTypeBc, derivationPath, "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); EXPECT_EQ(key.accounts.size(), 1); } { const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); - key.addAccount("bnb1devga6q804tx9fqrnx0vtu5r36kxgp9tmk4xkm", coinTypeBnb, derivationPath, ""); - key.addAccount("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coinTypeBsc, derivationPath, ""); + key.addAccount("bnb1utrnnjym7ustgw7pgyvtmnxay4qmt3ahh276nu", coinTypeBnb, derivationPath, ""); + key.addAccount("0x23b02dC8f67eD6cF8DCa47935791954286ffe7c9", coinTypeBsc, derivationPath, ""); EXPECT_EQ(key.accounts.size(), 3); } + { + const auto derivationPath = DerivationPath("m/60'/0'/0'/0/0"); + key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeEth, derivationPath, ""); + key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeBscLegacy, derivationPath, ""); + EXPECT_EQ(key.accounts.size(), 5); + } key.removeAccount(coinTypeBc); key.removeAccount(coinTypeBnb); key.removeAccount(coinTypeBsc); + key.removeAccount(coinTypeEth); + key.removeAccount(coinTypeBscLegacy); EXPECT_EQ(key.accounts.size(), 0); } diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 4450793d9be..d2dbc7bb0d9 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -140,13 +140,18 @@ TEST(HDWallet, DeriveAddressBitcoin) { TEST(HDWallet, DeriveEthereum) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeEthereum)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); + auto publicKey = TWPrivateKeyGetPublicKeySecp256k1(key.get(), false); + auto publicKey2 = TWPrivateKeyGetPublicKeySecp256k1(key2.get(), false); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey)); auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeEthereum, publicKey)); + auto address2 = WRAPS(TWCoinTypeDeriveAddressFromPublicKey(TWCoinTypeSmartChain, publicKey2)); assertHexEqual(publicKeyData, "0414acbe5a06c68210fcbb77763f9612e45a526990aeb69d692d705f276f558a5ae68268e9389bb099ed5ac84d8d6861110f63644f6e5b447e3f86b4bab5dee011"); assertStringsEqual(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); + assertStringsEqual(address2, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); } TEST(HDWallet, DeriveAddressEthereum) { @@ -258,7 +263,7 @@ TEST(HDWallet, DeriveElrond) { TEST(HDWallet, DeriveBinance) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinance)); - auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); + auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChainLegacy)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); auto keyData2 = WRAPD(TWPrivateKeyData(key2.get())); diff --git a/typescript/tests/index.test.ts b/typescript/tests/index.test.ts index 5a56ba6d408..58d33709d7a 100644 --- a/typescript/tests/index.test.ts +++ b/typescript/tests/index.test.ts @@ -26,13 +26,24 @@ describe('Wallet Core types tests', () => { const coin = CoinType.bsc; expect(coin).to.equal(10000714) expect(CoinType.id(coin)).to.equal('bsc') - expect(CoinType.name(coin)).to.equal('Smart Chain') + expect(CoinType.name(coin)).to.equal('Smart Chain Legacy') expect(CoinType.slip44(coin)).to.equal(714) expect(CoinType.symbol(coin)).to.equal('BNB') expect(CoinType.decimals(coin)).to.equal(18) expect(CoinType.derivationPath(coin)).to.equal(`m/44'/714'/0'/0/0`) }) + it('test CoinType.smartchain', () => { + const coin = CoinType.smartchain; + expect(coin).to.equal(20000714) + expect(CoinType.id(coin)).to.equal('smartchain') + expect(CoinType.name(coin)).to.equal('Smart Chain') + expect(CoinType.slip44(coin)).to.equal(714) + expect(CoinType.symbol(coin)).to.equal('BNB') + expect(CoinType.decimals(coin)).to.equal(18) + expect(CoinType.derivationPath(coin)).to.equal(`m/44'/60'/0'/0/0`) + }) + it('test Ethereum encoding SigningInput', () => { const input = TW.Ethereum.Proto.SigningInput.create({ toAddress: '0x3535353535353535353535353535353535353535',