diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp index 51870311..d0f44182 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -375,6 +375,18 @@ bytes generate_redeem_script(std::vector> ke /** 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()); @@ -409,6 +421,21 @@ 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); @@ -422,8 +449,18 @@ 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'; @@ -434,6 +471,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) { @@ -460,15 +524,32 @@ bool convertbits(bytes &out, const bytes &in) { } /** Encode a SegWit address. */ -std::string segwit_addr_encode(const std::string &hrp, uint8_t witver, const bytes &witprog) { - bytes enc; - enc.push_back(witver); - convertbits<8, 5, true>(enc, witprog); - std::string ret = bech32(hrp, enc); - return ret; +std::string segwit_addr_encode(const std::string& hrp, uint8_t witver, const bytes& witprog) +{ + bytes enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + std::string ret = bech32_encode(hrp, enc); + return ret; } -std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network) { +/** 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()); bytes wp(sh.data(), sh.data() + sh.data_size()); @@ -484,6 +565,14 @@ std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_networ 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); 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 18adb3ca..397a5e2d 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 @@ -13,6 +13,8 @@ enum bitcoin_network { bytes generate_redeem_script(std::vector> key_data); std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network = mainnet); bytes lock_script_for_redeem_script(const bytes &script); +bytes lock_script_from_pw_address(const std::string &address); + std::string get_weighted_multisig_address(const std::vector>& public_keys); std::vector signatures_for_raw_transaction(const bytes &unsigned_tx, @@ -74,6 +76,20 @@ 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; @@ -84,6 +100,17 @@ 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_pw_address(address)) + { + } + int64_t nValue; bytes scriptPubKey; diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index a68b39be..68ae4ce8 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -754,10 +755,20 @@ std::string sidechain_net_handler_bitcoin::transfer_all_btc(const std::string &f } } - fc::flat_map outs; - outs[to_address] = total_amount - min_amount; + btc_tx tx; + tx.hasWitness = true; + tx.nVersion = 2; + tx.nLockTime = 0; + for(const auto& utx: unspent_utxo) + { + tx.vin.push_back(btc_in(utx.txid_, utx.out_num_)); + } + tx.vout.push_back(btc_out(to_address, uint64_t((total_amount - min_amount) * 100000000.0))); - std::string reply_str = bitcoin_client->createrawtransaction(unspent_utxo, outs); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + + std::string reply_str = fc::to_hex((char*)&unsigned_tx[0], unsigned_tx.size()); return sign_and_send_transaction_with_wallet(reply_str); } @@ -768,13 +779,7 @@ std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet(co return ""; } - std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; - - std::stringstream ss(pw_address_json); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - std::string pw_address = json.get("address"); + std::string pw_address = obj->addresses.find(sidechain_type::bitcoin)->second; std::string txid = swdo.sidechain_transaction_id; std::string suid = swdo.sidechain_uid; @@ -784,20 +789,18 @@ std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet(co uint64_t min_fee_rate = 1000; fee_rate = std::max(fee_rate, min_fee_rate); deposit_amount -= fee_rate; // Deduct minimum relay fee - double transfer_amount = (double)deposit_amount / 100000000.0; - std::vector ins; - fc::flat_map outs; + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = true; + tx.vin.push_back(btc_in(txid, std::stoul(nvout))); + tx.vout.push_back(btc_out(pw_address, deposit_amount)); - btc_txout utxo; - utxo.txid_ = txid; - utxo.out_num_ = std::stoul(nvout); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); - ins.push_back(utxo); - - outs[pw_address] = transfer_amount; - - std::string reply_str = bitcoin_client->createrawtransaction(ins, outs); + std::string reply_str = fc::to_hex((char*)&unsigned_tx[0], unsigned_tx.size()); return sign_and_send_transaction_with_wallet(reply_str); } @@ -808,13 +811,7 @@ std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wall return ""; } - std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; - - std::stringstream ss(pw_address_json); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - std::string pw_address = json.get("address"); + std::string pw_address = obj->addresses.find(sidechain_type::bitcoin)->second; uint64_t fee_rate = bitcoin_client->estimatesmartfee(); uint64_t min_fee_rate = 1000; @@ -838,13 +835,22 @@ std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wall } } - fc::flat_map outs; - outs[swwo.withdraw_address] = swwo.withdraw_amount.value / 100000000.0; - if ((total_amount - min_amount) > 0.0) { - outs[pw_address] = total_amount - min_amount; + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = true; + for(const auto& utxo: unspent_utxo) + tx.vin.push_back(btc_in(utxo.txid_, utxo.amount_)); + tx.vout.push_back(btc_out(swwo.withdraw_address, swwo.withdraw_amount.value)); + if((total_amount - min_amount) > 0.0) + { + tx.vout.push_back(btc_out(pw_address, (total_amount - min_amount) * 100000000.0)); } - std::string reply_str = bitcoin_client->createrawtransaction(unspent_utxo, outs); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + + std::string reply_str = fc::to_hex((char*)&unsigned_tx[0], unsigned_tx.size()); return sign_and_send_transaction_with_wallet(reply_str); }