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 d369f92cf09..493c4f31b96 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 @@ -78,5 +78,6 @@ class CoinAddressDerivationTests { ZELCASH -> assertEquals("t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf", address) ARK -> assertEquals("Ac49m5pu5YpMMNgEbSYeZUEpRMHcSK3DfV", address) MONETARYUNIT -> assertEquals("7W3QRu8FttKzmYtRbXNKopeHweAKWuun2q", address) + RAVENCOIN -> assertEquals("RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS", address) } } diff --git a/coins.json b/coins.json index 8199716c56a..cedb0b04fe2 100644 --- a/coins.json +++ b/coins.json @@ -634,5 +634,20 @@ "xpub": "xpub", "xprv": "xprv", "explorer": "https://explorer.monetaryunit.org/tx/" + }, + { + "id": "ravencoin", + "name": "Ravencoin", + "symbol": "RVN", + "decimals": 8, + "blockchain": "Bitcoin", + "derivationPath": "m/44'/175'/0'/0/0", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "xpub": "xpub", + "xprv": "xprv", + "explorer": "https://ravencoin.network/tx/" } ] diff --git a/docs/coins.md b/docs/coins.md index e226cd3a257..965a2e0e68f 100644 --- a/docs/coins.md +++ b/docs/coins.md @@ -50,4 +50,5 @@ | 31102 | Ethersocial | ESN | | https://ethersocial.org/ | 163 | Ellaism | ELLA | | https://ellaism.org/ | 111 | Ark | ARK | | http://ark.io -| 20 | DigiByte | DGB | | https://www.digibyte.io \ No newline at end of file +| 20 | DigiByte | DGB | | https://www.digibyte.io +| 175 | Ravencoin | RVN | | https://ravencoin.org \ No newline at end of file diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 671d1b26603..7435596e722 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -73,7 +73,7 @@ enum TWCoinType { TWCoinTypeZelcash = 19167, TWCoinTypeARK = 111, TWCoinTypeMonetaryUnit = 31, - + TWCoinTypeRavencoin = 175, }; /// Returns the blockchain for a coin type. diff --git a/include/TrustWalletCore/TWP2PKHPrefix.h b/include/TrustWalletCore/TWP2PKHPrefix.h index 5d42b92d1bf..18bd45fb109 100644 --- a/include/TrustWalletCore/TWP2PKHPrefix.h +++ b/include/TrustWalletCore/TWP2PKHPrefix.h @@ -26,6 +26,7 @@ enum TWP2PKHPrefix { TWP2PKHPrefixZcashT = 0xB8, TWP2PKHPrefixZcoin = 0x52, TWP2PKHPrefixMonetaryUnit = 0x10, + TWP2PKHPrefixRavencoin = 0x3c, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWP2SHPrefix.h b/include/TrustWalletCore/TWP2SHPrefix.h index 4ff38d45d24..344bffe4156 100644 --- a/include/TrustWalletCore/TWP2SHPrefix.h +++ b/include/TrustWalletCore/TWP2SHPrefix.h @@ -25,6 +25,7 @@ enum TWP2SHPrefix { TWP2SHPrefixZcoin = 0x07, TWP2SHPrefixS = 0x3F, // Lux and DigiByte TWP2SHPrefixMonetaryUnit = 0x4C, + TWP2SHPrefixRavencoin = 0x7a, }; // Do not export TWP2SHPrefixGroestlcoin because it the same to diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 38bd40fe762..a555efe2bc0 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -253,11 +253,13 @@ void Script::encode(std::vector& data) const { Script Script::buildForAddress(const std::string& string) { static const std::vector p2pkhPrefixes = {TWP2PKHPrefixBitcoin, TWP2PKHPrefixIocoin, TWP2PKHPrefixLitecoin, TWP2PKHPrefixDash, TWP2PKHPrefixZcoin, TWP2PKHPrefixViacoin, - TWP2PKHPrefixD, TWP2PKHPrefixQtum, TWP2PKHPrefixMonetaryUnit}; + TWP2PKHPrefixD, TWP2PKHPrefixQtum, TWP2PKHPrefixMonetaryUnit, + TWP2PKHPrefixRavencoin}; static const std::vector p2shPrefixes = {TWP2SHPrefixBitcoin, TWP2SHPrefixIocoin, TWP2SHPrefixLitecoin, TWP2SHPrefixDash, TWP2SHPrefixZcoin, TWP2SHPrefixViacoin, - TWP2SHPrefixDogecoin, TWP2SHPrefixS, TWP2SHPrefixMonetaryUnit}; + TWP2SHPrefixDogecoin, TWP2SHPrefixS, TWP2SHPrefixMonetaryUnit, + TWP2SHPrefixRavencoin}; if (Address::isValid(string)) { auto address = Address(string); auto p2pkh = std::find(p2pkhPrefixes.begin(), p2pkhPrefixes.end(), address.bytes[0]); diff --git a/src/Coin.cpp b/src/Coin.cpp index 1de00dc6972..329a3e30c60 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -177,6 +177,9 @@ bool TW::validateAddress(TWCoinType coin, const std::string& string) { case TWCoinTypeMonetaryUnit: return Bitcoin::Address::isValid(string, {{TWP2PKHPrefixMonetaryUnit}, {TWP2SHPrefixMonetaryUnit}}); + + case TWCoinTypeRavencoin: + return Bitcoin::Address::isValid(string, {{TWP2PKHPrefixRavencoin}, {TWP2SHPrefixRavencoin}}); } } @@ -306,6 +309,9 @@ std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) { case TWCoinTypeMonetaryUnit: return Bitcoin::Address(publicKey, TWP2PKHPrefixMonetaryUnit).string(); + + case TWCoinTypeRavencoin: + return Bitcoin::Address(publicKey, TWP2PKHPrefixRavencoin).string(); } } diff --git a/swift/Sources/Addresses/CoinType+Address.swift b/swift/Sources/Addresses/CoinType+Address.swift index dab5ac7fa60..eceb809e4bc 100644 --- a/swift/Sources/Addresses/CoinType+Address.swift +++ b/swift/Sources/Addresses/CoinType+Address.swift @@ -87,6 +87,8 @@ public extension CoinType { return SemuxAddress(string: string) case .ark: return ARKAddress(string: string) + case .ravencoin: + if let addr = BitcoinAddress(string: string), prefixSet.contains(addr.prefix) { return addr } } return .none } @@ -120,6 +122,8 @@ public extension CoinType { return Set([P2SHPrefix.viacoin.rawValue, P2PKHPrefix.viacoin.rawValue]) case .monetaryUnit: return Set([P2SHPrefix.monetaryUnit.rawValue, P2PKHPrefix.monetaryUnit.rawValue]) + case .ravencoin: + return Set([P2SHPrefix.ravencoin.rawValue, P2PKHPrefix.ravencoin.rawValue]) default: return Set() } diff --git a/swift/Tests/Addresses/BitcoinAddressTests.swift b/swift/Tests/Addresses/BitcoinAddressTests.swift index 1cac41ff2e1..b00345caa1e 100644 --- a/swift/Tests/Addresses/BitcoinAddressTests.swift +++ b/swift/Tests/Addresses/BitcoinAddressTests.swift @@ -110,4 +110,60 @@ class BitcoinAddressTests: XCTestCase { XCTAssertTrue(SegwitAddress.isValidString(string: addressString3), "'\(addressString3)' should be a valid DigiByte Bech32 address") } + + func testInvalidDigiByteAddress() { + let addressString = "DTPQ92zp96TwpG2pRuUB3oEA3kWGRZPGhX" + + XCTAssertNil(BitcoinAddress(string: addressString)?.prefix) + XCTAssertFalse(BitcoinAddress.isValidString(string: addressString), + "'\(addressString)' should be an invalid DigiByte address") + + let addressString2 = "SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVX" + + XCTAssertNil(BitcoinAddress(string: addressString2)?.prefix) + XCTAssertFalse(BitcoinAddress.isValidString(string: addressString2), + "'\(addressString2)' should be an invalid DigiByte address") + + let addressString3 = "xgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu" + + XCTAssertNil(SegwitAddress(string: addressString3)?.hrp) + XCTAssertFalse(SegwitAddress.isValidString(string: addressString3), + "'\(addressString3)' should be an invalid DigiByte Bech32 address") + } + + func testValidRavencoinAddress() { + let addressString = "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" + + XCTAssertEqual(P2PKHPrefix.ravencoin.rawValue, BitcoinAddress(string: addressString)?.prefix) + XCTAssertTrue(BitcoinAddress.isValidString(string: addressString), + "'\(addressString)' should be a valid Ravencoin address") + + let addressString2 = "rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1" + + XCTAssertEqual(P2SHPrefix.ravencoin.rawValue, BitcoinAddress(string: addressString2)?.prefix) + XCTAssertTrue(BitcoinAddress.isValidString(string: addressString2), + "'\(addressString2)' should be a valid Ravencoin address") + + // testnet address + let addressString3 = "mwJAu1BWcRSQhepZ71wiGoSwsD6hnB5B7G" + + XCTAssertTrue(BitcoinAddress.isValidString(string: addressString3), + "'\(addressString3)' should be a valid Ravencoin testnet address") + } + + func testInvalidRavencoinAddress() { + // bad address + let addressString = "XHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" + + XCTAssertNil(BitcoinAddress(string: addressString)?.prefix) + XCTAssertFalse(BitcoinAddress.isValidString(string: addressString), + "'\(addressString)' should be an invalid Ravencoin address") + + // testnet address + let addressString2 = "mwJAu1BWcRSQhepZ71wiGoSwsD6hnB5B7G" + + XCTAssertFalse(CoinType.ravencoin.validate(address: addressString2), + "'\(addressString2)' should be an invalid Ravencoin address") + + } } diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 14f525a5acc..a31c8014431 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -175,6 +175,9 @@ class CoinAddressDerivationTests: XCTestCase { case .monetaryUnit: let expectedResult = "7W3QRu8FttKzmYtRbXNKopeHweAKWuun2q" AssetCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ravencoin: + let expectedResult = "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" + AssetCoinDerivation(coin, expectedResult, derivedAddress, address) } } } diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 20cf9cba5c5..661178bc2d1 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -338,4 +338,13 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(pubkey.data.hexString, "039fdd3652495d01b6a363f8db8b3adce09f83ea5c43ff872ad0a39192340256b0") XCTAssertEqual(address.description, "bc1qearv5ezm3xfgy2t98denkzxwst4f35fvz608wa") } + + func testDeriveRavencoin() { + let ravencoin = CoinType.ravencoin + let wallet = HDWallet.test + let key = wallet.getKeyForCoin(coin: ravencoin) + let address = ravencoin.deriveAddress(privateKey: key) + + XCTAssertEqual("RHQmrg7nNFnRUwg2mH7GafhRY3ZaF6FB2x", address) + } } diff --git a/tests/CoinAddressDerivationTests.cpp b/tests/CoinAddressDerivationTests.cpp index 4f06a507c4d..128d9b8b8ea 100644 --- a/tests/CoinAddressDerivationTests.cpp +++ b/tests/CoinAddressDerivationTests.cpp @@ -65,6 +65,7 @@ TEST(Coin, DeriveAddress) { EXPECT_EQ(TW::deriveAddress(TWCoinTypeDogecoin, privateKey), "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); EXPECT_EQ(TW::deriveAddress(TWCoinTypeSemux, privateKey), "0x1574f7f969f41e030a75677af25bd9373b9c87f1"); EXPECT_EQ(TW::deriveAddress(TWCoinTypeZilliqa, privateKey), "zil1j2cvtd7j9n7fnxfv2r3neucjw8tp4xz9sp07v4"); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeRavencoin, privateKey), "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); } } // namespace TW diff --git a/tests/CoinAddressValidationTests.cpp b/tests/CoinAddressValidationTests.cpp index 7f74893231a..d8d5d5b94d7 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/CoinAddressValidationTests.cpp @@ -181,9 +181,12 @@ TEST(Coin, ValidateAddressDGB){ EXPECT_TRUE(validateAddress(TWCoinTypeDigiByte, "dgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3d")); EXPECT_TRUE(validateAddress(TWCoinTypeDigiByte, "SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo")); - EXPECT_FALSE(validateAddress(TWCoinTypeDigiByte, "DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kpx")); - EXPECT_FALSE(validateAddress(TWCoinTypeDigiByte, "dgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3x")); - EXPECT_FALSE(validateAddress(TWCoinTypeDigiByte, "SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVx")); + // bad address + EXPECT_FALSE(validateAddress(TWCoinTypeDigiByte, "XBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4")); + // bad bech32 address + EXPECT_FALSE(validateAddress(TWCoinTypeDigiByte, "xgb1q3p2nf26ac6qtdrv4czh5nmp2eshfj9wyn9vv3d")); + // testnet address + EXPECT_FALSE(validateAddress(TWCoinTypeDigiByte, "ztijPBZmzdAkF6y79LHYGmqNm2CVfaoLqtz")); } TEST(Coin, validateAddressIocoin) { @@ -237,4 +240,14 @@ TEST(Coin, validateAddressCosmos) { EXPECT_FALSE(validateAddress(TWCoinTypeCosmos, "xosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp")); } +TEST(Coin, validateAddressRavencoin) { + EXPECT_TRUE(validateAddress(TWCoinTypeRavencoin, "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS")); + EXPECT_TRUE(validateAddress(TWCoinTypeRavencoin, "rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1")); + + // bad address + EXPECT_FALSE(validateAddress(TWCoinTypeRavencoin, "XHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS")); + // testnet address + EXPECT_FALSE(validateAddress(TWCoinTypeRavencoin, "mwJAu1BWcRSQhepZ71wiGoSwsD6hnB5B7G")); +} + } // namespace TW diff --git a/tests/interface/TWRavencoinTransactionTests.cpp b/tests/interface/TWRavencoinTransactionTests.cpp new file mode 100644 index 00000000000..2a4fb3815b6 --- /dev/null +++ b/tests/interface/TWRavencoinTransactionTests.cpp @@ -0,0 +1,93 @@ + +// Copyright © 2017-2019 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 "Bitcoin/OutPoint.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include +#include + +#include + +using namespace TW; +using namespace Bitcoin; + +TEST(RavencoinTransaction, SignTransaction) { + /* + https://iancoleman.io/bip39/ + Mnemonic - shoot island position soft burden budget tooth cruel issue economy destroy above + m/44'/175'/0'/0/0 Address - RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS + m/44'/175'/0'/0/0 Private key in Base58 encoding - L1At2vQpaHCmbiu333N3kD4nbDzJgvb8hxNp5S8bQApocFYuW1rx + m/44'/175'/0'/0/0 Private key in bytes - 75e4c520c92b3836e77dfe2715da469b71f7df86fc11ef328870735a700551fa + utxo - https://blockbook.ravencoin.org/tx/0c7e82b44eec71d634c013e2db3cb4fa26f87fbc90eb8734da93807d23605544 + tx - https://blockbook.ravencoin.org/tx/3717b528eb4925461d9de5a596d2eefe175985740b4fda153255e10135f236a6 + */ + + const int64_t utxo_amount = 100000000; + const int64_t amount = 50000000; + const int64_t fee = 2000000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWSignatureHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88"); + input.set_change_address("RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS"); + + auto hash0 = DATA("445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c"); + auto utxo0 = input.add_utxo(); + 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); + utxo0->set_amount(utxo_amount); + auto script0 = parse_hex("76a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac"); + utxo0->set_script(script0.data(), script0.size()); + + auto utxoKey0 = DATA("75e4c520c92b3836e77dfe2715da469b71f7df86fc11ef328870735a700551fa"); + input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); + + auto plan = Bitcoin::TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = utxo_amount - amount - fee; + + // Sign + auto signer = TW::Bitcoin::TransactionSigner(std::move(input), plan); + auto result = signer.sign(); + auto signedTx = result.payload(); + + ASSERT_TRUE(result); + ASSERT_EQ(fee, signer.plan.fee); + + Data serialized; + signedTx.encode(false, serialized); + ASSERT_EQ( + hex(serialized), + "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000" + ); +} + +TEST(RavencoinTransaction, LockScripts) { + // P2PKH + // https://blockbook.ravencoin.org/tx/3717b528eb4925461d9de5a596d2eefe175985740b4fda153255e10135f236a6 + + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88").get())); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac"); + + // P2SH + // https://ravencoin.network/api/tx/f600d07814677d1f60545c8f7f71260238595c4928d6fb87caa0f9dd732e9bb5 + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptBuildForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get())); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "a914bd92088bb7e82d611a9b94fbb74a0908152b784f87"); +} \ No newline at end of file