diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp index a11647de..b81a851c 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -351,7 +352,7 @@ void add_number_to_script(bytes &script, unsigned char data) { add_data_to_script(script, {data}); } -bytes generate_redeem_script(std::vector> key_data) { +bytes generate_redeem_script(std::vector> key_data) { int total_weight = 0; bytes result; add_number_to_script(result, 0); @@ -366,7 +367,7 @@ bytes generate_redeem_script(std::vector> ke result.push_back(OP_ADD); result.push_back(OP_ENDIF); } - int threshold_weight = 2 * total_weight / 3; + uint64_t threshold_weight = 2 * total_weight / 3; add_number_to_script(result, static_cast(threshold_weight)); result.push_back(OP_GREATERTHAN); return result; @@ -375,6 +376,17 @@ 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,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); @@ -422,8 +448,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'; @@ -434,6 +469,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) { @@ -464,10 +526,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()); @@ -476,14 +551,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); @@ -677,4 +761,26 @@ 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); +} + }} // 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..9cf7f05c 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,13 @@ 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); + +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, @@ -73,6 +77,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 +100,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_pw_address(address)) { + } + int64_t nValue; bytes scriptPubKey; diff --git a/tests/peerplays_sidechain/bitcoin_utils_test.cpp b/tests/peerplays_sidechain/bitcoin_utils_test.cpp index c0e6e7c1..c75e12de 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