Filling I/O queues. Create class bitcoin_address.
This commit is contained in:
parent
73106a3b7e
commit
9137f31960
33 changed files with 1141 additions and 315 deletions
|
|
@ -282,6 +282,10 @@ struct get_impacted_account_visitor
|
|||
_impacted.insert( op.affiliate );
|
||||
}
|
||||
void operator()( const affiliate_referral_payout_operation& op ) { }
|
||||
void operator()( const withdraw_pbtc_operation& op )
|
||||
{
|
||||
_impacted.insert( op.payer );
|
||||
}
|
||||
void operator()( const bitcoin_address_create_operation& op )
|
||||
{
|
||||
_impacted.insert( op.payer );
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ add_library( graphene_chain
|
|||
betting_market_group_object.cpp
|
||||
|
||||
affiliate_payout.cpp
|
||||
withdraw_pbtc_evaluator.cpp
|
||||
|
||||
${HEADERS}
|
||||
${PROTOCOL_HEADERS}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
#include <graphene/chain/event_evaluator.hpp>
|
||||
#include <graphene/chain/betting_market_evaluator.hpp>
|
||||
#include <graphene/chain/tournament_evaluator.hpp>
|
||||
#include <graphene/chain/withdraw_pbtc_evaluator.hpp>
|
||||
#include <graphene/chain/bitcoin_address_evaluator.hpp>
|
||||
|
||||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||
|
|
@ -241,6 +242,7 @@ void database::initialize_evaluators()
|
|||
register_evaluator<tournament_join_evaluator>();
|
||||
register_evaluator<game_move_evaluator>();
|
||||
register_evaluator<tournament_leave_evaluator>();
|
||||
register_evaluator<withdraw_pbtc_evaluator>();
|
||||
register_evaluator<bitcoin_address_create_evaluator>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
namespace graphene { namespace chain {
|
||||
|
||||
database::database() :
|
||||
_random_number_generator(fc::ripemd160().data())
|
||||
i_w_info(*this), _random_number_generator(fc::ripemd160().data())
|
||||
{
|
||||
initialize_indexes();
|
||||
initialize_evaluators();
|
||||
|
|
|
|||
|
|
@ -269,6 +269,10 @@ struct get_impacted_account_visitor
|
|||
_impacted.insert( op.affiliate );
|
||||
}
|
||||
void operator()( const affiliate_referral_payout_operation& op ) { }
|
||||
void operator()( const withdraw_pbtc_operation& op )
|
||||
{
|
||||
_impacted.insert( op.payer );
|
||||
}
|
||||
void operator()( const bitcoin_address_create_operation& op )
|
||||
{
|
||||
_impacted.insert( op.payer );
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@
|
|||
#include <graphene/db/generic_index.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
|
||||
#include <sidechain/btc_multisig_address.hpp>
|
||||
#include <sidechain/bitcoin_address.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
using namespace sidechain;
|
||||
|
||||
class bitcoin_address_object : public abstract_object<bitcoin_address_object>
|
||||
{
|
||||
public:
|
||||
|
|
@ -19,14 +17,14 @@ class bitcoin_address_object : public abstract_object<bitcoin_address_object>
|
|||
// multisig m-of-n (m = 5). Address is valid before count of changed witnesses < 5
|
||||
bool valid() { return count_invalid_pub_key < 5; } // TODO: move to global_properties
|
||||
|
||||
std::string get_address() const { return address.base58_address; }
|
||||
std::string get_address() const { return address.get_address(); }
|
||||
|
||||
void update_count_invalid_pub_key(const accounts_keys& incoming_wit_keys) {
|
||||
count_invalid_pub_key = incoming_wit_keys.size() - address.count_intersection(incoming_wit_keys);
|
||||
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;
|
||||
btc_multisig_segwit_address address;
|
||||
sidechain::btc_multisig_segwit_address address;
|
||||
uint8_t count_invalid_pub_key;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include <fc/crypto/hash_ctr_rng.hpp>
|
||||
|
||||
#include <graphene/chain/protocol/protocol.hpp>
|
||||
#include <sidechain/input_withdrawal_info.hpp>
|
||||
|
||||
#include <fc/log/logger.hpp>
|
||||
|
||||
|
|
@ -504,6 +505,13 @@ namespace graphene { namespace chain {
|
|||
///@}
|
||||
///@}
|
||||
|
||||
//////////////////// sidechain ////////////////////
|
||||
public:
|
||||
|
||||
sidechain::input_withdrawal_info i_w_info;
|
||||
|
||||
private:
|
||||
|
||||
vector< processed_transaction > _pending_tx;
|
||||
fork_database _fork_db;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
#include <graphene/db/generic_index.hpp>
|
||||
#include <sidechain/types.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
|
|
@ -21,10 +22,10 @@ class info_for_vout_object : public abstract_object<info_for_vout_object>
|
|||
|
||||
info_for_vout_id_type get_id()const { return id; }
|
||||
|
||||
account_id_type payer;
|
||||
// btc::payment_type addr_type;
|
||||
std::string data;
|
||||
uint64_t amount;
|
||||
account_id_type payer;
|
||||
sidechain::payment_type addr_type;
|
||||
std::string data;
|
||||
uint64_t amount;
|
||||
|
||||
bool created = false;
|
||||
};
|
||||
|
|
@ -44,5 +45,5 @@ typedef generic_index<info_for_vout_object, info_for_vout_multi_index_container>
|
|||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(data)(amount)(created) )
|
||||
FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(addr_type)(data)(amount)(created) )
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
#include <graphene/chain/protocol/event.hpp>
|
||||
#include <graphene/chain/protocol/betting_market.hpp>
|
||||
#include <graphene/chain/protocol/tournament.hpp>
|
||||
#include <graphene/chain/protocol/withdraw_pbtc.hpp>
|
||||
#include <graphene/chain/protocol/bitcoin_address.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
|
@ -131,6 +132,7 @@ namespace graphene { namespace chain {
|
|||
event_group_delete_operation,
|
||||
affiliate_payout_operation, // VIRTUAL
|
||||
affiliate_referral_payout_operation, // VIRTUAL
|
||||
withdraw_pbtc_operation,
|
||||
bitcoin_address_create_operation
|
||||
> operation;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
#include <graphene/chain/protocol/base.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
struct withdraw_pbtc_operation : public base_operation
|
||||
{
|
||||
struct fee_parameters_type {
|
||||
uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;
|
||||
uint32_t price_per_kbyte = 10;
|
||||
};
|
||||
|
||||
asset fee;
|
||||
account_id_type payer;
|
||||
|
||||
std::string data; // address or script
|
||||
uint64_t amount;
|
||||
|
||||
// object_id_type tx_obj_id;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT( graphene::chain::withdraw_pbtc_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
||||
FC_REFLECT( graphene::chain::withdraw_pbtc_operation, (fee)(payer)(data)(amount) )
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/evaluator.hpp>
|
||||
#include <sidechain/types.hpp>
|
||||
|
||||
using namespace sidechain;
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
class withdraw_pbtc_evaluator : public evaluator<withdraw_pbtc_evaluator>
|
||||
{
|
||||
public:
|
||||
typedef withdraw_pbtc_operation operation_type;
|
||||
|
||||
void_result do_evaluate(const withdraw_pbtc_operation& op);
|
||||
|
||||
object_id_type do_apply(const withdraw_pbtc_operation& op);
|
||||
|
||||
void reserve_issue( const withdraw_pbtc_operation& op );
|
||||
|
||||
bool check_amount( const withdraw_pbtc_operation& op );
|
||||
|
||||
payment_type type;
|
||||
};
|
||||
|
||||
} } // graphene::chain
|
||||
49
libraries/chain/withdraw_pbtc_evaluator.cpp
Normal file
49
libraries/chain/withdraw_pbtc_evaluator.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include <graphene/chain/withdraw_pbtc_evaluator.hpp>
|
||||
#include <graphene/chain/info_for_vout_object.hpp>
|
||||
|
||||
#include <sidechain/bitcoin_address.hpp>
|
||||
#include <sidechain/utils.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
void_result withdraw_pbtc_evaluator::do_evaluate(const withdraw_pbtc_operation& op)
|
||||
{
|
||||
database& d = db();
|
||||
|
||||
// FC_ASSERT( !d.is_sidechain_fork_needed() );
|
||||
FC_ASSERT( op.data.size() > 0 );
|
||||
type = bitcoin_address( op.data ).get_type();
|
||||
FC_ASSERT( type != payment_type::NULLDATA , "Invalid address type." );
|
||||
FC_ASSERT( check_amount( op ) );
|
||||
// asset acc_balance = db().get_balance( op.payer, d.get_sidechain_asset_id() );
|
||||
// FC_ASSERT( acc_balance.amount.value >= op.amount );
|
||||
|
||||
return void_result();
|
||||
}
|
||||
|
||||
object_id_type withdraw_pbtc_evaluator::do_apply(const withdraw_pbtc_operation& op)
|
||||
{
|
||||
database& d = db();
|
||||
|
||||
auto id = d.create<info_for_vout_object>( [&]( info_for_vout_object& obj ) {
|
||||
obj.payer = op.payer;
|
||||
obj.addr_type = type;
|
||||
obj.data = op.data;
|
||||
obj.amount = op.amount;
|
||||
} ).get_id();
|
||||
|
||||
reserve_issue( op );
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void withdraw_pbtc_evaluator::reserve_issue( const withdraw_pbtc_operation& op )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool withdraw_pbtc_evaluator::check_amount( const withdraw_pbtc_operation& op ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} } // namespace graphene::chain
|
||||
192
libraries/sidechain/bech32.cpp
Normal file
192
libraries/sidechain/bech32.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// 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 <sidechain/bech32.hpp>
|
||||
|
||||
// #include <bech32.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
typedef std::vector<uint8_t> 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 sidechain { 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<std::string, data> 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 sidechain::bech32
|
||||
222
libraries/sidechain/bitcoin_address.cpp
Normal file
222
libraries/sidechain/bitcoin_address.cpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#include <sidechain/bitcoin_address.hpp>
|
||||
#include <sstream>
|
||||
#include <fc/crypto/base58.hpp>
|
||||
#include <sidechain/segwit_addr.hpp>
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
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( const payment_type& type )
|
||||
{
|
||||
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<unsigned char>( hex_addr[0] ) == 0x00 ||
|
||||
static_cast<unsigned char>( 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<unsigned char>( hex_addr[0] ) == 0x05 ||
|
||||
static_cast<unsigned char>( 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[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[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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
#include <sidechain/btc_multisig_address.hpp>
|
||||
#include <sstream>
|
||||
#include <fc/crypto/base58.hpp>
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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[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[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<char> 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 accounts_keys& 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<char>(hash160.data(), hash160.data() + hash160.data_size() );
|
||||
base58_address = fc::to_base58( get_address_bytes( segwit_address ) );
|
||||
}
|
||||
|
||||
std::vector<char> btc_multisig_segwit_address::get_address_bytes( const std::vector<char>& script_hash )
|
||||
{
|
||||
std::vector<char> 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;
|
||||
}
|
||||
|
||||
}
|
||||
24
libraries/sidechain/include/sidechain/bech32.hpp
Normal file
24
libraries/sidechain/include/sidechain/bech32.hpp
Normal file
|
|
@ -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 <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t>& values);
|
||||
|
||||
/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
|
||||
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);
|
||||
|
||||
} } // namespace sidechain::bech32
|
||||
128
libraries/sidechain/include/sidechain/bitcoin_address.hpp
Normal file
128
libraries/sidechain/include/sidechain/bitcoin_address.hpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
#include <sidechain/types.hpp>
|
||||
#include <sidechain/utils.hpp>
|
||||
|
||||
using namespace graphene::chain;
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
const bytes op = {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( type ) ) {}
|
||||
|
||||
payment_type get_type() const { return type; }
|
||||
|
||||
std::string get_address() const { return address; }
|
||||
|
||||
bytes get_raw_address() const { return raw_address; }
|
||||
|
||||
private:
|
||||
|
||||
enum size_segwit_address { P2WSH = 32, P2WPKH = 20 };
|
||||
|
||||
payment_type determine_type();
|
||||
|
||||
bytes determine_raw_address( const payment_type& type );
|
||||
|
||||
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; }
|
||||
|
||||
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) );
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
#include <sidechain/utils.hpp>
|
||||
|
||||
using namespace graphene::chain;
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
const std::vector<char> op = {0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f}; // OP_1 - OP_15
|
||||
typedef std::map< account_id_type, public_key_type > accounts_keys;
|
||||
|
||||
class btc_multisig_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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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<char> get_address_bytes( const std::vector<char>& 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) );
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
|
||||
#include <sidechain/types.hpp>
|
||||
#include <sidechain/thread_safe_index.hpp>
|
||||
#include <fc/crypto/sha256.hpp>
|
||||
|
||||
|
|
@ -32,7 +30,7 @@ struct info_for_vin
|
|||
{
|
||||
info_for_vin() = default;
|
||||
|
||||
info_for_vin( const prev_out& _out, const std::string& _address, std::vector<char> _script = std::vector<char>() ) :
|
||||
info_for_vin( const prev_out& _out, const std::string& _address, bytes _script = bytes() ) :
|
||||
id( count_id_info_for_vin++ ), out( _out ), address( _address ), script( _script ) {
|
||||
identifier = fc::sha256::hash( out.hash_tx + std::to_string( out.n_vout ) );
|
||||
}
|
||||
|
|
@ -48,7 +46,7 @@ struct info_for_vin
|
|||
|
||||
prev_out out;
|
||||
std::string address;
|
||||
std::vector<char> script;
|
||||
bytes script;
|
||||
|
||||
bool created = false;
|
||||
};
|
||||
|
|
@ -77,7 +75,7 @@ public:
|
|||
input_withdrawal_info( graphene::chain::database& _db ) : db( _db ) {}
|
||||
|
||||
|
||||
void insert_info_for_vin( const prev_out& out, const std::string& address, std::vector<char> script = std::vector<char>() );
|
||||
void insert_info_for_vin( const prev_out& out, const std::string& address, bytes script = bytes() );
|
||||
|
||||
void modify_info_for_vin( const info_for_vin& obj, const std::function<void( info_for_vin& e )>& func );
|
||||
|
||||
|
|
@ -92,7 +90,7 @@ public:
|
|||
std::vector<info_for_vin> get_info_for_vins();
|
||||
|
||||
|
||||
void insert_info_for_vout( const graphene::chain::account_id_type& payer, /*ayment_type addr_type,*/ const std::string& data, const uint64_t& amount );
|
||||
void insert_info_for_vout( const graphene::chain::account_id_type& payer, const payment_type addr_type, const std::string& data, const uint64_t& amount );
|
||||
|
||||
void mark_as_used_vout( const graphene::chain::info_for_vout_object& obj );
|
||||
|
||||
|
|
|
|||
34
libraries/sidechain/include/sidechain/segwit_addr.hpp
Normal file
34
libraries/sidechain/include/sidechain/segwit_addr.hpp
Normal file
|
|
@ -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 <stdint.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace sidechain { namespace segwit_addr {
|
||||
|
||||
/** Decode a SegWit address. Returns (witver, witprog). witver = -1 means failure. */
|
||||
std::pair<int, std::vector<uint8_t> > 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<uint8_t>& witprog);
|
||||
|
||||
} }
|
||||
28
libraries/sidechain/include/sidechain/types.hpp
Normal file
28
libraries/sidechain/include/sidechain/types.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fc/reflect/reflect.hpp>
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
using bytes = std::vector<char>;
|
||||
using accounts_keys = std::map< graphene::chain::account_id_type, graphene::chain::public_key_type >;
|
||||
|
||||
enum class payment_type {
|
||||
NULLDATA,
|
||||
P2PK,
|
||||
P2PKH,
|
||||
P2SH,
|
||||
P2WPKH,
|
||||
P2WSH,
|
||||
P2SH_WPKH,
|
||||
P2SH_WSH
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
FC_REFLECT_ENUM( sidechain::payment_type, (NULLDATA)(P2PK)(P2PKH)(P2SH)(P2WPKH)(P2WSH)(P2SH_WPKH)(P2SH_WSH) );
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sidechain/types.hpp>
|
||||
#include <fc/crypto/hex.hpp>
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
std::vector<char> parse_hex( const std::string& str );
|
||||
bytes parse_hex( const std::string& str );
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ bool info_for_vin::comparer::operator() ( const info_for_vin& lhs, const info_fo
|
|||
return lhs.id < rhs.id;
|
||||
}
|
||||
|
||||
void input_withdrawal_info::insert_info_for_vin( const prev_out& out, const std::string& address, std::vector<char> script )
|
||||
void input_withdrawal_info::insert_info_for_vin( const prev_out& out, const std::string& address, bytes script )
|
||||
{
|
||||
info_for_vins.insert( info_for_vin( out, address, script ) );
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ std::vector<info_for_vin> input_withdrawal_info::get_info_for_vins()
|
|||
{
|
||||
for( size_t i = 0; itr_b != itr_e && i < 5 && !itr_b->created; i++ ) { // 5 amount vins to bitcoin transaction
|
||||
info_for_vin vin;
|
||||
vin.id = itr_b->id;
|
||||
vin.identifier = itr_b->identifier;
|
||||
vin.out.hash_tx = itr_b->out.hash_tx;
|
||||
vin.out.n_vout = itr_b->out.n_vout;
|
||||
|
|
@ -64,11 +65,11 @@ std::vector<info_for_vin> input_withdrawal_info::get_info_for_vins()
|
|||
return result;
|
||||
}
|
||||
|
||||
void input_withdrawal_info::insert_info_for_vout( const graphene::chain::account_id_type& payer, /*ayment_type addr_type,*/ const std::string& data, const uint64_t& amount )
|
||||
void input_withdrawal_info::insert_info_for_vout( const graphene::chain::account_id_type& payer, const payment_type addr_type, const std::string& data, const uint64_t& amount )
|
||||
{
|
||||
db.create<graphene::chain::info_for_vout_object>([&](graphene::chain::info_for_vout_object& obj) {
|
||||
obj.payer = payer;
|
||||
// obj.addr_type = addr_type;
|
||||
obj.addr_type = addr_type;
|
||||
obj.data = data;
|
||||
obj.amount = amount;
|
||||
});
|
||||
|
|
@ -108,7 +109,7 @@ std::vector<info_for_vout> input_withdrawal_info::get_info_for_vouts()
|
|||
for(size_t i = 0; i < 5 && itr != info_for_vout_idx.end() && !itr->created; i++) {
|
||||
info_for_vout vout;
|
||||
vout.payer = itr->payer;
|
||||
// vout.addr_type = itr->addr_type;
|
||||
vout.addr_type = itr->addr_type;
|
||||
vout.data = itr->data;
|
||||
vout.amount = itr->amount;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ private:
|
|||
|
||||
void handle_block( const std::string& block_hash );
|
||||
|
||||
std::vector<info_for_vin> extract_info_from_block( const std::string& _block );
|
||||
|
||||
inline uint64_t parse_amount(std::string raw);
|
||||
|
||||
std::unique_ptr<zmq_listener> listener;
|
||||
std::unique_ptr<bitcoin_rpc_client> bitcoin_client;
|
||||
std::unique_ptr<graphene::chain::database> db;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#include <sidechain/network/sidechain_net_manager.hpp>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
namespace sidechain {
|
||||
|
||||
sidechain_net_manager::sidechain_net_manager( graphene::chain::database* _db, std::string _ip,
|
||||
|
|
@ -24,12 +27,55 @@ void sidechain_net_manager::initialize_manager( graphene::chain::database* _db,
|
|||
} );
|
||||
}
|
||||
|
||||
std::vector<info_for_vin> sidechain_net_manager::extract_info_from_block( const std::string& _block )
|
||||
{
|
||||
|
||||
// std::map<std::string, bool> test = { {"2MsmG481n4W4AC1QcPgBUJQrVRTrLNM2GpB", false},
|
||||
// {"2N2LkZG2Zp9eXGjSJzP9taR1gYdZBPzH7S7", false},
|
||||
// {"2N4MCW3XggAxs9C8Dh2vNcx7uH8zUqoLMjA", false},
|
||||
// {"2NEkNoDyQ9tyREFDAPehi9Jr2m6EFiTDxAS", false},
|
||||
// {"2N1rQcKr4F14L8dfnNiUVpc4LzcZTWN9Kpd", false} };
|
||||
|
||||
std::stringstream ss( _block );
|
||||
boost::property_tree::ptree block;
|
||||
boost::property_tree::read_json( ss, block );
|
||||
|
||||
std::vector<info_for_vin> result;
|
||||
|
||||
for (const auto& tx_child : block.get_child("tx")) {
|
||||
const auto& tx = tx_child.second;
|
||||
|
||||
for ( const auto& o : tx.get_child("vout") ) {
|
||||
const auto script = o.second.get_child("scriptPubKey");
|
||||
|
||||
if( !script.count("addresses") ) continue;
|
||||
|
||||
for (const auto& addr : script.get_child("addresses")) { // in which cases there can be more addresses?
|
||||
const auto address_base58 = addr.second.get_value<std::string>();
|
||||
|
||||
// if( !test.count( address_base58 ) ) continue; // there is such an address in graphene
|
||||
|
||||
info_for_vin vin;
|
||||
vin.out.hash_tx = tx.get_child("txid").get_value<std::string>();
|
||||
vin.out.amount = parse_amount( o.second.get_child( "value" ).get_value<std::string>() );
|
||||
vin.out.n_vout = o.second.get_child( "n" ).get_value<uint32_t>();
|
||||
vin.address = address_base58;
|
||||
result.push_back( vin );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void sidechain_net_manager::update_tx_infos( const std::string& block_hash )
|
||||
{
|
||||
std::string block = bitcoin_client->receive_full_block( block_hash );
|
||||
if( block != "" ) {
|
||||
|
||||
const auto& vins = extract_info_from_block( block );
|
||||
for( const auto& v : vins ) {
|
||||
db->i_w_info.insert_info_for_vin( prev_out{ v.out.hash_tx, v.out.n_vout, v.out.amount }, v.address );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,4 +106,10 @@ void sidechain_net_manager::handle_block( const std::string& block_hash )
|
|||
update_tx_infos( block_hash );
|
||||
}
|
||||
|
||||
// Removes dot from amount output: "50.00000000"
|
||||
inline uint64_t sidechain_net_manager::parse_amount(std::string raw) {
|
||||
raw.erase(std::remove(raw.begin(), raw.end(), '.'), raw.end());
|
||||
return std::stoll(raw);
|
||||
}
|
||||
|
||||
}
|
||||
81
libraries/sidechain/segwit_addr.cpp
Normal file
81
libraries/sidechain/segwit_addr.cpp
Normal file
|
|
@ -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 <sidechain/segwit_addr.hpp>
|
||||
#include <sidechain/bech32.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
typedef std::vector<uint8_t> data;
|
||||
|
||||
/** Convert from one power-of-2 number base to another. */
|
||||
template<int frombits, int tobits, bool pad>
|
||||
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<int, data> decode(const std::string& hrp, const std::string& addr) {
|
||||
std::pair<std::string, data> 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;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace sidechain {
|
||||
|
||||
std::vector<char> parse_hex( const std::string& str )
|
||||
bytes parse_hex( const std::string& str )
|
||||
{
|
||||
std::vector<char> vec( str.size() / 2 );
|
||||
bytes vec( str.size() / 2 );
|
||||
fc::from_hex( str, vec.data(), vec.size() );
|
||||
return vec;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -767,6 +767,8 @@ class wallet_api
|
|||
string memo,
|
||||
bool broadcast = false);
|
||||
|
||||
signed_transaction withdraw_pBTC(account_id_type payer, string to, uint64_t amount, bool broadcast = false);
|
||||
|
||||
/**
|
||||
* This method works just like transfer, except it always broadcasts and
|
||||
* returns the transaction ID along with the signed transaction.
|
||||
|
|
@ -2057,4 +2059,5 @@ FC_API( graphene::wallet::wallet_api,
|
|||
(get_binned_order_book)
|
||||
(get_matched_bets_for_bettor)
|
||||
(get_all_matched_bets_for_bettor)
|
||||
(withdraw_pBTC)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2392,6 +2392,23 @@ public:
|
|||
return sign_transaction(tx, broadcast);
|
||||
} FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) }
|
||||
|
||||
signed_transaction withdraw_pBTC(account_id_type payer, string to, uint64_t amount, bool broadcast)
|
||||
{ try {
|
||||
FC_ASSERT( !is_locked() );
|
||||
|
||||
withdraw_pbtc_operation withdraw_pbtc_op;
|
||||
withdraw_pbtc_op.payer = payer;
|
||||
withdraw_pbtc_op.data = to;
|
||||
withdraw_pbtc_op.amount = amount;
|
||||
|
||||
signed_transaction tx;
|
||||
tx.operations.push_back(withdraw_pbtc_op);
|
||||
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
|
||||
tx.validate();
|
||||
|
||||
return sign_transaction(tx, broadcast);
|
||||
} FC_CAPTURE_AND_RETHROW( (payer)(to)(amount)(broadcast) ) }
|
||||
|
||||
signed_transaction issue_asset(string to_account, string amount, string symbol,
|
||||
string memo, bool broadcast = false)
|
||||
{
|
||||
|
|
@ -3870,6 +3887,12 @@ signed_transaction wallet_api::transfer(string from, string to, string amount,
|
|||
{
|
||||
return my->transfer(from, to, amount, asset_symbol, memo, broadcast);
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::withdraw_pBTC(account_id_type payer, string to, uint64_t amount, bool broadcast)
|
||||
{
|
||||
return my->withdraw_pBTC(payer, to, amount, broadcast);
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::create_asset(string issuer,
|
||||
string symbol,
|
||||
uint8_t precision,
|
||||
|
|
|
|||
29
tests/tests/bitcoin_address_obj_tests.cpp
Normal file
29
tests/tests/bitcoin_address_obj_tests.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
#include "../common/database_fixture.hpp"
|
||||
|
||||
#include <fc/crypto/digest.hpp>
|
||||
|
||||
#include <graphene/chain/bitcoin_address_object.hpp>
|
||||
|
||||
using namespace graphene::chain;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( bitcoin_addresses_obj_tests, database_fixture )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( create_bitcoin_address_test ) {
|
||||
transaction_evaluation_state context(&db);
|
||||
|
||||
bitcoin_address_create_operation op;
|
||||
op.payer = account_id_type();
|
||||
op.owner = account_id_type();
|
||||
|
||||
const auto& idx = db.get_index_type<bitcoin_address_index>().indices().get< by_id >();
|
||||
|
||||
BOOST_CHECK( idx.size() == 0 );
|
||||
|
||||
db.apply_operation( context, op );
|
||||
|
||||
auto btc_address = idx.begin();
|
||||
BOOST_CHECK(btc_address->count_invalid_pub_key == 1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
132
tests/tests/bitcoin_address_tests.cpp
Normal file
132
tests/tests/bitcoin_address_tests.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
#include <sidechain/bitcoin_address.hpp>
|
||||
|
||||
using namespace sidechain;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( bitcoin_address_tests )
|
||||
|
||||
fc::ecc::public_key_data create_public_key_data( const std::vector<char>& 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 );
|
||||
}
|
||||
|
||||
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<char> public_key1 = parse_hex( "03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010" );
|
||||
std::vector<char> public_key2 = parse_hex( "0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983" );
|
||||
std::vector<char> public_key3 = parse_hex( "033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f" );
|
||||
|
||||
std::vector<char> address = parse_hex( "a91460cb986f0926e7c4ca1984ca9f56767da2af031e87" );
|
||||
std::vector<char> 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_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.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<char> public_key1 = parse_hex( "03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb" );
|
||||
std::vector<char> public_key2 = parse_hex( "03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6" );
|
||||
std::vector<char> public_key3 = parse_hex( "033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1" );
|
||||
|
||||
std::vector<char> 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_address() == "2NGU4ogScHEHEpReUzi9RB2ha58KAFnkFyk" );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
#include <fc/crypto/digest.hpp>
|
||||
#include <sidechain/btc_multisig_address.hpp>
|
||||
|
||||
using namespace sidechain;
|
||||
|
||||
fc::ecc::public_key_data create_public_key_data( const std::vector<char>& 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<char> public_key1 = parse_hex( "03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010" );
|
||||
std::vector<char> public_key2 = parse_hex( "0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983" );
|
||||
std::vector<char> public_key3 = parse_hex( "033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f" );
|
||||
|
||||
std::vector<char> address = parse_hex( "a91460cb986f0926e7c4ca1984ca9f56767da2af031e87" );
|
||||
std::vector<char> 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<char> public_key1 = parse_hex( "03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb" );
|
||||
std::vector<char> public_key2 = parse_hex( "03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6" );
|
||||
std::vector<char> public_key3 = parse_hex( "033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1" );
|
||||
|
||||
std::vector<char> 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()
|
||||
|
|
@ -1,47 +1,49 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
#include <sidechain/input_withdrawal_info.hpp>
|
||||
#include "../common/database_fixture.hpp"
|
||||
#include <sidechain/types.hpp>
|
||||
|
||||
using namespace graphene::chain;
|
||||
using namespace sidechain;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( input_withdrawal_info_tests, database_fixture )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_insert_vin_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
sidechain::prev_out out = { "1", 1, 13 };
|
||||
input_withdrawal_info infos( db );
|
||||
prev_out out = { "1", 1, 13 };
|
||||
infos.insert_info_for_vin( out, "addr1", { 0x01, 0x02, 0x03 } );
|
||||
BOOST_CHECK( infos.size_info_for_vins() == 1 );
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_many_insert_vin_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 1; i <= 10; i++ ) {
|
||||
sidechain::prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } );
|
||||
}
|
||||
BOOST_CHECK( infos.size_info_for_vins() == 10 );
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_id_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
sidechain::prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } );
|
||||
BOOST_CHECK( infos.find_info_for_vin( static_cast< uint64_t >( i ) ).first );
|
||||
BOOST_CHECK( infos.find_info_for_vin( static_cast< uint64_t >( i ) ).second->id == i );
|
||||
}
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_check_data_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
sidechain::prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } );
|
||||
}
|
||||
|
||||
|
|
@ -56,13 +58,13 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_check_data_test ) {
|
|||
BOOST_CHECK( infos.find_info_for_vin( static_cast< uint64_t >( i ) ).second->script == script );
|
||||
}
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_modify_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
sidechain::prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } );
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_modify_test ) {
|
|||
if( i % 2 == 0 ) {
|
||||
auto iter = infos.find_info_for_vin( static_cast< uint64_t >( i ) );
|
||||
BOOST_CHECK( iter.first );
|
||||
infos.modify_info_for_vin( *iter.second, [&]( sidechain::info_for_vin& obj ) {
|
||||
infos.modify_info_for_vin( *iter.second, [&]( info_for_vin& obj ) {
|
||||
obj.out.hash_tx = std::to_string( i + 1 );
|
||||
obj.out.n_vout = i + 1;
|
||||
obj.out.amount = i + 1;
|
||||
|
|
@ -92,13 +94,13 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_modify_test ) {
|
|||
}
|
||||
}
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_remove_vin_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
sidechain::prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } );
|
||||
}
|
||||
|
||||
|
|
@ -118,13 +120,13 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_remove_vin_test ) {
|
|||
}
|
||||
}
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_get_info_for_vins_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
sidechain::prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
prev_out out = { std::to_string( i ), static_cast<uint32_t>( i ), static_cast< uint64_t >( i ) };
|
||||
infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } );
|
||||
}
|
||||
|
||||
|
|
@ -139,27 +141,27 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_get_info_for_vins_test ) {
|
|||
const auto& vins2 = infos.get_info_for_vins();
|
||||
BOOST_CHECK( vins2.size() == 3 );
|
||||
|
||||
sidechain::info_for_vin::count_id_info_for_vin = 0;
|
||||
info_for_vin::count_id_info_for_vin = 0;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_insert_vout_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
infos.insert_info_for_vout( account_id_type(), "1", 1 );
|
||||
input_withdrawal_info infos( db );
|
||||
infos.insert_info_for_vout( account_id_type(), payment_type::NULLDATA, "1", 1 );
|
||||
BOOST_CHECK( infos.size_info_for_vouts() == 1 );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_many_insert_vout_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 1; i <= 10; i++ ) {
|
||||
infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast<uint64_t>( i ) );
|
||||
infos.insert_info_for_vout( account_id_type(i), payment_type::NULLDATA, std::to_string( i ), static_cast<uint64_t>( i ) );
|
||||
}
|
||||
BOOST_CHECK( infos.size_info_for_vouts() == 10 );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_remove_vout_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast<uint64_t>( i ) );
|
||||
infos.insert_info_for_vout( account_id_type(i), payment_type::NULLDATA, std::to_string( i ), static_cast<uint64_t>( i ) );
|
||||
}
|
||||
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
|
|
@ -180,9 +182,9 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_remove_vout_test ) {
|
|||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( input_withdrawal_info_get_info_for_vouts_test ) {
|
||||
sidechain::input_withdrawal_info infos( db );
|
||||
input_withdrawal_info infos( db );
|
||||
for( size_t i = 0; i < 10; i++ ) {
|
||||
infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast<uint64_t>( i ) );
|
||||
infos.insert_info_for_vout( account_id_type(i), payment_type::NULLDATA, std::to_string( i ), static_cast<uint64_t>( i ) );
|
||||
}
|
||||
|
||||
const auto& vouts = infos.get_info_for_vouts();
|
||||
|
|
|
|||
Loading…
Reference in a new issue