From 79cc08c8bf346c594d8920d0a02a1c1da1141b00 Mon Sep 17 00:00:00 2001 From: gladcow Date: Mon, 27 Jan 2020 17:23:52 +0300 Subject: [PATCH] sending PW transfer in test --- .../peerplays_sidechain/bitcoin_utils.cpp | 391 +++++++++++++----- .../peerplays_sidechain/bitcoin_utils.hpp | 21 +- .../bitcoin_utils_test.cpp | 85 +++- 3 files changed, 376 insertions(+), 121 deletions(-) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp index 8ac185f8..c8eed5cd 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -10,43 +10,76 @@ namespace graphene { namespace peerplays_sidechain { static const unsigned char OP_IF = 0x63; static const unsigned char OP_ENDIF = 0x68; static const unsigned char OP_SWAP = 0x7c; +static const unsigned char OP_EQUAL = 0x87; static const unsigned char OP_ADD = 0x93; static const unsigned char OP_GREATERTHAN = 0xa0; +static const unsigned char OP_HASH160 = 0xa9; static const unsigned char OP_CHECKSIG = 0xac; class WriteBytesStream{ public: WriteBytesStream(bytes& buffer) : storage_(buffer) {} - bool write( const char* d, size_t s ) { - storage_.insert(storage_.end(), d, d + s); - return true; - } - - void add( const unsigned char* d, size_t s ) { + void write(const unsigned char* d, size_t s) { storage_.insert(storage_.end(), d, d + s); } - void add( const bytes& data) { - storage_.insert(storage_.end(), data.begin(), data.end()); - } - - void add(int32_t val) - { - const char* data = reinterpret_cast(&val); - write(data, sizeof(val)); - } - - void add(uint32_t val) - { - const char* data = reinterpret_cast(&val); - write(data, sizeof(val)); - } - - bool put(char c) { + bool put(unsigned char c) { storage_.push_back(c); return true; } + + void writedata8(uint8_t obj) + { + write((unsigned char*)&obj, 1); + } + + void writedata16(uint16_t obj) + { + obj = htole16(obj); + write((unsigned char*)&obj, 2); + } + + void writedata32(uint32_t obj) + { + obj = htole32(obj); + write((unsigned char*)&obj, 4); + } + + void writedata64(uint64_t obj) + { + obj = htole64(obj); + write((unsigned char*)&obj, 8); + } + + void write_compact_int(uint64_t val) + { + if (val < 253) + { + writedata8(val); + } + else if (val <= std::numeric_limits::max()) + { + writedata8(253); + writedata16(val); + } + else if (val <= std::numeric_limits::max()) + { + writedata8(254); + writedata32(val); + } + else + { + writedata8(255); + writedata64(val); + } + } + + void writedata(const bytes& data) + { + write_compact_int(data.size()); + write(&data[0], data.size()); + } private: bytes& storage_; }; @@ -55,7 +88,15 @@ class ReadBytesStream{ public: ReadBytesStream(const bytes& buffer, size_t pos = 0) : storage_(buffer), pos_(pos), end_(buffer.size()) {} - inline bool read( char* d, size_t s ) { + size_t current_pos() const { return pos_; } + void set_pos(size_t pos) + { + if(pos > end_) + FC_THROW("Invalid position in BTC tx buffer"); + pos_ = pos; + } + + inline bool read( unsigned char* d, size_t s ) { if( end_ - pos_ >= s ) { memcpy( d, &storage_[pos_], s ); pos_ += s; @@ -64,34 +105,7 @@ public: FC_THROW( "invalid bitcoin tx buffer" ); } - void direct_read( unsigned char* d, size_t s ) { - if( end_ - pos_ >= s ) { - memcpy( d, &storage_[pos_], s ); - pos_ += s; - } - FC_THROW( "invalid bitcoin tx buffer" ); - } - - void direct_read( bytes& data, size_t s) { - data.clear(); - data.resize(s); - direct_read(&data[0], s); - } - - void direct_read(int32_t& val) - { - char* data = reinterpret_cast(&val); - read(data, sizeof(val)); - } - - void direct_read(uint32_t& val) - { - char* data = reinterpret_cast(&val); - read(data, sizeof(val)); - } - - inline bool get( unsigned char& c ) { return get( *(char*)&c ); } - inline bool get( char& c ) + inline bool get( unsigned char& c ) { if( pos_ < end_ ) { c = storage_[pos_++]; @@ -99,19 +113,211 @@ public: } FC_THROW( "invalid bitcoin tx buffer" ); } + + uint8_t readdata8() + { + uint8_t obj; + read((unsigned char*)&obj, 1); + return obj; + } + uint16_t readdata16() + { + uint16_t obj; + read((unsigned char*)&obj, 2); + return le16toh(obj); + } + uint32_t readdata32() + { + uint32_t obj; + read((unsigned char*)&obj, 4); + return le32toh(obj); + } + uint64_t readdata64() + { + uint64_t obj; + read((unsigned char*)&obj, 8); + return le64toh(obj); + } + + uint64_t read_compact_int() + { + uint8_t size = readdata8(); + uint64_t ret = 0; + if (size < 253) + { + ret = size; + } + else if (size == 253) + { + ret = readdata16(); + if (ret < 253) + FC_THROW("non-canonical ReadCompactSize()"); + } + else if (size == 254) + { + ret = readdata32(); + if (ret < 0x10000u) + FC_THROW("non-canonical ReadCompactSize()"); + } + else + { + ret = readdata64(); + if (ret < 0x100000000ULL) + FC_THROW("non-canonical ReadCompactSize()"); + } + if (ret > (uint64_t)0x02000000) + FC_THROW("ReadCompactSize(): size too large"); + return ret; + } + + void readdata(bytes& data) + { + size_t s = read_compact_int(); + data.clear(); + data.resize(s); + read(&data[0], s); + } + private: const bytes& storage_; size_t pos_; size_t end_; }; +void btc_outpoint::to_bytes(bytes& stream) const +{ + WriteBytesStream str(stream); + // TODO: write size? + str.write((unsigned char*)hash.data(), hash.data_size()); + str.writedata32(n); +} + +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(); +} + +void btc_in::to_bytes(bytes& stream) const +{ + prevout.to_bytes(stream); + WriteBytesStream str(stream); + str.writedata(scriptSig); + str.writedata32(nSequence); +} + +size_t btc_in::fill_from_bytes(const bytes& data, size_t pos) +{ + pos = prevout.fill_from_bytes(data, pos); + ReadBytesStream str(data, pos); + str.readdata(scriptSig); + nSequence = str.readdata32(); + return str.current_pos(); +} + +void btc_out::to_bytes(bytes& stream) const +{ + WriteBytesStream str(stream); + str.writedata64(nValue); + str.writedata(scriptPubKey); +} + +size_t btc_out::fill_from_bytes(const bytes& data, size_t pos) +{ + ReadBytesStream str(data, pos); + nValue = str.readdata64(); + str.readdata(scriptPubKey); + return str.current_pos(); +} + +void btc_tx::to_bytes(bytes& stream) const +{ + WriteBytesStream str(stream); + str.writedata32(nVersion); + if(hasWitness) + { + // write dummy inputs and flag + str.write_compact_int(0); + unsigned char flags = 1; + str.put(flags); + } + str.write_compact_int(vin.size()); + for(const auto& in: vin) + in.to_bytes(stream); + str.write_compact_int(vout.size()); + for(const auto& out: vout) + out.to_bytes(stream); + if(hasWitness) + { + for(const auto& in: vin) + str.writedata(in.scriptWitness); + } + str.writedata32(nLockTime); +} + +size_t btc_tx::fill_from_bytes(const bytes& data, size_t pos) +{ + ReadBytesStream ds( data, pos ); + nVersion = ds.readdata32(); + unsigned char flags = 0; + vin.clear(); + vout.clear(); + hasWitness = false; + /* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */ + size_t vin_size = ds.read_compact_int(); + vin.resize(vin_size); + pos = ds.current_pos(); + for(auto& in: vin) + pos = in.fill_from_bytes(data, pos); + ds.set_pos(pos); + if (vin_size == 0) { + /* We read a dummy or an empty vin. */ + ds.get(flags); + if (flags != 0) { + size_t vin_size = ds.read_compact_int(); + vin.resize(vin_size); + pos = ds.current_pos(); + for(auto& in: vin) + pos = in.fill_from_bytes(data, pos); + ds.set_pos(pos); + size_t vout_size = ds.read_compact_int(); + vout.resize(vout_size); + pos = ds.current_pos(); + for(auto& out: vout) + pos = out.fill_from_bytes(data, pos); + ds.set_pos(pos); + hasWitness = true; + } + } else { + /* We read a non-empty vin. Assume a normal vout follows. */ + size_t vout_size = ds.read_compact_int(); + vout.resize(vout_size); + pos = ds.current_pos(); + for(auto& out: vout) + pos = out.fill_from_bytes(data, pos); + ds.set_pos(pos); + } + if (hasWitness) { + /* The witness flag is present, and we support witnesses. */ + for (auto& in: vin) + ds.readdata(in.scriptWitness); + + } + nLockTime = ds.readdata32(); + return ds.current_pos(); +} + + void add_data_to_script(bytes& script, const bytes& data) { WriteBytesStream str(script); - fc::raw::pack(str, data, 2); + str.writedata(data); } -bytes generate_redeem_script(fc::flat_map key_data) +bytes generate_redeem_script(std::vector > key_data) { int total_weight = 0; bytes result; @@ -162,6 +368,17 @@ std::string p2sh_address_from_redeem_script(const bytes& script, bitcoin_network return fc::to_base58(reinterpret_cast(&data[0]), data.size()); } +bytes lock_script_for_redeem_script(const bytes &script) +{ + bytes result; + result.push_back(OP_HASH160); + fc::ripemd160 h = fc::ripemd160::hash(reinterpret_cast(&script[0]), script.size()); + bytes shash(h.data(), h.data() + h.data_size()); + add_data_to_script(result, shash); + result.push_back(OP_EQUAL); + return result; +} + bytes signature_for_raw_transaction(const bytes& unsigned_tx, const fc::ecc::private_key& priv_key) { fc::sha256 digest = fc::sha256::hash(fc::sha256::hash(reinterpret_cast(&unsigned_tx[0]), unsigned_tx.size())); @@ -169,65 +386,13 @@ bytes signature_for_raw_transaction(const bytes& unsigned_tx, const fc::ecc::pri return bytes(res.begin(), res.begin() + res.size()); } -bytes btc_tx::to_bytes() const -{ - bytes res; - WriteBytesStream str(res); - str.add(nVersion); - if(hasWitness) - { - std::vector dummy; - fc::raw::pack(str, dummy); - unsigned char flags = 1; - str.put(flags); - } - fc::raw::pack(str, vin); - fc::raw::pack(str, vout); - if(hasWitness) - { - for(const auto& in: vin) - fc::raw::pack(str, in.scriptWitness); - } - str.add(nLockTime); - return res; -} - -void btc_tx::fill_from_bytes(const bytes& data) -{ - ReadBytesStream ds( data ); - ds.direct_read(nVersion); - unsigned char flags = 0; - vin.clear(); - vout.clear(); - hasWitness = false; - /* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */ - fc::raw::unpack(ds, vin); - if (vin.size() == 0) { - /* We read a dummy or an empty vin. */ - ds.get(flags); - if (flags != 0) { - fc::raw::unpack(ds, vin); - fc::raw::unpack(ds, vout); - hasWitness = true; - } - } else { - /* We read a non-empty vin. Assume a normal vout follows. */ - fc::raw::unpack(ds, vout); - } - if (hasWitness) { - /* The witness flag is present, and we support witnesses. */ - for (size_t i = 0; i < vin.size(); i++) { - fc::raw::unpack(ds, vin[i].scriptWitness); - } - } - ds.direct_read(nLockTime); -} bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, const bytes& redeem_script, const std::vector > &priv_keys) { btc_tx tx; tx.fill_from_bytes(unsigned_tx); - fc::ecc::compact_signature dummy; + fc::ecc::compact_signature dummy_sig; + bytes dummy_data(dummy_sig.begin(), dummy_sig.begin() + dummy_sig.size()); for(auto& in: tx.vin) { WriteBytesStream script(in.scriptSig); @@ -236,16 +401,18 @@ bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, const bytes& redeem if(key) { bytes signature = signature_for_raw_transaction(unsigned_tx, *key); - script.add(signature); + script.writedata(signature); } else { - script.add(dummy.begin(), dummy.size()); + script.writedata(dummy_data); } } - script.add(redeem_script); + script.writedata(redeem_script); } - return tx.to_bytes(); + bytes ret; + tx.to_bytes(ret); + return ret; } }} 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 8ca5475f..c7f01cd6 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,8 +10,9 @@ enum bitcoin_network { regtest }; -bytes generate_redeem_script(fc::flat_map key_data); +bytes generate_redeem_script(std::vector > key_data); std::string p2sh_address_from_redeem_script(const bytes& script, bitcoin_network network = mainnet); +bytes lock_script_for_redeem_script(const bytes& script); /* * unsigned_tx - tx, all inputs of which are spends of the PW P2SH address * returns signed transaction @@ -22,6 +23,9 @@ struct btc_outpoint { fc::uint256 hash; uint32_t n; + + void to_bytes(bytes& stream) const; + size_t fill_from_bytes(const bytes& data, size_t pos = 0); }; struct btc_in @@ -30,12 +34,18 @@ struct btc_in bytes scriptSig; uint32_t nSequence; bytes scriptWitness; + + void to_bytes(bytes& stream) const; + size_t fill_from_bytes(const bytes& data, size_t pos = 0); }; struct btc_out { int64_t nValue; bytes scriptPubKey; + + void to_bytes(bytes& stream) const; + size_t fill_from_bytes(const bytes& data, size_t pos = 0); }; struct btc_tx @@ -46,14 +56,9 @@ struct btc_tx uint32_t nLockTime; bool hasWitness; - bytes to_bytes() const; - void fill_from_bytes(const bytes& data); + void to_bytes(bytes& stream) const; + size_t fill_from_bytes(const bytes& data, size_t pos = 0); }; }} -FC_REFLECT(graphene::peerplays_sidechain::btc_outpoint, (hash)(n)) -// btc_in::scriptWitness is filled only in transaction serialization -FC_REFLECT(graphene::peerplays_sidechain::btc_in, (prevout)(scriptSig)(nSequence)) -FC_REFLECT(graphene::peerplays_sidechain::btc_out, (nValue)(scriptPubKey)) -// btc_tx is serialized manually diff --git a/tests/peerplays_sidechain/bitcoin_utils_test.cpp b/tests/peerplays_sidechain/bitcoin_utils_test.cpp index 3b66aff6..d70505ea 100644 --- a/tests/peerplays_sidechain/bitcoin_utils_test.cpp +++ b/tests/peerplays_sidechain/bitcoin_utils_test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include using namespace graphene::peerplays_sidechain; @@ -20,6 +21,88 @@ BOOST_AUTO_TEST_CASE(tx_serialization) BOOST_CHECK(tx.nLockTime == 0); BOOST_CHECK(tx.vin.size() == 1); BOOST_CHECK(tx.vout.size() == 2); - bytes buff = tx.to_bytes(); + bytes buff; + tx.to_bytes(buff); BOOST_CHECK(bintx == buff); } + +BOOST_AUTO_TEST_CASE(pw_transfer) +{ + // key set for the old Primary Wallet + std::vector priv_old; + for(unsigned i = 0; i < 15; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_old.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_old; + for(auto& key: priv_old) + pub_old.push_back(key.get_public_key()); + // old key weights + 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 + bytes redeem_old =generate_redeem_script(weights_old); + // Old PW address + std::string old_pw = p2sh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); + // This address was filled with testnet transaction 8d8a466f6c829175a8bb747860828b59e7774be0bbf79ffdc70d5e75348180ca + BOOST_REQUIRE(old_pw == "2NGLS3x8Vk3vN18672YmSnpASm7FxYcoWu6"); + + // key set for the new Primary Wallet + std::vector priv_new; + for(unsigned i = 16; i < 31; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_new.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_new; + for(auto& key: priv_new) + pub_new.push_back(key.get_public_key()); + // new key weights + 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 + bytes redeem_new =generate_redeem_script(weights_new); + // New PW address + std::string new_pw = p2sh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); + + BOOST_REQUIRE(new_pw == "2MyzbFRwNqj1Y4Q4oWELhDwz5DCHkTndE1S"); + + // 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] + // 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"); + // reverse hash due to the different from_hex algo + std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); + outpoint.n = 1; + btc_in input; + input.prevout = outpoint; + input.nSequence = 0xffffffff; + btc_out output; + output.nValue = 19000; + output.scriptPubKey = lock_script_for_redeem_script(redeem_new); + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = false; + tx.vin.push_back(input); + tx.vout.push_back(output); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + std::vector> keys_to_sign; + for(auto key: priv_old) + keys_to_sign.push_back(fc::optional(key)); + bytes signed_tx =sign_pw_transfer_transaction(unsigned_tx, redeem_old, keys_to_sign); + ilog(fc::to_hex(reinterpret_cast(&signed_tx[0]), signed_tx.size())); +}