diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 1131d697..44978017 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -117,7 +117,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/sidechain/CMakeLists.txt b/libraries/sidechain/CMakeLists.txt index 82de11e3..515acbe2 100644 --- a/libraries/sidechain/CMakeLists.txt +++ b/libraries/sidechain/CMakeLists.txt @@ -5,5 +5,5 @@ add_subdirectory( network ) add_library( sidechain STATIC ${SOURCES} ${HEADERS} ) -target_link_libraries( sidechain ${Boost_LIBRARIES} fc graphene_chain ) +target_link_libraries( sidechain fc graphene_chain ) target_include_directories( sidechain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/sidechain/btc_multisig_address.cpp b/libraries/sidechain/btc_multisig_address.cpp new file mode 100644 index 00000000..1c996ed5 --- /dev/null +++ b/libraries/sidechain/btc_multisig_address.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +namespace sidechain { + +btc_multisig_address::btc_multisig_address( const size_t n_required, const std::map< account_id_type, public_key_type >& keys ) : + keys_required ( n_required ), witnesses_keys( keys ) +{ + create_redeem_script(); + create_address(); +} + +size_t btc_multisig_address::count_intersection( const std::map< account_id_type, public_key_type >& 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[keys_required - 1] ); + for( 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[witnesses_keys.size() - 1] ); + redeem_script.push_back( OP_CHECKMULTISIG ); +} + +void btc_multisig_address::create_address() +{ + FC_ASSERT( redeem_script.size() > 0 ); + 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() ); + std::vector temp_addr_hash( sidechain::parse_hex( hash160.str() ) ); + + address.push_back( OP_HASH160 ); + std::stringstream ss; + ss << std::hex << temp_addr_hash.size(); + auto address_size_hex = sidechain::parse_hex( ss.str() ); + address.insert( address.end(), address_size_hex.begin(), address_size_hex.end() ); + address.insert( address.end(), temp_addr_hash.begin(), temp_addr_hash.end() ); + address.push_back( OP_EQUAL ); +} + +btc_multisig_segwit_address::btc_multisig_segwit_address( const size_t n_required, const std::map< account_id_type, public_key_type >& keys ) : + btc_multisig_address( n_required, keys ) +{ + create_witness_script(); + create_segwit_address(); +} + +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 || + segwit_address != addr.segwit_address || base58_address != addr.base58_address ) + return false; + return true; +} + +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() ); + + segwit_address = std::vector(hash160.data(), hash160.data() + hash160.data_size() ); + base58_address = fc::to_base58( get_address_bytes( segwit_address ) ); +} + +std::vector btc_multisig_segwit_address::get_address_bytes( const std::vector& script_hash ) +{ + std::vector 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/include/sidechain/btc_multisig_address.hpp b/libraries/sidechain/include/sidechain/btc_multisig_address.hpp new file mode 100644 index 00000000..3e7f7e96 --- /dev/null +++ b/libraries/sidechain/include/sidechain/btc_multisig_address.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +#include +#include + +using namespace graphene::chain; + +namespace sidechain { + +const std::vector op = {0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f}; // OP_1 - OP_15 + +class btc_multisig_address +{ + +public: + + btc_multisig_address() = default; + + btc_multisig_address( const size_t n_required, const std::map< account_id_type, public_key_type >& keys ); + + size_t count_intersection( const std::map< account_id_type, public_key_type >& keys ) const; + + virtual std::vector< char > get_hex_address() { return address; } + + std::vector< char > get_redeem_script() { 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 }; + + std::vector< char > address; + + std::vector< char > redeem_script; + + size_t keys_required = 0; + + const std::map< account_id_type, public_key_type > 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 std::map< account_id_type, public_key_type >& keys ); + + bool operator==( const btc_multisig_segwit_address& addr ) const; + + std::vector< char > get_hex_address() override { return segwit_address; } + + std::string get_base58_address() { return base58_address; } + + std::vector< char > get_witness_script() { return witness_script; } + +private: + + void create_witness_script(); + + void create_segwit_address(); + + std::vector get_address_bytes( const std::vector& script_hash ); + +public: + + std::vector< char > segwit_address; + + std::vector< char > witness_script; + + std::string base58_address; + +}; + +} + +FC_REFLECT( sidechain::btc_multisig_address, (address)(redeem_script)(keys_required)(witnesses_keys) ); + +FC_REFLECT_DERIVED( sidechain::btc_multisig_segwit_address, (sidechain::btc_multisig_address), + (segwit_address)(witness_script)(base58_address) ); diff --git a/libraries/sidechain/include/sidechain/utils.hpp b/libraries/sidechain/include/sidechain/utils.hpp new file mode 100644 index 00000000..fbcf75f6 --- /dev/null +++ b/libraries/sidechain/include/sidechain/utils.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +namespace sidechain { + +std::vector parse_hex( const std::string& str ); + +} diff --git a/libraries/sidechain/utils.cpp b/libraries/sidechain/utils.cpp new file mode 100644 index 00000000..55b47eaf --- /dev/null +++ b/libraries/sidechain/utils.cpp @@ -0,0 +1,12 @@ +#include + +namespace sidechain { + +std::vector parse_hex( const std::string& str ) +{ + std::vector vec( str.size() / 2 ); + fc::from_hex( str, vec.data(), vec.size() ); + return vec; +} + +} diff --git a/tests/tests/btc_multisig_address_tests.cpp b/tests/tests/btc_multisig_address_tests.cpp new file mode 100644 index 00000000..2d19b1e7 --- /dev/null +++ b/tests/tests/btc_multisig_address_tests.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +using namespace sidechain; + +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_SUITE( btc_multisig_address_tests ) + +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 ); + + sidechain::btc_multisig_segwit_address cma(2, { { account_id_type(1), public_key_type(key1) }, { account_id_type(2), public_key_type(key2) }, { account_id_type(3), public_key_type(key3) } }); + + BOOST_CHECK( address == cma.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 ); + + sidechain::btc_multisig_segwit_address address(2, { { account_id_type(1), public_key_type(key1) }, { account_id_type(2), public_key_type(key2) }, { account_id_type(3), public_key_type(key3) } }); + BOOST_CHECK( address.get_witness_script() == witness_script ); + BOOST_CHECK( address.get_base58_address() == "2NGU4ogScHEHEpReUzi9RB2ha58KAFnkFyk" ); +} + +BOOST_AUTO_TEST_SUITE_END()