Skip to content

Commit

Permalink
Add support for Ravencoin (#484)
Browse files Browse the repository at this point in the history
* Add support for Ravencoin - add new coin type for Ravencoin, add tests for Ravencoin address

* Add support for Ravencoin - add tests for tx signing and lock script

* Add support for Ravencoin - add tests for iOS and Android

* Add support for Ravencoin - PR feedback, update explorer url, add tests for address validation, add test for P2SH lock script

* Add support for Ravencoin - PR feedback, add test for testnet address

* Add support for Ravencoin - PR feedback, revert back to original explorer url
  • Loading branch information
johnnynanjiang authored and hewigovens committed Jun 3, 2019
1 parent e9f8499 commit bc9d6d3
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,6 @@ class CoinAddressDerivationTests {
ZELCASH -> assertEquals("t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf", address)
ARK -> assertEquals("Ac49m5pu5YpMMNgEbSYeZUEpRMHcSK3DfV", address)
MONETARYUNIT -> assertEquals("7W3QRu8FttKzmYtRbXNKopeHweAKWuun2q", address)
RAVENCOIN -> assertEquals("RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS", address)
}
}
15 changes: 15 additions & 0 deletions coins.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
}
]
3 changes: 2 additions & 1 deletion docs/coins.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@
| 31102 | Ethersocial | ESN | <img src="https://raw.githubusercontent.com/TrustWallet/tokens/master/coins/31102.png" width="32" />| https://ethersocial.org/
| 163 | Ellaism | ELLA | <img src="https://raw.githubusercontent.com/TrustWallet/tokens/master/coins/163.png" width="32" />| https://ellaism.org/
| 111 | Ark | ARK | <img src="https://raw.githubusercontent.com/TrustWallet/tokens/master/coins/111.png" width="32" />| http://ark.io
| 20 | DigiByte | DGB | <img src="https://raw.githubusercontent.com/TrustWallet/tokens/master/coins/20.png" width="32" />| https://www.digibyte.io
| 20 | DigiByte | DGB | <img src="https://raw.githubusercontent.com/TrustWallet/tokens/master/coins/20.png" width="32" />| https://www.digibyte.io
| 175 | Ravencoin | RVN | <img src="https://raw.githubusercontent.com/TrustWallet/tokens/master/coins/175.png" width="32" />| https://ravencoin.org
2 changes: 1 addition & 1 deletion include/TrustWalletCore/TWCoinType.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ enum TWCoinType {
TWCoinTypeZelcash = 19167,
TWCoinTypeARK = 111,
TWCoinTypeMonetaryUnit = 31,

TWCoinTypeRavencoin = 175,
};

/// Returns the blockchain for a coin type.
Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWP2PKHPrefix.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum TWP2PKHPrefix {
TWP2PKHPrefixZcashT = 0xB8,
TWP2PKHPrefixZcoin = 0x52,
TWP2PKHPrefixMonetaryUnit = 0x10,
TWP2PKHPrefixRavencoin = 0x3c,
};

TW_EXTERN_C_END
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWP2SHPrefix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/Bitcoin/Script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,13 @@ void Script::encode(std::vector<uint8_t>& data) const {
Script Script::buildForAddress(const std::string& string) {
static const std::vector<uint8_t> p2pkhPrefixes = {TWP2PKHPrefixBitcoin, TWP2PKHPrefixIocoin, TWP2PKHPrefixLitecoin,
TWP2PKHPrefixDash, TWP2PKHPrefixZcoin, TWP2PKHPrefixViacoin,
TWP2PKHPrefixD, TWP2PKHPrefixQtum, TWP2PKHPrefixMonetaryUnit};
TWP2PKHPrefixD, TWP2PKHPrefixQtum, TWP2PKHPrefixMonetaryUnit,
TWP2PKHPrefixRavencoin};

static const std::vector<uint8_t> 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]);
Expand Down
6 changes: 6 additions & 0 deletions src/Coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}});
}
}

Expand Down Expand Up @@ -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();
}
}

Expand Down
4 changes: 4 additions & 0 deletions swift/Sources/Addresses/CoinType+Address.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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()
}
Expand Down
56 changes: 56 additions & 0 deletions swift/Tests/Addresses/BitcoinAddressTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

}
}
3 changes: 3 additions & 0 deletions swift/Tests/CoinAddressDerivationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions swift/Tests/HDWalletTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
1 change: 1 addition & 0 deletions tests/CoinAddressDerivationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 16 additions & 3 deletions tests/CoinAddressValidationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
93 changes: 93 additions & 0 deletions tests/interface/TWRavencoinTransactionTests.cpp
Original file line number Diff line number Diff line change
@@ -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 <TrustWalletCore/TWBitcoinScript.h>
#include <TrustWalletCore/TWHDWallet.h>

#include <gtest/gtest.h>

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<TW::Bitcoin::Transaction>(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");
}

0 comments on commit bc9d6d3

Please sign in to comment.