From 11722fed0c9c295f4a37c238e19c7e8fac48b0f0 Mon Sep 17 00:00:00 2001 From: gladcow Date: Fri, 20 Mar 2020 13:51:48 +0300 Subject: [PATCH] signing and sending with bitcoin_utils --- .../peerplays_sidechain/bitcoin_utils.cpp | 201 +++++++++- .../peerplays_sidechain/bitcoin_utils.hpp | 36 +- .../sidechain_net_handler_bitcoin.hpp | 6 +- .../sidechain_net_handler_bitcoin.cpp | 358 +++--------------- .../bitcoin_utils_test.cpp | 16 +- 5 files changed, 284 insertions(+), 333 deletions(-) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp index 4e16b0b1..841e4931 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -31,10 +31,13 @@ static const unsigned char OP_16 = 0x60; static const unsigned char OP_IF = 0x63; static const unsigned char OP_ENDIF = 0x68; +static const unsigned char OP_DUP = 0x76; static const unsigned char OP_SWAP = 0x7c; static const unsigned char OP_EQUAL = 0x87; +static const unsigned char OP_EQUALVERIFY = 0x88; static const unsigned char OP_ADD = 0x93; static const unsigned char OP_GREATERTHAN = 0xa0; +static const unsigned char OP_GREATERTHANOREQUAL = 0xa2; static const unsigned char OP_HASH160 = 0xa9; static const unsigned char OP_CHECKSIG = 0xac; @@ -196,7 +199,6 @@ void btc_outpoint::to_bytes(bytes &stream) const { size_t btc_outpoint::fill_from_bytes(const bytes &data, size_t pos) { ReadBytesStream str(data, pos); - // TODO: read size? str.read((unsigned char *)hash.data(), hash.data_size()); n = str.readdata32(); return str.current_pos(); @@ -314,7 +316,7 @@ void add_data_to_script(bytes &script, const bytes &data) { str.writedata(data); } -void add_number_to_script(bytes &script, unsigned char data) { +void add_number_to_script(bytes &script, uint32_t data) { WriteBytesStream str(script); if (data == 0) str.put(OP_0); @@ -350,12 +352,22 @@ void add_number_to_script(bytes &script, unsigned char data) { str.put(OP_15); else if (data == 16) str.put(OP_16); - else - add_data_to_script(script, {data}); + else { + std::vector result; + while (data) { + result.push_back(data & 0xff); + data >>= 8; + } + // - If the most significant byte is >= 0x80 and the value is positive, push a + // new zero-byte to make the significant byte < 0x80 again. + if (result.back() & 0x80) + result.push_back(0); + add_data_to_script(script, result); + } } -bytes generate_redeem_script(std::vector> key_data) { - int total_weight = 0; +bytes generate_redeem_script(std::vector> key_data) { + uint32_t total_weight = 0; bytes result; add_number_to_script(result, 0); for (auto &p : key_data) { @@ -365,19 +377,30 @@ bytes generate_redeem_script(std::vector> ke add_data_to_script(result, bytes(raw_data.begin(), raw_data.begin() + raw_data.size())); result.push_back(OP_CHECKSIG); result.push_back(OP_IF); - add_number_to_script(result, static_cast(p.second)); + add_number_to_script(result, p.second); result.push_back(OP_ADD); result.push_back(OP_ENDIF); } - int threshold_weight = 2 * total_weight / 3; - add_number_to_script(result, static_cast(threshold_weight)); - result.push_back(OP_GREATERTHAN); + uint64_t threshold_weight = 2 * total_weight / 3; + add_number_to_script(result, threshold_weight); + result.push_back(OP_GREATERTHANOREQUAL); return result; } /** The Bech32 character set for encoding. */ const char *charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; +/** The Bech32 character set for decoding. */ +const int8_t charset_rev[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; + /** Concatenate two byte arrays. */ bytes cat(bytes x, const bytes &y) { x.insert(x.end(), y.begin(), y.end()); @@ -412,6 +435,20 @@ uint32_t polymod(const bytes &values) { return chk; } +/** Expand a HRP for use in checksum computation. */ +bytes bech32_expand_hrp(const std::string &hrp) { + bytes ret; + ret.reserve(hrp.size() + 90); + ret.resize(hrp.size() * 2 + 1); + for (size_t i = 0; i < hrp.size(); ++i) { + unsigned char c = hrp[i]; + ret[i] = c >> 5; + ret[i + hrp.size() + 1] = c & 0x1f; + } + ret[hrp.size()] = 0; + return ret; +} + /** Create a checksum. */ bytes bech32_checksum(const std::string &hrp, const bytes &values) { bytes enc = cat(expand_hrp(hrp), values); @@ -425,8 +462,17 @@ bytes bech32_checksum(const std::string &hrp, const bytes &values) { return ret; } +/** Verify a checksum. */ +bool bech32_verify_checksum(const std::string &hrp, const bytes &values) { + // PolyMod computes what value to xor into the final values to make the checksum 0. However, + // if we required that the checksum was 0, it would be the case that appending a 0 to a valid + // list of values would result in a new valid list. For that reason, Bech32 requires the + // resulting checksum to be 1 instead. + return polymod(cat(bech32_expand_hrp(hrp), values)) == 1; +} + /** Encode a Bech32 string. */ -std::string bech32(const std::string &hrp, const bytes &values) { +std::string bech32_encode(const std::string &hrp, const bytes &values) { bytes checksum = bech32_checksum(hrp, values); bytes combined = cat(values, checksum); std::string ret = hrp + '1'; @@ -437,6 +483,33 @@ std::string bech32(const std::string &hrp, const bytes &values) { return ret; } +/** Decode a Bech32 string. */ +bytes bech32_decode(const std::string &str) { + if (str.size() > 90) + FC_THROW("Invalid bech32 string ${a}", ("a", str)); + for (unsigned char c : str) { + if (c < 33 || c > 126) + FC_THROW("Invalid bech32 string ${a}", ("a", str)); + if (c >= 'A' && c <= 'Z') + FC_THROW("Invalid bech32 string ${a}", ("a", str)); + } + size_t pos = str.rfind('1'); + if (pos == str.npos || pos == 0 || pos + 7 > str.size()) + FC_THROW("Invalid bech32 string ${a}", ("a", str)); + std::string hrp = str.substr(0, pos); + bytes values(str.size() - 1 - pos); + for (size_t i = 0; i < str.size() - 1 - pos; ++i) { + unsigned char c = str[i + pos + 1]; + int8_t rev = (c < 33 || c > 126) ? -1 : charset_rev[c]; + if (rev == -1) + FC_THROW("Invalid bech32 string ${a}", ("a", str)); + values[i] = rev; + } + if (!bech32_verify_checksum(hrp, values)) + FC_THROW("Invalid bech32 string ${a}", ("a", str)); + return bytes(values.begin(), values.end() - 6); +} + /** Convert from one power-of-2 number base to another. */ template bool convertbits(bytes &out, const bytes &in) { @@ -467,10 +540,23 @@ std::string segwit_addr_encode(const std::string &hrp, uint8_t witver, const byt bytes enc; enc.push_back(witver); convertbits<8, 5, true>(enc, witprog); - std::string ret = bech32(hrp, enc); + std::string ret = bech32_encode(hrp, enc); return ret; } +/** Decode a SegWit address. */ +bytes segwit_addr_decode(const std::string &addr) { + bytes dec = bech32_decode(addr); + if (dec.size() < 1) + FC_THROW("Invalid bech32 address ${a}", ("a", addr)); + bytes conv; + if (!convertbits<5, 8, false>(conv, bytes(dec.begin() + 1, dec.end())) || + conv.size() < 2 || conv.size() > 40 || dec[0] > 16 || (dec[0] == 0 && conv.size() != 20 && conv.size() != 32)) { + FC_THROW("Invalid bech32 address ${a}", ("a", addr)); + } + return conv; +} + std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network) { // calc script hash fc::sha256 sh = fc::sha256::hash(reinterpret_cast(&script[0]), script.size()); @@ -479,14 +565,23 @@ std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_networ case (mainnet): return segwit_addr_encode("bc", 0, wp); case (testnet): - case (regtest): return segwit_addr_encode("tb", 0, wp); + case (regtest): + return segwit_addr_encode("bcrt", 0, wp); default: FC_THROW("Unknown bitcoin network type"); } FC_THROW("Unknown bitcoin network type"); } +bytes lock_script_from_pw_address(const std::string &address) { + bytes result; + result.push_back(OP_0); + bytes script_hash = segwit_addr_decode(address); + add_data_to_script(result, script_hash); + return result; +} + bytes lock_script_for_redeem_script(const bytes &script) { bytes result; result.push_back(OP_0); @@ -680,5 +775,85 @@ bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, const std::vector< return ret; } +std::string get_weighted_multisig_address(const std::vector> &public_keys, bitcoin_network network) { + std::vector> key_data; + for (auto p : public_keys) { + fc::ecc::public_key_data kd; + fc::from_hex(p.first, kd.begin(), kd.size()); + key_data.push_back(std::make_pair(fc::ecc::public_key(kd), p.second)); + } + bytes redeem_script = generate_redeem_script(key_data); + return p2wsh_address_from_redeem_script(redeem_script, network); +} + +bytes get_weighted_multisig_redeem_script(std::vector> public_keys) { + std::vector> key_data; + for (auto p : public_keys) + { + fc::ecc::public_key_data kd; + fc::from_hex(p.first, kd.begin(), kd.size()); + key_data.push_back(std::make_pair(fc::ecc::public_key(kd), p.second)); + } + return generate_redeem_script(key_data); +} + +std::string bytes_to_hex(const bytes &data) +{ + return fc::to_hex((char*)&data[0], data.size()); +} + +bytes hex_to_bytes(const std::string &hex_str) +{ + bytes res; + res.resize(hex_str.size() / 2); + fc::from_hex(hex_str, (char*)&res[0], res.size()); + return res; +} + +bool is_bech_address(const std::string &address) +{ + auto pos = address.find('1', 0); + if(pos == std::string::npos) + return false; + std::string hrp = address.substr(0, pos); + return (hrp == "bc") || (hrp == "tb") || (hrp == "bcrt"); +} + +bytes lock_script_from_legacy_address(const std::string &address) { + std::vector data = fc::from_base58(address); + bytes addr_data((unsigned char*)&data[0], (unsigned char*)&data[0] + data.size()); + // p2pkh + if( addr_data.size() == 25 && ( addr_data[0] == 0x00 || + addr_data[0] == 0x6f ) ) { + // drop checksum + addr_data.erase(addr_data.end() - 4); + bytes result; + result.push_back(OP_DUP); + result.push_back(OP_HASH160); + add_data_to_script(result, addr_data); + result.push_back(OP_EQUALVERIFY); + result.push_back(OP_CHECKSIG); + return result; + } + // p2sh + if( addr_data.size() == 25 && ( addr_data[0] == 0x05 || + addr_data[0] == 0xc4 ) ) { + // drop checksum + addr_data.erase(addr_data.end() - 4); + bytes result; + result.push_back(OP_HASH160); + add_data_to_script(result, addr_data); + result.push_back(OP_EQUAL); + return result; + } + FC_THROW("Unsupported bitcoin address type in ${addr}", ("addr", address)); +} + +bytes lock_script_from_address(const std::string &address) +{ + if(is_bech_address(address)) + return lock_script_from_pw_address(address); + return lock_script_from_legacy_address(address); +} }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp index 9b2dc0c1..eba39114 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp @@ -10,9 +10,14 @@ enum bitcoin_network { regtest }; -bytes generate_redeem_script(std::vector> key_data); -std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network = mainnet); +bytes generate_redeem_script(std::vector> key_data); +std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network = regtest); bytes lock_script_for_redeem_script(const bytes &script); +bytes lock_script_from_pw_address(const std::string &address); +bytes lock_script_from_address(const std::string &address); + +std::string get_weighted_multisig_address(const std::vector> &public_keys, bitcoin_network network = regtest); +bytes get_weighted_multisig_redeem_script(std::vector> public_keys); std::vector signatures_for_raw_transaction(const bytes &unsigned_tx, std::vector in_amounts, @@ -64,6 +69,10 @@ bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, const std::vector> &signatures, const bytes &redeem_script); + +std::string bytes_to_hex(const bytes& data); +bytes hex_to_bytes(const std::string& hex_str); + struct btc_outpoint { fc::uint256 hash; uint32_t n; @@ -73,6 +82,19 @@ struct btc_outpoint { }; struct btc_in { + btc_in() = default; + btc_in(const btc_in &) = default; + btc_in(btc_in &&) = default; + btc_in &operator=(const btc_in &) = default; + + btc_in(const std::string &txid, uint32_t out, uint32_t sequence = 0xffffffff) { + prevout.n = out; + prevout.hash = fc::uint256(txid); + // reverse hash due to the different from_hex algo in bitcoin + std::reverse(prevout.hash.data(), prevout.hash.data() + prevout.hash.data_size()); + nSequence = sequence; + } + btc_outpoint prevout; bytes scriptSig; uint32_t nSequence; @@ -83,6 +105,16 @@ struct btc_in { }; struct btc_out { + btc_out() = default; + btc_out(const btc_out &) = default; + btc_out(btc_out &&) = default; + btc_out &operator=(const btc_out &) = default; + + btc_out(const std::string &address, uint64_t amount) : + nValue(amount), + scriptPubKey(lock_script_from_address(address)) { + } + int64_t nValue; bytes scriptPubKey; 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 2bc3cb5f..394110cd 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 @@ -142,9 +142,9 @@ private: std::string sign_transaction_psbt(const sidechain_transaction_object &sto); std::string sign_transaction_standalone(const sidechain_transaction_object &sto); - bool send_transaction_raw(const sidechain_transaction_object &sto, std::string &sidechain_transaction); - bool send_transaction_psbt(const sidechain_transaction_object &sto, std::string &sidechain_transaction); - bool send_transaction_standalone(const sidechain_transaction_object &sto, std::string &sidechain_transaction); + 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 3ec14886..d092c330 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace graphene { namespace peerplays_sidechain { @@ -1483,24 +1484,23 @@ std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector< // Function to actually add signature should return transaction with added signature string, or empty string in case of failure std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_transaction_object &sto) { std::string new_tx = ""; - //new_tx = sign_transaction_raw(sto, complete); + //new_tx = sign_transaction_raw(sto); if (sto.object_id.type() == 30) { - new_tx = sign_transaction_psbt(sto, complete); + new_tx = sign_transaction_psbt(sto); } else { - new_tx = sign_transaction_standalone(sto, complete); + new_tx = sign_transaction_standalone(sto); } - //new_tx = sign_transaction_standalone(sto, complete); + //new_tx = sign_transaction_standalone(sto); return new_tx; } -bool sidechain_net_handler_bitcoin::send_transaction(const sidechain_transaction_object &sto, std::string &sidechain_transaction) { - sidechain_transaction = ""; - //return send_transaction_raw(sto, sidechain_transaction); - //return send_transaction_psbt(sto, sidechain_transaction); +std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_transaction_object &sto) { + //return send_transaction_raw(sto); + //return send_transaction_psbt(sto); if (sto.object_id.type() == 30) { - return send_transaction_psbt(sto, sidechain_transaction); + return send_transaction_psbt(sto); } else { - return send_transaction_standalone(sto, sidechain_transaction); + return send_transaction_standalone(sto); } } @@ -1598,99 +1598,6 @@ libbitcoin::machine::operation script_num(uint32_t val) return result; } -libbitcoin::chain::script get_weighted_multisig_witness_script(const std::vector> &son_pubkeys) -{ - using namespace libbitcoin; - using namespace libbitcoin::chain; - using namespace libbitcoin::machine; - using namespace libbitcoin::wallet; - - // Online visualizer/debugger - // https://siminchen.github.io/bitcoinIDE/build/editor.html - // - // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - // 03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275 OP_CHECKSIG - // OP_IF OP_1 OP_ELSE 0 OP_ENDIF - // OP_SWAP - // 02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // OP_SWAP - // 02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73 OP_CHECKSIG - // OP_IF OP_1 OP_ADD OP_ENDIF - // 11 OP_GREATERTHANOREQUAL - - libbitcoin::machine::operation::list witness_script_ops; - - uint16_t idx = 0; - uint32_t total_weight = 0; - for (auto son_pubkey : son_pubkeys) { - ec_public key = ec_public(son_pubkey.first); - data_chunk key_data = to_chunk(key.point()); - uint16_t weight = son_pubkey.second; - - total_weight = total_weight + weight; - - witness_script_ops.emplace_back(key_data); - witness_script_ops.emplace_back(opcode::checksig); - - witness_script_ops.emplace_back(opcode::if_); - witness_script_ops.emplace_back(script_num(weight)); - if (idx == 0) { - witness_script_ops.emplace_back(opcode::else_); - witness_script_ops.emplace_back(opcode::push_size_0); - } else { - witness_script_ops.emplace_back(opcode::add); - } - witness_script_ops.emplace_back(opcode::endif); - - if (idx < son_pubkeys.size() - 1) { - witness_script_ops.emplace_back(opcode::swap); - } - - idx = idx + 1; - } - witness_script_ops.emplace_back(script_num(total_weight * 2 / 3)); - witness_script_ops.emplace_back(opcode::greaterthanorequal); - - return script(witness_script_ops); -} - void read_tx_data_from_string(const std::string &string_buf, std::vector &tx, std::vector &in_amounts) { std::stringstream ss(string_buf); @@ -1718,81 +1625,14 @@ std::string save_tx_data_to_string(const std::vector &tx, const s } std::string sidechain_net_handler_bitcoin::create_multisig_address_standalone(const std::vector> &son_pubkeys) { - - //using namespace libbitcoin; - //using namespace libbitcoin::chain; - //using namespace libbitcoin::machine; - //using namespace libbitcoin::wallet; - // - //uint32_t nrequired = son_pubkeys.size() * 2 / 3 + 1; - //point_list keys; - //for (auto son_pubkey : son_pubkeys) { - // keys.push_back(ec_public(son_pubkey.first)); - //} - //script witness_script = script::to_pay_multisig_pattern(nrequired, keys); - // - //// sha256 of witness script - //data_chunk multisig_hash = to_chunk(sha256_hash(witness_script.to_data(0))); - // - //// redeem script - //libbitcoin::machine::operation::list redeemscript_ops{libbitcoin::machine::operation(opcode(0)), libbitcoin::machine::operation(multisig_hash)}; - //script redeem_script = script(redeemscript_ops); - // - //// address - //payment_address address = payment_address(redeem_script, payment_address_p2sh); - // - //std::stringstream ss; - // - // - //ss << "{\"result\": {\"address\": \"" << address.encoded() << "\", \"redeemScript\": \"" << encode_base16(witness_script.to_data(0)) << "\"" << "}, \"error\":null}"; - //std::string res = ss.str(); - // - //std::cout << "Redeem Script Hash: " << encode_base16(address.hash()) << std::endl; - //std::cout << "Payment Address: " << address.encoded() << std::endl; - //std::cout << "Redeem Script: " << redeem_script.to_string(0) << std::endl; - //std::cout << "Witness Script: " << witness_script.to_string(0) << std::endl; - //std::cout << "Witness Script: " << encode_base16(witness_script.to_data(0)) << std::endl; - // - //std::cout << res << std::endl; - ////create_multisig_address_psbt(son_pubkeys); - // - //return res; - - using namespace libbitcoin; - using namespace libbitcoin::chain; - using namespace libbitcoin::machine; - using namespace libbitcoin::wallet; - - script witness_script = get_weighted_multisig_witness_script(son_pubkeys); - - std::cout << "Witness Script is valid: " << witness_script.is_valid() << std::endl; - std::cout << "Witness Script operations are valid: " << witness_script.is_valid_operations() << std::endl; - - // sha256 of witness script - data_chunk multisig_hash = to_chunk(sha256_hash(witness_script.to_data(0))); - - // redeem script - libbitcoin::machine::operation::list redeemscript_ops{libbitcoin::machine::operation(opcode(0)), libbitcoin::machine::operation(multisig_hash)}; - script redeem_script = script(redeemscript_ops); - - // address - payment_address address = payment_address(redeem_script, payment_address_p2sh); + bytes redeem_script = get_weighted_multisig_redeem_script(son_pubkeys); + std::string address = get_weighted_multisig_address(son_pubkeys); std::stringstream ss; - - ss << "{\"result\": {\"address\": \"" << address.encoded() << "\", \"redeemScript\": \"" << encode_base16(witness_script.to_data(0)) << "\"" + ss << "{\"result\": {\"address\": \"" << address << "\", \"redeemScript\": \"" << bytes_to_hex(redeem_script) << "\"" << "}, \"error\":null}"; std::string res = ss.str(); - std::cout << "Redeem Script Hash: " << encode_base16(address.hash()) << std::endl; - std::cout << "Payment Address: " << address.encoded() << std::endl; - std::cout << "Redeem Script: " << redeem_script.to_string(0) << std::endl; - std::cout << "Witness Script: " << witness_script.to_string(0) << std::endl; - std::cout << "Witness Script: " << encode_base16(witness_script.to_data(0)) << std::endl; - - std::cout << res << std::endl; - //create_multisig_address_psbt(son_pubkeys); - return res; } @@ -1805,88 +1645,22 @@ std::string sidechain_net_handler_bitcoin::create_transaction_psbt(const std::ve } std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const std::vector &inputs, const fc::flat_map outputs) { - // Examples - - // Transaction with no inputs and outputs - //bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" createrawtransaction '[]' '[]' - //02000000000000000000 - //bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" decoderawtransaction 02000000000000000000 - //{ - // "txid": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", - // "hash": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", - // "version": 2, - // "size": 10, - // "vsize": 10, - // "weight": 40, - // "locktime": 0, - // "vin": [ - // ], - // "vout": [ - // ] - //} - - // Transaction with input and output - //{ - // "txid": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b", - // "hash": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b", - // "version": 2, - // "size": 83, - // "vsize": 83, - // "weight": 332, - // "locktime": 0, - // "vin": [ - // { - // "txid": "3d322dc2640239a2e68e182b254d19c88e5172a61947f94a105c3f57618092ff", - // "vout": 0, - // "scriptSig": { - // "asm": "", - // "hex": "" - // }, - // "sequence": 4294967295 - // } - // ], - // "vout": [ - // { - // "value": 1.00000000, - // "n": 0, - // "scriptPubKey": { - // "asm": "OP_HASH160 b87c323018cae236eb03a1f63000c85b672270f6 OP_EQUAL", - // "hex": "a914b87c323018cae236eb03a1f63000c85b672270f687", - // "reqSigs": 1, - // "type": "scripthash", - // "addresses": [ - // "2NA4h6sc9oZ4ogfNKU9Wp6fkqPZLZPqqpgf" - // ] - // } - // } - // ] - //} - libbitcoin::chain::transaction tx; - tx.set_version(2u); + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = true; std::vector in_amounts; for (auto in : inputs) { - libbitcoin::chain::input bin; - libbitcoin::hash_digest tx_id; - libbitcoin::decode_hash(tx_id, in.txid_); - bin.set_previous_output(libbitcoin::chain::output_point(tx_id, in.out_num_)); - bin.set_sequence(libbitcoin::max_input_sequence); - tx.inputs().push_back(bin); + tx.vin.push_back(btc_in(in.txid_, in.out_num_)); in_amounts.push_back(in.amount_); } for (auto out : outputs) { - libbitcoin::chain::output bout; - uint64_t satoshis = out.second * 100000000.0; - bout.set_value(satoshis); - libbitcoin::wallet::payment_address addr(out.first); - if (addr.version() == payment_address_p2sh) { - bout.set_script(libbitcoin::chain::script::to_pay_script_hash_pattern(addr)); - } else { - bout.set_script(libbitcoin::chain::script::to_pay_key_hash_pattern(addr)); - } - tx.outputs().push_back(bout); + tx.vout.push_back(btc_out(out.first, uint64_t(out.second * 100000000.0))); } - std::string tx_raw = save_tx_data_to_string(tx.to_data(), in_amounts); + bytes tx_buf; + tx.to_bytes(tx_buf); + std::string tx_raw = save_tx_data_to_string(tx_buf, in_amounts); return tx_raw; } @@ -1984,40 +1758,29 @@ 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); - uint16_t weight = 0; std::string prvkey = get_private_key(pubkey); - using namespace libbitcoin; - using namespace libbitcoin::machine; - using namespace libbitcoin::wallet; - - libbitcoin::data_chunk data; - libbitcoin::ec_secret key; - libbitcoin::decode_base16(key, prvkey); + bytes unsigned_tx; std::vector in_amounts; - read_tx_data_from_string(sto.transaction, data, in_amounts); - libbitcoin::chain::transaction tx; - if (!tx.from_data(data)) { - elog("Failed to decode transaction ${tx}", ("tx", sto.transaction)); - return ""; - } + read_tx_data_from_string(sto.transaction, unsigned_tx, in_amounts); std::vector> son_pubkeys; for (auto& son: sto.signers) { std::string pub_key = son.sidechain_public_keys.at(sidechain_type::bitcoin); son_pubkeys.push_back(std::make_pair(pub_key, son.weight)); - if (son.son_id == plugin.get_current_son_id()) - weight = son.weight; } - libbitcoin::chain::script witness_script = get_weighted_multisig_witness_script(son_pubkeys); - vector sigs; - sigs.resize(tx.inputs().size()); - for (unsigned int itr = 0; itr < sigs.size(); itr++) { - libbitcoin::chain::script::create_endorsement(sigs.at(itr), key, witness_script, tx, itr, sighash_algorithm::all, script_version::zero, in_amounts[itr]); + bytes witness_script = get_weighted_multisig_redeem_script(son_pubkeys); + + fc::optional btc_private_key = graphene::utilities::wif_to_key(prvkey); + if (!btc_private_key) + { + elog("Invalid private key ${pk}", ("pk", prvkey)); + return ""; } + vector sigs = signatures_for_raw_transaction(unsigned_tx, in_amounts, witness_script, *btc_private_key); std::string tx_signature = write_byte_arrays_to_string(sigs); - complete = (sto.current_weight + weight > sto.threshold); + ilog("signatures: ${s}", ("s", tx_signature)); return tx_signature; } @@ -2067,52 +1830,33 @@ std::string sidechain_net_handler_bitcoin::send_transaction_psbt(const sidechain return ""; } -bool sidechain_net_handler_bitcoin::send_transaction_standalone(const sidechain_transaction_object &sto, std::string &sidechain_transaction) { - sidechain_transaction = ""; - - libbitcoin::data_chunk tx_buf; +std::string sidechain_net_handler_bitcoin::send_transaction_standalone(const sidechain_transaction_object &sto) { + bytes unsigned_tx; std::vector in_amounts; - read_tx_data_from_string(sto.transaction, tx_buf, in_amounts); - libbitcoin::chain::transaction tx; - if (!tx.from_data(tx_buf)) { - elog("Failed to decode transaction ${tx}", ("tx", sto.transaction)); - return ""; - } + read_tx_data_from_string(sto.transaction, unsigned_tx, in_amounts); std::vector> son_pubkeys; for (auto& son: sto.signers) { std::string pub_key = son.sidechain_public_keys.at(sidechain_type::bitcoin); son_pubkeys.push_back(std::make_pair(pub_key, son.weight)); } - libbitcoin::chain::script witness_script = get_weighted_multisig_witness_script(son_pubkeys); + bytes witness_script = get_weighted_multisig_redeem_script(son_pubkeys); - std::vector scripts; - for (uint32_t idx = 0; idx < tx.inputs().size(); idx++) - scripts.push_back({witness_script.to_data(0)}); - - for (auto sig_data: sto.signatures) { - if (sig_data.second.size()) { - std::vector sigs = read_byte_arrays_from_string(sig_data.second); - FC_ASSERT(sigs.size() == scripts.size()); - // place signatures in reverse order - for (uint32_t idx = 0; idx < scripts.size(); idx++) - { - auto& s = scripts.at(idx); - s.insert(s.begin(), sigs[idx]); - } - } else { - for (uint32_t idx = 0; idx < scripts.size(); idx++) - { - auto& s = scripts.at(idx); - s.insert(s.begin(), libbitcoin::data_chunk()); - } - } + uint32_t inputs_number = in_amounts.size(); + vector dummy; + dummy.resize(inputs_number); + vector> signatures; + for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) { + if(sto.signatures[idx].second.empty()) + signatures.push_back(dummy); + else + signatures.push_back(read_byte_arrays_from_string(sto.signatures[idx].second)); } - - for (uint32_t idx = 0; idx < tx.inputs().size(); idx++) - tx.inputs()[idx].set_witness(scripts[idx]); - - return bitcoin_client->sendrawtransaction(libbitcoin::encode_base16(tx.to_data())); + bytes signed_tx = add_signatures_to_unsigned_tx(unsigned_tx, signatures, witness_script); + std::string txstr = bytes_to_hex(signed_tx); + std::string res = bitcoin_client->sendrawtransaction(txstr); + ilog("send_transaction_standalone: ${tx}, [${res}]", ("tx", txstr)("res", res)); + return res; } void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { diff --git a/tests/peerplays_sidechain/bitcoin_utils_test.cpp b/tests/peerplays_sidechain/bitcoin_utils_test.cpp index c0e6e7c1..58a01def 100644 --- a/tests/peerplays_sidechain/bitcoin_utils_test.cpp +++ b/tests/peerplays_sidechain/bitcoin_utils_test.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(pw_transfer) for(auto& key: priv_old) pub_old.push_back(key.get_public_key()); // old key weights - std::vector > weights_old; + std::vector > weights_old; for(unsigned i = 0; i < 15; ++i) weights_old.push_back(std::make_pair(pub_old[i], i + 1)); // redeem script for old PW @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(pw_transfer) for(auto& key: priv_new) pub_new.push_back(key.get_public_key()); // new key weights - std::vector > weights_new; + std::vector > weights_new; for(unsigned i = 0; i < 15; ++i) weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); // redeem script for new PW @@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign) for(auto& key: priv_old) pub_old.push_back(key.get_public_key()); // old key weights - std::vector > weights_old; + std::vector > weights_old; for(unsigned i = 0; i < 15; ++i) weights_old.push_back(std::make_pair(pub_old[i], i + 1)); // redeem script for old PW @@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign) for(auto& key: priv_new) pub_new.push_back(key.get_public_key()); // new key weights - std::vector > weights_new; + std::vector > weights_new; for(unsigned i = 0; i < 15; ++i) weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); // redeem script for new PW @@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign2) for(auto& key: priv_old) pub_old.push_back(key.get_public_key()); // old key weights - std::vector > weights_old; + std::vector > weights_old; for(unsigned i = 0; i < 15; ++i) weights_old.push_back(std::make_pair(pub_old[i], i + 1)); // redeem script for old PW @@ -268,7 +268,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign2) for(auto& key: priv_new) pub_new.push_back(key.get_public_key()); // new key weights - std::vector > weights_new; + std::vector > weights_new; for(unsigned i = 0; i < 15; ++i) weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); // redeem script for new PW @@ -345,7 +345,7 @@ BOOST_AUTO_TEST_CASE(pw_partially_sign) for(auto& key: priv_old) pub_old.push_back(key.get_public_key()); // old key weights - std::vector > weights_old; + std::vector > weights_old; for(unsigned i = 0; i < 15; ++i) weights_old.push_back(std::make_pair(pub_old[i], i + 1)); // redeem script for old PW @@ -370,7 +370,7 @@ BOOST_AUTO_TEST_CASE(pw_partially_sign) for(auto& key: priv_new) pub_new.push_back(key.get_public_key()); // new key weights - std::vector > weights_new; + std::vector > weights_new; for(unsigned i = 0; i < 15; ++i) weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); // redeem script for new PW