From b436b790fbc6eea4e169cc998d5fcd6c42bf0674 Mon Sep 17 00:00:00 2001 From: sierra19XX <15652887+sierra19XX@users.noreply.github.com> Date: Sun, 19 Apr 2020 06:18:04 +1000 Subject: [PATCH] SON Weighted Multi Signature Signing (#349) * Bring in the bitcoin utils code into plugin * Add tx creation, signing and tests * tx deserialization fix * add 10-of-14 multisig address test * Add signing and verification tests and sign_transaction_standalone * Add send_transaction_standalone function * Debug logs and additional tests * Fix for son deletion in the middle * Extend script_builder * Witness script for weighted wallet * btc_weighted_multisig_address implementation * Fix for bad-txns-nonstandard-inputs * Weighted multisignature address test * Create test tx with weighted multisig wallet * Fix the issues with tx signing * End to End test weighted multi sig * 1 or m-of-n deposit address support * Move network_type enum to the base class * btc_one_or_weighted_multisig_address implementation * Simplify redeem script * Fix error in redeem_script * btc_one_or_weighted_multisig_address tests * Refactor sidechain address mapping * CLANG code format * CLANG code format sidechain tests * Integration of deposit and rest of weighted wallets, withdrawal fee fix, whole code refactoring * Move util functions to Utils file * Add proper checks for withdraw fee * Deposit address creation, import deposit/withdraw addresses, some code cleanup Co-authored-by: satyakoneru <15652887+satyakoneru@users.noreply.github.com> Co-authored-by: gladcow Co-authored-by: Srdjan Obucina --- libraries/chain/db_notify.cpp | 6 +- .../chain/protocol/sidechain_address.hpp | 26 +- .../chain/sidechain_address_object.hpp | 14 +- .../chain/sidechain_address_evaluator.cpp | 4 + .../peerplays_sidechain/CMakeLists.txt | 10 +- .../peerplays_sidechain/bitcoin/bech32.cpp | 193 +++++ .../bitcoin/bitcoin_address.cpp | 416 +++++++++++ .../bitcoin/bitcoin_script.cpp | 65 ++ .../bitcoin/bitcoin_transaction.cpp | 257 +++++++ .../bitcoin/segwit_addr.cpp | 82 +++ .../bitcoin/sign_bitcoin_transaction.cpp | 153 ++++ .../peerplays_sidechain/bitcoin/utils.cpp | 99 +++ .../peerplays_sidechain/bitcoin_utils.cpp | 680 ------------------ .../peerplays_sidechain/bitcoin/bech32.hpp | 24 + .../bitcoin/bitcoin_address.hpp | 229 ++++++ .../bitcoin/bitcoin_script.hpp | 79 ++ .../bitcoin/bitcoin_transaction.hpp | 106 +++ .../bitcoin/segwit_addr.hpp | 34 + .../peerplays_sidechain/bitcoin/serialize.hpp | 354 +++++++++ .../bitcoin/sign_bitcoin_transaction.hpp | 35 + .../peerplays_sidechain/bitcoin/types.hpp | 54 ++ .../peerplays_sidechain/bitcoin/utils.hpp | 26 + .../peerplays_sidechain/bitcoin_utils.hpp | 104 --- .../peerplays_sidechain_plugin.hpp | 1 + .../sidechain_net_handler.hpp | 1 + .../sidechain_net_handler_bitcoin.hpp | 25 +- .../sidechain_net_handler_peerplays.hpp | 1 + .../peerplays_sidechain_plugin.cpp | 17 + .../sidechain_net_handler.cpp | 33 +- .../sidechain_net_handler_bitcoin.cpp | 624 ++++++++-------- .../sidechain_net_handler_peerplays.cpp | 4 + .../wallet/include/graphene/wallet/wallet.hpp | 10 +- libraries/wallet/wallet.cpp | 19 +- .../bitcoin_address_tests.cpp | 316 ++++++++ .../bitcoin_sign_tests.cpp | 387 ++++++++++ .../bitcoin_transaction_tests.cpp | 166 +++++ .../bitcoin_utils_test.cpp | 418 ----------- .../peerplays_sidechain_tests.cpp | 15 +- tests/tests/sidechain_addresses_test.cpp | 16 +- 39 files changed, 3491 insertions(+), 1612 deletions(-) create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp create mode 100644 libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp delete mode 100644 libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp delete mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp create mode 100644 tests/peerplays_sidechain/bitcoin_address_tests.cpp create mode 100644 tests/peerplays_sidechain/bitcoin_sign_tests.cpp create mode 100644 tests/peerplays_sidechain/bitcoin_transaction_tests.cpp delete mode 100644 tests/peerplays_sidechain/bitcoin_utils_test.cpp diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 0a942a76..f38225e0 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -330,13 +330,13 @@ struct get_impacted_account_visitor _impacted.insert( op.payer ); } void operator()( const sidechain_address_add_operation& op ) { - _impacted.insert( op.sidechain_address_account ); + _impacted.insert( op.payer ); } void operator()( const sidechain_address_update_operation& op ) { - _impacted.insert( op.sidechain_address_account ); + _impacted.insert( op.payer ); } void operator()( const sidechain_address_delete_operation& op ) { - _impacted.insert( op.sidechain_address_account ); + _impacted.insert( op.payer ); } void operator()( const sidechain_transaction_create_operation& op ) { _impacted.insert( op.payer ); diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp index 2702ce49..4fb21ebd 100644 --- a/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp @@ -10,12 +10,16 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = 0; }; asset fee; + account_id_type payer; + account_id_type sidechain_address_account; sidechain_type sidechain; + string deposit_public_key; string deposit_address; + string withdraw_public_key; string withdraw_address; - account_id_type fee_payer()const { return sidechain_address_account; } + account_id_type fee_payer()const { return payer; } share_type calculate_fee(const fee_parameters_type& k)const { return 0; } }; @@ -24,13 +28,17 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = 0; }; asset fee; + account_id_type payer; + sidechain_address_id_type sidechain_address_id; account_id_type sidechain_address_account; sidechain_type sidechain; + optional deposit_public_key; optional deposit_address; + optional withdraw_public_key; optional withdraw_address; - account_id_type fee_payer()const { return sidechain_address_account; } + account_id_type fee_payer()const { return payer; } share_type calculate_fee(const fee_parameters_type& k)const { return 0; } }; @@ -39,26 +47,28 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = 0; }; asset fee; + account_id_type payer; + sidechain_address_id_type sidechain_address_id; account_id_type sidechain_address_account; sidechain_type sidechain; - account_id_type fee_payer()const { return sidechain_address_account; } + account_id_type fee_payer()const { return payer; } share_type calculate_fee(const fee_parameters_type& k)const { return 0; } }; } } // namespace graphene::chain FC_REFLECT(graphene::chain::sidechain_address_add_operation::fee_parameters_type, (fee) ) -FC_REFLECT(graphene::chain::sidechain_address_add_operation, (fee) - (sidechain_address_account)(sidechain)(deposit_address)(withdraw_address) ) +FC_REFLECT(graphene::chain::sidechain_address_add_operation, (fee)(payer) + (sidechain_address_account)(sidechain)(deposit_public_key)(deposit_address)(withdraw_public_key)(withdraw_address) ) FC_REFLECT(graphene::chain::sidechain_address_update_operation::fee_parameters_type, (fee) ) -FC_REFLECT(graphene::chain::sidechain_address_update_operation, (fee) +FC_REFLECT(graphene::chain::sidechain_address_update_operation, (fee)(payer) (sidechain_address_id) - (sidechain_address_account)(sidechain)(deposit_address)(withdraw_address) ) + (sidechain_address_account)(sidechain)(deposit_public_key)(deposit_address)(withdraw_public_key)(withdraw_address) ) FC_REFLECT(graphene::chain::sidechain_address_delete_operation::fee_parameters_type, (fee) ) -FC_REFLECT(graphene::chain::sidechain_address_delete_operation, (fee) +FC_REFLECT(graphene::chain::sidechain_address_delete_operation, (fee)(payer) (sidechain_address_id) (sidechain_address_account)(sidechain) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_address_object.hpp b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp index ca28231b..b9bcda5a 100644 --- a/libraries/chain/include/graphene/chain/sidechain_address_object.hpp +++ b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp @@ -22,17 +22,23 @@ namespace graphene { namespace chain { account_id_type sidechain_address_account; sidechain_type sidechain; + string deposit_public_key; string deposit_address; + string withdraw_public_key; string withdraw_address; sidechain_address_object() : sidechain(sidechain_type::bitcoin), + deposit_public_key(""), deposit_address(""), + withdraw_public_key(""), withdraw_address("") {} }; struct by_account; struct by_sidechain; + struct by_deposit_public_key; + struct by_withdraw_public_key; struct by_account_and_sidechain; struct by_sidechain_and_deposit_address; using sidechain_address_multi_index_type = multi_index_container< @@ -47,6 +53,12 @@ namespace graphene { namespace chain { ordered_non_unique< tag, member >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, ordered_unique< tag, composite_key, @@ -66,4 +78,4 @@ namespace graphene { namespace chain { } } // graphene::chain FC_REFLECT_DERIVED( graphene::chain::sidechain_address_object, (graphene::db::object), - (sidechain_address_account) (sidechain) (deposit_address) (withdraw_address) ) + (sidechain_address_account) (sidechain) (deposit_public_key) (deposit_address) (withdraw_public_key) (withdraw_address) ) diff --git a/libraries/chain/sidechain_address_evaluator.cpp b/libraries/chain/sidechain_address_evaluator.cpp index 5382195d..45d1a32a 100644 --- a/libraries/chain/sidechain_address_evaluator.cpp +++ b/libraries/chain/sidechain_address_evaluator.cpp @@ -19,7 +19,9 @@ object_id_type add_sidechain_address_evaluator::do_apply(const sidechain_address const auto& new_sidechain_address_object = db().create( [&]( sidechain_address_object& obj ){ obj.sidechain_address_account = op.sidechain_address_account; obj.sidechain = op.sidechain; + obj.deposit_public_key = op.deposit_public_key; obj.deposit_address = op.deposit_address; + obj.withdraw_public_key = op.withdraw_public_key; obj.withdraw_address = op.withdraw_address; }); return new_sidechain_address_object.id; @@ -40,7 +42,9 @@ object_id_type update_sidechain_address_evaluator::do_apply(const sidechain_addr if(itr != idx.end()) { db().modify(*itr, [&op](sidechain_address_object &sao) { + if(op.deposit_public_key.valid()) sao.deposit_public_key = *op.deposit_public_key; if(op.deposit_address.valid()) sao.deposit_address = *op.deposit_address; + if(op.withdraw_public_key.valid()) sao.withdraw_public_key = *op.withdraw_public_key; if(op.withdraw_address.valid()) sao.withdraw_address = *op.withdraw_address; }); } diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt index e7d9acfe..61e27b94 100755 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB HEADERS "include/graphene/peerplays_sidechain/*.hpp") +file(GLOB_RECURSE HEADERS "include/graphene/peerplays_sidechain/*.hpp") add_library( peerplays_sidechain peerplays_sidechain_plugin.cpp @@ -6,7 +6,13 @@ add_library( peerplays_sidechain sidechain_net_handler.cpp sidechain_net_handler_bitcoin.cpp sidechain_net_handler_peerplays.cpp - bitcoin_utils.cpp + bitcoin/bech32.cpp + bitcoin/bitcoin_address.cpp + bitcoin/bitcoin_script.cpp + bitcoin/bitcoin_transaction.cpp + bitcoin/segwit_addr.cpp + bitcoin/utils.cpp + bitcoin/sign_bitcoin_transaction.cpp ) if (SUPPORT_MULTIPLE_SONS) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp new file mode 100644 index 00000000..69d157e4 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp @@ -0,0 +1,193 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +// #include + +namespace { + +typedef std::vector data; + +/** 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. */ +data Cat(data x, const data &y) { + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to + * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher + * bits correspond to earlier values. */ +uint32_t PolyMod(const data &v) { + // The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an + // implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) = + // 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that + // [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...]. + + // The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of + // v(x) mod g(x), where g(x) is the Bech32 generator, + // x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way + // that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a + // window of 1023 characters. Among the various possible BCH codes, one was selected to in + // fact guarantee detection of up to 4 errors within a window of 89 characters. + + // Note that the coefficients are elements of GF(32), here represented as decimal numbers + // between {}. In this finite field, addition is just XOR of the corresponding numbers. For + // example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires + // treating the bits of values themselves as coefficients of a polynomial over a smaller field, + // GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} = + // (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a + // = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. + + // During the course of the loop below, `c` contains the bitpacked coefficients of the + // polynomial constructed from just the values of v that were processed so far, mod g(x). In + // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value + // for `c`. + uint32_t c = 1; + for (auto v_i : v) { + // We want to update `c` to correspond to a polynomial with one extra term. If the initial + // value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to + // correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to + // process. Simplifying: + // c'(x) = (f(x) * x + v_i) mod g(x) + // ((f(x) mod g(x)) * x + v_i) mod g(x) + // (c(x) * x + v_i) mod g(x) + // If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute + // c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x) + // = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x) + // = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i + // If we call (x^6 mod g(x)) = k(x), this can be written as + // c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x) + + // First, determine the value of c0: + uint8_t c0 = c >> 25; + + // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i: + c = ((c & 0x1ffffff) << 5) ^ v_i; + + // Finally, for each set bit n in c0, conditionally add {2^n}k(x): + if (c0 & 1) + c ^= 0x3b6a57b2; // k(x) = {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18} + if (c0 & 2) + c ^= 0x26508e6d; // {2}k(x) = {19}x^5 + {5}x^4 + x^3 + {3}x^2 + {19}x + {13} + if (c0 & 4) + c ^= 0x1ea119fa; // {4}k(x) = {15}x^5 + {10}x^4 + {2}x^3 + {6}x^2 + {15}x + {26} + if (c0 & 8) + c ^= 0x3d4233dd; // {8}k(x) = {30}x^5 + {20}x^4 + {4}x^3 + {12}x^2 + {30}x + {29} + if (c0 & 16) + c ^= 0x2a1462b3; // {16}k(x) = {21}x^5 + x^4 + {8}x^3 + {24}x^2 + {21}x + {19} + } + return c; +} + +/** Convert to lower case. */ +inline unsigned char LowerCase(unsigned char c) { + return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; +} + +/** Expand a HRP for use in checksum computation. */ +data ExpandHRP(const std::string &hrp) { + data 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; +} + +/** Verify a checksum. */ +bool VerifyChecksum(const std::string &hrp, const data &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(ExpandHRP(hrp), values)) == 1; +} + +/** Create a checksum. */ +data CreateChecksum(const std::string &hrp, const data &values) { + data enc = Cat(ExpandHRP(hrp), values); + enc.resize(enc.size() + 6); // Append 6 zeroes + uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes. + data ret(6); + for (size_t i = 0; i < 6; ++i) { + // Convert the 5-bit groups in mod to checksum values. + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; +} + +} // namespace + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace bech32 { + +/** Encode a Bech32 string. */ +std::string Encode(const std::string &hrp, const data &values) { + data checksum = CreateChecksum(hrp, values); + data combined = Cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (auto c : combined) { + ret += CHARSET[c]; + } + return ret; +} + +/** Decode a Bech32 string. */ +std::pair Decode(const std::string &str) { + bool lower = false, upper = false; + for (size_t i = 0; i < str.size(); ++i) { + unsigned char c = str[i]; + if (c < 33 || c > 126) + return {}; + if (c >= 'a' && c <= 'z') + lower = true; + if (c >= 'A' && c <= 'Z') + upper = true; + } + if (lower && upper) + return {}; + size_t pos = str.rfind('1'); + if (str.size() > 90 || pos == str.npos || pos == 0 || pos + 7 > str.size()) { + return {}; + } + data 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) { + return {}; + } + values[i] = rev; + } + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += LowerCase(str[i]); + } + if (!VerifyChecksum(hrp, values)) { + return {}; + } + return {hrp, data(values.begin(), values.end() - 6)}; +} + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::bech32 diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp new file mode 100644 index 00000000..a45bf9b0 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +bool bitcoin_address::operator==(const bitcoin_address &btc_addr) const { + return (this->address == btc_addr.address) && + (this->type == btc_addr.type) && + (this->raw_address == btc_addr.raw_address) && + (this->network_type == btc_addr.network_type); +} + +bytes bitcoin_address::get_script() const { + switch (type) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << raw_address; + case payment_type::P2PK: + return script_builder() << raw_address << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << raw_address << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << raw_address << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << raw_address; + default: + return raw_address; + } +} + +payment_type bitcoin_address::determine_type() { + if (is_p2pk()) { + return payment_type::P2PK; + } else if (is_p2wpkh()) { + return payment_type::P2WPKH; + } else if (is_p2wsh()) { + return payment_type::P2WSH; + } else if (is_p2pkh()) { + return payment_type::P2PKH; + } else if (is_p2sh()) { + return payment_type::P2SH; + } else { + return payment_type::NULLDATA; + } +} + +bytes bitcoin_address::determine_raw_address() { + bytes result; + switch (type) { + case payment_type::P2PK: { + result = parse_hex(address); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + std::string prefix(address.compare(0, 4, "bcrt") == 0 ? std::string(address.begin(), address.begin() + 4) : std::string(address.begin(), address.begin() + 2)); + const auto &decode_bech32 = segwit_addr::decode(prefix, address); + result = bytes(decode_bech32.second.begin(), decode_bech32.second.end()); + break; + } + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + case payment_type::P2PKH: + case payment_type::P2SH: { + bytes hex_addr = fc::from_base58(address); + result = bytes(hex_addr.begin() + 1, hex_addr.begin() + 21); + break; + } + case payment_type::NULLDATA: + return result; + } + return result; +} + +bool bitcoin_address::check_segwit_address(const size_segwit_address &size) const { + if (!address.compare(0, 4, "bcrt") || !address.compare(0, 2, "bc") || !address.compare(0, 2, "tb")) { + std::string prefix(!address.compare(0, 4, "bcrt") ? std::string(address.begin(), address.begin() + 4) : std::string(address.begin(), address.begin() + 2)); + + const auto &decode_bech32 = segwit_addr::decode(prefix, address); + + if (decode_bech32.first == -1 || decode_bech32.second.size() != size) { + return false; + } + + return true; + } + return false; +} + +bool bitcoin_address::is_p2pk() const { + try { + bool prefix = !address.compare(0, 2, "02") || !address.compare(0, 2, "03"); + if (address.size() == 66 && prefix) { + parse_hex(address); + return true; + } + } catch (fc::exception e) { + return false; + } + return false; +} + +bool bitcoin_address::is_p2wpkh() const { + return check_segwit_address(size_segwit_address::P2WPKH); +} + +bool bitcoin_address::is_p2wsh() const { + return check_segwit_address(size_segwit_address::P2WSH); +} + +bool bitcoin_address::is_p2pkh() const { + try { + bytes hex_addr = fc::from_base58(address); + if (hex_addr.size() == 25 && (static_cast(hex_addr[0]) == 0x00 || + static_cast(hex_addr[0]) == 0x6f)) { + return true; + } + return false; + } catch (fc::exception e) { + return false; + } +} + +bool bitcoin_address::is_p2sh() const { + try { + bytes hex_addr = fc::from_base58(address); + if (hex_addr.size() == 25 && (static_cast(hex_addr[0]) == 0x05 || + static_cast(hex_addr[0]) == 0xc4)) { + return true; + } + return false; + } catch (fc::exception e) { + return false; + } +} + +btc_multisig_address::btc_multisig_address(const size_t n_required, const accounts_keys &keys) : + keys_required(n_required), + witnesses_keys(keys) { + create_redeem_script(); + create_address(); + type = payment_type::P2SH; +} + +size_t btc_multisig_address::count_intersection(const accounts_keys &keys) const { + FC_ASSERT(keys.size() > 0); + + int intersections_count = 0; + for (auto &key : keys) { + auto witness_key = witnesses_keys.find(key.first); + if (witness_key == witnesses_keys.end()) + continue; + if (key.second == witness_key->second) + intersections_count++; + } + return intersections_count; +} + +void btc_multisig_address::create_redeem_script() { + FC_ASSERT(keys_required > 0); + FC_ASSERT(keys_required < witnesses_keys.size()); + redeem_script.clear(); + redeem_script.push_back(op_num[keys_required - 1]); + for (const auto &key : witnesses_keys) { + std::stringstream ss; + ss << std::hex << key.second.key_data.size(); + auto key_size_hex = parse_hex(ss.str()); + redeem_script.insert(redeem_script.end(), key_size_hex.begin(), key_size_hex.end()); + redeem_script.insert(redeem_script.end(), key.second.key_data.begin(), key.second.key_data.end()); + } + redeem_script.push_back(op_num[witnesses_keys.size() - 1]); + redeem_script.push_back(OP_CHECKMULTISIG); +} + +void btc_multisig_address::create_address() { + FC_ASSERT(redeem_script.size() > 0); + raw_address.clear(); + fc::sha256 hash256 = fc::sha256::hash(redeem_script.data(), redeem_script.size()); + fc::ripemd160 hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + bytes temp_addr_hash(parse_hex(hash160.str())); + + raw_address.push_back(OP_HASH160); + std::stringstream ss; + ss << std::hex << temp_addr_hash.size(); + auto address_size_hex = parse_hex(ss.str()); + raw_address.insert(raw_address.end(), address_size_hex.begin(), address_size_hex.end()); + raw_address.insert(raw_address.end(), temp_addr_hash.begin(), temp_addr_hash.end()); + raw_address.push_back(OP_EQUAL); +} + +btc_multisig_segwit_address::btc_multisig_segwit_address(const size_t n_required, const accounts_keys &keys) : + btc_multisig_address(n_required, keys) { + create_witness_script(); + create_segwit_address(); + type = payment_type::P2SH; +} + +bool btc_multisig_segwit_address::operator==(const btc_multisig_segwit_address &addr) const { + if (address != addr.address || redeem_script != addr.redeem_script || + witnesses_keys != addr.witnesses_keys || witness_script != addr.witness_script || + raw_address != addr.raw_address) + return false; + return true; +} + +std::vector btc_multisig_segwit_address::get_keys() { + std::vector keys; + for (const auto &k : witnesses_keys) { + keys.push_back(k.second); + } + return keys; +} + +void btc_multisig_segwit_address::create_witness_script() { + const auto redeem_sha256 = fc::sha256::hash(redeem_script.data(), redeem_script.size()); + witness_script.push_back(OP_0); + witness_script.push_back(0x20); // PUSH_32 + witness_script.insert(witness_script.end(), redeem_sha256.data(), redeem_sha256.data() + redeem_sha256.data_size()); +} + +void btc_multisig_segwit_address::create_segwit_address() { + fc::sha256 hash256 = fc::sha256::hash(witness_script.data(), witness_script.size()); + fc::ripemd160 hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + + raw_address = bytes(hash160.data(), hash160.data() + hash160.data_size()); + address = fc::to_base58(get_address_bytes(raw_address)); +} + +bytes btc_multisig_segwit_address::get_address_bytes(const bytes &script_hash) { + bytes address_bytes(1, TESTNET_SCRIPT); // 1 byte version + address_bytes.insert(address_bytes.end(), script_hash.begin(), script_hash.end()); + fc::sha256 hash256 = fc::sha256::hash(fc::sha256::hash(address_bytes.data(), address_bytes.size())); + address_bytes.insert(address_bytes.end(), hash256.data(), hash256.data() + 4); // 4 byte checksum + + return address_bytes; +} + +btc_weighted_multisig_address::btc_weighted_multisig_address(const std::vector> &keys_data, + network ntype) { + network_type = ntype; + create_redeem_script(keys_data); + create_witness_script(); + create_segwit_address(); + type = payment_type::P2WSH; +} + +void btc_weighted_multisig_address::create_redeem_script(const std::vector> &keys_data) { + script_builder builder; + uint32_t total_weight = 0; + builder << uint32_t(0); + for (auto &p : keys_data) { + total_weight += p.second; + builder << op::SWAP; + builder << p.first.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(p.second); + builder << op::ADD; + builder << op::ENDIF; + } + uint32_t threshold_weight = 2 * total_weight / 3; + builder << threshold_weight; + builder << op::GREATERTHANOREQUAL; + + redeem_script_ = builder; + + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} + +void btc_weighted_multisig_address::create_witness_script() { + script_builder builder; + builder << op::_0; + builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); + + witness_script_ = builder; +} + +void btc_weighted_multisig_address::create_segwit_address() { + std::string hrp; + switch (network_type) { + case (network::mainnet): + hrp = "bc"; + break; + case (network::testnet): + hrp = "tb"; + break; + case (network::regtest): + hrp = "bcrt"; + break; + } + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); +} + +btc_one_or_m_of_n_multisig_address::btc_one_or_m_of_n_multisig_address(const fc::ecc::public_key &user_key_data, + const uint8_t nrequired, const std::vector &keys_data, + network ntype) { + network_type = ntype; + create_redeem_script(user_key_data, nrequired, keys_data); + create_witness_script(); + create_segwit_address(); + type = payment_type::P2WSH; +} +void btc_one_or_m_of_n_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, + const uint8_t nrequired, const std::vector &keys_data) { + script_builder builder; + builder << op::IF; + builder << user_key_data.serialize(); + builder << op::CHECKSIG; + builder << op::ELSE; + builder << static_cast(nrequired); + for (auto &key : keys_data) { + builder << key.serialize(); + } + builder << static_cast(keys_data.size()); + builder << op::CHECKMULTISIG; + builder << op::ENDIF; + redeem_script_ = builder; + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} +void btc_one_or_m_of_n_multisig_address::create_witness_script() { + script_builder builder; + builder << op::_0; + builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); + witness_script_ = builder; +} +void btc_one_or_m_of_n_multisig_address::create_segwit_address() { + std::string hrp; + switch (network_type) { + case (network::mainnet): + hrp = "bc"; + break; + case (network::testnet): + hrp = "tb"; + break; + case (network::regtest): + hrp = "bcrt"; + break; + } + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); +} + +btc_one_or_weighted_multisig_address::btc_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, + const std::vector> &keys_data, + bitcoin_address::network ntype) { + network_type = ntype; + create_redeem_script(user_key_data, keys_data); + create_witness_script(); + create_segwit_address(); + type = payment_type::P2WSH; +} + +void btc_one_or_weighted_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data) { + script_builder builder; + builder << user_key_data.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << op::_1; + builder << op::ELSE; + uint32_t total_weight = 0; + builder << uint32_t(0); + for (auto &p : keys_data) { + total_weight += p.second; + builder << op::SWAP; + builder << p.first.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(p.second); + builder << op::ADD; + builder << op::ENDIF; + } + uint32_t threshold_weight = 2 * total_weight / 3; + builder << threshold_weight; + builder << op::GREATERTHANOREQUAL; + builder << op::ENDIF; + redeem_script_ = builder; + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} + +void btc_one_or_weighted_multisig_address::create_witness_script() { + script_builder builder; + builder << op::_0; + builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); + witness_script_ = builder; +} + +void btc_one_or_weighted_multisig_address::create_segwit_address() { + std::string hrp; + switch (network_type) { + case (network::mainnet): + hrp = "bc"; + break; + case (network::testnet): + hrp = "tb"; + break; + case (network::regtest): + hrp = "bcrt"; + break; + } + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp new file mode 100644 index 00000000..07078147 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp @@ -0,0 +1,65 @@ +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +script_builder &script_builder::operator<<(op opcode) { + const auto op_byte = static_cast(opcode); + if (op_byte < 0 || op_byte > 0xff) + throw std::runtime_error("script_builder::operator<<(OP): invalid opcode"); + script.push_back(op_byte); + return *this; +} + +script_builder &script_builder::operator<<(uint32_t number) { + if (number == 0) { + script.push_back(static_cast(op::_0)); + } else if (number <= 16) { + script.push_back(static_cast(op::_1) + number - 1); + } else { + bytes pack_buf; + while (number) { + pack_buf.push_back(number & 0xff); + number >>= 8; + } + // - If the most significant byte is >= 0x80 and the value is positive, push a + // new zero-byte to make the significant byte < 0x80 again. So, the result can + // be 5 bytes max. + if (pack_buf.back() & 0x80) + pack_buf.push_back(0); + *this << pack_buf; + } + + return *this; +} + +script_builder &script_builder::operator<<(size_t size) { + write_compact_size(script, size); + return *this; +} + +script_builder &script_builder::operator<<(const bytes &sc) { + write_compact_size(script, sc.size()); + script.insert(script.end(), sc.begin(), sc.end()); + return *this; +} + +script_builder &script_builder::operator<<(const fc::sha256 &hash) { + write_compact_size(script, hash.data_size()); + script.insert(script.end(), hash.data(), hash.data() + hash.data_size()); + return *this; +} + +script_builder &script_builder::operator<<(const fc::ripemd160 &hash) { + write_compact_size(script, hash.data_size()); + script.insert(script.end(), hash.data(), hash.data() + hash.data_size()); + return *this; +} + +script_builder &script_builder::operator<<(const fc::ecc::public_key_data &pubkey_data) { + write_compact_size(script, pubkey_data.size()); + script.insert(script.end(), pubkey_data.begin(), pubkey_data.begin() + pubkey_data.size()); + return *this; +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp new file mode 100644 index 00000000..b4fde6dc --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp @@ -0,0 +1,257 @@ +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +bool out_point::operator==(const out_point &op) const { + if (this->hash == op.hash && + this->n == op.n) { + return true; + } + return false; +} + +bool tx_in::operator==(const tx_in &ti) const { + if (this->prevout == ti.prevout && + this->scriptSig == ti.scriptSig && + this->nSequence == ti.nSequence) { + return true; + } + return false; +} + +bool tx_out::operator==(const tx_out &to) const { + if (this->value == to.value && + this->scriptPubKey == to.scriptPubKey) { + return true; + } + return false; +} + +bool tx_out::is_p2wsh() const { + if (scriptPubKey.size() == 34 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x20)) { + return true; + } + return false; +} + +bool tx_out::is_p2wpkh() const { + if (scriptPubKey.size() == 22 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x14)) { + return true; + } + return false; +} + +bool tx_out::is_p2pkh() const { + if (scriptPubKey.size() == 25 && scriptPubKey[0] == static_cast(0x76) && scriptPubKey[1] == static_cast(0xa9) && + scriptPubKey[2] == static_cast(0x14) && scriptPubKey[23] == static_cast(0x88) && scriptPubKey[24] == static_cast(0xac)) { + return true; + } + return false; +} + +bool tx_out::is_p2sh() const { + if (scriptPubKey.size() == 23 && scriptPubKey[0] == static_cast(0xa9) && + scriptPubKey[1] == static_cast(0x14) && scriptPubKey[22] == static_cast(0x87)) { + return true; + } + return false; +} + +bool tx_out::is_p2pk() const { + if (scriptPubKey.size() == 35 && scriptPubKey[0] == static_cast(0x21) && scriptPubKey[34] == static_cast(0xac)) { + return true; + } + return false; +} + +bytes tx_out::get_data_or_script() const { + if (is_p2pkh()) { + return bytes(scriptPubKey.begin() + 3, scriptPubKey.begin() + 23); + } else if (is_p2sh() || is_p2wpkh()) { + return bytes(scriptPubKey.begin() + 2, scriptPubKey.begin() + 22); + } else if (is_p2wsh()) { + return bytes(scriptPubKey.begin() + 2, scriptPubKey.begin() + 34); + } else if (is_p2pk()) { + return bytes(scriptPubKey.begin() + 1, scriptPubKey.begin() + 34); + } + return scriptPubKey; +} + +bool bitcoin_transaction::operator!=(const bitcoin_transaction &bt) const { + if (this->nVersion != bt.nVersion || + this->vin != bt.vin || + this->vout != bt.vout || + this->nLockTime != bt.nLockTime) { + return true; + } + return false; +} + +fc::sha256 bitcoin_transaction::get_hash() const { + const auto bytes = pack(*this, true); + const auto hash = fc::sha256::hash(fc::sha256::hash(bytes.data(), bytes.size())); + std::reverse(hash.data(), hash.data() + hash.data_size()); + return hash; +} + +fc::sha256 bitcoin_transaction::get_txid() const { + const auto bytes = pack(*this, false); + const auto hash = fc::sha256::hash(fc::sha256::hash(bytes.data(), bytes.size())); + std::reverse(hash.data(), hash.data() + hash.data_size()); + return hash; +} + +size_t bitcoin_transaction::get_vsize() const { + static const auto witness_scale_factor = 4; + + fc::datastream no_wit_ds; + pack(no_wit_ds, *this, false); + + fc::datastream wit_ds; + pack(wit_ds, *this, true); + + const size_t weight = no_wit_ds.tellp() * (witness_scale_factor - 1) + wit_ds.tellp(); + const size_t vsize = (weight + witness_scale_factor - 1) / witness_scale_factor; + + return vsize; +} + +void bitcoin_transaction_builder::set_version(int32_t version) { + tx.nVersion = version; +} + +void bitcoin_transaction_builder::set_locktime(uint32_t lock_time) { + tx.nLockTime = lock_time; +} + +void bitcoin_transaction_builder::add_in(const fc::sha256 &txid, uint32_t n_out, const bytes &script_code, bool front, uint32_t sequence) { + add_in(payment_type::P2WSH, txid, n_out, script_code, front, sequence); +} + +void bitcoin_transaction_builder::add_in(payment_type type, const fc::sha256 &txid, uint32_t n_out, const bytes &script_code, bool front, uint32_t sequence) { + out_point prevout; + prevout.hash = txid; + prevout.n = n_out; + + tx_in txin; + txin.prevout = prevout; + txin.nSequence = sequence; + + add_in(type, txin, script_code, front); +} + +void bitcoin_transaction_builder::add_in(payment_type type, tx_in txin, const bytes &script_code, bool front) { + switch (type) { + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + FC_ASSERT(script_code != bytes()); + txin.scriptSig = script_code; + break; + default: { + if (txin.prevout.hash == fc::sha256("0000000000000000000000000000000000000000000000000000000000000000")) { //coinbase + FC_ASSERT(script_code != bytes()); + txin.scriptSig = script_code; + } + break; + } + } + + if (front) { + tx.vin.insert(tx.vin.begin(), txin); + } else { + tx.vin.push_back(txin); + } +} + +void bitcoin_transaction_builder::add_out(payment_type type, int64_t amount, const std::string &base58_address, bool front) { + // TODO: add checks + const auto address_bytes = fc::from_base58(base58_address); + add_out(type, amount, bytes(address_bytes.begin() + 1, address_bytes.begin() + 1 + 20), front); +} + +void bitcoin_transaction_builder::add_out(payment_type type, int64_t amount, const fc::ecc::public_key_data &pubkey, bool front) { + FC_ASSERT(is_payment_to_pubkey(type)); + + if (type == payment_type::P2PK) { + const auto pubkey_bytes = bytes(pubkey.begin(), pubkey.begin() + pubkey.size()); + add_out(type, amount, pubkey_bytes, front); + } else { + const auto hash256 = fc::sha256::hash(pubkey.begin(), pubkey.size()); + const auto hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + add_out(type, amount, bytes(hash160.data(), hash160.data() + hash160.data_size()), front); + } +} + +void bitcoin_transaction_builder::add_out(payment_type type, int64_t amount, const bytes &script_code, bool front) { + tx_out out; + out.value = amount; + out.scriptPubKey = get_script_pubkey(type, script_code); + + if (front) { + tx.vout.insert(tx.vout.begin(), out); + } else { + tx.vout.push_back(out); + } +} + +void bitcoin_transaction_builder::add_out_all_type(const uint64_t &amount, const bitcoin_address &address, bool front) { + switch (address.get_type()) { + case payment_type::P2PK: { + bytes raw_address(address.get_raw_address()); + fc::ecc::public_key_data public_key; + std::copy(raw_address.begin(), raw_address.end(), public_key.begin()); + add_out(address.get_type(), amount, public_key, front); + break; + } + case payment_type::P2PKH: + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: { + add_out(address.get_type(), amount, address.get_address(), front); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + add_out(address.get_type(), amount, address.get_raw_address(), front); + break; + } + default: + break; + } +} + +inline bool bitcoin_transaction_builder::is_payment_to_pubkey(payment_type type) { + switch (type) { + case payment_type::P2PK: + case payment_type::P2PKH: + case payment_type::P2WPKH: + case payment_type::P2SH_WPKH: + return true; + default: + return false; + } +} + +bytes bitcoin_transaction_builder::get_script_pubkey(payment_type type, const bytes &script_code) { + switch (type) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << script_code; + case payment_type::P2PK: + return script_builder() << script_code << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << script_code << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << script_code << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << script_code; + default: + return script_code; + } +} +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp new file mode 100644 index 00000000..2fd9be43 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp @@ -0,0 +1,82 @@ +/* Copyright (c) 2017 Pieter Wuille + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +namespace { + +typedef std::vector data; + +/** Convert from one power-of-2 number base to another. */ +template +bool convertbits(data &out, const data &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; +} + +} // namespace + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace segwit_addr { + +/** Decode a SegWit address. */ +std::pair decode(const std::string &hrp, const std::string &addr) { + std::pair dec = bech32::Decode(addr); + if (dec.first != hrp || dec.second.size() < 1) + return std::make_pair(-1, data()); + data conv; + if (!convertbits<5, 8, false>(conv, data(dec.second.begin() + 1, dec.second.end())) || + conv.size() < 2 || conv.size() > 40 || dec.second[0] > 16 || (dec.second[0] == 0 && conv.size() != 20 && conv.size() != 32)) { + return std::make_pair(-1, data()); + } + return std::make_pair(dec.second[0], conv); +} + +/** Encode a SegWit address. */ +std::string encode(const std::string &hrp, int witver, const data &witprog) { + data enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + std::string ret = bech32::Encode(hrp, enc); + if (decode(hrp, ret).first == -1) + return ""; + return ret; +} + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::segwit_addr diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp new file mode 100644 index 00000000..edb45c5b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp @@ -0,0 +1,153 @@ +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +const secp256k1_context_t *btc_context() { + static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; +} + +fc::sha256 get_signature_hash(const bitcoin_transaction &tx, const bytes &scriptCode, int64_t amount, + size_t in_index, int hash_type, bool is_witness) { + fc::datastream ps; + if (is_witness) + pack_tx_witness_signature(ps, scriptCode, tx, in_index, amount, hash_type); + else + pack_tx_signature(ps, scriptCode, tx, in_index, hash_type); + + std::vector vec(ps.tellp()); + if (!vec.empty()) { + fc::datastream ds(vec.data(), vec.size()); + if (is_witness) + pack_tx_witness_signature(ds, scriptCode, tx, in_index, amount, hash_type); + else + pack_tx_signature(ds, scriptCode, tx, in_index, hash_type); + } + + return fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); +} + +std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context_t *context_sign) { + bytes sig; + sig.resize(72); + int sig_len = sig.size(); + + FC_ASSERT(secp256k1_ecdsa_sign( + context_sign, + reinterpret_cast(hash.data()), + reinterpret_cast(sig.data()), + &sig_len, + reinterpret_cast(privkey.data()), + secp256k1_nonce_function_rfc6979, + nullptr)); // TODO: replace assert with exception + + sig.resize(sig_len); + + return sig; +} + +std::vector sign_witness_transaction_part(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const bytes &privkey, + const secp256k1_context_t *context_sign, int hash_type) { + FC_ASSERT(tx.vin.size() == redeem_scripts.size() && tx.vin.size() == amounts.size()); + FC_ASSERT(!privkey.empty()); + + std::vector signatures; + for (size_t i = 0; i < tx.vin.size(); i++) { + const auto sighash = get_signature_hash(tx, redeem_scripts[i], static_cast(amounts[i]), i, hash_type, true); + auto sig = privkey_sign(privkey, sighash, context_sign); + sig.push_back(static_cast(hash_type)); + + signatures.push_back(sig); + } + return signatures; +} + +void sign_witness_transaction_finalize(bitcoin_transaction &tx, const std::vector &redeem_scripts, bool use_mulisig_workaround) { + FC_ASSERT(tx.vin.size() == redeem_scripts.size()); + + for (size_t i = 0; i < tx.vin.size(); i++) { + if (use_mulisig_workaround) + tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), bytes()); // Bitcoin workaround CHECKMULTISIG bug + tx.vin[i].scriptWitness.push_back(redeem_scripts[i]); + } +} + +bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context_t *context) { + std::vector sig_temp(sig.begin(), sig.end()); + std::vector pubkey_temp(pubkey.begin(), pubkey.end()); + std::vector msg_temp(msg.begin(), msg.end()); + + int result = secp256k1_ecdsa_verify(context, msg_temp.data(), sig_temp.data(), sig_temp.size(), pubkey_temp.data(), pubkey_temp.size()); + return result == 1; +} + +std::vector> sort_sigs(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const secp256k1_context_t *context) { + FC_ASSERT(redeem_scripts.size() == amounts.size()); + + using data = std::pair; + struct comp { + bool operator()(const data &lhs, const data &rhs) const { + return lhs.first < rhs.first; + } + }; + + std::vector> new_stacks; + + for (size_t i = 0; i < redeem_scripts.size(); i++) { + const std::vector &keys = get_pubkey_from_redeemScript(redeem_scripts[i]); + const auto &sighash = get_signature_hash(tx, redeem_scripts[i], static_cast(amounts[i]), i, 1, true).str(); + bytes sighash_temp(parse_hex(sighash)); + + std::vector stack(tx.vin[i].scriptWitness); + std::vector marker(tx.vin[i].scriptWitness.size(), false); + std::set sigs; + + for (size_t j = 0; j < keys.size(); j++) { + for (size_t l = 0; l < stack.size(); l++) { + if (!verify_sig(stack[l], keys[j], sighash_temp, context) || marker[l]) + continue; + sigs.insert(std::make_pair(j, stack[l])); + marker[l] = true; + break; + } + } + + std::vector temp_sig; + for (auto s : sigs) { + temp_sig.push_back(s.second); + } + new_stacks.push_back(temp_sig); + } + return new_stacks; +} + +void add_signatures_to_transaction_multisig(bitcoin_transaction &tx, std::vector> &signature_set) { + for (unsigned int i = 0; i < signature_set.size(); i++) { + std::vector signatures = signature_set[i]; + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.push_back(signatures[i]); + } +} + +void add_signatures_to_transaction_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set) { + for (unsigned int i = 0; i < signature_set.size(); i++) { + std::vector signatures = signature_set[i]; + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + // push signatures in reverse order because script starts to check the top signature on the stack first + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]); + } +} + +void add_signatures_to_transaction_user_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set) { + add_signatures_to_transaction_weighted_multisig(tx, signature_set); + for (size_t itr = 0; itr < tx.vin.size(); itr++) { + tx.vin[0].scriptWitness.push_back(bytes()); + } +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp new file mode 100644 index 00000000..6d802a2f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +fc::ecc::public_key_data create_public_key_data(const std::vector &public_key) { + FC_ASSERT(public_key.size() == 33); + fc::ecc::public_key_data key; + for (size_t i = 0; i < 33; i++) { + key.at(i) = public_key[i]; + } + return key; +} + +bytes get_privkey_bytes(const std::string &privkey_base58) { + const auto privkey = fc::from_base58(privkey_base58); + // Remove version and hash + return bytes(privkey.cbegin() + 1, privkey.cbegin() + 1 + 32); +} + +bytes parse_hex(const std::string &str) { + bytes vec(str.size() / 2); + fc::from_hex(str, vec.data(), vec.size()); + return vec; +} + +std::vector get_pubkey_from_redeemScript(bytes script) { + FC_ASSERT(script.size() >= 37); + + script.erase(script.begin()); + script.erase(script.end() - 2, script.end()); + + std::vector result; + uint64_t count = script.size() / 34; + for (size_t i = 0; i < count; i++) { + result.push_back(bytes(script.begin() + (34 * i) + 1, script.begin() + (34 * (i + 1)))); + } + return result; +} + +bytes public_key_data_to_bytes(const fc::ecc::public_key_data &key) { + bytes result; + result.resize(key.size()); + std::copy(key.begin(), key.end(), result.begin()); + return result; +} + +std::vector read_byte_arrays_from_string(const std::string &string_buf) { + std::stringstream ss(string_buf); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + std::vector data; + for (auto &v : json) { + std::string hex = v.second.data(); + bytes item; + item.resize(hex.size() / 2); + fc::from_hex(hex, (char *)&item[0], item.size()); + data.push_back(item); + } + return data; +} + +std::string write_transaction_signatures(const std::vector &data) { + std::string res = "["; + for (unsigned int idx = 0; idx < data.size(); ++idx) { + res += "\"" + fc::to_hex((char *)&data[idx][0], data[idx].size()) + "\""; + if (idx != data.size() - 1) + res += ","; + } + res += "]"; + return res; +} + +void read_transaction_data(const std::string &string_buf, std::string &tx_hex, std::vector &in_amounts, std::string &redeem_script) { + std::stringstream ss(string_buf); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + tx_hex = json.get("tx_hex"); + in_amounts.clear(); + for (auto &v : json.get_child("in_amounts")) + in_amounts.push_back(fc::to_uint64(v.second.data())); + redeem_script = json.get("redeem_script"); +} + +std::string write_transaction_data(const std::string &tx, const std::vector &in_amounts, const std::string &redeem_script) { + std::string res = "{\"tx_hex\":\"" + tx + "\",\"in_amounts\":["; + for (unsigned int idx = 0; idx < in_amounts.size(); ++idx) { + res += fc::to_string(in_amounts[idx]); + if (idx != in_amounts.size() - 1) + res += ","; + } + res += "],\"redeem_script\":\"" + redeem_script + "\"}"; + return res; +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp deleted file mode 100644 index a11647de..00000000 --- a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp +++ /dev/null @@ -1,680 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -namespace graphene { namespace peerplays_sidechain { - -static const unsigned char OP_0 = 0x00; -static const unsigned char OP_1 = 0x51; -static const unsigned char OP_2 = 0x52; -static const unsigned char OP_3 = 0x53; -static const unsigned char OP_4 = 0x54; -static const unsigned char OP_5 = 0x55; -static const unsigned char OP_6 = 0x56; -static const unsigned char OP_7 = 0x57; -static const unsigned char OP_8 = 0x58; -static const unsigned char OP_9 = 0x59; -static const unsigned char OP_10 = 0x5a; -static const unsigned char OP_11 = 0x5b; -static const unsigned char OP_12 = 0x5c; -static const unsigned char OP_13 = 0x5d; -static const unsigned char OP_14 = 0x5e; -static const unsigned char OP_15 = 0x5f; -static const unsigned char OP_16 = 0x60; - -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) { - } - - void write(const unsigned char *d, size_t s) { - storage_.insert(storage_.end(), d, d + s); - } - - 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_; -}; - -class ReadBytesStream { -public: - ReadBytesStream(const bytes &buffer, size_t pos = 0) : - storage_(buffer), - pos_(pos), - end_(buffer.size()) { - } - - 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; - return true; - } - FC_THROW("invalid bitcoin tx buffer"); - } - - inline bool get(unsigned char &c) { - if (pos_ < end_) { - c = storage_[pos_++]; - return true; - } - 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.write_compact_int(in.scriptWitness.size()); - for (const auto &stack_item : in.scriptWitness) - str.writedata(stack_item); - } - } - 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) { - unsigned int size = ds.read_compact_int(); - in.scriptWitness.resize(size); - for (auto &stack_item : in.scriptWitness) - ds.readdata(stack_item); - } - } - nLockTime = ds.readdata32(); - return ds.current_pos(); -} - -void add_data_to_script(bytes &script, const bytes &data) { - WriteBytesStream str(script); - str.writedata(data); -} - -void add_number_to_script(bytes &script, unsigned char data) { - WriteBytesStream str(script); - if (data == 0) - str.put(OP_0); - else if (data == 1) - str.put(OP_1); - else if (data == 2) - str.put(OP_2); - else if (data == 3) - str.put(OP_3); - else if (data == 4) - str.put(OP_4); - else if (data == 5) - str.put(OP_5); - else if (data == 6) - str.put(OP_6); - else if (data == 7) - str.put(OP_7); - else if (data == 8) - str.put(OP_8); - else if (data == 9) - str.put(OP_9); - else if (data == 10) - str.put(OP_10); - else if (data == 11) - str.put(OP_11); - else if (data == 12) - str.put(OP_12); - else if (data == 13) - str.put(OP_13); - else if (data == 14) - str.put(OP_14); - else if (data == 15) - str.put(OP_15); - else if (data == 16) - str.put(OP_16); - else - add_data_to_script(script, {data}); -} - -bytes generate_redeem_script(std::vector> key_data) { - int total_weight = 0; - bytes result; - add_number_to_script(result, 0); - for (auto &p : key_data) { - total_weight += p.second; - result.push_back(OP_SWAP); - auto raw_data = p.first.serialize(); - add_data_to_script(result, bytes(raw_data.begin(), raw_data.begin() + raw_data.size())); - result.push_back(OP_CHECKSIG); - result.push_back(OP_IF); - add_number_to_script(result, static_cast(p.second)); - result.push_back(OP_ADD); - result.push_back(OP_ENDIF); - } - int threshold_weight = 2 * total_weight / 3; - add_number_to_script(result, static_cast(threshold_weight)); - result.push_back(OP_GREATERTHAN); - 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) { - // calc script hash - fc::sha256 sh = fc::sha256::hash(reinterpret_cast(&script[0]), script.size()); - bytes wp(sh.data(), sh.data() + sh.data_size()); - switch (network) { - case (mainnet): - return segwit_addr_encode("bc", 0, wp); - case (testnet): - case (regtest): - return segwit_addr_encode("tb", 0, wp); - default: - FC_THROW("Unknown bitcoin network type"); - } - FC_THROW("Unknown bitcoin network type"); -} - -bytes lock_script_for_redeem_script(const bytes &script) { - bytes result; - result.push_back(OP_0); - fc::sha256 h = fc::sha256::hash(reinterpret_cast(&script[0]), script.size()); - bytes shash(h.data(), h.data() + h.data_size()); - add_data_to_script(result, shash); - return result; -} - -bytes hash_prevouts(const btc_tx &unsigned_tx) { - fc::sha256::encoder hasher; - for (const auto &in : unsigned_tx.vin) { - bytes data; - in.prevout.to_bytes(data); - hasher.write(reinterpret_cast(&data[0]), data.size()); - } - fc::sha256 res = fc::sha256::hash(hasher.result()); - return bytes(res.data(), res.data() + res.data_size()); -} - -bytes hash_sequence(const btc_tx &unsigned_tx) { - fc::sha256::encoder hasher; - for (const auto &in : unsigned_tx.vin) { - hasher.write(reinterpret_cast(&in.nSequence), sizeof(in.nSequence)); - } - fc::sha256 res = fc::sha256::hash(hasher.result()); - return bytes(res.data(), res.data() + res.data_size()); -} - -bytes hash_outputs(const btc_tx &unsigned_tx) { - fc::sha256::encoder hasher; - for (const auto &out : unsigned_tx.vout) { - bytes data; - out.to_bytes(data); - hasher.write(reinterpret_cast(&data[0]), data.size()); - } - fc::sha256 res = fc::sha256::hash(hasher.result()); - return bytes(res.data(), res.data() + res.data_size()); -} - -const secp256k1_context_t *btc_get_context() { - static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); - return ctx; -} - -bytes der_sign(const fc::ecc::private_key &priv_key, const fc::sha256 &digest) { - fc::ecc::signature result; - int size = result.size(); - FC_ASSERT(secp256k1_ecdsa_sign(btc_get_context(), - (unsigned char *)digest.data(), - (unsigned char *)result.begin(), - &size, - (unsigned char *)priv_key.get_secret().data(), - secp256k1_nonce_function_rfc6979, - nullptr)); - return bytes(result.begin(), result.begin() + size); -} - -std::vector signatures_for_raw_transaction(const bytes &unsigned_tx, - std::vector in_amounts, - const bytes &redeem_script, - const fc::ecc::private_key &priv_key) { - btc_tx tx; - tx.fill_from_bytes(unsigned_tx); - - FC_ASSERT(tx.vin.size() == in_amounts.size(), "Incorrect input amounts data"); - - std::vector results; - auto cur_amount = in_amounts.begin(); - // pre-calc reused values - bytes hashPrevouts = hash_prevouts(tx); - bytes hashSequence = hash_sequence(tx); - bytes hashOutputs = hash_outputs(tx); - // calc digest for every input according to BIP143 - // implement SIGHASH_ALL scheme - for (const auto &in : tx.vin) { - fc::sha256::encoder hasher; - hasher.write(reinterpret_cast(&tx.nVersion), sizeof(tx.nVersion)); - hasher.write(reinterpret_cast(&hashPrevouts[0]), hashPrevouts.size()); - hasher.write(reinterpret_cast(&hashSequence[0]), hashSequence.size()); - bytes data; - in.prevout.to_bytes(data); - hasher.write(reinterpret_cast(&data[0]), data.size()); - bytes serializedScript; - WriteBytesStream stream(serializedScript); - stream.writedata(redeem_script); - hasher.write(reinterpret_cast(&serializedScript[0]), serializedScript.size()); - uint64_t amount = *cur_amount++; - hasher.write(reinterpret_cast(&amount), sizeof(amount)); - hasher.write(reinterpret_cast(&in.nSequence), sizeof(in.nSequence)); - hasher.write(reinterpret_cast(&hashOutputs[0]), hashOutputs.size()); - hasher.write(reinterpret_cast(&tx.nLockTime), sizeof(tx.nLockTime)); - // add sigtype SIGHASH_ALL - uint32_t sigtype = 1; - hasher.write(reinterpret_cast(&sigtype), sizeof(sigtype)); - - fc::sha256 digest = fc::sha256::hash(hasher.result()); - //std::vector res = priv_key.sign(digest); - //bytes s_data(res.begin(), res.end()); - bytes s_data = der_sign(priv_key, digest); - s_data.push_back(1); - results.push_back(s_data); - } - return results; -} - -bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, std::vector in_amounts, const bytes &redeem_script, const std::vector> &priv_keys) { - btc_tx tx; - tx.fill_from_bytes(unsigned_tx); - bytes dummy_data; - for (auto key : priv_keys) { - if (key) { - std::vector signatures = signatures_for_raw_transaction(unsigned_tx, in_amounts, redeem_script, *key); - FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); - // push signatures in reverse order because script starts to check the top signature on the stack first - for (unsigned int i = 0; i < tx.vin.size(); i++) - tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]); - } else { - for (unsigned int i = 0; i < tx.vin.size(); i++) - tx.vin[i].scriptWitness.push_back(dummy_data); - } - } - - for (auto &in : tx.vin) { - in.scriptWitness.push_back(redeem_script); - } - - tx.hasWitness = true; - bytes ret; - tx.to_bytes(ret); - return ret; -} - -bytes add_dummy_signatures_for_pw_transfer(const bytes &unsigned_tx, - const bytes &redeem_script, - unsigned int key_count) { - btc_tx tx; - tx.fill_from_bytes(unsigned_tx); - - bytes dummy_data; - for (auto &in : tx.vin) { - for (unsigned i = 0; i < key_count; i++) - in.scriptWitness.push_back(dummy_data); - in.scriptWitness.push_back(redeem_script); - } - - tx.hasWitness = true; - bytes ret; - tx.to_bytes(ret); - return ret; -} - -bytes partially_sign_pw_transfer_transaction(const bytes &partially_signed_tx, - std::vector in_amounts, - const fc::ecc::private_key &priv_key, - unsigned int key_idx) { - btc_tx tx; - tx.fill_from_bytes(partially_signed_tx); - FC_ASSERT(tx.vin.size() > 0); - bytes redeem_script = tx.vin[0].scriptWitness.back(); - std::vector signatures = signatures_for_raw_transaction(partially_signed_tx, in_amounts, redeem_script, priv_key); - FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); - // push signatures in reverse order because script starts to check the top signature on the stack first - unsigned witness_idx = tx.vin[0].scriptWitness.size() - 2 - key_idx; - for (unsigned int i = 0; i < tx.vin.size(); i++) - tx.vin[i].scriptWitness[witness_idx] = signatures[i]; - bytes ret; - tx.to_bytes(ret); - return ret; -} - -bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, const std::vector> &signature_set, const bytes &redeem_script) { - btc_tx tx; - tx.fill_from_bytes(unsigned_tx); - bytes dummy_data; - for (unsigned int i = 0; i < signature_set.size(); i++) { - std::vector signatures = signature_set[i]; - FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); - // push signatures in reverse order because script starts to check the top signature on the stack first - for (unsigned int i = 0; i < tx.vin.size(); i++) - tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]); - } - - for (auto &in : tx.vin) { - in.scriptWitness.push_back(redeem_script); - } - - tx.hasWitness = true; - bytes ret; - tx.to_bytes(ret); - return ret; -} - -}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp new file mode 100644 index 00000000..c98d22bf --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Bech32 is a string encoding format used in newer address types. +// The output consists of a human-readable part (alphanumeric), a +// separator character (1), and a base32 data section, the last +// 6 characters of which are a checksum. +// +// For more information, see BIP 173. + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace bech32 { + +/** Encode a Bech32 string. Returns the empty string in case of failure. */ +std::string Encode(const std::string &hrp, const std::vector &values); + +/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ +std::pair> Decode(const std::string &str); + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::bech32 diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp new file mode 100644 index 00000000..cf71fdfa --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp @@ -0,0 +1,229 @@ +#pragma once + +#include +#include + +using namespace graphene::chain; + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +const bytes op_num = {0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f}; // OP_1 - OP_15 + +class bitcoin_address { + +public: + enum network { + mainnet, + testnet, + regtest + }; + + bitcoin_address() = default; + + bitcoin_address(const std::string &addr, network ntype = network::regtest) : + address(addr), + type(determine_type()), + raw_address(determine_raw_address()), + network_type(ntype) { + } + + bool operator==(const bitcoin_address &btc_addr) const; + + payment_type get_type() const { + return type; + } + + std::string get_address() const { + return address; + } + + bytes get_raw_address() const { + return raw_address; + } + + bytes get_script() const; + + network get_network_type() const { + return network_type; + } + +private: + enum size_segwit_address { P2WSH = 32, + P2WPKH = 20 }; + + payment_type determine_type(); + + bytes determine_raw_address(); + + bool check_segwit_address(const size_segwit_address &size) const; + + bool is_p2pk() const; + + bool is_p2wpkh() const; + + bool is_p2wsh() const; + + bool is_p2pkh() const; + + bool is_p2sh() const; + +public: + std::string address; + + payment_type type; + + bytes raw_address; + + network network_type; +}; + +class btc_multisig_address : public bitcoin_address { + +public: + btc_multisig_address() = default; + + btc_multisig_address(const size_t n_required, const accounts_keys &keys); + + size_t count_intersection(const accounts_keys &keys) const; + + bytes get_redeem_script() const { + return redeem_script; + } + +private: + void create_redeem_script(); + + void create_address(); + +public: + enum address_types { MAINNET_SCRIPT = 5, + TESTNET_SCRIPT = 196 }; + + enum { OP_0 = 0x00, + OP_EQUAL = 0x87, + OP_HASH160 = 0xa9, + OP_CHECKMULTISIG = 0xae }; + + bytes redeem_script; + + size_t keys_required = 0; + + accounts_keys witnesses_keys; +}; + +// multisig segwit address (P2WSH) +// https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa +class btc_multisig_segwit_address : public btc_multisig_address { + +public: + btc_multisig_segwit_address() = default; + + btc_multisig_segwit_address(const size_t n_required, const accounts_keys &keys); + + bool operator==(const btc_multisig_segwit_address &addr) const; + + bytes get_witness_script() const { + return witness_script; + } + + std::vector get_keys(); + +private: + void create_witness_script(); + + void create_segwit_address(); + + bytes get_address_bytes(const bytes &script_hash); + +public: + bytes witness_script; +}; + +class btc_weighted_multisig_address : public bitcoin_address { + +public: + btc_weighted_multisig_address() = default; + + btc_weighted_multisig_address(const std::vector> &keys_data, + network network_type = network::regtest); + + bytes get_redeem_script() const { + return redeem_script_; + } + bytes get_witness_script() const { + return witness_script_; + } + +private: + void create_redeem_script(const std::vector> &keys_data); + void create_witness_script(); + void create_segwit_address(); + +public: + bytes redeem_script_; + bytes witness_script_; +}; + +class btc_one_or_m_of_n_multisig_address : public bitcoin_address { +public: + btc_one_or_m_of_n_multisig_address() = default; + btc_one_or_m_of_n_multisig_address(const fc::ecc::public_key &user_key_data, const uint8_t nrequired, const std::vector &keys_data, + network network_type = network::regtest); + bytes get_redeem_script() const { + return redeem_script_; + } + bytes get_witness_script() const { + return witness_script_; + } + +private: + void create_redeem_script(const fc::ecc::public_key &user_key_data, const uint8_t nrequired, const std::vector &keys_data); + void create_witness_script(); + void create_segwit_address(); + +public: + bytes redeem_script_; + bytes witness_script_; +}; + +class btc_one_or_weighted_multisig_address : public bitcoin_address { +public: + btc_one_or_weighted_multisig_address() = default; + btc_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data, + network network_type = network::regtest); + bytes get_redeem_script() const { + return redeem_script_; + } + bytes get_witness_script() const { + return witness_script_; + } + +private: + void create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data); + void create_witness_script(); + void create_segwit_address(); + +public: + bytes redeem_script_; + bytes witness_script_; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin + +FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::bitcoin_address::network, + (mainnet)(testnet)(regtest)) + +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::bitcoin_address, (address)(type)(raw_address)(network_type)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_multisig_address, (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script)(keys_required)(witnesses_keys)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_multisig_segwit_address, (graphene::peerplays_sidechain::bitcoin::btc_multisig_address), (witness_script)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_weighted_multisig_address, + (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script_)(witness_script_)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_one_or_m_of_n_multisig_address, + (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script_)(witness_script_)); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp new file mode 100644 index 00000000..afea853f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +enum class op { + // push value + _0 = 0x00, + _1 = 0x51, + _2 = 0x52, + _3 = 0x53, + _4 = 0x54, + _5 = 0x55, + _6 = 0x56, + _7 = 0x57, + _8 = 0x58, + _9 = 0x59, + _10 = 0x5a, + _11 = 0x5b, + _12 = 0x5c, + _13 = 0x5d, + _14 = 0x5e, + _15 = 0x5f, + _16 = 0x60, + + // control + IF = 0x63, + NOTIF = 0x64, + ELSE = 0x67, + ENDIF = 0x68, + RETURN = 0x6a, + + // stack ops + DUP = 0x76, + SWAP = 0x7c, + + // bit logic + EQUAL = 0x87, + EQUALVERIFY = 0x88, + ADD = 0x93, + GREATERTHAN = 0xa0, + GREATERTHANOREQUAL = 0xa2, + + // crypto + HASH160 = 0xa9, + CHECKSIG = 0xac, + CHECKMULTISIG = 0xae, + // Locktime + CHECKLOCKTIMEVERIFY = 0xb1, + CHECKSEQUENCEVERIFY = 0xb2, +}; + +class script_builder { + +public: + script_builder &operator<<(op opcode); + + script_builder &operator<<(uint32_t number); + + script_builder &operator<<(size_t size); + + script_builder &operator<<(const bytes &sc); + + script_builder &operator<<(const fc::sha256 &hash); + + script_builder &operator<<(const fc::ripemd160 &hash); + + script_builder &operator<<(const fc::ecc::public_key_data &pubkey_data); + + operator bytes() const { + return std::move(script); + } + +private: + bytes script; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp new file mode 100644 index 00000000..5668ffc2 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp @@ -0,0 +1,106 @@ +#pragma once +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +struct out_point { + fc::sha256 hash; + uint32_t n = 0; + out_point() = default; + out_point(fc::sha256 _hash, uint32_t _n) : + hash(_hash), + n(_n) { + } + bool operator==(const out_point &op) const; +}; + +struct tx_in { + + bool operator==(const tx_in &ti) const; + + out_point prevout; + bytes scriptSig; + uint32_t nSequence = 0xffffffff; + std::vector scriptWitness; +}; + +struct tx_out { + int64_t value = 0; + bytes scriptPubKey; + + bool operator==(const tx_out &to) const; + + bool is_p2wsh() const; + + bool is_p2wpkh() const; + + bool is_p2pkh() const; + + bool is_p2sh() const; + + bool is_p2pk() const; + + bytes get_data_or_script() const; +}; + +struct bitcoin_transaction { + + bool operator!=(const bitcoin_transaction &bt) const; + + int32_t nVersion = 1; + std::vector vin; + std::vector vout; + uint32_t nLockTime = 0; + + fc::sha256 get_hash() const; + fc::sha256 get_txid() const; + size_t get_vsize() const; +}; + +class bitcoin_transaction_builder { + +public: + bitcoin_transaction_builder() = default; + + bitcoin_transaction_builder(const bitcoin_transaction _tx) : + tx(_tx) { + } + + void set_version(int32_t version); + + void set_locktime(uint32_t lock_time); + + void add_in(const fc::sha256 &txid, uint32_t n_out, + const bytes &script_code, bool front = false, uint32_t sequence = 0xffffffff); + + void add_in(payment_type type, const fc::sha256 &txid, uint32_t n_out, + const bytes &script_code, bool front = false, uint32_t sequence = 0xffffffff); + + void add_in(payment_type type, tx_in txin, const bytes &script_code, bool front = false); + + void add_out(payment_type type, int64_t amount, const std::string &base58_address, bool front = false); + + void add_out(payment_type type, int64_t amount, const fc::ecc::public_key_data &pubkey, bool front = false); + + void add_out(payment_type type, int64_t amount, const bytes &script_code, bool front = false); + + void add_out_all_type(const uint64_t &amount, const bitcoin_address &address, bool front = false); + + bitcoin_transaction get_transaction() const { + return tx; + } + +private: + inline bool is_payment_to_pubkey(payment_type type); + + bytes get_script_pubkey(payment_type type, const bytes &script_code); + + bitcoin_transaction tx; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin + +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::out_point, (hash)(n)) +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::tx_in, (prevout)(scriptSig)(nSequence)(scriptWitness)) +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::tx_out, (value)(scriptPubKey)) +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::bitcoin_transaction, (nVersion)(vin)(vout)(nLockTime)) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp new file mode 100644 index 00000000..b7f2748e --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp @@ -0,0 +1,34 @@ +/* Copyright (c) 2017 Pieter Wuille + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace segwit_addr { + +/** Decode a SegWit address. Returns (witver, witprog). witver = -1 means failure. */ +std::pair> decode(const std::string &hrp, const std::string &addr); + +/** Encode a SegWit address. Empty string means failure. */ +std::string encode(const std::string &hrp, int witver, const std::vector &witprog); + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::segwit_addr \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp new file mode 100644 index 00000000..2f1dfffe --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp @@ -0,0 +1,354 @@ +#pragma once + +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +inline void write_compact_size(bytes &vec, size_t size) { + bytes sb; + sb.reserve(2); + if (size < 253) { + sb.insert(sb.end(), static_cast(size)); + } else if (size <= std::numeric_limits::max()) { + uint16_t tmp = htole16(static_cast(size)); + sb.insert(sb.end(), static_cast(253)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } else if (size <= std::numeric_limits::max()) { + uint32_t tmp = htole32(static_cast(size)); + sb.insert(sb.end(), static_cast(254)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } else { + uint64_t tmp = htole64(static_cast(size)); + sb.insert(sb.end(), static_cast(255)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } + vec.insert(vec.end(), sb.begin(), sb.end()); +} + +template +inline void pack_compact_size(Stream &s, size_t size) { + if (size < 253) { + fc::raw::pack(s, static_cast(size)); + } else if (size <= std::numeric_limits::max()) { + fc::raw::pack(s, static_cast(253)); + fc::raw::pack(s, htole16(static_cast(size))); + } else if (size <= std::numeric_limits::max()) { + fc::raw::pack(s, static_cast(254)); + fc::raw::pack(s, htole32(static_cast(size))); + } else { + fc::raw::pack(s, static_cast(255)); + fc::raw::pack(s, htole64(static_cast(size))); + } +} + +template +inline uint64_t unpack_compact_size(Stream &s) { + uint8_t size; + uint64_t size_ret; + + fc::raw::unpack(s, size); + + if (size < 253) { + size_ret = size; + } else if (size == 253) { + uint16_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le16toh(tmp); + if (size_ret < 253) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } else if (size == 254) { + uint32_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le32toh(tmp); + if (size_ret < 0x10000u) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } else { + uint32_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le64toh(tmp); + if (size_ret < 0x100000000ULL) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } + + if (size_ret > 0x08000000) + FC_THROW_EXCEPTION(fc::parse_error_exception, "unpack_compact_size(): size too large"); + + return size_ret; +} + +template +inline void unpack(Stream &s, int64_t &u, uint32_t _max_depth = FC_PACK_MAX_DEPTH) { + s.read((char *)&u, sizeof(u)); +} + +template +inline void unpack(Stream &s, int32_t &u, uint32_t _max_depth = FC_PACK_MAX_DEPTH) { + s.read((char *)&u, sizeof(u)); +} + +template +inline void pack(Stream &s, const std::vector &v) { + pack_compact_size(s, v.size()); + if (!v.empty()) + s.write(v.data(), v.size()); +} + +template +inline void unpack(Stream &s, std::vector &v) { + const auto size = unpack_compact_size(s); + v.resize(size); + if (size) + s.read(v.data(), size); +} + +template +inline void pack(Stream &s, const T &val) { + fc::raw::pack(s, val); +} + +template +inline void unpack(Stream &s, T &val) { + fc::raw::unpack(s, val); +} + +template +inline void pack(Stream &s, const out_point &op) { + fc::sha256 reversed(op.hash); + std::reverse(reversed.data(), reversed.data() + reversed.data_size()); + s.write(reversed.data(), reversed.data_size()); + pack(s, op.n); +} + +template +inline void unpack(Stream &s, out_point &op) { + uint64_t hash_size = op.hash.data_size(); + std::unique_ptr hash_data(new char[hash_size]); + s.read(hash_data.get(), hash_size); + std::reverse(hash_data.get(), hash_data.get() + hash_size); + + op.hash = fc::sha256(hash_data.get(), hash_size); + unpack(s, op.n); +} + +template +inline void pack(Stream &s, const tx_in &in) { + pack(s, in.prevout); + pack(s, in.scriptSig); + pack(s, in.nSequence); +} + +template +inline void unpack(Stream &s, tx_in &in) { + unpack(s, in.prevout); + unpack(s, in.scriptSig); + unpack(s, in.nSequence); +} + +template +inline void pack(Stream &s, const tx_out &out) { + pack(s, out.value); + pack(s, out.scriptPubKey); +} + +template +inline void unpack(Stream &s, tx_out &out) { + unpack(s, out.value); + unpack(s, out.scriptPubKey); +} + +template +inline void pack(Stream &s, const bitcoin_transaction &tx, bool with_witness = true) { + uint8_t flags = 0; + + if (with_witness) { + for (const auto &in : tx.vin) { + if (!in.scriptWitness.empty()) { + flags |= 1; + break; + } + } + } + + pack(s, tx.nVersion); + + if (flags) { + pack_compact_size(s, 0); + pack(s, flags); + } + + pack_compact_size(s, tx.vin.size()); + for (const auto &in : tx.vin) + pack(s, in); + + pack_compact_size(s, tx.vout.size()); + for (const auto &out : tx.vout) + pack(s, out); + + if (flags & 1) { + for (const auto in : tx.vin) { + pack_compact_size(s, in.scriptWitness.size()); + for (const auto &sc : in.scriptWitness) + pack(s, sc); + } + } + + pack(s, tx.nLockTime); +} + +template +inline void unpack(Stream &s, bitcoin_transaction &tx) { + uint8_t flags = 0; + + unpack(s, tx.nVersion); + + auto vin_size = unpack_compact_size(s); + if (vin_size == 0) { + unpack(s, flags); + vin_size = unpack_compact_size(s); + } + + tx.vin.reserve(vin_size); + for (size_t i = 0; i < vin_size; i++) { + tx_in in; + unpack(s, in); + tx.vin.push_back(in); + } + + const auto vout_size = unpack_compact_size(s); + tx.vout.reserve(vout_size); + for (size_t i = 0; i < vout_size; i++) { + tx_out out; + unpack(s, out); + tx.vout.push_back(out); + } + + if (flags & 1) { + for (auto &in : tx.vin) { + uint64_t stack_size = unpack_compact_size(s); + in.scriptWitness.reserve(stack_size); + for (uint64_t i = 0; i < stack_size; i++) { + std::vector script; + unpack(s, script); + in.scriptWitness.push_back(script); + } + } + } + + unpack(s, tx.nLockTime); +} + +inline std::vector pack(const bitcoin_transaction &v, bool with_witness = true) { + fc::datastream ps; + pack(ps, v, with_witness); + std::vector vec(ps.tellp()); + + if (!vec.empty()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + pack(ds, v, with_witness); + } + return vec; +} + +inline bitcoin_transaction unpack(const std::vector &s) { + try { + bitcoin_transaction tmp; + if (!s.empty()) { + fc::datastream ds(s.data(), size_t(s.size())); + unpack(ds, tmp); + } + return tmp; + } + FC_RETHROW_EXCEPTIONS(warn, "error unpacking ${type}", ("type", "transaction")) +} + +template +inline void pack_tx_signature(Stream &s, const std::vector &scriptPubKey, const bitcoin_transaction &tx, unsigned int in_index, int hash_type) { + pack(s, tx.nVersion); + + pack_compact_size(s, tx.vin.size()); + for (size_t i = 0; i < tx.vin.size(); i++) { + const auto &in = tx.vin[i]; + pack(s, in.prevout); + if (i == in_index) + pack(s, scriptPubKey); + else + pack_compact_size(s, 0); // Blank signature + pack(s, in.nSequence); + } + + pack_compact_size(s, tx.vout.size()); + for (const auto &out : tx.vout) + pack(s, out); + + pack(s, tx.nLockTime); + pack(s, hash_type); +} + +template +inline void pack_tx_witness_signature(Stream &s, const std::vector &scriptCode, const bitcoin_transaction &tx, unsigned int in_index, int64_t amount, int hash_type) { + + fc::sha256 hash_prevouts; + fc::sha256 hash_sequence; + fc::sha256 hash_output; + + { + fc::datastream ps; + for (const auto in : tx.vin) + pack(ps, in.prevout); + + std::vector vec(ps.tellp()); + if (vec.size()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + for (const auto in : tx.vin) + pack(ds, in.prevout); + } + + hash_prevouts = fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); + } + + { + fc::datastream ps; + for (const auto in : tx.vin) + pack(ps, in.nSequence); + + std::vector vec(ps.tellp()); + if (vec.size()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + for (const auto in : tx.vin) + pack(ds, in.nSequence); + } + + hash_sequence = fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); + }; + + { + fc::datastream ps; + for (const auto out : tx.vout) + pack(ps, out); + + std::vector vec(ps.tellp()); + if (vec.size()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + for (const auto out : tx.vout) + pack(ds, out); + } + + hash_output = fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); + } + + pack(s, tx.nVersion); + pack(s, hash_prevouts); + pack(s, hash_sequence); + + pack(s, tx.vin[in_index].prevout); + pack(s, scriptCode); + pack(s, amount); + pack(s, tx.vin[in_index].nSequence); + + pack(s, hash_output); + pack(s, tx.nLockTime); + pack(s, hash_type); +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp new file mode 100644 index 00000000..41808562 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +class bitcoin_transaction; + +const secp256k1_context_t *btc_context(); + +fc::sha256 get_signature_hash(const bitcoin_transaction &tx, const bytes &scriptPubKey, int64_t amount, + size_t in_index, int hash_type, bool is_witness); + +std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context_t *context_sign = nullptr); + +std::vector sign_witness_transaction_part(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const bytes &privkey, + const secp256k1_context_t *context_sign = nullptr, int hash_type = 1); + +void sign_witness_transaction_finalize(bitcoin_transaction &tx, const std::vector &redeem_scripts, bool use_mulisig_workaround = true); + +bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context_t *context); + +std::vector> sort_sigs(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const secp256k1_context_t *context); + +void add_signatures_to_transaction_multisig(bitcoin_transaction &tx, std::vector> &signature_set); + +void add_signatures_to_transaction_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set); + +void add_signatures_to_transaction_user_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set); + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp new file mode 100644 index 00000000..0997194b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +class bitcoin_transaction; + +using bytes = std::vector; +using accounts_keys = std::map; +using full_btc_transaction = std::pair; + +enum class payment_type { + NULLDATA, + P2PK, + P2PKH, + P2SH, + P2WPKH, + P2WSH, + P2SH_WPKH, + P2SH_WSH +}; + +enum class sidechain_proposal_type { + ISSUE_BTC, + SEND_BTC_TRANSACTION, + REVERT_BTC_TRANSACTION +}; + +struct prev_out { + bool operator!=(const prev_out &obj) const { + if (this->hash_tx != obj.hash_tx || + this->n_vout != obj.n_vout || + this->amount != obj.amount) { + return true; + } + return false; + } + + std::string hash_tx; + uint32_t n_vout; + uint64_t amount; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin + +FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::payment_type, (NULLDATA)(P2PK)(P2PKH)(P2SH)(P2WPKH)(P2WSH)(P2SH_WPKH)(P2SH_WSH)); +FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::sidechain_proposal_type, (ISSUE_BTC)(SEND_BTC_TRANSACTION)(REVERT_BTC_TRANSACTION)); +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::prev_out, (hash_tx)(n_vout)(amount)); 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 new file mode 100644 index 00000000..4e1c4b58 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +fc::ecc::public_key_data create_public_key_data(const std::vector &public_key); + +bytes get_privkey_bytes(const std::string &privkey_base58); + +bytes parse_hex(const std::string &str); + +std::vector get_pubkey_from_redeemScript(bytes script); + +bytes public_key_data_to_bytes(const fc::ecc::public_key_data &key); + +std::vector read_byte_arrays_from_string(const std::string &string_buf); + +std::string write_transaction_signatures(const std::vector &data); + +void read_transaction_data(const std::string &string_buf, std::string &tx_hex, std::vector &in_amounts, std::string &redeem_script); + +std::string write_transaction_data(const std::string &tx, const std::vector &in_amounts, const std::string &redeem_script); + +}}} // namespace graphene::peerplays_sidechain::bitcoin \ No newline at end of file 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 deleted file mode 100644 index 9b2dc0c1..00000000 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once -#include -#include - -namespace graphene { namespace peerplays_sidechain { - -enum bitcoin_network { - mainnet, - testnet, - regtest -}; - -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); - -std::vector signatures_for_raw_transaction(const bytes &unsigned_tx, - std::vector in_amounts, - const bytes &redeem_script, - const fc::ecc::private_key &priv_key); - -/* - * unsigned_tx - tx, all inputs of which are spends of the PW P2SH address - * returns signed transaction - */ -bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, - std::vector in_amounts, - const bytes &redeem_script, - const std::vector> &priv_keys); - -/// -////// \brief Adds dummy signatures instead of real signatures -////// \param unsigned_tx -////// \param redeem_script -////// \param key_count -////// \return can be used as partially signed tx -bytes add_dummy_signatures_for_pw_transfer(const bytes &unsigned_tx, - const bytes &redeem_script, - unsigned int key_count); - -/// -/// \brief replaces dummy sgnatures in partially signed tx with real tx -/// \param partially_signed_tx -/// \param in_amounts -/// \param priv_key -/// \param key_idx -/// \return -/// -bytes partially_sign_pw_transfer_transaction(const bytes &partially_signed_tx, - std::vector in_amounts, - const fc::ecc::private_key &priv_key, - unsigned int key_idx); - -/// -/// \brief Creates ready to publish bitcoin transaction from unsigned tx and -/// full set of the signatures. This is alternative way to create tx -/// with partially_sign_pw_transfer_transaction -/// \param unsigned_tx -/// \param signatures -/// \param redeem_script -/// \return -/// -bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, - const std::vector> &signatures, - const bytes &redeem_script); - -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 { - btc_outpoint prevout; - bytes scriptSig; - uint32_t nSequence; - std::vector 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 { - std::vector vin; - std::vector vout; - int32_t nVersion; - uint32_t nLockTime; - bool hasWitness; - - void to_bytes(bytes &stream) const; - size_t fill_from_bytes(const bytes &data, size_t pos = 0); -}; - -}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp index c69efaf4..b6719e0c 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -31,6 +31,7 @@ public: const son_object get_current_son_object(); const son_object get_son_object(son_id_type son_id); bool is_active_son(son_id_type son_id); + bool is_son_deleted(son_id_type son_id); fc::ecc::private_key get_private_key(son_id_type son_id); fc::ecc::private_key get_private_key(chain::public_key_type public_key); }; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp index ab1f4c39..28e2dff9 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp @@ -38,6 +38,7 @@ public: virtual bool process_proposal(const proposal_object &po) = 0; virtual void process_primary_wallet() = 0; + virtual void process_sidechain_addresses() = 0; virtual bool process_deposit(const son_wallet_deposit_object &swdo) = 0; virtual bool process_withdrawal(const son_wallet_withdraw_object &swwo) = 0; virtual std::string process_sidechain_transaction(const sidechain_transaction_object &sto) = 0; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp index 1f87997d..03c6cb6c 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -14,7 +14,7 @@ class btc_txout { public: std::string txid_; unsigned int out_num_; - double amount_; + uint64_t amount_; }; class bitcoin_rpc_client { @@ -35,16 +35,16 @@ public: std::string getaddressinfo(const std::string &address); std::string getblock(const std::string &block_hash, int32_t verbosity = 2); std::string gettransaction(const std::string &txid, const bool include_watch_only = false); - void importaddress(const std::string &address_or_script); + void importaddress(const std::string &address_or_script, const std::string &label = "", const bool rescan = true, const bool p2sh = false); std::vector listunspent(const uint32_t minconf = 1, const uint32_t maxconf = 9999999); std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf = 1, const uint32_t maxconf = 9999999); std::string loadwallet(const std::string &filename); std::string sendrawtransaction(const std::string &tx_hex); std::string signrawtransactionwithwallet(const std::string &tx_hash); std::string unloadwallet(const std::string &filename); - std::string walletlock(); + //std::string walletlock(); std::string walletprocesspsbt(std::string const &tx_psbt); - bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60); + //bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60); private: fc::http::reply send_post_request(std::string body, bool show_log = false); @@ -87,6 +87,7 @@ public: bool process_proposal(const proposal_object &po); void process_primary_wallet(); + void process_sidechain_addresses(); bool process_deposit(const son_wallet_deposit_object &swdo); bool process_withdrawal(const son_wallet_withdraw_object &swwo); std::string process_sidechain_transaction(const sidechain_transaction_object &sto); @@ -106,26 +107,18 @@ private: fc::future on_changed_objects_task; + std::string create_primary_wallet_address(const std::vector &son_pubkeys); + std::string create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address); std::string create_deposit_transaction(const son_wallet_deposit_object &swdo); std::string create_withdrawal_transaction(const son_wallet_withdraw_object &swwo); - std::string create_transaction(const std::vector &inputs, const fc::flat_map outputs); + std::string create_transaction(const std::vector &inputs, const fc::flat_map outputs, std::string &redeem_script); std::string sign_transaction(const sidechain_transaction_object &sto); std::string send_transaction(const sidechain_transaction_object &sto); - std::string create_transaction_raw(const std::vector &inputs, const fc::flat_map outputs); - std::string create_transaction_psbt(const std::vector &inputs, const fc::flat_map outputs); - std::string create_transaction_standalone(const std::vector &inputs, const fc::flat_map outputs); - - std::string sign_transaction_raw(const sidechain_transaction_object &sto); - std::string sign_transaction_psbt(const sidechain_transaction_object &sto); - std::string sign_transaction_standalone(const sidechain_transaction_object &sto); - - std::string send_transaction_raw(const sidechain_transaction_object &sto); - std::string send_transaction_psbt(const sidechain_transaction_object &sto); - void handle_event(const std::string &event_data); + std::string get_redeemscript_for_userdeposit(const std::string &user_address); std::vector extract_info_from_block(const std::string &_block); void on_changed_objects(const vector &ids, const flat_set &accounts); void on_changed_objects_cb(const vector &ids, const flat_set &accounts); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp index 5c764fb8..342d5094 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp @@ -15,6 +15,7 @@ public: bool process_proposal(const proposal_object &po); void process_primary_wallet(); + void process_sidechain_addresses(); bool process_deposit(const son_wallet_deposit_object &swdo); bool process_withdrawal(const son_wallet_withdraw_object &swwo); std::string process_sidechain_transaction(const sidechain_transaction_object &sto); diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index 026cb3b5..491d516b 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -37,6 +37,7 @@ public: const son_object get_current_son_object(); const son_object get_son_object(son_id_type son_id); bool is_active_son(son_id_type son_id); + bool is_son_deleted(son_id_type son_id); bool is_son_delete_op_valid(const chain::operation &op); bool is_son_down_op_valid(const chain::operation &op); bool is_valid_son_proposal(const chain::proposal_object &proposal); @@ -268,6 +269,15 @@ bool peerplays_sidechain_plugin_impl::is_active_son(son_id_type son_id) { return (it != active_son_ids.end()); } +bool peerplays_sidechain_plugin_impl::is_son_deleted(son_id_type son_id) { + const auto &idx = plugin.database().get_index_type().indices().get(); + auto son_obj = idx.find(son_id); + if (son_obj == idx.end()) + return true; + + return false; +} + bool peerplays_sidechain_plugin_impl::is_son_delete_op_valid(const chain::operation &op) { son_delete_operation delete_op = op.get(); return plugin.database().is_son_dereg_valid(delete_op.son_id); @@ -375,6 +385,9 @@ void peerplays_sidechain_plugin_impl::son_processing() { ("scheduled_son_id", scheduled_son_id)("now", now)); for (son_id_type son_id : plugin.get_sons()) { + if (plugin.is_son_deleted(son_id)) { + continue; + } current_son_id = son_id; // These tasks are executed by @@ -646,6 +659,10 @@ bool peerplays_sidechain_plugin::is_active_son(son_id_type son_id) { return my->is_active_son(son_id); } +bool peerplays_sidechain_plugin::is_son_deleted(son_id_type son_id) { + return my->is_son_deleted(son_id); +} + fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(son_id_type son_id) { return my->get_private_key(son_id); } diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp index 68ca01e3..b7f7ba5d 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp @@ -315,6 +315,7 @@ void sidechain_net_handler::process_proposals() { void sidechain_net_handler::process_active_sons_change() { process_primary_wallet(); + process_sidechain_addresses(); } void sidechain_net_handler::process_deposits() { @@ -326,7 +327,7 @@ void sidechain_net_handler::process_deposits() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_deposit_object &swdo) { - if(swdo.id == object_id_type(0, 0, 0)) + if (swdo.id == object_id_type(0, 0, 0)) return; ilog("Deposit to process: ${swdo}", ("swdo", swdo)); @@ -379,7 +380,7 @@ void sidechain_net_handler::process_withdrawals() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_withdraw_object &swwo) { - if(swwo.id == object_id_type(0, 0, 0)) + if (swwo.id == object_id_type(0, 0, 0)) return; ilog("Withdraw to process: ${swwo}", ("swwo", swwo)); @@ -426,7 +427,7 @@ void sidechain_net_handler::process_sidechain_transactions() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, false)); std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { - if(sto.id == object_id_type(0, 0, 0)) + if (sto.id == object_id_type(0, 0, 0)) return; ilog("Sidechain transaction to process: ${sto}", ("sto", sto.id)); @@ -461,7 +462,7 @@ void sidechain_net_handler::send_sidechain_transactions() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { - if(sto.id == object_id_type(0, 0, 0)) + if (sto.id == object_id_type(0, 0, 0)) return; ilog("Sidechain transaction to send: ${sto}", ("sto", sto.id)); @@ -490,28 +491,4 @@ void sidechain_net_handler::send_sidechain_transactions() { }); } -bool sidechain_net_handler::process_proposal(const proposal_object &po) { - FC_ASSERT(false, "process_proposal not implemented"); -} - -void sidechain_net_handler::process_primary_wallet() { - FC_ASSERT(false, "process_primary_wallet not implemented"); -} - -bool sidechain_net_handler::process_deposit(const son_wallet_deposit_object &swdo) { - FC_ASSERT(false, "process_deposit not implemented"); -} - -bool sidechain_net_handler::process_withdrawal(const son_wallet_withdraw_object &swwo) { - FC_ASSERT(false, "process_withdrawal not implemented"); -} - -std::string sidechain_net_handler::process_sidechain_transaction(const sidechain_transaction_object &sto) { - FC_ASSERT(false, "process_sidechain_transaction not implemented"); -} - -std::string sidechain_net_handler::send_sidechain_transaction(const sidechain_transaction_object &sto) { - FC_ASSERT(false, "send_sidechain_transaction not implemented"); -} - }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index 16baa862..ed410244 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -16,6 +16,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace graphene { namespace peerplays_sidechain { @@ -44,7 +49,7 @@ std::string bitcoin_rpc_client::addmultisigaddress(const uint32_t nrequired, con pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); } params = params + pubkeys + std::string("]"); - body = body + params + std::string(", null, \"p2sh-segwit\"] }"); + body = body + params + std::string(", null, \"bech32\"] }"); const auto reply = send_post_request(body); @@ -457,8 +462,10 @@ std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t std::string bitcoin_rpc_client::gettransaction(const std::string &txid, const bool include_watch_only) { std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"gettransaction\", \"method\": " - "\"gettransaction\", \"params\": [\"" + - txid + "\"] }"); + "\"gettransaction\", \"params\": ["); + + std::string params = "\"" + txid + "\", " + (include_watch_only ? "true" : "false"); + body = body + params + "] }"; const auto reply = send_post_request(body); @@ -481,10 +488,15 @@ std::string bitcoin_rpc_client::gettransaction(const std::string &txid, const bo return ""; } -void bitcoin_rpc_client::importaddress(const std::string &address_or_script) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importaddress\", " - "\"method\": \"importaddress\", \"params\": [" + - std::string("\"") + address_or_script + std::string("\"") + std::string("] }")); +void bitcoin_rpc_client::importaddress(const std::string &address_or_script, const std::string &label, const bool rescan, const bool p2sh) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importaddress\", " + "\"method\": \"importaddress\", \"params\": ["); + + std::string params = "\"" + address_or_script + "\", " + + "\"" + label + "\", " + + (rescan ? "true" : "false") + ", " + + (p2sh ? "true" : "false"); + body = body + params + "] }"; const auto reply = send_post_request(body); @@ -528,7 +540,9 @@ std::vector bitcoin_rpc_client::listunspent(const uint32_t minconf, c btc_txout txo; txo.txid_ = entry.second.get_child("txid").get_value(); txo.out_num_ = entry.second.get_child("vout").get_value(); - txo.amount_ = entry.second.get_child("amount").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); result.push_back(txo); } } @@ -566,7 +580,9 @@ std::vector bitcoin_rpc_client::listunspent_by_address_and_amount(con btc_txout txo; txo.txid_ = entry.second.get_child("txid").get_value(); txo.out_num_ = entry.second.get_child("vout").get_value(); - txo.amount_ = entry.second.get_child("amount").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); result.push_back(txo); } } @@ -685,32 +701,32 @@ std::string bitcoin_rpc_client::unloadwallet(const std::string &filename) { return ""; } -std::string bitcoin_rpc_client::walletlock() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletlock\", \"method\": " - "\"walletlock\", \"params\": [] }"); - - const auto reply = send_post_request(body); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; -} +//std::string bitcoin_rpc_client::walletlock() { +// std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletlock\", \"method\": " +// "\"walletlock\", \"params\": [] }"); +// +// const auto reply = send_post_request(body); +// +// if (reply.body.empty()) { +// wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); +// return ""; +// } +// +// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); +// boost::property_tree::ptree json; +// boost::property_tree::read_json(ss, json); +// +// if (reply.status == 200) { +// std::stringstream ss; +// boost::property_tree::json_parser::write_json(ss, json.get_child("result")); +// return ss.str(); +// } +// +// if (json.count("error") && !json.get_child("error").empty()) { +// wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); +// } +// return ""; +//} std::string bitcoin_rpc_client::walletprocesspsbt(std::string const &tx_psbt) { std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletprocesspsbt\", \"method\": " @@ -738,31 +754,31 @@ std::string bitcoin_rpc_client::walletprocesspsbt(std::string const &tx_psbt) { return ""; } -bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_t timeout) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletpassphrase\", \"method\": " - "\"walletpassphrase\", \"params\": [\"" + - passphrase + "\", " + std::to_string(timeout) + "] }"); - - const auto reply = send_post_request(body); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return false; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return true; - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return false; -} +//bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_t timeout) { +// std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletpassphrase\", \"method\": " +// "\"walletpassphrase\", \"params\": [\"" + +// passphrase + "\", " + std::to_string(timeout) + "] }"); +// +// const auto reply = send_post_request(body); +// +// if (reply.body.empty()) { +// wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); +// return false; +// } +// +// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); +// boost::property_tree::ptree json; +// boost::property_tree::read_json(ss, json); +// +// if (reply.status == 200) { +// return true; +// } +// +// if (json.count("error") && !json.get_child("error").empty()) { +// wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); +// } +// return false; +//} fc::http::reply bitcoin_rpc_client::send_post_request(std::string body, bool show_log) { fc::http::connection conn; @@ -954,8 +970,7 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); } - uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1; - string reply_str = bitcoin_client->createmultisig(nrequired, son_pubkeys_bitcoin); + string reply_str = create_primary_wallet_address(active_sons); std::stringstream active_pw_ss(reply_str); boost::property_tree::ptree active_pw_pt; @@ -1010,7 +1025,7 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) uint64_t swdo_amount = swdo->sidechain_amount.value; uint64_t swdo_vout = std::stoll(swdo->sidechain_uid.substr(swdo->sidechain_uid.find_last_of("-") + 1)); - std::string tx_str = bitcoin_client->gettransaction(swdo_txid); + std::string tx_str = bitcoin_client->gettransaction(swdo_txid, true); std::stringstream tx_ss(tx_str); boost::property_tree::ptree tx_json; boost::property_tree::read_json(tx_ss, tx_json); @@ -1025,12 +1040,14 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) for (auto &input : tx_json.get_child("result.details")) { tx_address = input.second.get("address"); - std::string tx_amount_s = input.second.get("amount"); - tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); - tx_amount = std::stoll(tx_amount_s); - std::string tx_vout_s = input.second.get("vout"); - tx_vout = std::stoll(tx_vout_s); - break; + if ((tx_address == swdo_address) && (input.second.get("category") == "receive")) { + std::string tx_amount_s = input.second.get("amount"); + tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); + tx_amount = std::stoll(tx_amount_s); + std::string tx_vout_s = input.second.get("vout"); + tx_vout = std::stoll(tx_vout_s); + break; + } } should_approve = (swdo_txid == tx_txid) && @@ -1104,16 +1121,7 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { const chain::global_property_object &gpo = database.get_global_properties(); auto active_sons = gpo.active_sons; - vector son_pubkeys_bitcoin; - for (const son_info &si : active_sons) { - son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); - } - - if (!wallet_password.empty()) { - bitcoin_client->walletpassphrase(wallet_password, 5); - } - uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1; - string reply_str = bitcoin_client->createmultisig(nrequired, son_pubkeys_bitcoin); + string reply_str = create_primary_wallet_address(active_sons); std::stringstream active_pw_ss(reply_str); boost::property_tree::ptree active_pw_pt; @@ -1165,6 +1173,50 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { } } +void sidechain_net_handler_bitcoin::process_sidechain_addresses() { + using namespace bitcoin; + + const chain::global_property_object &gpo = database.get_global_properties(); + std::vector> pubkeys; + for (auto &son : gpo.active_sons) { + std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkeys.push_back(std::make_pair(pubkey, son.weight)); + } + + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&](const sidechain_address_object &sao) { + auto usr_pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(sao.deposit_public_key))); + + btc_one_or_weighted_multisig_address addr(usr_pubkey, pubkeys); + + sidechain_address_update_operation op; + op.payer = plugin.get_current_son_object().son_account; + op.sidechain_address_id = sao.id; + op.sidechain_address_account = sao.sidechain_address_account; + op.sidechain = sao.sidechain; + op.deposit_public_key = sao.deposit_public_key; + op.deposit_address = addr.get_address(); + op.withdraw_public_key = sao.withdraw_public_key; + op.withdraw_address = sao.withdraw_address; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + }); +} + bool sidechain_net_handler_bitcoin::process_deposit(const son_wallet_deposit_object &swdo) { if (proposal_exists(chain::operation::tag::value, swdo.id)) { @@ -1244,20 +1296,6 @@ bool sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw } std::string sidechain_net_handler_bitcoin::process_sidechain_transaction(const sidechain_transaction_object &sto) { - //// Uncomment to get signing in order from sto.signers - //son_id_type invalid_signer = son_id_type(0xFFFFFFFF); - //son_id_type next_signer = invalid_signer; - //for (auto &signer : sto.signers) { - // if (signer.second == false) { - // next_signer = signer.first; - // break; - // } - //} - // - //if ((next_signer == invalid_signer) || (next_signer != plugin.get_current_son_id())) { - // return ""; - //} - return sign_transaction(sto); } @@ -1265,12 +1303,35 @@ std::string sidechain_net_handler_bitcoin::send_sidechain_transaction(const side return send_transaction(sto); } +std::string sidechain_net_handler_bitcoin::create_primary_wallet_address(const std::vector &son_pubkeys) { + using namespace bitcoin; + + std::vector> pubkey_weights; + for (auto &son : son_pubkeys) { + std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkey_weights.push_back(std::make_pair(pub_key, son.weight)); + } + + btc_weighted_multisig_address addr(pubkey_weights); + + std::stringstream ss; + + ss << "{\"result\": {\"address\": \"" << addr.get_address() << "\", \"redeemScript\": \"" << fc::to_hex(addr.get_redeem_script()) << "\"" + << "}, \"error\":null}"; + + std::string res = ss.str(); + ilog("Weighted Multisig Address = ${a}", ("a", res)); + return res; +} + std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address) { std::stringstream prev_sw_ss(prev_swo.addresses.find(sidechain_type::bitcoin)->second); boost::property_tree::ptree prev_sw_pt; boost::property_tree::read_json(prev_sw_ss, prev_sw_pt); std::string prev_pw_address = prev_sw_pt.get("address"); + std::string prev_redeem_script = prev_sw_pt.get("redeemScript"); if (prev_pw_address == new_sw_address) { wlog("BTC previous and new primary wallet addresses are same. No funds moving needed [from ${prev_sw} to ${new_sw_address}]", ("prev_swo", prev_swo.id)("active_sw", new_sw_address)); @@ -1281,8 +1342,7 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(con uint64_t min_fee_rate = 1000; fee_rate = std::max(fee_rate, min_fee_rate); - double min_amount = ((double)fee_rate / 100000000.0); // Account only for relay fee for now - double total_amount = 0.0; + uint64_t total_amount = 0.0; std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0); if (inputs.size() == 0) { @@ -1293,16 +1353,16 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(con total_amount += utx.amount_; } - if (min_amount >= total_amount) { + if (fee_rate >= total_amount) { elog("Failed not enough BTC to transfer from ${fa}", ("fa", prev_pw_address)); return ""; } } fc::flat_map outputs; - outputs[new_sw_address] = total_amount - min_amount; + outputs[new_sw_address] = double(total_amount - fee_rate) / 100000000.0; - return create_transaction(inputs, outputs); + return create_transaction(inputs, outputs, prev_redeem_script); } std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_wallet_deposit_object &swdo) { @@ -1311,7 +1371,8 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_ if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { return ""; } - + //Get redeem script for deposit address + std::string redeem_script = get_redeemscript_for_userdeposit(swdo.sidechain_to); std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; std::stringstream ss(pw_address_json); @@ -1336,12 +1397,13 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_ btc_txout utxo; utxo.txid_ = txid; utxo.out_num_ = std::stoul(nvout); + utxo.amount_ = swdo.sidechain_amount.value; inputs.push_back(utxo); outputs[pw_address] = transfer_amount; - return create_transaction(inputs, outputs); + return create_transaction(inputs, outputs, redeem_script); } std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) { @@ -1358,13 +1420,13 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s boost::property_tree::read_json(ss, json); std::string pw_address = json.get("address"); + std::string redeem_script = json.get("redeemScript"); uint64_t fee_rate = bitcoin_client->estimatesmartfee(); uint64_t min_fee_rate = 1000; fee_rate = std::max(fee_rate, min_fee_rate); - double min_amount = ((double)(swwo.withdraw_amount.value + fee_rate) / 100000000.0); // Account only for relay fee for now - double total_amount = 0.0; + uint64_t total_amount = 0; std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0); if (inputs.size() == 0) { @@ -1375,254 +1437,119 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s total_amount += utx.amount_; } - if (min_amount > total_amount) { + if ((swwo.withdraw_amount.value > total_amount) || (fee_rate >= swwo.withdraw_amount.value)) { elog("Failed not enough BTC to spend for ${pw}", ("pw", pw_address)); return ""; } } fc::flat_map outputs; - outputs[swwo.withdraw_address] = swwo.withdraw_amount.value / 100000000.0; - if ((total_amount - min_amount) > 0.0) { - outputs[pw_address] = total_amount - min_amount; + outputs[swwo.withdraw_address] = (swwo.withdraw_amount.value - fee_rate) / 100000000.0; + if ((total_amount - swwo.withdraw_amount.value) > 0.0) { + outputs[pw_address] = double(total_amount - swwo.withdraw_amount.value) / 100000000.0; } - return create_transaction(inputs, outputs); + return create_transaction(inputs, outputs, redeem_script); } -// Creates transaction in any format // Function to actually create transaction should return transaction string, or empty string in case of failure -std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector &inputs, const fc::flat_map outputs) { - std::string new_tx = ""; - //new_tx = create_transaction_raw(inputs, outputs); - new_tx = create_transaction_psbt(inputs, outputs); - //new_tx = create_transaction_standalone(inputs, outputs); - return new_tx; +std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector &inputs, const fc::flat_map outputs, std::string &redeem_script) { + using namespace bitcoin; + + bitcoin_transaction_builder tb; + std::vector in_amounts; + + tb.set_version(2); + // Set vins + for (auto in : inputs) { + tb.add_in(fc::sha256(in.txid_), in.out_num_, bitcoin::bytes()); + in_amounts.push_back(in.amount_); + } + // Set vouts + for (auto out : outputs) { + uint64_t satoshis = out.second * 100000000.0; + tb.add_out_all_type(satoshis, out.first); + } + + const auto tx = tb.get_transaction(); + std::string hex_tx = fc::to_hex(pack(tx)); + std::string tx_raw = write_transaction_data(hex_tx, in_amounts, redeem_script); + ilog("Raw transaction ${tx}", ("tx", tx_raw)); + return tx_raw; } // Adds signature to transaction // Function to actually add signature should return transaction with added signature string, or empty string in case of failure std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_transaction_object &sto) { - std::string new_tx = ""; - //new_tx = sign_transaction_raw(sto); - new_tx = sign_transaction_psbt(sto); - //new_tx = sign_transaction_standalone(sto); - return new_tx; + using namespace bitcoin; + std::string pubkey = plugin.get_current_son_object().sidechain_public_keys.at(sidechain); + std::string prvkey = get_private_key(pubkey); + std::vector in_amounts; + std::string tx_hex; + std::string redeem_script; + + fc::optional btc_private_key = graphene::utilities::wif_to_key(prvkey); + if (!btc_private_key) { + elog("Invalid private key ${pk}", ("pk", prvkey)); + return ""; + } + const auto secret = btc_private_key->get_secret(); + bitcoin::bytes privkey_signing(secret.data(), secret.data() + secret.data_size()); + + read_transaction_data(sto.transaction, tx_hex, in_amounts, redeem_script); + + ilog("Sign transaction retreived: ${s}", ("s", tx_hex)); + + bitcoin_transaction tx = unpack(parse_hex(tx_hex)); + std::vector redeem_scripts(tx.vin.size(), parse_hex(redeem_script)); + auto sigs = sign_witness_transaction_part(tx, redeem_scripts, in_amounts, privkey_signing, btc_context(), 1); + std::string tx_signature = write_transaction_signatures(sigs); + + ilog("Signatures: son-id = ${son}, pkey = ${prvkey}, tx_signature = ${s}", ("son", plugin.get_current_son_id())("prvkey", prvkey)("s", tx_signature)); + + return tx_signature; } std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_transaction_object &sto) { - //return send_transaction_raw(sto); - return send_transaction_psbt(sto); -} + using namespace bitcoin; + std::vector in_amounts; + std::string tx_hex; + std::string redeem_script; -std::string sidechain_net_handler_bitcoin::create_transaction_raw(const std::vector &inputs, const fc::flat_map outputs) { - return bitcoin_client->createrawtransaction(inputs, outputs); -} + read_transaction_data(sto.transaction, tx_hex, in_amounts, redeem_script); -std::string sidechain_net_handler_bitcoin::create_transaction_psbt(const std::vector &inputs, const fc::flat_map outputs) { - return bitcoin_client->createpsbt(inputs, outputs); -} + ilog("Send transaction retreived: ${s}", ("s", tx_hex)); -std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const std::vector &inputs, const fc::flat_map outputs) { - // Examples + bitcoin_transaction tx = unpack(parse_hex(tx_hex)); - // Transaction with no inputs and outputs - //bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" createrawtransaction '[]' '[]' - //02000000000000000000 - //bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" decoderawtransaction 02000000000000000000 - //{ - // "txid": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", - // "hash": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", - // "version": 2, - // "size": 10, - // "vsize": 10, - // "weight": 40, - // "locktime": 0, - // "vin": [ - // ], - // "vout": [ - // ] - //} + std::vector redeem_scripts(tx.vin.size(), parse_hex(redeem_script)); - // Transaction with input and output - //{ - // "txid": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b", - // "hash": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b", - // "version": 2, - // "size": 83, - // "vsize": 83, - // "weight": 332, - // "locktime": 0, - // "vin": [ - // { - // "txid": "3d322dc2640239a2e68e182b254d19c88e5172a61947f94a105c3f57618092ff", - // "vout": 0, - // "scriptSig": { - // "asm": "", - // "hex": "" - // }, - // "sequence": 4294967295 - // } - // ], - // "vout": [ - // { - // "value": 1.00000000, - // "n": 0, - // "scriptPubKey": { - // "asm": "OP_HASH160 b87c323018cae236eb03a1f63000c85b672270f6 OP_EQUAL", - // "hex": "a914b87c323018cae236eb03a1f63000c85b672270f687", - // "reqSigs": 1, - // "type": "scripthash", - // "addresses": [ - // "2NA4h6sc9oZ4ogfNKU9Wp6fkqPZLZPqqpgf" - // ] - // } - // } - // ] - //} - - return ""; -} - -std::string sidechain_net_handler_bitcoin::sign_transaction_raw(const sidechain_transaction_object &sto) { - if (sto.transaction.empty()) { - elog("Signing failed, tx string is empty"); - return ""; + uint32_t inputs_number = in_amounts.size(); + vector dummy; + dummy.resize(inputs_number); + //Organise weighted address signatures + //Add dummies for empty signatures + vector> signatures; + for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) { + if (sto.signatures[idx].second.empty()) + signatures.push_back(dummy); + else + signatures.push_back(read_byte_arrays_from_string(sto.signatures[idx].second)); } - - if (!wallet_password.empty()) { - bitcoin_client->walletpassphrase(wallet_password, 5); + //Add empty sig for user signature for Deposit transaction + if (sto.object_id.type() == son_wallet_deposit_object::type_id) { + add_signatures_to_transaction_user_weighted_multisig(tx, signatures); + } else { + add_signatures_to_transaction_weighted_multisig(tx, signatures); } + //Add redeemscripts to vins and make tx ready for sending + sign_witness_transaction_finalize(tx, redeem_scripts, false); + std::string final_tx_hex = fc::to_hex(pack(tx)); + std::string res = bitcoin_client->sendrawtransaction(final_tx_hex); - std::string reply_str = bitcoin_client->signrawtransactionwithwallet(sto.transaction); + ilog("Send transaction: ${tx}, [${res}]", ("tx", final_tx_hex)("res", res)); - std::stringstream ss(reply_str); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - boost::property_tree::ptree json_res = json.get_child("result"); - - if ((json_res.count("hex") == 0) || (json_res.count("complete") == 0)) { - elog("Failed to process raw transaction ${tx}", ("tx", sto.transaction)); - return ""; - } - - std::string new_tx_raw = json_res.get("hex"); - bool complete_raw = json_res.get("complete"); - - if (complete_raw) { - return new_tx_raw; - } - return new_tx_raw; -} - -std::string sidechain_net_handler_bitcoin::sign_transaction_psbt(const sidechain_transaction_object &sto) { - if (sto.transaction.empty()) { - elog("Signing failed, tx string is empty"); - return ""; - } - - if (!wallet_password.empty()) { - bitcoin_client->walletpassphrase(wallet_password, 5); - } - - std::string reply_str = bitcoin_client->walletprocesspsbt(sto.transaction); - - std::stringstream ss(reply_str); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - boost::property_tree::ptree json_res = json.get_child("result"); - - if ((json_res.count("psbt") == 0) || (json_res.count("complete") == 0)) { - elog("Failed to process psbt transaction ${tx}", ("tx", sto.transaction)); - return ""; - } - - std::string new_tx_psbt = json_res.get("psbt"); - bool complete_psbt = json_res.get("complete"); - - if (!complete_psbt) { - // Try to combine and finalize - vector psbts; - for (auto signature : sto.signatures) { - if (!signature.second.empty()) { - psbts.push_back(signature.second); - } - } - psbts.push_back(new_tx_psbt); - - std::string reply_str = bitcoin_client->combinepsbt(psbts); - - std::stringstream ss(reply_str); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - if (json.count("error") && json.get_child("error").empty()) { - - std::string new_tx_psbt = json.get("result"); - - std::string reply_str_fin = bitcoin_client->finalizepsbt(new_tx_psbt); - std::stringstream ss_fin(reply_str_fin); - boost::property_tree::ptree json_fin; - boost::property_tree::read_json(ss_fin, json_fin); - boost::property_tree::ptree json_res = json_fin.get_child("result"); - - if (json_res.count("hex") && json_res.count("complete")) { - complete_psbt = json_res.get("complete"); - } - } - } - - return new_tx_psbt; -} - -std::string sidechain_net_handler_bitcoin::sign_transaction_standalone(const sidechain_transaction_object &sto) { - - return ""; -} - -std::string sidechain_net_handler_bitcoin::send_transaction_raw(const sidechain_transaction_object &sto) { - return bitcoin_client->sendrawtransaction(sto.transaction); -} - -std::string sidechain_net_handler_bitcoin::send_transaction_psbt(const sidechain_transaction_object &sto) { - vector psbts; - for (auto signature : sto.signatures) { - if (!signature.second.empty()) { - psbts.push_back(signature.second); - } - } - - std::string reply_str = bitcoin_client->combinepsbt(psbts); - - std::stringstream ss(reply_str); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (json.count("error") && !json.get_child("error").empty()) { - elog("Failed to combine psbt transactions from ${sto}", ("sto", sto)); - return ""; - } - - std::string new_tx_psbt = json.get("result"); - - std::string reply_str_fin = bitcoin_client->finalizepsbt(new_tx_psbt); - std::stringstream ss_fin(reply_str_fin); - boost::property_tree::ptree json_fin; - boost::property_tree::read_json(ss_fin, json_fin); - boost::property_tree::ptree json_res = json_fin.get_child("result"); - - if ((json_res.count("hex") == 0) || (json_res.count("complete") == 0)) { - elog("Failed to finalize psbt transaction ${tx}", ("tx", new_tx_psbt)); - return ""; - } - - std::string new_tx_raw = json_res.get("hex"); - bool complete_raw = json_res.get("complete"); - - if (complete_raw) { - return bitcoin_client->sendrawtransaction(new_tx_raw); - } - - return ""; + return res; } void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { @@ -1633,6 +1560,7 @@ void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); for (const auto &v : vins) { + // !!! EXTRACT DEPOSIT ADDRESS FROM SIDECHAIN ADDRESS OBJECT const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, v.address)); if (addr_itr == sidechain_addresses_idx.end()) continue; @@ -1661,6 +1589,31 @@ void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) } } +std::string sidechain_net_handler_bitcoin::get_redeemscript_for_userdeposit(const std::string &user_address) { + using namespace bitcoin; + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, user_address)); + if (addr_itr == sidechain_addresses_idx.end()) { + return ""; + } + + const auto &idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return ""; + } + + std::vector> pubkey_weights; + for (auto &son : obj->sons) { + std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkey_weights.push_back(std::make_pair(pub_key, son.weight)); + } + auto user_pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(addr_itr->deposit_public_key))); + btc_one_or_weighted_multisig_address deposit_addr(user_pub_key, pubkey_weights); + return fc::to_hex(deposit_addr.get_redeem_script()); +} + std::vector sidechain_net_handler_bitcoin::extract_info_from_block(const std::string &_block) { std::stringstream ss(_block); boost::property_tree::ptree block; @@ -1716,28 +1669,23 @@ void sidechain_net_handler_bitcoin::on_changed_objects_cb(const vectorwalletpassphrase(wallet_password, 5); - } - - std::string pw_address = ""; if (pw_pt.count("address")) { - pw_address = pw_pt.get("address"); + std::string pw_address = pw_pt.get("address"); bitcoin_client->importaddress(pw_address); } - std::string pw_redeem_script = ""; if (pw_pt.count("redeemScript")) { - pw_redeem_script = pw_pt.get("redeemScript"); - bitcoin_client->importaddress(pw_redeem_script); + std::string pw_redeem_script = pw_pt.get("redeemScript"); + bitcoin_client->importaddress(pw_redeem_script, "", true, true); } - - vector son_pubkeys_bitcoin; - for (const son_info &si : swo->sons) { - son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); - } - uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1; - bitcoin_client->addmultisigaddress(nrequired, son_pubkeys_bitcoin); + } + } + if (id.is()) { + const auto &sai = database.get_index_type().indices().get(); + auto sao = sai.find(id); + if (sao != sai.end()) { + bitcoin_client->importaddress(sao->deposit_address); + bitcoin_client->importaddress(sao->withdraw_address); } } } diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp index b7cdb640..2f8bb932 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp @@ -133,6 +133,10 @@ void sidechain_net_handler_peerplays::process_primary_wallet() { return; } +void sidechain_net_handler_peerplays::process_sidechain_addresses() { + return; +} + bool sidechain_net_handler_peerplays::process_deposit(const son_wallet_deposit_object &swdo) { return true; } diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index df4bfc3c..b3022719 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1361,7 +1361,7 @@ class wallet_api /** * Update a SON object owned by the given account. * - * @param witness The name of the SON's owner account. Also accepts the ID of the owner account or the ID of the SON. + * @param owner_account The name of the SON's owner account. Also accepts the ID of the owner account or the ID of the SON. * @param url Same as for create_son. The empty string makes it remain the same. * @param block_signing_key The new block signing public key. The empty string makes it remain the same. * @param sidechain_public_keys The new set of sidechain public keys. The empty string makes it remain the same. @@ -1451,14 +1451,18 @@ class wallet_api * * @param account the name or id of the account who owns the address * @param sidechain a sidechain to whom address belongs + * @param deposit_public_key sidechain public key used for deposit address * @param deposit_address sidechain address for deposits + * @param withdraw_public_key sidechain public key used for withdraw address * @param withdraw_address sidechain address for withdrawals * @param broadcast true to broadcast the transaction on the network * @returns the signed transaction adding sidechain address */ signed_transaction add_sidechain_address(string account, sidechain_type sidechain, + string deposit_public_key, string deposit_address, + string withdraw_public_key, string withdraw_address, bool broadcast = false); @@ -1468,14 +1472,18 @@ class wallet_api * * @param account the name or id of the account who owns the address * @param sidechain a sidechain to whom address belongs + * @param deposit_public_key sidechain public key used for deposit address * @param deposit_address sidechain address for deposits + * @param withdraw_public_key sidechain public key used for withdraw address * @param withdraw_address sidechain address for withdrawals * @param broadcast true to broadcast the transaction on the network * @returns the signed transaction updating sidechain address */ signed_transaction update_sidechain_address(string account, sidechain_type sidechain, + string deposit_public_key, string deposit_address, + string withdraw_public_key, string withdraw_address, bool broadcast = false); diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d4146c92..c00b08dc 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2097,16 +2097,21 @@ public: signed_transaction add_sidechain_address(string account, sidechain_type sidechain, + string deposit_public_key, string deposit_address, + string withdraw_public_key, string withdraw_address, bool broadcast /* = false */) { try { account_id_type sidechain_address_account_id = get_account_id(account); sidechain_address_add_operation op; + op.payer = sidechain_address_account_id; op.sidechain_address_account = sidechain_address_account_id; op.sidechain = sidechain; + op.deposit_public_key = deposit_public_key; op.deposit_address = deposit_address; + op.withdraw_public_key = withdraw_public_key; op.withdraw_address = withdraw_address; signed_transaction tx; @@ -2119,7 +2124,9 @@ public: signed_transaction update_sidechain_address(string account, sidechain_type sidechain, + string deposit_public_key, string deposit_address, + string withdraw_public_key, string withdraw_address, bool broadcast /* = false */) { try { @@ -2129,10 +2136,13 @@ public: FC_THROW("No sidechain address for account ${account} and sidechain ${sidechain}", ("account", sidechain_address_account_id)("sidechain", sidechain)); sidechain_address_update_operation op; + op.payer = sidechain_address_account_id; op.sidechain_address_id = sao->id; op.sidechain_address_account = sidechain_address_account_id; op.sidechain = sidechain; + op.deposit_public_key = deposit_public_key; op.deposit_address = deposit_address; + op.withdraw_public_key = withdraw_public_key; op.withdraw_address = withdraw_address; signed_transaction tx; @@ -2153,6 +2163,7 @@ public: FC_THROW("No sidechain address for account ${account} and sidechain ${sidechain}", ("account", sidechain_address_account_id)("sidechain", sidechain)); sidechain_address_delete_operation op; + op.payer = sidechain_address_account_id; op.sidechain_address_id = sao->id; op.sidechain_address_account = sidechain_address_account_id; op.sidechain = sidechain; @@ -4816,20 +4827,24 @@ vector> wallet_api::get_son_wallets(uint32_t limit) signed_transaction wallet_api::add_sidechain_address(string account, sidechain_type sidechain, + string deposit_public_key, string deposit_address, + string withdraw_public_key, string withdraw_address, bool broadcast /* = false */) { - return my->add_sidechain_address(account, sidechain, deposit_address, withdraw_address, broadcast); + return my->add_sidechain_address(account, sidechain, deposit_public_key, deposit_address, withdraw_public_key, withdraw_address, broadcast); } signed_transaction wallet_api::update_sidechain_address(string account, sidechain_type sidechain, + string deposit_public_key, string deposit_address, + string withdraw_public_key, string withdraw_address, bool broadcast /* = false */) { - return my->update_sidechain_address(account, sidechain, deposit_address, withdraw_address, broadcast); + return my->update_sidechain_address(account, sidechain, deposit_public_key, deposit_address, withdraw_public_key, withdraw_address, broadcast); } signed_transaction wallet_api::delete_sidechain_address(string account, diff --git a/tests/peerplays_sidechain/bitcoin_address_tests.cpp b/tests/peerplays_sidechain/bitcoin_address_tests.cpp new file mode 100644 index 00000000..73bf21e5 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_address_tests.cpp @@ -0,0 +1,316 @@ +#include +#include + +using namespace graphene::peerplays_sidechain::bitcoin; + +BOOST_AUTO_TEST_SUITE(bitcoin_address_tests) + +fc::ecc::public_key_data create_public_key_data(const std::vector &public_key) { + FC_ASSERT(public_key.size() == 33); + fc::ecc::public_key_data key; + for (size_t i = 0; i < 33; i++) { + key.at(i) = public_key[i]; + } + return key; +} + +BOOST_AUTO_TEST_CASE(addresses_type_test) { + // public_key + std::string compressed("03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22"); + BOOST_CHECK(bitcoin_address(compressed).get_type() == payment_type::P2PK); + + std::string uncompressed("04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904"); + BOOST_CHECK(bitcoin_address(uncompressed).get_type() == payment_type::NULLDATA); + + // segwit_address + std::string p2wpkh_mainnet("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); + BOOST_CHECK(bitcoin_address(p2wpkh_mainnet).get_type() == payment_type::P2WPKH); + + std::string p2wpkh_testnet("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"); + BOOST_CHECK(bitcoin_address(p2wpkh_testnet).get_type() == payment_type::P2WPKH); + + std::string p2wsh("bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9"); + BOOST_CHECK(bitcoin_address(p2wsh).get_type() == payment_type::P2WSH); + + // base58 + std::string p2pkh_mainnet("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem"); + BOOST_CHECK(bitcoin_address(p2pkh_mainnet).get_type() == payment_type::P2PKH); + + std::string p2pkh_testnet("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"); + BOOST_CHECK(bitcoin_address(p2pkh_testnet).get_type() == payment_type::P2PKH); + + std::string p2sh_mainnet("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"); + BOOST_CHECK(bitcoin_address(p2sh_mainnet).get_type() == payment_type::P2SH); + + std::string p2sh_testnet("2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc"); + BOOST_CHECK(bitcoin_address(p2sh_testnet).get_type() == payment_type::P2SH); + + std::string p2sh_regtest1("2NAL3YhMF4VcbRQdectN8XPMJipvATGefTZ"); + BOOST_CHECK(bitcoin_address(p2sh_regtest1).get_type() == payment_type::P2SH); +} + +BOOST_AUTO_TEST_CASE(addresses_raw_test) { + // public_key + std::string compressed("03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22"); + bytes standard_compressed(parse_hex(compressed)); + BOOST_CHECK(bitcoin_address(compressed).get_raw_address() == standard_compressed); + + std::string uncompressed("04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904"); + BOOST_CHECK(bitcoin_address(uncompressed).get_raw_address() == bytes()); + + // segwit_address + std::string p2wpkh_mainnet("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); + bytes standard_p2wpkh_mainnet(parse_hex("751e76e8199196d454941c45d1b3a323f1433bd6")); + BOOST_CHECK(bitcoin_address(p2wpkh_mainnet).get_raw_address() == standard_p2wpkh_mainnet); + + std::string p2wpkh_testnet("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"); + bytes standard_p2wpkh_testnet(parse_hex("751e76e8199196d454941c45d1b3a323f1433bd6")); + BOOST_CHECK(bitcoin_address(p2wpkh_testnet).get_raw_address() == standard_p2wpkh_testnet); + + std::string p2wsh("bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9"); + bytes standard_p2wsh(parse_hex("c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d")); + BOOST_CHECK(bitcoin_address(p2wsh).get_raw_address() == standard_p2wsh); + + // base58 + std::string p2pkh_mainnet("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem"); + bytes standard_p2pkh_mainnet(parse_hex("47376c6f537d62177a2c41c4ca9b45829ab99083")); + BOOST_CHECK(bitcoin_address(p2pkh_mainnet).get_raw_address() == standard_p2pkh_mainnet); + + std::string p2pkh_testnet("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"); + bytes standard_p2pkh_testnet(parse_hex("243f1394f44554f4ce3fd68649c19adc483ce924")); + BOOST_CHECK(bitcoin_address(p2pkh_testnet).get_raw_address() == standard_p2pkh_testnet); + + std::string p2sh_mainnet("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"); + bytes standard_p2sh_mainnet(parse_hex("8f55563b9a19f321c211e9b9f38cdf686ea07845")); + BOOST_CHECK(bitcoin_address(p2sh_mainnet).get_raw_address() == standard_p2sh_mainnet); + + std::string p2sh_testnet("2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc"); + bytes standard_p2sh_testnet(parse_hex("4e9f39ca4688ff102128ea4ccda34105324305b0")); + BOOST_CHECK(bitcoin_address(p2sh_testnet).get_raw_address() == standard_p2sh_testnet); +} + +BOOST_AUTO_TEST_CASE(create_multisig_address_test) { + + std::vector public_key1 = parse_hex("03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010"); + std::vector public_key2 = parse_hex("0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983"); + std::vector public_key3 = parse_hex("033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f"); + + std::vector address = parse_hex("a91460cb986f0926e7c4ca1984ca9f56767da2af031e87"); + std::vector redeem_script = parse_hex("522103db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010210320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b441798321033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f53ae"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + + btc_multisig_address cma(2, {{son_id_type(1), public_key_type(key1)}, {son_id_type(2), public_key_type(key2)}, {son_id_type(3), public_key_type(key3)}}); + + BOOST_CHECK(address == cma.raw_address); + BOOST_CHECK(redeem_script == cma.redeem_script); +} + +BOOST_AUTO_TEST_CASE(create_segwit_address_test) { + // https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa + std::vector public_key1 = parse_hex("03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb"); + std::vector public_key2 = parse_hex("03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6"); + std::vector public_key3 = parse_hex("033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1"); + + std::vector witness_script = parse_hex("0020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + + btc_multisig_segwit_address address(2, {{son_id_type(1), public_key_type(key1)}, {son_id_type(2), public_key_type(key2)}, {son_id_type(3), public_key_type(key3)}}); + BOOST_CHECK(address.get_witness_script() == witness_script); + BOOST_CHECK(address.get_address() == "2NGU4ogScHEHEpReUzi9RB2ha58KAFnkFyk"); +} + +BOOST_AUTO_TEST_CASE(create_segwit_address_10_of_14_test) { + std::vector public_key1 = parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"); + std::vector public_key2 = parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"); + std::vector public_key3 = parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"); + std::vector public_key4 = parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"); + std::vector public_key5 = parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"); + std::vector public_key6 = parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"); + std::vector public_key7 = parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"); + std::vector public_key8 = parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"); + std::vector public_key9 = parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"); + std::vector public_key10 = parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"); + std::vector public_key11 = parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"); + std::vector public_key12 = parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"); + std::vector public_key13 = parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"); + std::vector public_key14 = parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"); + + std::vector witness_script = parse_hex("0020b70a52b55974d3c8c1a2055bf23e2d6421942135c7be1f786ad8cbce2f532cef"); + std::vector redeem_script = parse_hex("5a2103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae85eae"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + fc::ecc::public_key_data key4 = create_public_key_data(public_key4); + fc::ecc::public_key_data key5 = create_public_key_data(public_key5); + fc::ecc::public_key_data key6 = create_public_key_data(public_key6); + fc::ecc::public_key_data key7 = create_public_key_data(public_key7); + fc::ecc::public_key_data key8 = create_public_key_data(public_key8); + fc::ecc::public_key_data key9 = create_public_key_data(public_key9); + fc::ecc::public_key_data key10 = create_public_key_data(public_key10); + fc::ecc::public_key_data key11 = create_public_key_data(public_key11); + fc::ecc::public_key_data key12 = create_public_key_data(public_key12); + fc::ecc::public_key_data key13 = create_public_key_data(public_key13); + fc::ecc::public_key_data key14 = create_public_key_data(public_key14); + + btc_multisig_segwit_address address(10, { + {son_id_type(1), public_key_type(key1)}, + {son_id_type(2), public_key_type(key2)}, + {son_id_type(3), public_key_type(key3)}, + {son_id_type(4), public_key_type(key4)}, + {son_id_type(5), public_key_type(key5)}, + {son_id_type(6), public_key_type(key6)}, + {son_id_type(7), public_key_type(key7)}, + {son_id_type(8), public_key_type(key8)}, + {son_id_type(9), public_key_type(key9)}, + {son_id_type(10), public_key_type(key10)}, + {son_id_type(11), public_key_type(key11)}, + {son_id_type(12), public_key_type(key12)}, + {son_id_type(13), public_key_type(key13)}, + {son_id_type(14), public_key_type(key14)}, + }); + BOOST_CHECK(address.get_witness_script() == witness_script); + BOOST_CHECK(address.get_redeem_script() == redeem_script); + BOOST_CHECK(address.get_address() == "2MwhYhBrZeb6mTf1kaWcYfLBCCQoMqQybFZ"); +} + +BOOST_AUTO_TEST_CASE(create_segwit_address_11_of_15_test) { + std::vector public_key1 = parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"); + std::vector public_key2 = parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"); + std::vector public_key3 = parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"); + std::vector public_key4 = parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"); + std::vector public_key5 = parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"); + std::vector public_key6 = parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"); + std::vector public_key7 = parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"); + std::vector public_key8 = parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"); + std::vector public_key9 = parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"); + std::vector public_key10 = parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"); + std::vector public_key11 = parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"); + std::vector public_key12 = parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"); + std::vector public_key13 = parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"); + std::vector public_key14 = parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"); + std::vector public_key15 = parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + fc::ecc::public_key_data key4 = create_public_key_data(public_key4); + fc::ecc::public_key_data key5 = create_public_key_data(public_key5); + fc::ecc::public_key_data key6 = create_public_key_data(public_key6); + fc::ecc::public_key_data key7 = create_public_key_data(public_key7); + fc::ecc::public_key_data key8 = create_public_key_data(public_key8); + fc::ecc::public_key_data key9 = create_public_key_data(public_key9); + fc::ecc::public_key_data key10 = create_public_key_data(public_key10); + fc::ecc::public_key_data key11 = create_public_key_data(public_key11); + fc::ecc::public_key_data key12 = create_public_key_data(public_key12); + fc::ecc::public_key_data key13 = create_public_key_data(public_key13); + fc::ecc::public_key_data key14 = create_public_key_data(public_key14); + fc::ecc::public_key_data key15 = create_public_key_data(public_key15); + + std::vector witness_script = parse_hex("00205fcdce3c62b477553b8dc957a935adda8305cbd47a408f07d2cb867d588279eb"); + std::vector redeem_script = parse_hex("5b2103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae"); + + btc_multisig_segwit_address address(11, {{son_id_type(1), public_key_type(key1)}, {son_id_type(2), public_key_type(key2)}, {son_id_type(3), public_key_type(key3)}, {son_id_type(4), public_key_type(key4)}, {son_id_type(5), public_key_type(key5)}, {son_id_type(6), public_key_type(key6)}, {son_id_type(7), public_key_type(key7)}, {son_id_type(8), public_key_type(key8)}, {son_id_type(9), public_key_type(key9)}, {son_id_type(10), public_key_type(key10)}, {son_id_type(11), public_key_type(key11)}, {son_id_type(12), public_key_type(key12)}, {son_id_type(13), public_key_type(key13)}, {son_id_type(14), public_key_type(key14)}, {son_id_type(15), public_key_type(key15)}}); + + BOOST_CHECK(address.get_witness_script() == witness_script); + BOOST_CHECK(address.get_redeem_script() == redeem_script); + BOOST_CHECK(address.get_address() == "2NAL3YhMF4VcbRQdectN8XPMJipvATGefTZ"); +} + +BOOST_AUTO_TEST_CASE(btc_weighted_multisig_address_test) { + std::vector priv_old; + for (uint32_t 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()); + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_old[i], i + 1)); + + btc_weighted_multisig_address addr(weights, btc_weighted_multisig_address::network::testnet); + BOOST_CHECK(addr.get_address() == "tb1qaeuy4c0qnudq5u2c8pndd7zyudal3g5eew7y9396592udxdcje4surl6cm"); +} + +BOOST_AUTO_TEST_CASE(create_1_or_2_of_15_segwit_address_test) { + auto public_key1 = fc::ecc::public_key(create_public_key_data(parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"))); + auto public_key2 = fc::ecc::public_key(create_public_key_data(parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"))); + auto public_key3 = fc::ecc::public_key(create_public_key_data(parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"))); + auto public_key4 = fc::ecc::public_key(create_public_key_data(parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"))); + auto public_key5 = fc::ecc::public_key(create_public_key_data(parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"))); + auto public_key6 = fc::ecc::public_key(create_public_key_data(parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"))); + auto public_key7 = fc::ecc::public_key(create_public_key_data(parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"))); + auto public_key8 = fc::ecc::public_key(create_public_key_data(parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"))); + auto public_key9 = fc::ecc::public_key(create_public_key_data(parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"))); + auto public_key10 = fc::ecc::public_key(create_public_key_data(parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"))); + auto public_key11 = fc::ecc::public_key(create_public_key_data(parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"))); + auto public_key12 = fc::ecc::public_key(create_public_key_data(parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"))); + auto public_key13 = fc::ecc::public_key(create_public_key_data(parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"))); + auto public_key14 = fc::ecc::public_key(create_public_key_data(parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"))); + auto public_key15 = fc::ecc::public_key(create_public_key_data(parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"))); + + auto user_key = fc::ecc::public_key(create_public_key_data(parse_hex("0368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cd"))); + + btc_one_or_m_of_n_multisig_address address(user_key, 2, {public_key1, public_key2, public_key3, public_key4, public_key5, public_key6, public_key7, public_key8, public_key9, public_key10, public_key11, public_key12, public_key13, public_key14, public_key15}); + + std::vector redeem_script = parse_hex("63210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae68"); + + BOOST_CHECK(address.get_address() == "bcrt1qp8vplzrn7alzpjq8cw4ynd6xqzassmefrh48dzsj0krvkq85dywq9txkqr"); + BOOST_CHECK(address.get_redeem_script() == redeem_script); +} + +BOOST_AUTO_TEST_CASE(create_1_or_multiweighted_seggwit_address_test) { + auto public_key1 = fc::ecc::public_key(create_public_key_data(parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"))); + auto public_key2 = fc::ecc::public_key(create_public_key_data(parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"))); + auto public_key3 = fc::ecc::public_key(create_public_key_data(parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"))); + auto public_key4 = fc::ecc::public_key(create_public_key_data(parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"))); + auto public_key5 = fc::ecc::public_key(create_public_key_data(parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"))); + auto public_key6 = fc::ecc::public_key(create_public_key_data(parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"))); + auto public_key7 = fc::ecc::public_key(create_public_key_data(parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"))); + auto public_key8 = fc::ecc::public_key(create_public_key_data(parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"))); + auto public_key9 = fc::ecc::public_key(create_public_key_data(parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"))); + auto public_key10 = fc::ecc::public_key(create_public_key_data(parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"))); + auto public_key11 = fc::ecc::public_key(create_public_key_data(parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"))); + auto public_key12 = fc::ecc::public_key(create_public_key_data(parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"))); + auto public_key13 = fc::ecc::public_key(create_public_key_data(parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"))); + auto public_key14 = fc::ecc::public_key(create_public_key_data(parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"))); + auto public_key15 = fc::ecc::public_key(create_public_key_data(parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"))); + + auto user_pub_key = fc::ecc::public_key(create_public_key_data(parse_hex("0368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cd"))); + + // key weights + std::vector> weights; + weights.push_back(std::make_pair(public_key1, 1)); + weights.push_back(std::make_pair(public_key2, 1)); + weights.push_back(std::make_pair(public_key3, 1)); + weights.push_back(std::make_pair(public_key4, 1)); + weights.push_back(std::make_pair(public_key5, 1)); + weights.push_back(std::make_pair(public_key6, 1)); + weights.push_back(std::make_pair(public_key7, 1)); + weights.push_back(std::make_pair(public_key8, 1)); + weights.push_back(std::make_pair(public_key9, 1)); + weights.push_back(std::make_pair(public_key10, 1)); + weights.push_back(std::make_pair(public_key11, 1)); + weights.push_back(std::make_pair(public_key12, 1)); + weights.push_back(std::make_pair(public_key13, 1)); + weights.push_back(std::make_pair(public_key14, 1)); + weights.push_back(std::make_pair(public_key15, 1)); + + btc_one_or_weighted_multisig_address addr(user_pub_key, weights); + auto redeem_script = parse_hex("210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac635167007c2103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275ac635193687c2102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4ac635193687c21025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61ac635193687c210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866ac635193687c21037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666ac635193687c2102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccfac635193687c210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7fac635193687c210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84ac635193687c21023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccfac635193687c210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5ac635193687c21024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8daac635193687c2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1ac635193687c2102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8ac635193687c210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8ac635193687c2102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73ac635193685aa268"); + + BOOST_CHECK(addr.get_address() == "bcrt1qawr44trakzl4qev8ed88samt3g3g5mgcjppc6ffs5h4wyakqzavq6fkcc6"); + BOOST_CHECK(addr.get_redeem_script() == redeem_script); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/peerplays_sidechain/bitcoin_sign_tests.cpp b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp new file mode 100644 index 00000000..e6a1da34 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp @@ -0,0 +1,387 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace graphene::peerplays_sidechain::bitcoin; +using namespace fc::ecc; + +BOOST_AUTO_TEST_SUITE(bitcoin_sign_tests) + +const secp256k1_context_t *btc_context() { + static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; +} + +inline bytes get_privkey_bytes(const std::string &privkey_base58) { + const auto privkey = fc::from_base58(privkey_base58); + // Remove version and hash + return bytes(privkey.cbegin() + 1, privkey.cbegin() + 1 + 32); +} + +BOOST_AUTO_TEST_CASE(btc_tx_witness_signature_test) { + bitcoin_transaction tx; + tx.nVersion = 1; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("0a510f49749aaaa2638048132eafea959dd8e47e79332dbcb2a14189870e3145"); + tx.vin[0].prevout.n = 1; + tx.vin[0].scriptSig = parse_hex("220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1"); + tx.vin[0].nSequence = 4294967295; + + tx.vout[0].value = 20000000; + tx.vout[0].scriptPubKey = parse_hex("76a9143ebc40e411ed3c76f86711507ab952300890397288ac"); + + const auto privkey_1 = get_privkey_bytes("cQPUeypiYqp8J8Y8dGXUhvWGPHXTYYs3haryjdquwvMLAabXAnzF"); + const auto privkey_2 = get_privkey_bytes("cTG9AXoZjPbUmU2rx4ojeUBm3P88q3ZvRR6YWTUeVJHyke5KbVPM"); + const auto redeemScript = parse_hex("522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae"); + uint64_t amount = 20021300; + int32_t hash_type = 1; + + tx.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx, {redeemScript}, {amount}, privkey_1, btc_context(), hash_type)[0]); + tx.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx, {redeemScript}, {amount}, privkey_2, btc_context(), hash_type)[0]); + sign_witness_transaction_finalize(tx, {redeemScript}); + + BOOST_CHECK(fc::to_hex(pack(tx)) == "0100000000010145310e878941a1b2bc2d33797ee4d89d95eaaf2e13488063a2aa9a74490f510a0100000023220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1ffffffff01002d3101000000001976a9143ebc40e411ed3c76f86711507ab952300890397288ac0400473044022001dd489a5d4e2fbd8a3ade27177f6b49296ba7695c40dbbe650ea83f106415fd02200b23a0602d8ff1bdf79dee118205fc7e9b40672bf31563e5741feb53fb86388501483045022100f88f040e90cc5dc6c6189d04718376ac19ed996bf9e4a3c29c3718d90ffd27180220761711f16c9e3a44f71aab55cbc0634907a1fa8bb635d971a9a01d368727bea10169522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae00000000"); +} + +BOOST_AUTO_TEST_CASE(verify_sig_test) { + std::string hash("df074d23cbedea48308aa2161c5b0da3893cc898e143c285ae4d5d787b366f10"); + bytes vec_hash(parse_hex(hash)); + + std::string key1("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"); + bytes vec_key1(parse_hex(key1)); + + std::string sig("3044022051641c36fc6bc1e7ddd0022259c3f3a8dce0ac7fa4538c8b303c49e14b216b5302204e64c88a7f0279d902ccb338ffd42941ccdbbd7ddf8ba17d1ebf693f1f0b3ed901"); + bytes vec_sig(parse_hex(sig)); + + BOOST_CHECK(verify_sig(vec_sig, vec_key1, vec_hash, btc_context())); + + std::string key2("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435ca"); + bytes vec_key2(parse_hex(key2)); + + BOOST_CHECK(!verify_sig(vec_sig, vec_key2, vec_hash, btc_context())); +} + +BOOST_AUTO_TEST_CASE(get_pubkey_from_redeemScript_test) { + std::string script("5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae"); + + std::vector keys = {parse_hex("025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf")}; + + std::vector keys_from_script = get_pubkey_from_redeemScript(parse_hex(script)); + + BOOST_CHECK(keys.size() == keys_from_script.size()); + for (size_t i = 0; i < keys.size(); i++) { + BOOST_CHECK(keys[i] == keys_from_script[i]); + } + + std::string script2("5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e95bae"); + + std::vector keys_from_script2 = get_pubkey_from_redeemScript(parse_hex(script2)); + BOOST_CHECK(keys_from_script2.size() == 1); + BOOST_CHECK(keys_from_script2[0] == parse_hex("025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9")); +} + +BOOST_AUTO_TEST_CASE(sort_sig_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e937fd2942f0f14dd46a122e138d00cfabd93572b4876da77ab57c2a76ee73af\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\",\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\"]},{\"prevout\":{\"hash\":\"ae34ad50ab112e6cc51e6e3a87c48798b67255f8c8a8af9d427cbf55207ecfd1\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\",\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\"]},{\"prevout\":{\"hash\":\"e04ee70b6aa2180caa32aaa4ff00c80b62e5572c369e05986d3a0e0b6d9d7455\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\",\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\"]},{\"prevout\":{\"hash\":\"6bc22ed725ba7c164df3a878113a11e4fbc3d1bbffee0083e75cb14e7bb5bd38\",\"n\":1},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\",\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\"]}],\"vout\":[{\"value\":\"9590365272\",\"scriptPubKey\":\"00206a19177b8e4d76408c574118681f204c1a7065040636d5288af41f67c25a85f0\"},{\"value\":240000,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":240000,\"scriptPubKey\":\"2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471bac\"},{\"value\":240000,\"scriptPubKey\":\"2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c80ac\"},{\"value\":240000,\"scriptPubKey\":\"21021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d99390091ac\"},{\"value\":240000,\"scriptPubKey\":\"2103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b0ac\"},{\"value\":240000,\"scriptPubKey\":\"21028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfaac\"},{\"value\":240000,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":240000,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":240000,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":240000,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":240000,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[5993981520,1200000000,1200000000,1200000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"],[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"],[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"],[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(already_sorted_sigs_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e937fd2942f0f14dd46a122e138d00cfabd93572b4876da77ab57c2a76ee73af\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"]},{\"prevout\":{\"hash\":\"ae34ad50ab112e6cc51e6e3a87c48798b67255f8c8a8af9d427cbf55207ecfd1\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"]},{\"prevout\":{\"hash\":\"e04ee70b6aa2180caa32aaa4ff00c80b62e5572c369e05986d3a0e0b6d9d7455\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"]},{\"prevout\":{\"hash\":\"6bc22ed725ba7c164df3a878113a11e4fbc3d1bbffee0083e75cb14e7bb5bd38\",\"n\":1},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]}],\"vout\":[{\"value\":\"9590365272\",\"scriptPubKey\":\"00206a19177b8e4d76408c574118681f204c1a7065040636d5288af41f67c25a85f0\"},{\"value\":240000,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":240000,\"scriptPubKey\":\"2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471bac\"},{\"value\":240000,\"scriptPubKey\":\"2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c80ac\"},{\"value\":240000,\"scriptPubKey\":\"21021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d99390091ac\"},{\"value\":240000,\"scriptPubKey\":\"2103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b0ac\"},{\"value\":240000,\"scriptPubKey\":\"21028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfaac\"},{\"value\":240000,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":240000,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":240000,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":240000,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":240000,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[5993981520,1200000000,1200000000,1200000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"],[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"],[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"],[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(all_signatures_are_same_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e35635fce1cbe1e347a5f8a3c5dccd79db9d43a6fb4b27991894a8bce1a70e03\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\"]},{\"prevout\":{\"hash\":\"1826f1ba0ed5034806cf1cb3eaa5dc9abf04a319048ae1e73e3df75dcc9f0bca\",\"n\":1},\"scriptSig\":\"2200203b9e077c0043e8f394a273baffc0aed01d10d8c894ad39810257d63be9a315e0\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\"]}],\"vout\":[{\"value\":1997997990,\"scriptPubKey\":\"0020a40e801531fdca0fb550013a9140aaaf8d9eb106c427258fbc86d0e0c52c432d\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[998998995,1000000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\"],[\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(same_signatures_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"f8d70d29817a78e160c53dcffcf6a783008f1cc43c64f7efb49e4de3a23ef016\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220228c930388a0420aa9a17acdf414763bec0f57f92ecf8db51f02e3f8d82428aa0220417f2d0fbc5fd00d5d158a2e7fe7188857119b8d16d11f82f513594a28dbcbfa01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"3045022100d95008906e848a8165fbc0d3ed6d11643bc4814d4f8ae84c8c84a97c8c14885002206a2d703ffcca22309b843b55b746994fb08c15fbbf0aadbf900398e8768c62dc01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\"]},{\"prevout\":{\"hash\":\"dd54a8c470be54b02cd937a1758761863c2faf920b2645d2dd35bd0308ef0dfb\",\"n\":1},\"scriptSig\":\"220020f38dc1aecea9e28bea4410e6aa807be49cf6472b9a718750080b9703e80d9fe9\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100bdc4d1151d0567bb4e377b473100eaf41544bb547bc6d82b0a0dae8e8e833a6d022013caa911c553558abe6fdf1a6853ca6bab6912d90676595cfd4d11afd4f7966301\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100e1262b0e14df0f6f99d850651caa6b8881f7cbf811ad549cb0d6a1b1369beec902204232af72b6bfcb21a83d555374dc622275b7c2cac31b4f56d76b87d88fdc586e01\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\"]}],\"vout\":[{\"value\":2996996985,\"scriptPubKey\":\"0020a4d938999fff18a140d830009f8c9a2c5ab00d61cc3ffea10ee703b7d9b24b9a\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":66667,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":66667,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":66667,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":66667,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[1997997990,1000000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\"\"30440220228c930388a0420aa9a17acdf414763bec0f57f92ecf8db51f02e3f8d82428aa0220417f2d0fbc5fd00d5d158a2e7fe7188857119b8d16d11f82f513594a28dbcbfa01\",\"3045022100d95008906e848a8165fbc0d3ed6d11643bc4814d4f8ae84c8c84a97c8c14885002206a2d703ffcca22309b843b55b746994fb08c15fbbf0aadbf900398e8768c62dc01\"],[\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100bdc4d1151d0567bb4e377b473100eaf41544bb547bc6d82b0a0dae8e8e833a6d022013caa911c553558abe6fdf1a6853ca6bab6912d90676595cfd4d11afd4f7966301\",\"3045022100e1262b0e14df0f6f99d850651caa6b8881f7cbf811ad549cb0d6a1b1369beec902204232af72b6bfcb21a83d555374dc622275b7c2cac31b4f56d76b87d88fdc586e01\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(weighted_multisig_spend_test) { + // create weighted multisig addess in regtest + std::vector priv_keys; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_keys.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_keys; + for (auto &key : priv_keys) { + pub_keys.push_back(key.get_public_key()); + } + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_keys[i], i + 1)); + + btc_weighted_multisig_address addr(weights); + BOOST_CHECK(addr.get_address() == "bcrt1qaeuy4c0qnudq5u2c8pndd7zyudal3g5eew7y9396592udxdcje4s364udp"); + + bytes redeem_script = addr.get_redeem_script(); + + // this address was filled with regtest transaction + // id 8c67ac4899aadb68672775df338d7d857604f12784e24fa1fc1471a73b5df012 + // output 1, 10000 satoshis + + // now send it back to bcrt1qavkxpjkc30x0euepxup8qe2yfzpjyepzq0qctu + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("8c67ac4899aadb68672775df338d7d857604f12784e24fa1fc1471a73b5df012"); + tx.vin[0].prevout.n = 1; + tx.vin[0].nSequence = 0xffffffff; + + tx.vout[0].value = 9000; + bitcoin_address to_address("bcrt1qavkxpjkc30x0euepxup8qe2yfzpjyepzq0qctu"); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + for (auto &key : priv_keys) { + bytes key_data(key.get_secret().data(), key.get_secret().data() + key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + // insert signatures in reverse order + tx.vin[0].scriptWitness.insert(tx.vin[0].scriptWitness.begin(), sigs[0]); + } + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000000010112f05d3ba77114fca14fe28427f10476857d8d33df75276768dbaa9948ac678c0100000000ffffffff012823000000000000160014eb2c60cad88bccfcf3213702706544488322642210473044022036b79512d547927c7e285aea1403fbf00efdca2be752a0ec6d37144b20365992022073fa2373af073fb0b66e55d958cb301ed473352f8ae29a7fcf9bf7bf6974057d014730440220243edabbec391f070f1167a9fa09fd72d35958da8c6c24252b97401e2dfa7b7d0220629bcaaa8bdba62c1d551d061b6d47014325cbf492c31340f2f1f9de033566380148304502210080bdc7cca6d9b7899d6cf69bdcca150260363cfe01b4a382671abaefce4e9ccf02200bd041441fd2c40a564fee80107ea4ffb9a427b4382235ed0b8fa6706ad18a760147304402206a1e6976a054cea828c856cc8e08f4a84a3edb310e35bc786d19178f4d1bd9ae022073982176a827f53b5eda735602ea07fda258c3381b545c439a5b7e2356274b3c01483045022100eaad11b5de0d2b2d8ca99d863d5d6d800ef5055750cb2aff670718df38a81ca8022011c08e83664c1a16fec693d7437b3984683a6eb67223247fc860565ab8f076fc014830450221008b26a8b5c9a2f1d10a34005698672730a3a933008f49cd379dbec66cc8df4d7602207734aed9afb75efcf3a807a8d99878abc83f917b1e391e13cf382bbf42aec6af01473044022041393a16e4c14e017939524296d96176bff955c83ca68e1b9bf58d5e57ad6be102207f0a8efcab6d6dcb782987dfd3ad55c8343c9563ad614ef12bf722d004858276014730440220670e1c79f96f98bb244cee62c2b23206d3de7d3901d52feecd4861583be86cea02203b1f5214b2295ca03262d2e40a272ff861f03ca852efbb38d8c947a29e37d45e01483045022100e0fa5b20a3c82303d36c41fe56a055a038476645f11441d252a457533987b3d10220356950292144a4b56ba7dde31dcba1774486a9a22be9b8f1be9a9121f95ca4e801483045022100de5679f833d47ba63a947dc5cd0507b63b9fa3e9c1f1e77991c43f965d8921700220152961a323de0e2f44bdd9ee6782bca834f3a023e8597aee9907ed23dcd153880147304402203eea68df28f2963d05850531d4a27ec4eedc3c81f49c4e1476e4514e2a8b0d3d0220494ecdb8750eb01903a6203a1f5a1ede722e7ed6f5a02a4156cf8f9ea1a01cc401473044022033662119e15efb098df8e3e31ff3a2437ce2f5d72df414938985f10757aa7a7e022044c17def78afcb82f0f86ed0ead4c28732e574e3981823a53f5bb84fea703e020147304402206f5f7b8cbe0b9a80083341f3c57704ccfbe062e4649c3fdbf5883557bc8739b902202104a68fa4764832ff61082e02f8ba971cb8db81eaa9eb9a84a61067f8515eef014830450221009c80e3500e5fdcb4a72a3e1e1c3ce72602d380eb295b322c3964c6abf91fd22b022063afa4017b9de88dd492a38072f0bbad514660d6ed38d5f73759e7262b87ed76014730440220273d6a97b382c0bdceb5df7ff59542ec3851d09b2a760fae6f023eb9f927638602201f486799014a9388596c7e4b86efa6365c2d5f8f65be6a119c08a2e6ceb8943001fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a200000000"); + // this transaction was published in regtest and was accepted, + // its id is 08a997948feb50850df809e8f0c7b41a7bd0d249a75b83813d8626d5b51affba +} + +BOOST_AUTO_TEST_CASE(user_sig_one_or_2_of_15_multisig_spend_test) { + const auto user_pkey = get_privkey_bytes("cPzX5utDDBt2kfK4uz43e78zMxZSdfiGrV9wnkmqrqPnxfQCB2Rc"); + const auto son1_pkey = get_privkey_bytes("cSKyTeXidmj93dgbMFqgzD7yvxzA7QAYr5j9qDnY9seyhyv7gH2m"); + const auto son2_pkey = get_privkey_bytes("cQBBNyEw6P3pgc2NjPpKR2YoCpio9s3qEMkFkY7v9hByLAxeLQ3s"); + const auto son3_pkey = get_privkey_bytes("cQLKc4UgKyCdXY3PosndszEZTsB6mTrg4avZF6kDphrULKd2W6L4"); + const auto son4_pkey = get_privkey_bytes("cN43k9sqQimgzChZm9Qz1V1bdkjVwB3mcSHsEuj6bfUa4SP2AsTk"); + + auto public_key1 = fc::ecc::public_key(create_public_key_data(parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"))); + auto public_key2 = fc::ecc::public_key(create_public_key_data(parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"))); + auto public_key3 = fc::ecc::public_key(create_public_key_data(parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"))); + auto public_key4 = fc::ecc::public_key(create_public_key_data(parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"))); + auto public_key5 = fc::ecc::public_key(create_public_key_data(parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"))); + auto public_key6 = fc::ecc::public_key(create_public_key_data(parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"))); + auto public_key7 = fc::ecc::public_key(create_public_key_data(parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"))); + auto public_key8 = fc::ecc::public_key(create_public_key_data(parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"))); + auto public_key9 = fc::ecc::public_key(create_public_key_data(parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"))); + auto public_key10 = fc::ecc::public_key(create_public_key_data(parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"))); + auto public_key11 = fc::ecc::public_key(create_public_key_data(parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"))); + auto public_key12 = fc::ecc::public_key(create_public_key_data(parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"))); + auto public_key13 = fc::ecc::public_key(create_public_key_data(parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"))); + auto public_key14 = fc::ecc::public_key(create_public_key_data(parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"))); + auto public_key15 = fc::ecc::public_key(create_public_key_data(parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"))); + + auto user_key = fc::ecc::public_key(create_public_key_data(parse_hex("0368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cd"))); + + btc_one_or_m_of_n_multisig_address addr(user_key, 2, {public_key1, public_key2, public_key3, public_key4, public_key5, public_key6, public_key7, public_key8, public_key9, public_key10, public_key11, public_key12, public_key13, public_key14, public_key15}); + + BOOST_CHECK(addr.get_address() == "bcrt1qp8vplzrn7alzpjq8cw4ynd6xqzassmefrh48dzsj0krvkq85dywq9txkqr"); + BOOST_CHECK(addr.get_redeem_script() == parse_hex("63210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae68")); + + bytes redeem_script = addr.get_redeem_script(); + //User spend + bitcoin_transaction tx1; + tx1.nVersion = 2; + tx1.vin.resize(1); + tx1.vout.resize(1); + tx1.nLockTime = 0; + + tx1.vin[0].prevout.hash = fc::sha256("52fceb33bee6d44c34161ef8cf26ff1c4dd8f89833a99769039a62582967770c"); + tx1.vin[0].prevout.n = 0; + tx1.vin[0].nSequence = 0xffffffff; + + tx1.vout[0].value = 4999000000; + bitcoin_address to_address("bcrt1qettl5u3jlh6e8s6hxpqsdpevxqmq947f0jnnfraeg4jsvhsme2uqplzjgt"); + tx1.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 5000000000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + tx1.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx1, {redeem_script}, {amount}, user_pkey, btc_context(), hash_type)[0]); + bytes one = {0x01}; + tx1.vin[0].scriptWitness.push_back(one); + + sign_witness_transaction_finalize(tx1, {redeem_script}, false); + + BOOST_CHECK(fc::to_hex(pack(tx1)) == "020000000001010c77672958629a036997a93398f8d84d1cff26cff81e16344cd4e6be33ebfc520000000000ffffffff01c0aff62901000000220020cad7fa7232fdf593c357304106872c303602d7c97ca7348fb94565065e1bcab803483045022100d885af9d14af025b475c66ddd8ce0afa6d45edee1bb2ac8e840f8b6d70bc3c2702202a7231c10b3f6198343b45a56728e8241ec8ba14ad66d498b67db2ce8268e6fd010101fd270263210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae6800000000"); + + //SON Spend + bitcoin_transaction tx2; + tx2.nVersion = 2; + tx2.vin.resize(1); + tx2.vout.resize(1); + tx2.nLockTime = 0; + + tx2.vin[0].prevout.hash = fc::sha256("397b28da9488f587f363a45804bcf9af8ea9a5a2e50fb1ceed2cf2fa6181a552"); + tx2.vin[0].prevout.n = 1; + tx2.vin[0].nSequence = 0xffffffff; + + tx2.vout[0].value = 4999000000; + tx2.vout[0].scriptPubKey = to_address.get_script(); + + tx2.vin[0].scriptWitness.push_back(bytes()); + tx2.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx2, {redeem_script}, {amount}, son2_pkey, btc_context(), hash_type)[0]); + tx2.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx2, {redeem_script}, {amount}, son4_pkey, btc_context(), hash_type)[0]); + tx2.vin[0].scriptWitness.push_back(bytes()); + + sign_witness_transaction_finalize(tx2, {redeem_script}, false); + + BOOST_CHECK(fc::to_hex(pack(tx2)) == "0200000000010152a58161faf22cedceb10fe5a2a5a98eaff9bc0458a463f387f58894da287b390100000000ffffffff01c0aff62901000000220020cad7fa7232fdf593c357304106872c303602d7c97ca7348fb94565065e1bcab80500483045022100ed26e6a78f87a50f2b3998c3c47ef5beca112b7d4359486239b37f48d37457da02201ab89289678b9626459f91307fcd3ba1de833cad29c78f3dcab60845df73217d0147304402207d7caa06a956fbb1191a0768a8652b0530d9124a2f3a95685d4a39a3469c227302202d78c0cc7bc17e93eae1d592aed01cdc4d3297763e11ebee5f6f85e1923683140100fd270263210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae6800000000"); +} + +BOOST_AUTO_TEST_CASE(user_sig_one_or_weighted_multisig_spend_test) { + std::vector priv_keys; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_keys.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_keys; + for (auto &key : priv_keys) { + pub_keys.push_back(key.get_public_key()); + } + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_keys[i], i + 1)); + + fc::sha256 h = fc::sha256::hash("user", 4); + fc::ecc::private_key user_key = fc::ecc::private_key::generate_from_seed(h); + fc::ecc::public_key user_pub_key = user_key.get_public_key(); + + btc_one_or_weighted_multisig_address addr(user_pub_key, weights); + BOOST_CHECK(addr.get_address() == "bcrt1q8vvjs50thpujagzvmx22czzrq9qgr3w4qqcwv697w09fahhq8c3syahmxz"); + + bytes redeem_script = addr.get_redeem_script(); + + { + // this address was filled with regtest transaction + // id b28a0a75fae5eb72aa61766e765cc97002af1ec2d38e6cea7e8723c299009560 + // output 0, 10000 satoshis + + // now send it to 2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1 + // with single user signature + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("b28a0a75fae5eb72aa61766e765cc97002af1ec2d38e6cea7e8723c299009560"); + tx.vin[0].prevout.n = 0; + tx.vin[0].nSequence = 0xffffffff; + + tx.vout[0].value = 9000; + bitcoin_address to_address("2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1"); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + bytes key_data(user_key.get_secret().data(), user_key.get_secret().data() + user_key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + tx.vin[0].scriptWitness.push_back(sigs[0]); + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + // this transaction was published in regtest and was accepted, + // its id is 56d6804b142a7ec49d980304ac5efb7472c626e04a8499aa182574955de0a2ef + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000000010160950099c223877eea6c8ed3c21eaf0270c95c766e7661aa72ebe5fa750a8ab20000000000ffffffff01282300000000000017a9140b552f4a72cb614717878b20743d9e38e618130a8702483045022100f763578fea27776100a06341816a2a0a84a5c50848d33dfc941c11c64c9fdb6e022061d85666f70aed96cf73be299712cba84706527cb138c21a2229cb32d72a4c7c01fd83022102d2c1cb1575d323b6120b6e5bcc9ce5ad373e88e73e675030f1c2c5261b4dbc86ac635167007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a26800000000"); + } + + { + // this address was filled again with regtest transaction + // id 1e6641086684a42533a3d62538ab1c82ee5f650f2ab1a5cc7354ecebb33cc858 + // output 0, 10000 satoshis + + // now send it to the primary wallet with sons signatures + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("1e6641086684a42533a3d62538ab1c82ee5f650f2ab1a5cc7354ecebb33cc858"); + tx.vin[0].prevout.n = 0; + tx.vin[0].nSequence = 0xffffffff; + + tx.vout[0].value = 9000; + btc_weighted_multisig_address to_address(weights); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + for (auto &key : priv_keys) { + bytes key_data(key.get_secret().data(), key.get_secret().data() + key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + // insert signatures in reverse order + tx.vin[0].scriptWitness.insert(tx.vin[0].scriptWitness.begin(), sigs[0]); + } + // add empty sig for user signature + tx.vin[0].scriptWitness.push_back(bytes()); + + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + // this transaction was published in regtest and was accepted, + // its id is d22c9ffbfaa96ab1fe547f1117eaf1b37a8936bce4f281ac57547bbfff903c57 + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000000010158c83cb3ebec5473cca5b12a0f655fee821cab3825d6a33325a484660841661e0000000000ffffffff012823000000000000220020d237e9a42a33b434462d7e353cf51f40cd29c24504793fc02a3bc6e90ef72d9311483045022100b01c553bf72adee13cd88f7bd63f513a3c1b73f4e35ad84a97978f9c8aa178f002203008b49303349bfb4037fedbb637e510839dfbaf6a397cf57e9af6232f59af270147304402205f84acce6907810a59042474d64ed74de644e471944a319c7306ff4dd0df359302204f6d265a4e93f48659efb517d65bc1fa0057dee7fb81a01d5bb44223d8b0cef001483045022100f89f5bbcd55b6ae182f69a23bc66de4a0b14ecd900acf3fafbb14522bdce04c2022058ddb079bf0d99653a5f4e1aed32ac374cc961564337babdcae05b1e609c2b4001473044022018335d12abde4c3d6857720082560e82a3587662eadabf3c42e750fea380b0c502204507a76b902333aedbd0bdafa9986d16c86ceae2e3e88e1bc50914d4696951c5014730440220379c869e443cd3173b005a42e863ce47094a8e548d785c40b19c18f2da12e45802201154928e181036ad206f67aae239c16f155dcd9d97df85cd5070db79649f1f1d014830450221008833a468bee3192ed0394a00b4ceb187af5ff0b53dca74679d682651a4514c63022022d3410aeaf10c17f2f14b32edef64c2ab3c9e6bc9bf4166ef6245cfdc7faf350147304402206d006c240ca3bd09a0e5a82126fd7d4ad7da0f97aa5db926f4baf57f395400bc0220254f4e0b41bf49846d0da971ffe66dfc6db64465e40f72ccf37de778f38a634f0147304402205eb030ad8c6e13cf1ccd1e11c5fd5843988707711a005388dfbabc1325d2203d022013a5833e13ea64749af9b55ef731e3dcf2cfe63619d7a73c539b6e89a5104f650147304402203971c81233314c12293a7294d06d5eac4dcf534ecfb60a3b64474b5a2018b836022005accaf77da74177663a6c150bd74ed0b435b051f9d1cb3921799daf37ee3d5301473044022075686e69342526ba233defa2c9bea78b6d5090dc787eb028def26b1a317b023b0220477a407a0e054122445ae6e9cf2a5de3db7c76fa111bc5df735605581113836b01483045022100badf6da6a34ca1a6f8b21095aee2fa1f7a343c4b18e929451c5179c48f35685e022042342caaeeb3c960720cbf81c2df809d6daca6485a1e5fb827d58f4fa272365201483045022100c707d2cde92b35bd3a675d9b5ed087dcbe8d870d3e436e5118b7b98cd74932680220215224192fda8468e234bda7ab71826fb93c56e9aebbae24e9f937d476905ba001483045022100f1def0f6f6cb19d289df32ef1d300fc821fdb9f53d6bb28c93eb127f95dadf4702203bcdfcb5b9aff850e57c8b193931698831de899f052e7e9da587dfbf65f57c5601483045022100ece59a1ff30d3976a2b7e9d94e461ca35b2cb7ec0778a0afaa3088293f85d24c022028a3cf1fe597674769b175fab2555c282eaf004bd33d5554c3e26169ae508793014830450221008159b605a1c39cb1bba0f28fe60fdbc1fee7af54e7fd35edef74e7b9067b696a022005b8460e35fa253be00989bec86471c1c32d1e8309ad16676bf99170345eb7990100fd83022102d2c1cb1575d323b6120b6e5bcc9ce5ad373e88e73e675030f1c2c5261b4dbc86ac635167007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a26800000000"); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp b/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp new file mode 100644 index 00000000..20fe878c --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include + +using namespace graphene::peerplays_sidechain::bitcoin; + +BOOST_AUTO_TEST_SUITE(bitcoin_transaction_tests) + +BOOST_AUTO_TEST_CASE(serialize_bitcoin_transaction_test) { + out_point prevout; + prevout.hash = fc::sha256("89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a"); + prevout.n = 0; + + tx_in in; + in.prevout = prevout; + in.scriptSig = parse_hex("473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101"); + in.nSequence = 4294967294; + + tx_out out1; + out1.value = 3500000000; + out1.scriptPubKey = parse_hex("76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac"); + + tx_out out2; + out2.value = 1499996160; + out2.scriptPubKey = parse_hex("76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac"); + + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin = {in}; + tx.vout = {out1, out2}; + tx.nLockTime = 101; + + const auto serialized = pack(tx); + + const auto expected = parse_hex("02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000"); + + BOOST_CHECK_EQUAL_COLLECTIONS(serialized.cbegin(), serialized.cend(), expected.cbegin(), expected.cend()); +} + +BOOST_AUTO_TEST_CASE(deserialize_bitcoin_transaction_test) { + bitcoin_transaction tx = unpack(parse_hex("02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000")); + + BOOST_CHECK_EQUAL(tx.nVersion, 2); + BOOST_CHECK_EQUAL(tx.nLockTime, 101); + + BOOST_REQUIRE_EQUAL(tx.vin.size(), 1); + BOOST_CHECK_EQUAL(tx.vin[0].prevout.hash.str(), "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a"); + BOOST_CHECK_EQUAL(tx.vin[0].prevout.n, 0); + + BOOST_CHECK(fc::to_hex(tx.vin[0].scriptSig) == "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101"); + BOOST_CHECK_EQUAL(tx.vin[0].nSequence, 4294967294); + + BOOST_REQUIRE_EQUAL(tx.vout.size(), 2); + BOOST_CHECK_EQUAL(tx.vout[0].value, 3500000000); + BOOST_CHECK(fc::to_hex(tx.vout[0].scriptPubKey) == "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac"); + BOOST_CHECK_EQUAL(tx.vout[1].value, 1499996160); + BOOST_CHECK(fc::to_hex(tx.vout[1].scriptPubKey) == "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac"); +} + +BOOST_AUTO_TEST_CASE(btc_tx_methods_test) { + const auto tx = unpack(parse_hex("0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f856040000002322002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1ffffffff0188b3f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02473044022038421164c6468c63dc7bf724aa9d48d8e5abe3935564d38182addf733ad4cd81022076362326b22dd7bfaf211d5b17220723659e4fe3359740ced5762d0e497b7dcc012321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac00000000")); + + BOOST_CHECK(tx.get_hash().str() == "a5947589e2762107ff650958ba0e3a3cf341f53281d15593530bf9762c4edab1"); + BOOST_CHECK(tx.get_txid().str() == "954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88"); + BOOST_CHECK(tx.get_vsize() == 148); +} + +BOOST_AUTO_TEST_CASE(bitcoin_transaction_builder_test) { + // All tests are only to verefy the compilation of transactions, the transactions are not real(not valid) + { // P2PKH to P2PKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2PKH, fc::sha256("5d42b45d5a3ddcf2421b208885871121551acf6ea5cc1c1b4e666537ab6fcbef"), 0, bytes()); + tb.add_out(payment_type::P2PKH, 4999990000, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000001efcb6fab3765664e1b1ccca56ecf1a552111878588201b42f2dc3d5a5db4425d0000000000ffffffff01f0ca052a010000001976a9143307bf6f98832e53a48b144d65c6a95700a93ffb88ac00000000"); + } /* + { // coinbase to P2PK + const auto pubkey = fc::raw::unpack( parse_hex( "02028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00" ) ); + out_point prevout; + prevout.hash = fc::sha256( "0000000000000000000000000000000000000000000000000000000000000000" ); + prevout.n = 0xffffffff; + + tx_in txin; + txin.prevout = prevout; + + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH, txin, parse_hex( "022a020101" ) ); + tb.add_out( payment_type::P2PK, 625000000, pubkey); + tb.add_out( payment_type::NULLDATA, 0, parse_hex( "21030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac" ) ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05022a020101ffffffff0240be402500000000232102028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00ac0000000000000000256a2321030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac00000000" ); + }*/ + { // P2SH to P2SH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2SH, fc::sha256("40eee3ae1760e3a8532263678cdf64569e6ad06abc133af64f735e52562bccc8"), 0, bytes()); + tb.add_out(payment_type::P2SH, 0xffffffff, "3P14159f73E4gFr7JterCCQh9QjiTjiZrG"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000001c8cc2b56525e734ff63a13bc6ad06a9e5664df8c67632253a8e36017aee3ee400000000000ffffffff01ffffffff0000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a8700000000"); + } + { // P2PK to NULLDATA + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2PK, fc::sha256("fa897a4a2b8bc507db6cf4425e81ca7ebde89a369e07d608ac7f7c311cb13b4f"), 0, bytes()); + tb.add_out(payment_type::NULLDATA, 0, parse_hex("ffffffff")); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000014f3bb11c317c7fac08d6079e369ae8bd7eca815e42f46cdb07c58b2b4a7a89fa0000000000ffffffff010000000000000000066a04ffffffff00000000"); + } + { // P2PK+P2PKH to P2PKH,P2PKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2PK, fc::sha256("13d29149e08b6ca63263f3dddd303b32f5aab646ebc6b7db84756d80a227f6d9"), 0, bytes()); + tb.add_in(payment_type::P2PKH, fc::sha256("a8e7f661925cdd2c0e37fc93c03540c113aa6bcea02b35de09377127f76d0da3"), 0, bytes()); + tb.add_out(payment_type::P2PKH, 4999990000, "mzg9RZ1p29uNXu4uTWoMdMERdVXZpunJhW"); + tb.add_out(payment_type::P2PKH, 4999990000, "n2SPW6abRxUnnTSSHp73VGahbPW4WT9GaK"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000002d9f627a2806d7584dbb7c6eb46b6aaf5323b30ddddf36332a66c8be04991d2130000000000ffffffffa30d6df727713709de352ba0ce6baa13c14035c093fc370e2cdd5c9261f6e7a80000000000ffffffff02f0ca052a010000001976a914d2276c7ed7af07f697175cc2cbcbbf32da81caba88acf0ca052a010000001976a914e57d9a9af070998bedce991c4d8e39f9c51eb93a88ac00000000"); + } + { // P2WPKH to P2WPKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2WPKH, fc::sha256("56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 1, bytes()); + tb.add_out(payment_type::P2WPKH, 99988480, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001600143307bf6f98832e53a48b144d65c6a95700a93ffb00000000"); + } + { // P2WSH to P2WSH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2WSH, fc::sha256("56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 2, bytes()); + tb.add_out(payment_type::P2WSH, 99988360, "p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560200000000ffffffff0188b3f505000000001600140000010966776006953d5567439e5e39f86a0d2700000000"); + } + { // P2SH(WPKH) to P2SH(WPKH) + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2SH_WPKH, fc::sha256("56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 3, parse_hex("ab68025513c3dbd2f7b92a94e0581f5d50f654e7")); + tb.add_out(payment_type::P2SH_WPKH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560300000014ab68025513c3dbd2f7b92a94e0581f5d50f654e7ffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000"); + } + { // P2SH(WSH) to P2SH(WSH) + bitcoin_transaction_builder tb; + tb.set_version(2); + const auto redeem_script = parse_hex("21038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac"); + tb.add_in(payment_type::P2SH_WSH, fc::sha256("fca01bd539623013f6f945dc6173c395394621ffaa53a9eb6da6e9a2e7c9400e"), 0, redeem_script); + tb.add_out(payment_type::P2SH_WSH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000010e40c9e7a2e9a66deba953aaff21463995c37361dc45f9f613306239d51ba0fc000000002321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acacffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000"); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/peerplays_sidechain/bitcoin_utils_test.cpp b/tests/peerplays_sidechain/bitcoin_utils_test.cpp deleted file mode 100644 index c0e6e7c1..00000000 --- a/tests/peerplays_sidechain/bitcoin_utils_test.cpp +++ /dev/null @@ -1,418 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using namespace graphene::peerplays_sidechain; - -BOOST_AUTO_TEST_CASE(tx_serialization) -{ - // use real mainnet transaction - // txid: 6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315 - // json: {"txid":"6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315","hash":"6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315","version":1,"size":224,"vsize":224,"weight":896,"locktime":0,"vin":[{"txid":"55d079ca797fee81416b71b373abedd8722e33c9f73177be0166b5d5fdac478b","vout":0,"scriptSig":{"asm":"3045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253[ALL] 02be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4","hex":"483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4"},"sequence":4294967295}],"vout":[{"value":1.26491535,"n":0,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 95783804d28e528fbc4b48c7700471e6845804eb OP_EQUALVERIFY OP_CHECKSIG","hex":"76a91495783804d28e528fbc4b48c7700471e6845804eb88ac","reqSigs":1,"type":"pubkeyhash","addresses":["1EdKhXv7zjGowPzgDQ4z1wa2ukVrXRXXkP"]}},{"value":0.0002,"n":1,"scriptPubKey":{"asm":"OP_HASH160 fb0670971091da8248b5c900c6515727a20e8662 OP_EQUAL","hex":"a914fb0670971091da8248b5c900c6515727a20e866287","reqSigs":1,"type":"scripthash","addresses":["3QaKF8zobqcqY8aS6nxCD5ZYdiRfL3RCmU"]}}]} - // hex: "01000000018b47acfdd5b56601be7731f7c9332e72d8edab73b3716b4181ee7f79ca79d055000000006b483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4ffffffff028f1b8a07000000001976a91495783804d28e528fbc4b48c7700471e6845804eb88ac204e00000000000017a914fb0670971091da8248b5c900c6515727a20e86628700000000" - fc::string strtx("01000000018b47acfdd5b56601be7731f7c9332e72d8edab73b3716b4181ee7f79ca79d055000000006b483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4ffffffff028f1b8a07000000001976a91495783804d28e528fbc4b48c7700471e6845804eb88ac204e00000000000017a914fb0670971091da8248b5c900c6515727a20e86628700000000"); - bytes bintx; - bintx.resize(strtx.length() / 2); - fc::from_hex(strtx, reinterpret_cast(&bintx[0]), bintx.size()); - btc_tx tx; - BOOST_CHECK_NO_THROW(tx.fill_from_bytes(bintx)); - BOOST_CHECK(tx.nVersion == 1); - BOOST_CHECK(tx.nLockTime == 0); - BOOST_CHECK(tx.vin.size() == 1); - BOOST_CHECK(tx.vout.size() == 2); - 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)); - } - // print old keys - for(auto key: priv_old) - { - fc::sha256 secret = key.get_secret(); - bytes data({239}); - data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); - fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); - data.insert(data.end(), cs.data(), cs.data() + 4); - } - 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 = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); - // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 - BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); - - bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); - - // 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 = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); - BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); - - // 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: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766: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("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766"); - // reverse hash due to the different from_hex algo - std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); - outpoint.n = 0; - 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 in_amounts({20000}); - 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, in_amounts, redeem_old, keys_to_sign); - // this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850 - BOOST_CHECK(fc::to_hex((char*)&signed_tx[0], signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); -} - -BOOST_AUTO_TEST_CASE(pw_separate_sign) -{ - // 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)); - } - // print old keys - for(auto key: priv_old) - { - fc::sha256 secret = key.get_secret(); - bytes data({239}); - data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); - fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); - data.insert(data.end(), cs.data(), cs.data() + 4); - } - 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 = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); - // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 - BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); - - bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); - - // 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 = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); - BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); - - // 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: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766: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("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766"); - // reverse hash due to the different from_hex algo - std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); - outpoint.n = 0; - 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 in_amounts({20000}); - - // prepare tx with dummy signs - bytes partially_signed_tx = add_dummy_signatures_for_pw_transfer(unsigned_tx, redeem_old, 15); - - // sign with every old key one by one - for(unsigned idx = 0; idx < 15; idx++) - partially_signed_tx = partially_sign_pw_transfer_transaction(partially_signed_tx, in_amounts, priv_old[idx], idx); - - // now this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850 - BOOST_CHECK(fc::to_hex((char*)&partially_signed_tx[0], partially_signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); -} - -BOOST_AUTO_TEST_CASE(pw_separate_sign2) -{ - // 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)); - } - // print old keys - for(auto key: priv_old) - { - fc::sha256 secret = key.get_secret(); - bytes data({239}); - data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); - fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); - data.insert(data.end(), cs.data(), cs.data() + 4); - } - 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 = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); - // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 - BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); - - bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); - - // 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 = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); - BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); - - // 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: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766: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("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766"); - // reverse hash due to the different from_hex algo - std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); - outpoint.n = 0; - 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 in_amounts({20000}); - - // gather all signatures from all SONs separatelly - std::vector > signature_set; - for(auto key: priv_old) - { - std::vector signatures = signatures_for_raw_transaction(unsigned_tx, in_amounts, redeem_old, key); - signature_set.push_back(signatures); - } - - // create signed tx with all signatures - bytes signed_tx = add_signatures_to_unsigned_tx(unsigned_tx, signature_set, redeem_old); - - // now this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850 - BOOST_CHECK(fc::to_hex((char*)&signed_tx[0], signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); -} - -BOOST_AUTO_TEST_CASE(pw_partially_sign) -{ - // 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)); - } - // print old keys - for(auto key: priv_old) - { - fc::sha256 secret = key.get_secret(); - bytes data({239}); - data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); - fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); - data.insert(data.end(), cs.data(), cs.data() + 4); - } - 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 = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); - // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 - BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); - - bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); - - // 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 = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); - BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); - - // try to move funds from old wallet to new one - - // Spent 1 UTXO: [7007b77fcd5fe097d02679252aa112900d08ab20c06052f4148265b21b1f9fbf:0] - // with 29999 satoshis - // So, we creating a raw transaction with 1 input and one output that gets - // 29999 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx) - btc_outpoint outpoint; - outpoint.hash = fc::uint256("7007b77fcd5fe097d02679252aa112900d08ab20c06052f4148265b21b1f9fbf"); - // reverse hash due to the different from_hex algo - std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); - outpoint.n = 0; - btc_in input; - input.prevout = outpoint; - input.nSequence = 0xffffffff; - btc_out output; - output.nValue = 29000; - 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 in_amounts({29999}); - - // prepare tx with dummy signs - bytes partially_signed_tx = add_dummy_signatures_for_pw_transfer(unsigned_tx, redeem_old, 15); - - // sign with every old key one by one except the first one - for(unsigned idx = 1; idx < 15; idx++) - partially_signed_tx = partially_sign_pw_transfer_transaction(partially_signed_tx, in_amounts, priv_old[idx], idx); - - // now this is real testnet tx with id e86455c40da6993b6fed70daea2046287b206ab5c16e1ab58c4dfb4a7d6efb84 - BOOST_CHECK(fc::to_hex((char*)&partially_signed_tx[0], partially_signed_tx.size()) == "02000000000101bf9f1f1bb2658214f45260c020ab080d9012a12a257926d097e05fcd7fb707700000000000ffffffff0148710000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10483045022100c4c567419754c5c1768e959a35633012e8d22ccc90d7cd1b88d6d430a513fbbd0220729c2a3520d0cae7dd6dcd928624ffa3e0b6ce0c4f5c340653a6c18549182588014830450221008c868ea2cdf5b23bdf9e6c7d7c283b8424aeb4aec43621424baef1ee77dd399a02205431f608006f0f0dcd392fab4f25328808b45d4a73852a197e947b289faefece01483045022100aecac85bbb81bc0a4e127c15090c5ab82a62b9e27a9a6eb8eddf8de294aa9d920220482f2ba8d7b62e9f3f7a68b0ef3236bc56e44481d3eb59f62d1daf4b191dc86001483045022100eb27943f8b511a36b1a843f9b3ddf6930aece5a3c0be697dbafc921924fc049c022065ba3e1e4ad57f56337143136c5d3ee3f56dd60f36e798f07b5646e29343d7320147304402206e24158484ebb2cd14b9c410ecd04841d806d8464ce9a827533484c8ad8d921b022021baec9cd0ad46e7b19c8de7df286093b835df5c6243e90b14f5748dc1b7c13901473044022067bfaf0e39d72e49a081d4e43828746ab7524c4764e445173dd96cc7e6187d46022063ef107375cc45d1c26b1e1c87b97694f71645187ad871db9c05b8e981a0da8601483045022100da0162de3e4a5268b616b9d01a1a4f64b0c371c6b44fb1f740a264455f2bc20d02203a0b45a98a341722ad65ae4ad68538d617b1cfbb229751f875615317eaf15dd4014830450221008220c4f97585e67966d4435ad8497eb89945f13dd8ff24048b830582349041a002204cb03f7271895637a31ce6479d15672c2d70528148e3cd6196e6f722117745c50147304402203e83ab4b15bb0680f82779335acf9a3ce45316150a4538d5e3d25cb863fcec5702204b3913874077ed2cae4e10f8786053b6f157973a54d156d5863f13accca595f50147304402201420d2a2830278ffff5842ecb7173a23642f179435443e780b3d1fe04be5c32e02203818202390e0e63b4309b89f9cce08c0f4dfa539c2ed59b05e24325671e2747c0147304402205624ca9d47ae04afd8fff705706d6853f8c679abb385f19e01c36f9380a0bad602203dc817fc55497e4c1759a3dbfff1662faca593a9f10d3a9b3e24d5ee3165d4400147304402203a959f9a34587c56b86826e6ab65644ab19cbd09ca078459eb59956b02bc753002206df5ded568d0e3e3645f8cb8ca02874dd1bfa82933eb5e01ff2e5a773633e51601483045022100a84ed5be60b9e095d40f3f6bd698425cb9c4d8f95e8b43ca6c5120a6c599e9eb022064c703952d18d753f9198d78188a26888e6b06c832d93f8075311d57a13240160147304402202e71d3af33a18397b90072098881fdbdb8d6e4ffa34d21141212dd815c97d00f02207195f1c06a8f44ca72af15fdaba89b07cf6daef9be981c432b9f5c10f1e374200100fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); -} diff --git a/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp b/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp index 8c4db6df..84577384 100644 --- a/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp +++ b/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp @@ -2,19 +2,16 @@ #define BOOST_TEST_MODULE Peerplays SON Tests -BOOST_AUTO_TEST_CASE(peerplays_sidechain) -{ - +BOOST_AUTO_TEST_CASE(peerplays_sidechain) { } +#include #include #include -#include -boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { - std::srand(time(NULL)); - std::cout << "Random number generator seeded to " << time(NULL) << std::endl; +boost::unit_test::test_suite *init_unit_test_suite(int argc, char *argv[]) { + std::srand(time(NULL)); + std::cout << "Random number generator seeded to " << time(NULL) << std::endl; - return nullptr; + return nullptr; } - diff --git a/tests/tests/sidechain_addresses_test.cpp b/tests/tests/sidechain_addresses_test.cpp index e3fb115f..f0dbe520 100644 --- a/tests/tests/sidechain_addresses_test.cpp +++ b/tests/tests/sidechain_addresses_test.cpp @@ -27,10 +27,12 @@ BOOST_AUTO_TEST_CASE( sidechain_address_add_test ) { BOOST_TEST_MESSAGE("Send sidechain_address_add_operation"); sidechain_address_add_operation op; - + op.payer = alice_id; op.sidechain_address_account = alice_id; op.sidechain = sidechain_type::bitcoin; + op.deposit_public_key = "deposit_public_key"; op.deposit_address = "deposit_address"; + op.withdraw_public_key = "withdraw_public_key"; op.withdraw_address = "withdraw_address"; trx.operations.push_back(op); @@ -47,7 +49,9 @@ BOOST_AUTO_TEST_CASE( sidechain_address_add_test ) { BOOST_REQUIRE( obj != idx.end() ); BOOST_CHECK( obj->sidechain_address_account == alice_id ); BOOST_CHECK( obj->sidechain == sidechain_type::bitcoin ); + BOOST_CHECK( obj->deposit_public_key == "deposit_public_key" ); BOOST_CHECK( obj->deposit_address == "deposit_address" ); + BOOST_CHECK( obj->withdraw_public_key == "withdraw_public_key" ); BOOST_CHECK( obj->withdraw_address == "withdraw_address" ); } @@ -64,17 +68,22 @@ BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) { auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin ) ); BOOST_REQUIRE( obj != idx.end() ); + std::string new_deposit_public_key = "new_deposit_public_key"; std::string new_deposit_address = "new_deposit_address"; + std::string new_withdraw_public_key = "new_withdraw_public_key"; std::string new_withdraw_address = "new_withdraw_address"; { BOOST_TEST_MESSAGE("Send sidechain_address_update_operation"); sidechain_address_update_operation op; + op.payer = alice_id; op.sidechain_address_id = sidechain_address_id_type(0); op.sidechain_address_account = obj->sidechain_address_account; op.sidechain = obj->sidechain; + op.deposit_public_key = new_deposit_public_key; op.deposit_address = new_deposit_address; + op.withdraw_public_key = new_withdraw_public_key; op.withdraw_address = new_withdraw_address; trx.operations.push_back(op); @@ -92,7 +101,9 @@ BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) { BOOST_REQUIRE( obj != idx.end() ); BOOST_CHECK( obj->sidechain_address_account == obj->sidechain_address_account ); BOOST_CHECK( obj->sidechain == obj->sidechain ); + BOOST_CHECK( obj->deposit_public_key == new_deposit_public_key ); BOOST_CHECK( obj->deposit_address == new_deposit_address ); + BOOST_CHECK( obj->withdraw_public_key == new_withdraw_public_key ); BOOST_CHECK( obj->withdraw_address == new_withdraw_address ); } } @@ -114,8 +125,9 @@ BOOST_AUTO_TEST_CASE( sidechain_address_delete_test ) { BOOST_TEST_MESSAGE("Send sidechain_address_delete_operation"); sidechain_address_delete_operation op; + op.payer = alice_id; op.sidechain_address_id = sidechain_address_id_type(0); - op.sidechain_address_account = obj->sidechain_address_account; + op.sidechain_address_account = alice_id; op.sidechain = obj->sidechain; trx.operations.push_back(op);