From 8676940a281604688771e96ceb1e65a35d98e8e5 Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Fri, 6 Dec 2019 00:25:44 +0100 Subject: [PATCH] WIP: Sidechain objects --- libraries/CMakeLists.txt | 1 + libraries/app/database_api.cpp | 28 ++ .../app/include/graphene/app/database_api.hpp | 5 + libraries/chain/CMakeLists.txt | 2 +- .../graphene/chain/bitcoin_address_object.hpp | 46 +++ .../graphene/chain/bitcoin_transaction.hpp | 96 +++++ .../chain/include/graphene/chain/config.hpp | 14 + .../chain/protocol/bitcoin_address.hpp | 26 ++ .../chain/protocol/bitcoin_transaction.hpp | 96 +++++ .../include/graphene/chain/protocol/types.hpp | 6 + libraries/sidechain/CMakeLists.txt | 10 + .../sidechain/bitcoin/bitcoin_address.cpp | 258 +++++++++++++ .../sidechain/bitcoin/bitcoin_script.cpp | 60 +++ .../sidechain/bitcoin/bitcoin_transaction.cpp | 280 ++++++++++++++ libraries/sidechain/bitcoin/segwit_addr.cpp | 81 ++++ .../include/sidechain/bitcoin/bech32.hpp | 24 ++ .../sidechain/bitcoin/bitcoin_address.hpp | 133 +++++++ .../sidechain/bitcoin/bitcoin_script.hpp | 69 ++++ .../sidechain/bitcoin/bitcoin_transaction.hpp | 100 +++++ .../include/sidechain/bitcoin/segwit_addr.hpp | 34 ++ .../include/sidechain/bitcoin/serialize.hpp | 360 ++++++++++++++++++ .../include/sidechain/bitcoin/types.hpp | 60 +++ .../include/sidechain/bitcoin/utils.hpp | 15 + 23 files changed, 1803 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/include/graphene/chain/bitcoin_address_object.hpp create mode 100644 libraries/chain/include/graphene/chain/bitcoin_transaction.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp create mode 100644 libraries/sidechain/CMakeLists.txt create mode 100644 libraries/sidechain/bitcoin/bitcoin_address.cpp create mode 100644 libraries/sidechain/bitcoin/bitcoin_script.cpp create mode 100644 libraries/sidechain/bitcoin/bitcoin_transaction.cpp create mode 100644 libraries/sidechain/bitcoin/segwit_addr.cpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/bech32.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/bitcoin_address.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/bitcoin_script.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/bitcoin_transaction.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/segwit_addr.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/serialize.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/types.hpp create mode 100644 libraries/sidechain/include/sidechain/bitcoin/utils.hpp diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 18ca3130..f871b449 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory( utilities ) add_subdirectory( app ) add_subdirectory( plugins ) add_subdirectory( wallet ) +add_subdirectory( sidechain ) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index b9ae31b6..165b3272 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -177,6 +177,8 @@ class database_api_impl : public std::enable_shared_from_this vector get_tournaments_by_state(tournament_id_type stop, unsigned limit, tournament_id_type start, tournament_state state); vector get_registered_tournaments(account_id_type account_filter, uint32_t limit) const; + //Sidechain + vector get_bitcoin_addresses(const account_id_type& acc_id) const; //private: template @@ -2222,6 +2224,32 @@ vector database_api_impl::get_registered_tournaments(account return tournament_ids; } +////////////////////////////////////////////////////////////////////// +// // +// Sidechain // +// // +////////////////////////////////////////////////////////////////////// + +vector database_api::get_bitcoin_addresses(const account_id_type& acc) const +{ + return my->get_bitcoin_addresses(acc); +} + +vector database_api_impl::get_bitcoin_addresses(const account_id_type& acc) const +{ + vector result; + + const auto& btc_addr_idx = _db.get_index_type().indices().get(); + + auto itr = btc_addr_idx.lower_bound( acc ); + while( itr != btc_addr_idx.end() && itr->owner == acc ) + { + result.push_back( *itr ); + ++itr; + } + return result; +} + ////////////////////////////////////////////////////////////////////// // // // Private methods // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index d597110e..8de6771b 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -48,6 +48,7 @@ #include #include #include +#include #include @@ -706,6 +707,8 @@ class database_api */ vector get_registered_tournaments(account_id_type account_filter, uint32_t limit) const; + vector get_bitcoin_addresses(const account_id_type& acc_id) const; + private: std::shared_ptr< database_api_impl > my; }; @@ -840,4 +843,6 @@ FC_API(graphene::app::database_api, (get_tournaments_by_state) (get_tournaments ) (get_registered_tournaments) + + (get_bitcoin_addresses) ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 5688eabf..933d8e87 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -122,7 +122,7 @@ add_library( graphene_chain ) add_dependencies( graphene_chain build_hardfork_hpp ) -target_link_libraries( graphene_chain fc graphene_db ) +target_link_libraries( graphene_chain fc graphene_db sidechain ) target_include_directories( graphene_chain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" ) diff --git a/libraries/chain/include/graphene/chain/bitcoin_address_object.hpp b/libraries/chain/include/graphene/chain/bitcoin_address_object.hpp new file mode 100644 index 00000000..c16abcc4 --- /dev/null +++ b/libraries/chain/include/graphene/chain/bitcoin_address_object.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include + +namespace graphene { namespace chain { + +class bitcoin_address_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = bitcoin_address_object_type; + + bitcoin_address_id_type get_id()const { return id; } + // multisig m-of-n (m = 5). Address is valid before count of changed witnesses < 5 + bool valid() { return count_invalid_pub_key < SIDECHAIN_NUMBER_INVALID_KEYS; } + + std::string get_address() const { return address.get_address(); } + + void update_count_invalid_pub_key( const sidechain::accounts_keys& incoming_wit_keys ) { + count_invalid_pub_key = incoming_wit_keys.size() - address.count_intersection( incoming_wit_keys ); + } + + account_id_type owner; + sidechain::btc_multisig_segwit_address address; + uint8_t count_invalid_pub_key; +}; + +struct by_address; +struct by_owner; + +typedef boost::multi_index_container< + bitcoin_address_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, const_mem_fun< bitcoin_address_object, std::string, &bitcoin_address_object::get_address > >, + ordered_non_unique< tag, member< bitcoin_address_object, account_id_type, &bitcoin_address_object::owner > > + > +> bitcoin_address_multi_index_container; +typedef generic_index bitcoin_address_index; + +} } + +FC_REFLECT_DERIVED( graphene::chain::bitcoin_address_object, (graphene::chain::object), (owner)(address)(count_invalid_pub_key) ) diff --git a/libraries/chain/include/graphene/chain/bitcoin_transaction.hpp b/libraries/chain/include/graphene/chain/bitcoin_transaction.hpp new file mode 100644 index 00000000..ac2c02b3 --- /dev/null +++ b/libraries/chain/include/graphene/chain/bitcoin_transaction.hpp @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + struct bitcoin_transaction_send_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + sidechain::info_for_vin pw_vin; + std::vector< sidechain::info_for_vin > vins; + std::vector< info_for_vout_id_type > vouts; + + sidechain::bitcoin_transaction transaction; + uint64_t fee_for_size; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + share_type fee_required = k.fee; + fee_required += calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); + return fee_required; + } + }; + + struct bitcoin_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + proposal_id_type proposal_id; + std::vector signatures; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct revert_trx_info + { + revert_trx_info() = default; + revert_trx_info( fc::sha256 trx_id, std::set< fc::sha256 > vins ): transaction_id( trx_id ), valid_vins( vins ) {} + + fc::sha256 transaction_id; + std::set< fc::sha256 > valid_vins; + + bool operator ==( const revert_trx_info& trx_info ){ + return this->transaction_id == trx_info.transaction_id && this->valid_vins == trx_info.valid_vins; + } + + bool operator !=( const revert_trx_info& trx_info ){ + return this->transaction_id != trx_info.transaction_id || this->valid_vins != trx_info.valid_vins; + } + }; + + struct bitcoin_transaction_revert_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + std::vector< revert_trx_info > transactions_info; + + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return k.fee; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(pw_vin)(vins)(vouts)(transaction)(fee_for_size) ) + +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signatures) ) + +FC_REFLECT( graphene::chain::revert_trx_info, (transaction_id)(valid_vins) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation, (fee)(payer)(transactions_info) ) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 37b0885d..80b0f1f5 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -240,3 +240,17 @@ #define SWEEPS_DEFAULT_DISTRIBUTION_ASSET (graphene::chain::asset_id_type(0)) #define SWEEPS_VESTING_BALANCE_MULTIPLIER 100000000 #define SWEEPS_ACCUMULATOR_ACCOUNT (graphene::chain::account_id_type(0)) + +////////////////////////////////////////////////////////////////////// SideChain +#define SIDECHAIN_SYMBOL "pBTC" +#define SIDECHAIN_PRECISION_DIGITS 8 +#define SIDECHAIN_MAX_SHARE_SUPPLY int64_t(21000000ll * 100000000ll) +#define SIDECHAIN_NUMBER_INVALID_KEYS 5 +#define SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG 5 +#define SIDECHAIN_DEFAULT_NUMBER_OF_CONFIRMATIONS 6 +#define SIDECHAIN_DEFAULT_MAX_UNCONFIRMED_VOUTS 25 +#define SIDECHAIN_DEFAULT_MAX_CONDENSING_TX_VINS 5 +#define SIDECHAIN_DEFAULT_MAX_CONDENSING_TX_VOUTS 5 +#define SIDECHAIN_DEFAULT_PERCENT_PAYMENT_TO_WITNESSES (GRAPHENE_1_PERCENT/10) +#define SIDECHAIN_NULL_VIN_IDENTIFIER "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b4" // fc::sha256::hash( "" + std::to_string( 0 ) ) - ( 8 bytes ) +////////////////////////////////////////////////////////////////////// diff --git a/libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp b/libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp new file mode 100644 index 00000000..e55dc87b --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct bitcoin_address_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type payer; + account_id_type owner; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + return k.fee; + } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_address_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::bitcoin_address_create_operation, (fee)(payer)(owner) ) diff --git a/libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp b/libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp new file mode 100644 index 00000000..ac2c02b3 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + struct bitcoin_transaction_send_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + sidechain::info_for_vin pw_vin; + std::vector< sidechain::info_for_vin > vins; + std::vector< info_for_vout_id_type > vouts; + + sidechain::bitcoin_transaction transaction; + uint64_t fee_for_size; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + share_type fee_required = k.fee; + fee_required += calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); + return fee_required; + } + }; + + struct bitcoin_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + proposal_id_type proposal_id; + std::vector signatures; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct revert_trx_info + { + revert_trx_info() = default; + revert_trx_info( fc::sha256 trx_id, std::set< fc::sha256 > vins ): transaction_id( trx_id ), valid_vins( vins ) {} + + fc::sha256 transaction_id; + std::set< fc::sha256 > valid_vins; + + bool operator ==( const revert_trx_info& trx_info ){ + return this->transaction_id == trx_info.transaction_id && this->valid_vins == trx_info.valid_vins; + } + + bool operator !=( const revert_trx_info& trx_info ){ + return this->transaction_id != trx_info.transaction_id || this->valid_vins != trx_info.valid_vins; + } + }; + + struct bitcoin_transaction_revert_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + std::vector< revert_trx_info > transactions_info; + + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return k.fee; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(pw_vin)(vins)(vouts)(transaction)(fee_for_size) ) + +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signatures) ) + +FC_REFLECT( graphene::chain::revert_trx_info, (transaction_id)(valid_vins) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation, (fee)(payer)(transactions_info) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index bcdd1a83..f140f71a 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -147,6 +147,7 @@ namespace graphene { namespace chain { bet_object_type, son_object_type, son_proposal_object_type, + bitcoin_address_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -209,6 +210,7 @@ namespace graphene { namespace chain { class bet_object; class son_object; class son_proposal_object; + class bitcoin_address_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -238,6 +240,8 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, son_object_type, son_object> son_id_type; typedef object_id< protocol_ids, son_proposal_object_type, son_proposal_object> son_proposal_id_type; + typedef object_id< protocol_ids, bitcoin_address_object_type, bitcoin_address_object> bitcoin_address_id_type; + // implementation types class global_property_object; class dynamic_global_property_object; @@ -421,6 +425,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (bet_object_type) (son_object_type) (son_proposal_object_type) + (bitcoin_address_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -493,6 +498,7 @@ FC_REFLECT_TYPENAME( graphene::chain::global_betting_statistics_id_type ) FC_REFLECT_TYPENAME( graphene::chain::tournament_details_id_type ) FC_REFLECT_TYPENAME( graphene::chain::son_id_type ) FC_REFLECT_TYPENAME( graphene::chain::son_proposal_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::bitcoin_address_id_type ) FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/sidechain/CMakeLists.txt b/libraries/sidechain/CMakeLists.txt new file mode 100644 index 00000000..86829ef2 --- /dev/null +++ b/libraries/sidechain/CMakeLists.txt @@ -0,0 +1,10 @@ +file( GLOB SOURCES "*.cpp" ) +file( GLOB HEADERS "include/*.hpp" ) + +file( GLOB SOURCES_BITCOIN "bitcoin/*.cpp" ) +file( GLOB HEADERS_BITCOIN "include/bitcoin/*.hpp" ) + +add_library( sidechain STATIC ${SOURCES} ${HEADERS} ${SOURCES_BITCOIN} ${HEADERS_BITCOIN} ) + +target_link_libraries( sidechain fc graphene_chain ) +target_include_directories( sidechain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/sidechain/bitcoin/bitcoin_address.cpp b/libraries/sidechain/bitcoin/bitcoin_address.cpp new file mode 100644 index 00000000..365889b0 --- /dev/null +++ b/libraries/sidechain/bitcoin/bitcoin_address.cpp @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include + +namespace sidechain { + +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 ); +} + +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 = sidechain::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( sidechain::parse_hex( hash160.str() ) ); + + raw_address.push_back( OP_HASH160 ); + std::stringstream ss; + ss << std::hex << temp_addr_hash.size(); + auto address_size_hex = sidechain::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; +} + +} diff --git a/libraries/sidechain/bitcoin/bitcoin_script.cpp b/libraries/sidechain/bitcoin/bitcoin_script.cpp new file mode 100644 index 00000000..434c404b --- /dev/null +++ b/libraries/sidechain/bitcoin/bitcoin_script.cpp @@ -0,0 +1,60 @@ +#include +#include + +namespace sidechain { + +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<<( uint8_t number ) +{ + FC_ASSERT( 0 <= number && number <= 16 ); + + if ( number == 0 ) + script.push_back( static_cast( op::_0 ) ); + else + script.push_back( static_cast( op::_1 ) + number - 1 ); + + return *this; +} + +script_builder& script_builder::operator<<( size_t size ) +{ + sidechain::write_compact_size( script, size ); + return *this; +} + +script_builder& script_builder::operator<<( const bytes& sc ) { + sidechain::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 ) +{ + sidechain::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 ) +{ + sidechain::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 ) +{ + sidechain::write_compact_size( script, pubkey_data.size() ); + script.insert( script.end(), pubkey_data.begin(), pubkey_data.begin() + pubkey_data.size() ); + return *this; +} + +} diff --git a/libraries/sidechain/bitcoin/bitcoin_transaction.cpp b/libraries/sidechain/bitcoin/bitcoin_transaction.cpp new file mode 100644 index 00000000..ea5c53d7 --- /dev/null +++ b/libraries/sidechain/bitcoin/bitcoin_transaction.cpp @@ -0,0 +1,280 @@ +#include + +#include +#include +#include + +namespace sidechain { + +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( 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; + } + } +} + +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; + } +} + +} diff --git a/libraries/sidechain/bitcoin/segwit_addr.cpp b/libraries/sidechain/bitcoin/segwit_addr.cpp new file mode 100644 index 00000000..883c9541 --- /dev/null +++ b/libraries/sidechain/bitcoin/segwit_addr.cpp @@ -0,0 +1,81 @@ +/* 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 sidechain { namespace segwit_addr { + +/** Decode a SegWit address. */ +std::pair decode(const std::string& hrp, const std::string& addr) { + std::pair dec = sidechain::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 = sidechain::bech32::Encode(hrp, enc); + if (decode(hrp, ret).first == -1) return ""; + return ret; +} + +} } diff --git a/libraries/sidechain/include/sidechain/bitcoin/bech32.hpp b/libraries/sidechain/include/sidechain/bitcoin/bech32.hpp new file mode 100644 index 00000000..d5361312 --- /dev/null +++ b/libraries/sidechain/include/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 sidechain { 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 sidechain::bech32 diff --git a/libraries/sidechain/include/sidechain/bitcoin/bitcoin_address.hpp b/libraries/sidechain/include/sidechain/bitcoin/bitcoin_address.hpp new file mode 100644 index 00000000..2bbdc10d --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin/bitcoin_address.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +using namespace graphene::chain; + +namespace sidechain { + +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: + + bitcoin_address() = default; + + bitcoin_address( const std::string& addr ) : address( addr ), type( determine_type() ), + raw_address( determine_raw_address() ) {} + + 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; + +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; + +}; + +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; + +}; + +} + +FC_REFLECT( sidechain::bitcoin_address, (address)(type)(raw_address) ); + +FC_REFLECT_DERIVED( sidechain::btc_multisig_address, (sidechain::bitcoin_address), + (redeem_script)(keys_required)(witnesses_keys) ); + +FC_REFLECT_DERIVED( sidechain::btc_multisig_segwit_address, (sidechain::btc_multisig_address), (witness_script) ); diff --git a/libraries/sidechain/include/sidechain/bitcoin/bitcoin_script.hpp b/libraries/sidechain/include/sidechain/bitcoin/bitcoin_script.hpp new file mode 100644 index 00000000..48b64093 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin/bitcoin_script.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +namespace sidechain { + +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 + RETURN = 0x6a, + + // stack ops + DUP = 0x76, + + // bit logic + EQUAL = 0x87, + EQUALVERIFY = 0x88, + + // crypto + HASH160 = 0xa9, + CHECKSIG = 0xac, + CHECKMULTISIG = 0xae, +}; + +class script_builder { + +public: + + script_builder& operator<<( op opcode ); + + script_builder& operator<<( uint8_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; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/bitcoin/bitcoin_transaction.hpp b/libraries/sidechain/include/sidechain/bitcoin/bitcoin_transaction.hpp new file mode 100644 index 00000000..3a41212c --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin/bitcoin_transaction.hpp @@ -0,0 +1,100 @@ +#pragma once +#include + +namespace sidechain { + +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( 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; + +}; + +} + +FC_REFLECT( sidechain::out_point, (hash)(n) ) +FC_REFLECT( sidechain::tx_in, (prevout)(scriptSig)(nSequence)(scriptWitness) ) +FC_REFLECT( sidechain::tx_out, (value)(scriptPubKey) ) +FC_REFLECT( sidechain::bitcoin_transaction, (nVersion)(vin)(vout)(nLockTime) ) diff --git a/libraries/sidechain/include/sidechain/bitcoin/segwit_addr.hpp b/libraries/sidechain/include/sidechain/bitcoin/segwit_addr.hpp new file mode 100644 index 00000000..d115e035 --- /dev/null +++ b/libraries/sidechain/include/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 sidechain { 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); + +} } diff --git a/libraries/sidechain/include/sidechain/bitcoin/serialize.hpp b/libraries/sidechain/include/sidechain/bitcoin/serialize.hpp new file mode 100644 index 00000000..908a3bdb --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin/serialize.hpp @@ -0,0 +1,360 @@ +#pragma once + +#include +#include + +namespace sidechain { + +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 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 ); +} + +} diff --git a/libraries/sidechain/include/sidechain/bitcoin/types.hpp b/libraries/sidechain/include/sidechain/bitcoin/types.hpp new file mode 100644 index 00000000..b894ee68 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin/types.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace sidechain { + +class bitcoin_transaction; + +using bytes = std::vector; +using accounts_keys = std::map< graphene::chain::account_id_type, graphene::chain::public_key_type >; +//using info_for_vout = graphene::chain::info_for_vout_object; +//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; +}; + +} + +FC_REFLECT_ENUM( sidechain::payment_type, (NULLDATA)(P2PK)(P2PKH)(P2SH)(P2WPKH)(P2WSH)(P2SH_WPKH)(P2SH_WSH) ); +FC_REFLECT_ENUM( sidechain::sidechain_proposal_type, (ISSUE_BTC)(SEND_BTC_TRANSACTION)(REVERT_BTC_TRANSACTION) ); +FC_REFLECT( sidechain::prev_out, (hash_tx)(n_vout)(amount) ); diff --git a/libraries/sidechain/include/sidechain/bitcoin/utils.hpp b/libraries/sidechain/include/sidechain/bitcoin/utils.hpp new file mode 100644 index 00000000..cc4e03b0 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin/utils.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace sidechain { + +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 ); + +}