Add tx creation, signing and tests

This commit is contained in:
satyakoneru 2020-04-14 13:06:13 +00:00
parent c4a48f1317
commit b153513863
10 changed files with 592 additions and 22 deletions

View file

@ -7,12 +7,13 @@ add_library( peerplays_sidechain
sidechain_net_handler_bitcoin.cpp
sidechain_net_handler_peerplays.cpp
bitcoin_utils.cpp
bitcoin/bech32.cpp
bitcoin/bech32.cpp
bitcoin/bitcoin_address.cpp
bitcoin/bitcoin_script.cpp
bitcoin/bitcoin_transaction.cpp
bitcoin/segwit_addr.cpp
bitcoin/utils.cpp
bitcoin/sign_bitcoin_transaction.cpp
)
if (SUPPORT_MULTIPLE_SONS)

View file

@ -0,0 +1,125 @@
#include <graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp>
#include <graphene/peerplays_sidechain/bitcoin/serialize.hpp>
namespace graphene { namespace peerplays_sidechain { namespace bitcoin {
fc::sha256 get_signature_hash( const bitcoin_transaction& tx, const bytes& scriptCode, int64_t amount,
size_t in_index, int hash_type, bool is_witness )
{
fc::datastream<size_t> ps;
if ( is_witness )
pack_tx_witness_signature( ps, scriptCode, tx, in_index, amount, hash_type );
else
pack_tx_signature( ps, scriptCode, tx, in_index, hash_type );
std::vector<char> vec( ps.tellp() );
if ( !vec.empty() ) {
fc::datastream<char*> ds( vec.data(), vec.size() );
if ( is_witness )
pack_tx_witness_signature( ds, scriptCode, tx, in_index, amount, hash_type );
else
pack_tx_signature( ds, scriptCode, tx, in_index, hash_type );
}
return fc::sha256::hash( fc::sha256::hash( vec.data(), vec.size() ) );
}
std::vector<char> privkey_sign( const bytes& privkey, const fc::sha256 &hash, const secp256k1_context_t* context_sign )
{
bytes sig;
sig.resize( 72 );
int sig_len = sig.size();
FC_ASSERT( secp256k1_ecdsa_sign(
context_sign,
reinterpret_cast<unsigned char*>( hash.data() ),
reinterpret_cast<unsigned char*>( sig.data() ),
&sig_len,
reinterpret_cast<const unsigned char*>( privkey.data() ),
secp256k1_nonce_function_rfc6979,
nullptr ) ); // TODO: replace assert with exception
sig.resize( sig_len );
return sig;
}
std::vector<bytes> sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& amounts, const bytes& privkey,
const secp256k1_context_t* context_sign, int hash_type )
{
FC_ASSERT( tx.vin.size() == redeem_scripts.size() && tx.vin.size() == amounts.size() );
FC_ASSERT( !privkey.empty() );
std::vector< bytes > signatures;
for( size_t i = 0; i < tx.vin.size(); i++ ) {
const auto sighash = get_signature_hash( tx, redeem_scripts[i], static_cast<int64_t>( amounts[i] ), i, hash_type, true );
auto sig = privkey_sign( privkey, sighash, context_sign );
sig.push_back( static_cast<uint8_t>( hash_type ) );
signatures.push_back( sig );
}
return signatures;
}
void sign_witness_transaction_finalize( bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts )
{
FC_ASSERT( tx.vin.size() == redeem_scripts.size() );
for( size_t i = 0; i < tx.vin.size(); i++ ) {
tx.vin[i].scriptWitness.insert( tx.vin[i].scriptWitness.begin(), bytes() ); // Bitcoin workaround CHECKMULTISIG bug
tx.vin[i].scriptWitness.push_back( redeem_scripts[i] );
}
}
bool verify_sig( const bytes& sig, const bytes& pubkey, const bytes& msg, const secp256k1_context_t* context )
{
std::vector<unsigned char> sig_temp( sig.begin(), sig.end() );
std::vector<unsigned char> pubkey_temp( pubkey.begin(), pubkey.end() );
std::vector<unsigned char> msg_temp( msg.begin(), msg.end() );
int result = secp256k1_ecdsa_verify( context, msg_temp.data(), sig_temp.data(), sig_temp.size(), pubkey_temp.data(), pubkey_temp.size() );
return result == 1;
}
std::vector<std::vector<bytes>> sort_sigs( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& amounts, const secp256k1_context_t* context )
{
FC_ASSERT( redeem_scripts.size() == amounts.size() );
using data = std::pair<size_t, bytes>;
struct comp {
bool operator() (const data& lhs, const data& rhs) const { return lhs.first < rhs.first; }
};
std::vector<std::vector<bytes>> new_stacks;
for( size_t i = 0; i < redeem_scripts.size(); i++ ) {
const std::vector<bytes>& keys = get_pubkey_from_redeemScript( redeem_scripts[i] );
const auto& sighash = get_signature_hash( tx, redeem_scripts[i], static_cast<int64_t>( amounts[i] ), i, 1, true ).str();
bytes sighash_temp( parse_hex( sighash ) );
std::vector<bytes> stack( tx.vin[i].scriptWitness );
std::vector<bool> marker( tx.vin[i].scriptWitness.size(), false );
std::set<data, comp> sigs;
for( size_t j = 0; j < keys.size(); j++ ) {
for( size_t l = 0; l < stack.size(); l++ ) {
if( !verify_sig( stack[l], keys[j], sighash_temp, context ) || marker[l] )
continue;
sigs.insert(std::make_pair(j, stack[l]));
marker[l] = true;
break;
}
}
std::vector<bytes> temp_sig;
for( auto s : sigs ) {
temp_sig.push_back( s.second );
}
new_stacks.push_back( temp_sig );
}
return new_stacks;
}
} } }

