use bech32 address format
This commit is contained in:
parent
dcd6febb1d
commit
3e1e4a5395
2 changed files with 109 additions and 23 deletions
|
|
@ -349,32 +349,116 @@ bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int> > 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<int frombits, int tobits, bool pad>
|
||||
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<const char*>(&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<const char*>(&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<const char*>(&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<const char*>(&data[0]), data.size());
|
||||
FC_THROW("Unknown bitcoin network type [${type}]", ("type", network));
|
||||
}
|
||||
|
||||
bytes lock_script_for_redeem_script(const bytes &script)
|
||||
|
|
|
|||
|
|
@ -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<fc::ecc::private_key> 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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue