diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp index a45bf9b0..b9c21282 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp @@ -413,4 +413,45 @@ void btc_one_or_weighted_multisig_address::create_segwit_address() { address = segwit_addr::encode(hrp, 0, hash_data); } +btc_timelocked_one_or_weighted_multisig_address::btc_timelocked_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, uint32_t latency, const std::vector > &keys_data, bitcoin_address::network ntype) : + btc_one_or_weighted_multisig_address(user_key_data, keys_data, ntype), + latency_(latency) +{ + create_redeem_script(user_key_data, keys_data); + create_witness_script(); + create_segwit_address(); +} + +void btc_timelocked_one_or_weighted_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector > &keys_data) +{ + script_builder builder; + builder << user_key_data.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(latency_); + builder << op::CHECKSEQUENCEVERIFY; + builder << op::DROP; + builder << op::_1; + builder << op::ELSE; + uint32_t total_weight = 0; + builder << uint32_t(0); + for (auto &p : keys_data) { + total_weight += p.second; + builder << op::SWAP; + builder << p.first.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(p.second); + builder << op::ADD; + builder << op::ENDIF; + } + uint32_t threshold_weight = 2 * total_weight / 3; + builder << threshold_weight; + builder << op::GREATERTHANOREQUAL; + builder << op::ENDIF; + redeem_script_ = builder; + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} + }}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp index cf71fdfa..ac9c433b 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp @@ -198,7 +198,7 @@ public: return witness_script_; } -private: +protected: void create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data); void create_witness_script(); void create_segwit_address(); @@ -208,6 +208,18 @@ public: bytes witness_script_; }; +class btc_timelocked_one_or_weighted_multisig_address : public btc_one_or_weighted_multisig_address { +public: + btc_timelocked_one_or_weighted_multisig_address() = default; + btc_timelocked_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, uint32_t latency, const std::vector> &keys_data, + network network_type = network::regtest); + +private: + void create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data); + + uint32_t latency_; +}; + }}} // namespace graphene::peerplays_sidechain::bitcoin FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::bitcoin_address::network, @@ -227,3 +239,11 @@ FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_weighted_multisig FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_one_or_m_of_n_multisig_address, (graphene::peerplays_sidechain::bitcoin::bitcoin_address), (redeem_script_)(witness_script_)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_one_or_weighted_multisig_address, + (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script_)(witness_script_)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_timelocked_one_or_weighted_multisig_address, + (graphene::peerplays_sidechain::bitcoin::btc_one_or_weighted_multisig_address), + (latency_)); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp index afea853f..7b746449 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp @@ -32,6 +32,7 @@ enum class op { RETURN = 0x6a, // stack ops + DROP = 0x75, DUP = 0x76, SWAP = 0x7c, diff --git a/tests/peerplays_sidechain/bitcoin_sign_tests.cpp b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp index e6a1da34..293127f9 100644 --- a/tests/peerplays_sidechain/bitcoin_sign_tests.cpp +++ b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp @@ -384,4 +384,66 @@ BOOST_AUTO_TEST_CASE(user_sig_one_or_weighted_multisig_spend_test) { } } +BOOST_AUTO_TEST_CASE(user_sig_timelocked_one_or_weighted_multisig_spend_test) { + std::vector priv_keys; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_keys.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_keys; + for (auto &key : priv_keys) { + pub_keys.push_back(key.get_public_key()); + } + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_keys[i], i + 1)); + + fc::sha256 h = fc::sha256::hash("user", 4); + fc::ecc::private_key user_key = fc::ecc::private_key::generate_from_seed(h); + fc::ecc::public_key user_pub_key = user_key.get_public_key(); + + uint32_t latency = 1; + btc_timelocked_one_or_weighted_multisig_address addr(user_pub_key, latency, weights); + BOOST_CHECK(addr.get_address() == "bcrt1q5p7phluyzsdcvdyk3dqyv50xtw9n55kjk34l067csj6xezl4kr7qdqmcus"); + + bytes redeem_script = addr.get_redeem_script(); + + { + // this address was filled with regtest transaction + // id f4d274b0452d8fa641bae1dfdc4527ac16af65dda30f5ac80031f1f9d24e2ca0 + // output 0, 10000 satoshis + + // now send it to 2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1 + // with single user signature + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("f4d274b0452d8fa641bae1dfdc4527ac16af65dda30f5ac80031f1f9d24e2ca0"); + tx.vin[0].prevout.n = 0; + tx.vin[0].nSequence = 0x1; + + tx.vout[0].value = 9000; + bitcoin_address to_address("2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1"); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + bytes key_data(user_key.get_secret().data(), user_key.get_secret().data() + user_key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + tx.vin[0].scriptWitness.push_back(sigs[0]); + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + // this transaction was published in regtest and was accepted only 1 block later then source tx + // its id is f23f75322fc39523973eeeb734efe57c54e57e957f76e3f4c6393afd0a8bf658 + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000000101a02c4ed2f9f13100c85a0fa3dd65af16ac2745dcdfe1ba41a68f2d45b074d2f400000000000100000001282300000000000017a9140b552f4a72cb614717878b20743d9e38e618130a87024730440220376c642575139ba7856ecdc0489c4c703b39439a810af1b2bea0d984c049bd0202201fcb87b50606cadc38c4a771cabd1d2ac83e90145554e023257a07febb112c5f01fd86022102d2c1cb1575d323b6120b6e5bcc9ce5ad373e88e73e675030f1c2c5261b4dbc86ac6351b2755167007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a26800000000"); + } + +} + BOOST_AUTO_TEST_SUITE_END()