View file

@ -5,6 +5,7 @@
#include <fc/io/raw.hpp>
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
#include <secp256k1.h>
#include <boost/property_tree/json_parser.hpp>
namespace graphene { namespace peerplays_sidechain {

View file

@ -357,4 +357,4 @@ inline void pack_tx_witness_signature( Stream& s, const std::vector<char>& scrip
pack( s, hash_type );
}
} } }
} } }

View file

@ -0,0 +1,27 @@
#pragma once
#include <graphene/peerplays_sidechain/bitcoin/types.hpp>
#include <fc/crypto/sha256.hpp>
#include <secp256k1.h>
namespace graphene { namespace peerplays_sidechain { namespace bitcoin {
class bitcoin_transaction;
fc::sha256 get_signature_hash( const bitcoin_transaction& tx, const bytes& scriptPubKey, int64_t amount,
size_t in_index, int hash_type, bool is_witness );
std::vector<char> privkey_sign( const bytes& privkey, const fc::sha256 &hash, const secp256k1_context_t* context_sign = nullptr );
std::vector<bytes> sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& amounts, const bytes& privkey,
const secp256k1_context_t* context_sign = nullptr, int hash_type = 1 );
void sign_witness_transaction_finalize( bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts );
bool verify_sig( const bytes& sig, const bytes& pubkey, const bytes& msg, const secp256k1_context_t* context );
std::vector<std::vector<bytes>> sort_sigs( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& amounts, const secp256k1_context_t* context );
} } }

View file

