From 3e1e4a53958598166ab9766c095f26c7e3168066 Mon Sep 17 00:00:00 2001 From: gladcow Date: Mon, 10 Feb 2020 13:57:47 +0300 Subject: [PATCH] use bech32 address format --- .../peerplays_sidechain/bitcoin_utils.cpp | 118 +++++++++++++++--- .../bitcoin_utils_test.cpp | 14 ++- 2 files changed, 109 insertions(+), 23 deletions(-) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp index 9cb53999..30e0801a 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -349,32 +349,116 @@ bytes generate_redeem_script(std::vector > k return result; } +/** The Bech32 character set for encoding. */ +const char* charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** Concatenate two byte arrays. */ +bytes cat(bytes x, const bytes& y) { + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** Expand a HRP for use in checksum computation. */ +bytes expand_hrp(const std::string& hrp) { + bytes ret; + 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; +} + +/** Find the polynomial with value coefficients mod the generator as 30-bit. */ +uint32_t polymod(const bytes& values) { + uint32_t chk = 1; + for (size_t i = 0; i < values.size(); ++i) { + uint8_t top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[i] ^ + (-((top >> 0) & 1) & 0x3b6a57b2UL) ^ + (-((top >> 1) & 1) & 0x26508e6dUL) ^ + (-((top >> 2) & 1) & 0x1ea119faUL) ^ + (-((top >> 3) & 1) & 0x3d4233ddUL) ^ + (-((top >> 4) & 1) & 0x2a1462b3UL); + } + return chk; +} + +/** Create a checksum. */ +bytes bech32_checksum(const std::string& hrp, const bytes& values) { + bytes enc = cat(expand_hrp(hrp), values); + enc.resize(enc.size() + 6); + uint32_t mod = polymod(enc) ^ 1; + bytes ret; + ret.resize(6); + for (size_t i = 0; i < 6; ++i) { + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; +} + +/** Encode a Bech32 string. */ +std::string bech32(const std::string& hrp, const bytes& values) { + bytes checksum = bech32_checksum(hrp, values); + bytes combined = cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (size_t i = 0; i < combined.size(); ++i) { + ret += charset[combined[i]]; + } + return ret; +} + +/** Convert from one power-of-2 number base to another. */ +template +bool convertbits(bytes& out, const bytes& in) { + int acc = 0; + int bits = 0; + const int maxv = (1 << tobits) - 1; + const int max_acc = (1 << (frombits + tobits - 1)) - 1; + for (size_t i = 0; i < in.size(); ++i) { + int value = in[i]; + acc = ((acc << frombits) | value) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + } + if (pad) { + if (bits) out.push_back((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + +/** 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 p2wsh_address_from_redeem_script(const bytes& script, bitcoin_network network) { - bytes data; - // add version byte + // calc script hash + fc::sha256 sh = fc::sha256::hash(fc::sha256::hash(reinterpret_cast(&script[0]), script.size())); + bytes wp(sh.data(), sh.data() + sh.data_size()); switch (network) { case(mainnet): - data.push_back(5); - break; + return segwit_addr_encode("bc", 0, wp); case(testnet): - data.push_back(196); - break; case(regtest): - data.push_back(196); - break; + return segwit_addr_encode("tb", 0, wp); default: FC_THROW("Unknown bitcoin network type [${type}]", ("type", network)); } - // add redeem script hash - fc::ripemd160 h = fc::ripemd160::hash(reinterpret_cast(&script[0]), script.size()); - data.insert(data.end(), h.data(), h.data() + h.data_size()); - // calc control sum - fc::sha256 cs = fc::sha256::hash(fc::sha256::hash(reinterpret_cast(&data[0]), data.size())); - // add first 4 bytes of control sum - data.insert(data.end(), cs.data(), cs.data() + 4); - // return base58 encoded data - return fc::to_base58(reinterpret_cast(&data[0]), data.size()); + FC_THROW("Unknown bitcoin network type [${type}]", ("type", network)); } bytes lock_script_for_redeem_script(const bytes &script) diff --git a/tests/peerplays_sidechain/bitcoin_utils_test.cpp b/tests/peerplays_sidechain/bitcoin_utils_test.cpp index 9e1aa1b6..5e3a420a 100644 --- a/tests/peerplays_sidechain/bitcoin_utils_test.cpp +++ b/tests/peerplays_sidechain/bitcoin_utils_test.cpp @@ -47,8 +47,9 @@ BOOST_AUTO_TEST_CASE(pw_transfer) bytes redeem_old =generate_redeem_script(weights_old); // Old PW address std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); - // This address was filled with testnet transaction 8d8a466f6c829175a8bb747860828b59e7774be0bbf79ffdc70d5e75348180ca - BOOST_REQUIRE(old_pw == "2NGLS3x8Vk3vN18672YmSnpASm7FxYcoWu6"); + // This address was filled with testnet transaction 0c0549133bbd5c2b1e629109c58e9af36bd65aeb1ca8570491b6779d72e3cefd + ilog(old_pw); + BOOST_REQUIRE(old_pw == "tb1q624r67hvhysxdwztuxgg4ksw7q4kzs4vxfgp96vjj2jcjw0q4c0qj6gmue"); // key set for the new Primary Wallet std::vector priv_new; @@ -70,22 +71,23 @@ BOOST_AUTO_TEST_CASE(pw_transfer) // New PW address std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); - BOOST_REQUIRE(new_pw == "2MyzbFRwNqj1Y4Q4oWELhDwz5DCHkTndE1S"); + ilog(new_pw); + BOOST_REQUIRE(new_pw == "tb1qhhaes30wwvt3ces3g2dsx3j48gr7fsqagqgk45hpc0dtnaww5d6qsd7em0"); // try to move funds from old wallet to new one // get unspent outputs for old wallet with list_uspent (address should be // added to wallet with import_address before). It should return - // 1 UTXO: [8d8a466f6c829175a8bb747860828b59e7774be0bbf79ffdc70d5e75348180ca:1] + // 1 UTXO: [0c0549133bbd5c2b1e629109c58e9af36bd65aeb1ca8570491b6779d72e3cefd:0] // with 20000 satoshis // So, we creating a raw transaction with 1 input and one output that gets // 20000 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx) // Here we just serialize the transaction without scriptSig in inputs then sign it. btc_outpoint outpoint; - outpoint.hash = fc::uint256("8d8a466f6c829175a8bb747860828b59e7774be0bbf79ffdc70d5e75348180ca"); + outpoint.hash = fc::uint256("0c0549133bbd5c2b1e629109c58e9af36bd65aeb1ca8570491b6779d72e3cefd"); // reverse hash due to the different from_hex algo std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); - outpoint.n = 1; + outpoint.n = 0; btc_in input; input.prevout = outpoint; input.nSequence = 0xffffffff;