diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt index 2b9d1db9..849a8024 100755 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -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) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp new file mode 100644 index 00000000..a28f3f42 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp @@ -0,0 +1,125 @@ +#include +#include + +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 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 vec( ps.tellp() ); + if ( !vec.empty() ) { + fc::datastream 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 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( hash.data() ), + reinterpret_cast( sig.data() ), + &sig_len, + reinterpret_cast( privkey.data() ), + secp256k1_nonce_function_rfc6979, + nullptr ) ); // TODO: replace assert with exception + + sig.resize( sig_len ); + + return sig; +} + +std::vector sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& 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( amounts[i] ), i, hash_type, true ); + auto sig = privkey_sign( privkey, sighash, context_sign ); + sig.push_back( static_cast( hash_type ) ); + + signatures.push_back( sig ); + } + return signatures; +} + +void sign_witness_transaction_finalize( bitcoin_transaction& tx, const std::vector& 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 sig_temp( sig.begin(), sig.end() ); + std::vector pubkey_temp( pubkey.begin(), pubkey.end() ); + std::vector 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> sort_sigs( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& amounts, const secp256k1_context_t* context ) +{ + FC_ASSERT( redeem_scripts.size() == amounts.size() ); + + using data = std::pair; + struct comp { + bool operator() (const data& lhs, const data& rhs) const { return lhs.first < rhs.first; } + }; + + std::vector> new_stacks; + + for( size_t i = 0; i < redeem_scripts.size(); i++ ) { + const std::vector& keys = get_pubkey_from_redeemScript( redeem_scripts[i] ); + const auto& sighash = get_signature_hash( tx, redeem_scripts[i], static_cast( amounts[i] ), i, 1, true ).str(); + bytes sighash_temp( parse_hex( sighash ) ); + + std::vector stack( tx.vin[i].scriptWitness ); + std::vector marker( tx.vin[i].scriptWitness.size(), false ); + std::set 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 temp_sig; + for( auto s : sigs ) { + temp_sig.push_back( s.second ); + } + new_stacks.push_back( temp_sig ); + } + return new_stacks; +} + +} } } diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp index a11647de..45d7d6e6 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace graphene { namespace peerplays_sidechain { diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp index 19e10225..5a9bdac3 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp @@ -357,4 +357,4 @@ inline void pack_tx_witness_signature( Stream& s, const std::vector& scrip pack( s, hash_type ); } -} } } \ No newline at end of file +} } } diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp new file mode 100644 index 00000000..49d23cd7 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +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 privkey_sign( const bytes& privkey, const fc::sha256 &hash, const secp256k1_context_t* context_sign = nullptr ); + +std::vector sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& 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& redeem_scripts ); + +bool verify_sig( const bytes& sig, const bytes& pubkey, const bytes& msg, const secp256k1_context_t* context ); + +std::vector> sort_sigs( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& amounts, const secp256k1_context_t* context ); + +} } } \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp index 4d3740be..fe5f2bbd 100755 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp @@ -12,7 +12,7 @@ namespace graphene { namespace peerplays_sidechain { namespace bitcoin { class bitcoin_transaction; using bytes = std::vector; -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; enum class payment_type diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp index d483e23c..1ec13b40 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -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 extract_info_from_block(const std::string &_block); diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index c6c4fb3a..5a9ff3fc 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include namespace graphene { namespace peerplays_sidechain { @@ -527,7 +529,9 @@ std::vector bitcoin_rpc_client::listunspent(const uint32_t minconf, c btc_txout txo; txo.txid_ = entry.second.get_child("txid").get_value(); txo.out_num_ = entry.second.get_child("vout").get_value(); - txo.amount_ = entry.second.get_child("amount").get_value(); + string amount = entry.second.get_child("amount").get_value(); + 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 bitcoin_rpc_client::listunspent_by_address_and_amount(con btc_txout txo; txo.txid_ = entry.second.get_child("txid").get_value(); txo.out_num_ = entry.second.get_child("vout").get_value(); - txo.amount_ = entry.second.get_child("amount").get_value(); + string amount = entry.second.get_child("amount").get_value(); + 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 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 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 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 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 &inputs, const fc::flat_map 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> 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 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>& 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 &tx, std::vector &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("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 &tx, const std::vector &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 &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 &inputs, const fc::flat_map 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 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 != "") { diff --git a/tests/peerplays_sidechain/bitcoin_address_tests.cpp b/tests/peerplays_sidechain/bitcoin_address_tests.cpp new file mode 100644 index 00000000..77e88e04 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_address_tests.cpp @@ -0,0 +1,134 @@ +#include +#include + +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& 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 public_key1 = parse_hex( "03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010" ); + std::vector public_key2 = parse_hex( "0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983" ); + std::vector public_key3 = parse_hex( "033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f" ); + + std::vector address = parse_hex( "a91460cb986f0926e7c4ca1984ca9f56767da2af031e87" ); + std::vector 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 public_key1 = parse_hex( "03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb" ); + std::vector public_key2 = parse_hex( "03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6" ); + std::vector public_key3 = parse_hex( "033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1" ); + + std::vector 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() diff --git a/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp b/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp new file mode 100644 index 00000000..796db061 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp @@ -0,0 +1,170 @@ +#include +#include +#include +#include + +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( 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( 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()