@ -12,7 +12,7 @@ namespace graphene { namespace peerplays_sidechain { namespace bitcoin {
class bitcoin_transaction;
using bytes = std::vector<char>;
using accounts_keys = std::map< graphene::chain::account_id_type, graphene::chain::public_key_type >;
using accounts_keys = std::map< graphene::chain::son_id_type, graphene::chain::public_key_type >;
using full_btc_transaction = std::pair<bitcoin_transaction, uint64_t>;
enum class payment_type

View file

@ -14,7 +14,7 @@ class btc_txout {
public:
std::string txid_;
unsigned int out_num_;
double amount_;
uint64_t amount_;
};
class bitcoin_rpc_client {
@ -124,6 +124,7 @@ private:
std::string send_transaction_raw(const sidechain_transaction_object &sto);
std::string send_transaction_psbt(const sidechain_transaction_object &sto);
std::string send_transaction_standalone(const sidechain_transaction_object &sto);
void handle_event(const std::string &event_data);
std::vector<info_for_vin> extract_info_from_block(const std::string &_block);

View file

@ -15,6 +15,8 @@
#include <graphene/chain/protocol/son_wallet.hpp>
#include <graphene/chain/son_info.hpp>
#include <graphene/chain/son_wallet_object.hpp>
#include <graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp>
#include <graphene/peerplays_sidechain/bitcoin/serialize.hpp>
namespace graphene { namespace peerplays_sidechain {
@ -527,7 +529,9 @@ std::vector<btc_txout> bitcoin_rpc_client::listunspent(const uint32_t minconf, c
btc_txout txo;
txo.txid_ = entry.second.get_child("txid").get_value<std::string>();
txo.out_num_ = entry.second.get_child("vout").get_value<unsigned int>();
txo.amount_ = entry.second.get_child("amount").get_value<double>();
string amount = entry.second.get_child("amount").get_value<std::string>();
amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end());
txo.amount_ = std::stoll(amount);
result.push_back(txo);
}
}
@ -565,7 +569,9 @@ std::vector<btc_txout> bitcoin_rpc_client::listunspent_by_address_and_amount(con
btc_txout txo;
txo.txid_ = entry.second.get_child("txid").get_value<std::string>();
txo.out_num_ = entry.second.get_child("vout").get_value<unsigned int>();
txo.amount_ = entry.second.get_child("amount").get_value<double>();
string amount = entry.second.get_child("amount").get_value<std::string>();
amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end());
txo.amount_ = std::stoll(amount);
result.push_back(txo);
}
}
@ -1269,8 +1275,7 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction() {
uint64_t min_fee_rate = 1000;
fee_rate = std::max(fee_rate, min_fee_rate);
double min_amount = ((double)fee_rate / 100000000.0); // Account only for relay fee for now
double total_amount = 0.0;
uint64_t total_amount = 0.0;
std::vector<btc_txout> inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0);
if (inputs.size() == 0) {
@ -1281,14 +1286,15 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction() {
total_amount += utx.amount_;
}
if (min_amount >= total_amount) {
if (fee_rate >= total_amount) {
elog("Failed not enough BTC to transfer from ${fa}", ("fa", prev_pw_address));
return "";
}
}
fc::flat_map<std::string, double> outputs;
outputs[active_pw_address] = total_amount - min_amount;
//outputs[active_pw_address] = total_amount - min_amount;
outputs[active_pw_address] = double(total_amount - fee_rate) / 100000000.0;
return create_transaction(inputs, outputs);
}
@ -1329,7 +1335,8 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_
outputs[pw_address] = transfer_amount;
return create_transaction(inputs, outputs);
//return create_transaction(inputs, outputs);
return create_transaction_psbt(inputs, outputs);
}
std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) {
@ -1351,8 +1358,7 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
uint64_t min_fee_rate = 1000;
fee_rate = std::max(fee_rate, min_fee_rate);
double min_amount = ((double)(swwo.withdraw_amount.value + fee_rate) / 100000000.0); // Account only for relay fee for now
double total_amount = 0.0;
uint64_t total_amount = 0;
std::vector<btc_txout> inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0);
if (inputs.size() == 0) {
@ -1363,7 +1369,7 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
total_amount += utx.amount_;
}
if (min_amount > total_amount) {
if (fee_rate > total_amount) {
elog("Failed not enough BTC to spend for ${pw}", ("pw", pw_address));
return "";
}
@ -1371,8 +1377,8 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
fc::flat_map<std::string, double> outputs;
outputs[swwo.withdraw_address] = swwo.withdraw_amount.value / 100000000.0;
if ((total_amount - min_amount) > 0.0) {
outputs[pw_address] = total_amount - min_amount;
if ((total_amount - fee_rate) > 0.0) {
outputs[pw_address] = double(total_amount - fee_rate) / 100000000.0;
}
return create_transaction(inputs, outputs);
@ -1383,8 +1389,8 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs) {
std::string new_tx = "";
//new_tx = create_transaction_raw(inputs, outputs);
new_tx = create_transaction_psbt(inputs, outputs);
//new_tx = create_transaction_standalone(inputs, outputs);
//new_tx = create_transaction_psbt(inputs, outputs);
new_tx = create_transaction_standalone(inputs, outputs);
return new_tx;
}
@ -1393,14 +1399,90 @@ std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector<
std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_transaction_object &sto) {
std::string new_tx = "";
//new_tx = sign_transaction_raw(sto);
new_tx = sign_transaction_psbt(sto);
//new_tx = sign_transaction_standalone(sto);
if (sto.object_id.type() == 30) {
new_tx = sign_transaction_psbt(sto);
} else {
new_tx = sign_transaction_standalone(sto);
}
return new_tx;
}
std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_transaction_object &sto) {
//return send_transaction_raw(sto);
return send_transaction_psbt(sto);
//return send_transaction_psbt(sto);
if (sto.object_id.type() == 30) {
return send_transaction_psbt(sto);
} else {
return send_transaction_standalone(sto);
}
}
std::vector<std::vector<unsigned char>> read_byte_arrays_from_string(const std::string &string_buf)
{
std::stringstream ss(string_buf);
boost::property_tree::ptree json;
boost::property_tree::read_json(ss, json);
std::vector<bytes> data;
for(auto &v: json)
{
std::string hex = v.second.data();
bytes item;
item.resize(hex.size() / 2);
fc::from_hex(hex, (char*)&item[0], item.size());
data.push_back(item);
}
return data;
}
std::string write_byte_arrays_to_string(const std::vector<std::vector<unsigned char>>& data)
{
std::string res = "[";
for (unsigned int idx = 0; idx < data.size(); ++idx) {
res += "\"" + fc::to_hex((char*)&data[idx][0], data[idx].size()) + "\"";
if (idx != data.size() - 1)
res += ",";
}
res += "]";
return res;
}
void read_tx_data_from_string(const std::string &string_buf, std::vector<unsigned char> &tx, std::vector<uint64_t> &in_amounts)
{
std::stringstream ss(string_buf);
boost::property_tree::ptree json;
boost::property_tree::read_json(ss, json);
std::string tx_hex = json.get<std::string>("tx_hex");
tx.clear();
tx.resize(tx_hex.size() / 2);
fc::from_hex(tx_hex, (char*)&tx[0], tx.size());
in_amounts.clear();
for(auto &v: json.get_child("in_amounts"))
in_amounts.push_back(fc::to_uint64(v.second.data()));
}
std::string save_tx_data_to_string(const std::vector<unsigned char> &tx, const std::vector<uint64_t> &in_amounts)
{
std::string res = "{\"tx_hex\":\"" + fc::to_hex((const char*)&tx[0], tx.size()) + "\",\"in_amounts\":[";
for (unsigned int idx = 0; idx < in_amounts.size(); ++idx) {
res += fc::to_string(in_amounts[idx]);
if (idx != in_amounts.size() - 1)
res += ",";
}
res += "]}";
return res;
}
std::string save_tx_data_to_string(const std::string &tx, const std::vector<uint64_t> &in_amounts)
{
std::string res = "{\"tx_hex\":\"" + tx + "\",\"in_amounts\":[";
for (unsigned int idx = 0; idx < in_amounts.size(); ++idx) {
res += fc::to_string(in_amounts[idx]);
if (idx != in_amounts.size() - 1)
res += ",";
}
res += "]}";
return res;
}
std::string sidechain_net_handler_bitcoin::create_transaction_raw(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs) {
@ -1468,8 +1550,30 @@ std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const s
// }
// ]
//}
using namespace bitcoin;
bitcoin_transaction_builder tb;
std::vector<uint64_t> in_amounts;
return "";
tb.set_version( 2 );
for (auto in : inputs) {
tb.add_in( payment_type::P2WSH, fc::sha256(in.txid_), in.out_num_, bitcoin::bytes() );
in_amounts.push_back(in.amount_);
}
for (auto out : outputs) {
uint64_t satoshis = out.second * 100000000.0;
tb.add_out( payment_type::P2WPKH, satoshis, out.first);
}
const auto tx = tb.get_transaction();
std::string hex_tx = fc::to_hex( pack( tx ) );
std::string tx_raw = save_tx_data_to_string(hex_tx, in_amounts);
wlog("skoneru: Raw transaction ${tx}", ("tx", tx_raw));
return tx_raw;
}
std::string sidechain_net_handler_bitcoin::sign_transaction_raw(const sidechain_transaction_object &sto) {
@ -1563,7 +1667,10 @@ std::string sidechain_net_handler_bitcoin::sign_transaction_psbt(const sidechain
}
std::string sidechain_net_handler_bitcoin::sign_transaction_standalone(const sidechain_transaction_object &sto) {
std::string pubkey = plugin.get_current_son_object().sidechain_public_keys.at(sidechain);
std::string prvkey = get_private_key(pubkey);
return "";
}
@ -1613,6 +1720,10 @@ std::string sidechain_net_handler_bitcoin::send_transaction_psbt(const sidechain
return "";
}
std::string sidechain_net_handler_bitcoin::send_transaction_standalone(const sidechain_transaction_object &sto) {
return bitcoin_client->sendrawtransaction(sto.transaction);
}
void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) {
std::string block = bitcoin_client->getblock(event_data);
if (block != "") {

View file

@ -0,0 +1,134 @@
#include <boost/test/unit_test.hpp>
#include <graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp>
using namespace graphene::peerplays_sidechain::bitcoin;
BOOST_AUTO_TEST_SUITE( bitcoin_address_tests )
fc::ecc::public_key_data create_public_key_data( const std::vector<char>& public_key )
{
FC_ASSERT( public_key.size() == 33 );
fc::ecc::public_key_data key;
for(size_t i = 0; i < 33; i++) {
key.at(i) = public_key[i];
}
return key;
}
BOOST_AUTO_TEST_CASE( addresses_type_test )
{
// public_key
std::string compressed( "03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22" );
BOOST_CHECK( bitcoin_address( compressed ).get_type() == payment_type::P2PK );
std::string uncompressed( "04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904" );
BOOST_CHECK( bitcoin_address( uncompressed ).get_type() == payment_type::NULLDATA );
// segwit_address
std::string p2wpkh_mainnet( "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" );
BOOST_CHECK( bitcoin_address( p2wpkh_mainnet ).get_type() == payment_type::P2WPKH );
std::string p2wpkh_testnet( "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" );
BOOST_CHECK( bitcoin_address( p2wpkh_testnet ).get_type() == payment_type::P2WPKH );
std::string p2wsh( "bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9" );
BOOST_CHECK( bitcoin_address( p2wsh ).get_type() == payment_type::P2WSH );
// base58
std::string p2pkh_mainnet( "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem" );
BOOST_CHECK( bitcoin_address( p2pkh_mainnet ).get_type() == payment_type::P2PKH );
std::string p2pkh_testnet( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" );
BOOST_CHECK( bitcoin_address( p2pkh_testnet ).get_type() == payment_type::P2PKH );
std::string p2sh_mainnet( "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" );
BOOST_CHECK( bitcoin_address( p2sh_mainnet ).get_type() == payment_type::P2SH );
std::string p2sh_testnet( "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc" );
BOOST_CHECK( bitcoin_address( p2sh_testnet ).get_type() == payment_type::P2SH );
}
BOOST_AUTO_TEST_CASE( addresses_raw_test )
{
// public_key
std::string compressed( "03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22" );
bytes standard_compressed( parse_hex( compressed ) );
BOOST_CHECK( bitcoin_address( compressed ).get_raw_address() == standard_compressed );
std::string uncompressed( "04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904" );
BOOST_CHECK( bitcoin_address( uncompressed ).get_raw_address() == bytes() );
// segwit_address
std::string p2wpkh_mainnet( "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" );
bytes standard_p2wpkh_mainnet( parse_hex( "751e76e8199196d454941c45d1b3a323f1433bd6" ) );
BOOST_CHECK( bitcoin_address( p2wpkh_mainnet ).get_raw_address() == standard_p2wpkh_mainnet );
std::string p2wpkh_testnet( "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" );
bytes standard_p2wpkh_testnet( parse_hex( "751e76e8199196d454941c45d1b3a323f1433bd6" ) );
BOOST_CHECK( bitcoin_address( p2wpkh_testnet ).get_raw_address() == standard_p2wpkh_testnet );
std::string p2wsh( "bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9" );
bytes standard_p2wsh( parse_hex( "c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d" ) );
BOOST_CHECK( bitcoin_address( p2wsh ).get_raw_address() == standard_p2wsh );
// base58
std::string p2pkh_mainnet( "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem" );
bytes standard_p2pkh_mainnet( parse_hex( "47376c6f537d62177a2c41c4ca9b45829ab99083" ) );
BOOST_CHECK( bitcoin_address( p2pkh_mainnet ).get_raw_address() == standard_p2pkh_mainnet );
std::string p2pkh_testnet( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" );
bytes standard_p2pkh_testnet( parse_hex( "243f1394f44554f4ce3fd68649c19adc483ce924" ) );
BOOST_CHECK( bitcoin_address( p2pkh_testnet ).get_raw_address() == standard_p2pkh_testnet );
std::string p2sh_mainnet( "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" );
bytes standard_p2sh_mainnet( parse_hex( "8f55563b9a19f321c211e9b9f38cdf686ea07845" ) );
BOOST_CHECK( bitcoin_address( p2sh_mainnet ).get_raw_address() == standard_p2sh_mainnet );
std::string p2sh_testnet( "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc" );
bytes standard_p2sh_testnet( parse_hex( "4e9f39ca4688ff102128ea4ccda34105324305b0" ) );
BOOST_CHECK( bitcoin_address( p2sh_testnet ).get_raw_address() == standard_p2sh_testnet );
}
BOOST_AUTO_TEST_CASE( create_multisig_address_test )
{
std::vector<char> public_key1 = parse_hex( "03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010" );
std::vector<char> public_key2 = parse_hex( "0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983" );
std::vector<char> public_key3 = parse_hex( "033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f" );
std::vector<char> address = parse_hex( "a91460cb986f0926e7c4ca1984ca9f56767da2af031e87" );
std::vector<char> redeem_script = parse_hex( "522103db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010210320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b441798321033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f53ae" );
fc::ecc::public_key_data key1 = create_public_key_data( public_key1 );
fc::ecc::public_key_data key2 = create_public_key_data( public_key2 );
fc::ecc::public_key_data key3 = create_public_key_data( public_key3 );
btc_multisig_address cma(2, { { son_id_type(1), public_key_type(key1) }, { son_id_type(2), public_key_type(key2) }, { son_id_type(3), public_key_type(key3) } });
BOOST_CHECK( address == cma.raw_address );
BOOST_CHECK( redeem_script == cma.redeem_script );
}
BOOST_AUTO_TEST_CASE( create_segwit_address_test )
{
// https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa
std::vector<char> public_key1 = parse_hex( "03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb" );
std::vector<char> public_key2 = parse_hex( "03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6" );
std::vector<char> public_key3 = parse_hex( "033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1" );
std::vector<char> witness_script = parse_hex("0020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1");
fc::ecc::public_key_data key1 = create_public_key_data( public_key1 );
fc::ecc::public_key_data key2 = create_public_key_data( public_key2 );
fc::ecc::public_key_data key3 = create_public_key_data( public_key3 );
btc_multisig_segwit_address address(2, { { son_id_type(1), public_key_type(key1) }, { son_id_type(2), public_key_type(key2) }, { son_id_type(3), public_key_type(key3) } });
BOOST_CHECK( address.get_witness_script() == witness_script );
BOOST_CHECK( address.get_address() == "2NGU4ogScHEHEpReUzi9RB2ha58KAFnkFyk" );
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -0,0 +1,170 @@
#include <boost/test/unit_test.hpp>
#include <graphene/peerplays_sidechain/bitcoin/utils.hpp>
#include <graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp>
#include <graphene/peerplays_sidechain/bitcoin/serialize.hpp>
using namespace graphene::peerplays_sidechain::bitcoin;
BOOST_AUTO_TEST_SUITE( bitcoin_transaction_tests )
BOOST_AUTO_TEST_CASE( serialize_bitcoin_transaction_test )
{
out_point prevout;
prevout.hash = fc::sha256( "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a" );
prevout.n = 0;
tx_in in;
in.prevout = prevout;
in.scriptSig = parse_hex( "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101" );
in.nSequence = 4294967294;
tx_out out1;
out1.value = 3500000000;
out1.scriptPubKey = parse_hex( "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac" );
tx_out out2;
out2.value = 1499996160;
out2.scriptPubKey = parse_hex( "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac" );
bitcoin_transaction tx;
tx.nVersion = 2;
tx.vin = { in };
tx.vout = { out1, out2 };
tx.nLockTime = 101;
const auto serialized = pack( tx );
const auto expected = parse_hex( "02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000" );
BOOST_CHECK_EQUAL_COLLECTIONS( serialized.cbegin(), serialized.cend(), expected.cbegin(), expected.cend() );
}
/*
BOOST_AUTO_TEST_CASE( deserialize_bitcoin_transaction_test )
{
bitcoin_transaction tx = unpack(parse_hex( "02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000" ) );
BOOST_CHECK_EQUAL( tx.nVersion, 2 );
BOOST_CHECK_EQUAL( tx.nLockTime, 101 );
BOOST_REQUIRE_EQUAL( tx.vin.size(), 1 );
BOOST_CHECK_EQUAL( tx.vin[0].prevout.hash.str(), "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a" );
BOOST_CHECK_EQUAL( tx.vin[0].prevout.n, 0 );
BOOST_CHECK( fc::to_hex( tx.vin[0].scriptSig) == "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101" );
BOOST_CHECK_EQUAL( tx.vin[0].nSequence, 4294967294 );
BOOST_REQUIRE_EQUAL( tx.vout.size(), 2 );
BOOST_CHECK_EQUAL( tx.vout[0].value, 3500000000 );
BOOST_CHECK( fc::to_hex( tx.vout[0].scriptPubKey ) == "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac" );
BOOST_CHECK_EQUAL(tx.vout[1].value, 1499996160);
BOOST_CHECK( fc::to_hex( tx.vout[1].scriptPubKey ) == "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac" );
}*/
/*
BOOST_AUTO_TEST_CASE( btc_tx_methods_test ) {
const auto tx = unpack<bitcoin_transaction>( parse_hex( "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f856040000002322002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1ffffffff0188b3f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02473044022038421164c6468c63dc7bf724aa9d48d8e5abe3935564d38182addf733ad4cd81022076362326b22dd7bfaf211d5b17220723659e4fe3359740ced5762d0e497b7dcc012321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac00000000" ) );
BOOST_CHECK( tx.get_hash().str() == "a5947589e2762107ff650958ba0e3a3cf341f53281d15593530bf9762c4edab1" );
BOOST_CHECK( tx.get_txid().str() == "954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88" );
BOOST_CHECK( tx.get_vsize() == 148 );
}
*/
BOOST_AUTO_TEST_CASE( bitcoin_transaction_builder_test )
{
// All tests are only to verefy the compilation of transactions, the transactions are not real(not valid)
{ // P2PKH to P2PKH
bitcoin_transaction_builder tb;
tb.set_version( 2 );
tb.add_in( payment_type::P2PKH, fc::sha256( "5d42b45d5a3ddcf2421b208885871121551acf6ea5cc1c1b4e666537ab6fcbef" ), 0, bytes() );
tb.add_out( payment_type::P2PKH, 4999990000, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex(pack( tx ) ) == "0200000001efcb6fab3765664e1b1ccca56ecf1a552111878588201b42f2dc3d5a5db4425d0000000000ffffffff01f0ca052a010000001976a9143307bf6f98832e53a48b144d65c6a95700a93ffb88ac00000000");
}/*
{ // coinbase to P2PK
const auto pubkey = fc::raw::unpack<fc::ecc::public_key_data>( parse_hex( "02028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00" ) );
out_point prevout;
prevout.hash = fc::sha256( "0000000000000000000000000000000000000000000000000000000000000000" );
prevout.n = 0xffffffff;
tx_in txin;
txin.prevout = prevout;
bitcoin_transaction_builder tb;
tb.set_version( 2 );
tb.add_in( payment_type::P2SH, txin, parse_hex( "022a020101" ) );
tb.add_out( payment_type::P2PK, 625000000, pubkey);
tb.add_out( payment_type::NULLDATA, 0, parse_hex( "21030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac" ) );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05022a020101ffffffff0240be402500000000232102028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00ac0000000000000000256a2321030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac00000000" );
}*/
{ // P2SH to P2SH
bitcoin_transaction_builder tb;
tb.set_version( 2 );
tb.add_in( payment_type::P2SH, fc::sha256( "40eee3ae1760e3a8532263678cdf64569e6ad06abc133af64f735e52562bccc8" ), 0, bytes() );
tb.add_out( payment_type::P2SH, 0xffffffff, "3P14159f73E4gFr7JterCCQh9QjiTjiZrG" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0200000001c8cc2b56525e734ff63a13bc6ad06a9e5664df8c67632253a8e36017aee3ee400000000000ffffffff01ffffffff0000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a8700000000" );
}
{ // P2PK to NULLDATA
bitcoin_transaction_builder tb;
tb.set_version( 2 );
tb.add_in( payment_type::P2PK, fc::sha256( "fa897a4a2b8bc507db6cf4425e81ca7ebde89a369e07d608ac7f7c311cb13b4f" ), 0, bytes() );
tb.add_out( payment_type::NULLDATA, 0, parse_hex( "ffffffff" ) );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000014f3bb11c317c7fac08d6079e369ae8bd7eca815e42f46cdb07c58b2b4a7a89fa0000000000ffffffff010000000000000000066a04ffffffff00000000" );
}
{ // P2PK+P2PKH to P2PKH,P2PKH
bitcoin_transaction_builder tb;
tb.set_version(2);
tb.add_in( payment_type::P2PK, fc::sha256( "13d29149e08b6ca63263f3dddd303b32f5aab646ebc6b7db84756d80a227f6d9" ), 0, bytes() );
tb.add_in( payment_type::P2PKH, fc::sha256( "a8e7f661925cdd2c0e37fc93c03540c113aa6bcea02b35de09377127f76d0da3" ), 0, bytes() );
tb.add_out( payment_type::P2PKH, 4999990000, "mzg9RZ1p29uNXu4uTWoMdMERdVXZpunJhW" );
tb.add_out( payment_type::P2PKH, 4999990000, "n2SPW6abRxUnnTSSHp73VGahbPW4WT9GaK" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0200000002d9f627a2806d7584dbb7c6eb46b6aaf5323b30ddddf36332a66c8be04991d2130000000000ffffffffa30d6df727713709de352ba0ce6baa13c14035c093fc370e2cdd5c9261f6e7a80000000000ffffffff02f0ca052a010000001976a914d2276c7ed7af07f697175cc2cbcbbf32da81caba88acf0ca052a010000001976a914e57d9a9af070998bedce991c4d8e39f9c51eb93a88ac00000000" );
}
{ // P2WPKH to P2WPKH
bitcoin_transaction_builder tb;
tb.set_version(2);
tb.add_in( payment_type::P2WPKH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115" ), 1, bytes() );
tb.add_out( payment_type::P2WPKH, 99988480, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001600143307bf6f98832e53a48b144d65c6a95700a93ffb00000000" );
}
{ // P2WSH to P2WSH
bitcoin_transaction_builder tb;
tb.set_version(2);
tb.add_in( payment_type::P2WSH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 2, bytes() );
tb.add_out( payment_type::P2WSH, 99988360, "p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560200000000ffffffff0188b3f505000000001600140000010966776006953d5567439e5e39f86a0d2700000000" );
}
{ // P2SH(WPKH) to P2SH(WPKH)
bitcoin_transaction_builder tb;
tb.set_version( 2 );
tb.add_in( payment_type::P2SH_WPKH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115" ), 3, parse_hex( "ab68025513c3dbd2f7b92a94e0581f5d50f654e7" ) );
tb.add_out( payment_type::P2SH_WPKH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560300000014ab68025513c3dbd2f7b92a94e0581f5d50f654e7ffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000" );
}
{ // P2SH(WSH) to P2SH(WSH)
bitcoin_transaction_builder tb;
tb.set_version(2);
const auto redeem_script = parse_hex( "21038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac");
tb.add_in( payment_type::P2SH_WSH, fc::sha256( "fca01bd539623013f6f945dc6173c395394621ffaa53a9eb6da6e9a2e7c9400e" ), 0, redeem_script );
tb.add_out( payment_type::P2SH_WSH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd" );
const auto tx = tb.get_transaction();
BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010e40c9e7a2e9a66deba953aaff21463995c37361dc45f9f613306239d51ba0fc000000002321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acacffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000" );
}
}
BOOST_AUTO_TEST_SUITE_END()