2019-04-12 06:36:43 +02:00
|
|
|
// Copyright (c) 2014-2019, The Monero Project
|
2014-07-23 15:03:52 +02:00
|
|
|
//
|
|
|
|
// All rights reserved.
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
|
|
// permitted provided that the following conditions are met:
|
|
|
|
//
|
|
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
|
|
// conditions and the following disclaimer.
|
|
|
|
//
|
|
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
|
|
// materials provided with the distribution.
|
|
|
|
//
|
|
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
|
|
// used to endorse or promote products derived from this software without specific
|
|
|
|
// prior written permission.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
//
|
|
|
|
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
2014-03-03 23:07:58 +01:00
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <boost/serialization/vector.hpp>
|
|
|
|
#include <boost/serialization/utility.hpp>
|
|
|
|
#include <boost/serialization/variant.hpp>
|
|
|
|
#include <boost/serialization/set.hpp>
|
|
|
|
#include <boost/serialization/map.hpp>
|
|
|
|
#include <boost/serialization/is_bitwise_serializable.hpp>
|
2016-06-29 19:18:18 +02:00
|
|
|
#include <boost/archive/binary_iarchive.hpp>
|
2016-12-16 10:10:03 +01:00
|
|
|
#include <boost/archive/portable_binary_iarchive.hpp>
|
|
|
|
#include <boost/archive/portable_binary_oarchive.hpp>
|
2014-03-03 23:07:58 +01:00
|
|
|
#include "cryptonote_basic.h"
|
|
|
|
#include "common/unordered_containers_boost_serialization.h"
|
2019-10-12 19:27:45 +02:00
|
|
|
#include "common/util.h"
|
2014-03-03 23:07:58 +01:00
|
|
|
#include "crypto/crypto.h"
|
2016-06-14 18:25:00 +02:00
|
|
|
#include "ringct/rctTypes.h"
|
2016-07-23 13:09:33 +02:00
|
|
|
#include "ringct/rctOps.h"
|
2014-03-03 23:07:58 +01:00
|
|
|
|
|
|
|
namespace boost
|
|
|
|
{
|
|
|
|
namespace serialization
|
|
|
|
{
|
|
|
|
|
|
|
|
//---------------------------------------------------
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::public_key &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::public_key)]>(x);
|
|
|
|
}
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::secret_key &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::secret_key)]>(x);
|
|
|
|
}
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::key_derivation &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::key_derivation)]>(x);
|
|
|
|
}
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::key_image &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::key_image)]>(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::signature &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::signature)]>(x);
|
|
|
|
}
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::hash &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::hash)]>(x);
|
|
|
|
}
|
2017-02-19 03:42:10 +01:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, crypto::hash8 &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(crypto::hash8)]>(x);
|
|
|
|
}
|
2014-03-03 23:07:58 +01:00
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.keys;
|
|
|
|
a & x.script;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txout_to_key &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.key;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txout_to_scripthash &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txin_gen &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txin_to_script &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.prev;
|
|
|
|
a & x.prevout;
|
|
|
|
a & x.sigset;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txin_to_scripthash &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.prev;
|
|
|
|
a & x.prevout;
|
|
|
|
a & x.script;
|
|
|
|
a & x.sigset;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txin_to_key &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.amount;
|
|
|
|
a & x.key_offsets;
|
|
|
|
a & x.k_image;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::tx_out &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.amount;
|
|
|
|
a & x.target;
|
|
|
|
}
|
|
|
|
|
2019-06-11 20:53:46 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txversion &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
uint16_t v = static_cast<uint16_t>(x);
|
|
|
|
a & v;
|
2019-10-12 19:27:45 +02:00
|
|
|
if (v >= tools::enum_count<cryptonote::txversion>)
|
2019-06-11 20:53:46 +02:00
|
|
|
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported tx version");
|
|
|
|
x = static_cast<cryptonote::txversion>(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::txtype &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
uint16_t txtype = static_cast<uint16_t>(x);
|
|
|
|
a & txtype;
|
2019-10-12 19:27:45 +02:00
|
|
|
if (txtype >= tools::enum_count<cryptonote::txtype>)
|
2019-06-11 20:53:46 +02:00
|
|
|
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported tx type");
|
|
|
|
x = static_cast<cryptonote::txtype>(txtype);
|
|
|
|
}
|
2014-03-03 23:07:58 +01:00
|
|
|
|
2016-08-06 20:19:25 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::transaction_prefix &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.version;
|
2019-06-11 20:53:46 +02:00
|
|
|
if (x.version >= cryptonote::txversion::v3_per_output_unlock_times)
|
2018-08-06 02:23:52 +02:00
|
|
|
{
|
|
|
|
a & x.output_unlock_times;
|
2019-06-11 20:53:46 +02:00
|
|
|
if (x.version == cryptonote::txversion::v3_per_output_unlock_times) {
|
Relax deregistration rules
The replaces the deregistration mechanism with a new state change
mechanism (beginning at the v12 fork) which can change a service node's
network status via three potential values (and is extensible in the
future to handle more):
- deregistered -- this is the same as the existing deregistration; the
SN is instantly removed from the SN list.
- decommissioned -- this is a sort of temporary deregistration: your SN
remains in the service node list, but is removed from the rewards list
and from any network duties.
- recommissioned -- this tx is sent by a quorum if they observe a
decommissioned SN sending uptime proofs again. Upon reception, the SN
is reactivated and put on the end of the reward list.
Since this is broadening the quorum use, this also renames the relevant
quorum to a "obligations" quorum (since it validates SN obligations),
while the transactions are "state_change" transactions (since they
change the state of a registered SN).
The new parameters added to service_node_rules.h control how this works:
// Service node decommissioning: as service nodes stay up they earn "credits" (measured in blocks)
// towards a future outage. A new service node starts out with INITIAL_CREDIT, and then builds up
// CREDIT_PER_DAY for each day the service node remains active up to a maximum of
// DECOMMISSION_MAX_CREDIT.
//
// If a service node stops sending uptime proofs, a quorum will consider whether the service node
// has built up enough credits (at least MINIMUM): if so, instead of submitting a deregistration,
// it instead submits a decommission. This removes the service node from the list of active
// service nodes both for rewards and for any active network duties. If the service node comes
// back online (i.e. starts sending the required performance proofs again) before the credits run
// out then a quorum will reinstate the service node using a recommission transaction, which adds
// the service node back to the bottom of the service node reward list, and resets its accumulated
// credits to 0. If it does not come back online within the required number of blocks (i.e. the
// accumulated credit at the point of decommissioning) then a quorum will send a permanent
// deregistration transaction to the network, starting a 30-day deregistration count down.
This commit currently includes values (which are not necessarily
finalized):
- 8 hours (240 blocks) of credit required for activation of a
decommission (rather than a deregister)
- 0 initial credits at registration
- a maximum of 24 hours (720 blocks) of credits
- credits accumulate at a rate that you hit 24 hours of credits after 30
days of operation.
Miscellaneous other details of this PR:
- a new TX extra tag is used for the state change (including
deregistrations). The old extra tag has no version or type tag, so
couldn't be reused. The data in the new tag is slightly more
efficiently packed than the old deregistration transaction, so it gets
used for deregistrations (starting at the v12 fork) as well.
- Correct validator/worker selection required generalizing the shuffle
function to be able to shuffle just part of a vector. This lets us
stick any down service nodes at the end of the potential list, then
select validators by only shuffling the part of the index vector that
contains active service indices. Once the validators are selected, the
remainder of the list (this time including decommissioned SN indices) is
shuffled to select quorum workers to check, thus allowing decommisioned
nodes to be randomly included in the nodes to check without being
selected as a validator.
- Swarm recalculation was not quite right: swarms were recalculated on
SN registrations, even if those registrations were include shared node
registrations, but *not* recalculated on stakes. Starting with the
upgrade this behaviour is fixed (swarms aren't actually used currently
and aren't consensus-relevant so recalculating early won't hurt
anything).
- Details on decomm/dereg are added to RPC info and print_sn/print_sn_status
- Slightly improves the % of reward output in the print_sn output by
rounding it to two digits, and reserves space in the output string to
avoid excessive reallocations.
- Adds various debugging at higher debug levels to quorum voting (into
all of voting itself, vote transmission, and vote reception).
- Reset service node list internal data structure version to 0. The SN
list has to be rescanned anyway at upgrade (its size has changed), so we
might as well reset the version and remove the version-dependent
serialization code. (Note that the affected code here is for SN states
in lmdb storage, not for SN-to-SN communication serialization).
2019-06-18 23:57:02 +02:00
|
|
|
bool is_deregister = x.type == cryptonote::txtype::state_change;
|
2019-06-11 20:53:46 +02:00
|
|
|
a & is_deregister;
|
Relax deregistration rules
The replaces the deregistration mechanism with a new state change
mechanism (beginning at the v12 fork) which can change a service node's
network status via three potential values (and is extensible in the
future to handle more):
- deregistered -- this is the same as the existing deregistration; the
SN is instantly removed from the SN list.
- decommissioned -- this is a sort of temporary deregistration: your SN
remains in the service node list, but is removed from the rewards list
and from any network duties.
- recommissioned -- this tx is sent by a quorum if they observe a
decommissioned SN sending uptime proofs again. Upon reception, the SN
is reactivated and put on the end of the reward list.
Since this is broadening the quorum use, this also renames the relevant
quorum to a "obligations" quorum (since it validates SN obligations),
while the transactions are "state_change" transactions (since they
change the state of a registered SN).
The new parameters added to service_node_rules.h control how this works:
// Service node decommissioning: as service nodes stay up they earn "credits" (measured in blocks)
// towards a future outage. A new service node starts out with INITIAL_CREDIT, and then builds up
// CREDIT_PER_DAY for each day the service node remains active up to a maximum of
// DECOMMISSION_MAX_CREDIT.
//
// If a service node stops sending uptime proofs, a quorum will consider whether the service node
// has built up enough credits (at least MINIMUM): if so, instead of submitting a deregistration,
// it instead submits a decommission. This removes the service node from the list of active
// service nodes both for rewards and for any active network duties. If the service node comes
// back online (i.e. starts sending the required performance proofs again) before the credits run
// out then a quorum will reinstate the service node using a recommission transaction, which adds
// the service node back to the bottom of the service node reward list, and resets its accumulated
// credits to 0. If it does not come back online within the required number of blocks (i.e. the
// accumulated credit at the point of decommissioning) then a quorum will send a permanent
// deregistration transaction to the network, starting a 30-day deregistration count down.
This commit currently includes values (which are not necessarily
finalized):
- 8 hours (240 blocks) of credit required for activation of a
decommission (rather than a deregister)
- 0 initial credits at registration
- a maximum of 24 hours (720 blocks) of credits
- credits accumulate at a rate that you hit 24 hours of credits after 30
days of operation.
Miscellaneous other details of this PR:
- a new TX extra tag is used for the state change (including
deregistrations). The old extra tag has no version or type tag, so
couldn't be reused. The data in the new tag is slightly more
efficiently packed than the old deregistration transaction, so it gets
used for deregistrations (starting at the v12 fork) as well.
- Correct validator/worker selection required generalizing the shuffle
function to be able to shuffle just part of a vector. This lets us
stick any down service nodes at the end of the potential list, then
select validators by only shuffling the part of the index vector that
contains active service indices. Once the validators are selected, the
remainder of the list (this time including decommissioned SN indices) is
shuffled to select quorum workers to check, thus allowing decommisioned
nodes to be randomly included in the nodes to check without being
selected as a validator.
- Swarm recalculation was not quite right: swarms were recalculated on
SN registrations, even if those registrations were include shared node
registrations, but *not* recalculated on stakes. Starting with the
upgrade this behaviour is fixed (swarms aren't actually used currently
and aren't consensus-relevant so recalculating early won't hurt
anything).
- Details on decomm/dereg are added to RPC info and print_sn/print_sn_status
- Slightly improves the % of reward output in the print_sn output by
rounding it to two digits, and reserves space in the output string to
avoid excessive reallocations.
- Adds various debugging at higher debug levels to quorum voting (into
all of voting itself, vote transmission, and vote reception).
- Reset service node list internal data structure version to 0. The SN
list has to be rescanned anyway at upgrade (its size has changed), so we
might as well reset the version and remove the version-dependent
serialization code. (Note that the affected code here is for SN states
in lmdb storage, not for SN-to-SN communication serialization).
2019-06-18 23:57:02 +02:00
|
|
|
x.type = is_deregister ? cryptonote::txtype::state_change : cryptonote::txtype::standard;
|
2019-06-11 20:53:46 +02:00
|
|
|
}
|
2018-08-06 02:23:52 +02:00
|
|
|
}
|
2016-08-06 20:19:25 +02:00
|
|
|
a & x.unlock_time;
|
|
|
|
a & x.vin;
|
|
|
|
a & x.vout;
|
|
|
|
a & x.extra;
|
2019-06-11 20:53:46 +02:00
|
|
|
if (x.version >= cryptonote::txversion::v4_tx_types)
|
2019-01-25 04:15:52 +01:00
|
|
|
a & x.type;
|
2016-08-06 20:19:25 +02:00
|
|
|
}
|
|
|
|
|
2014-03-03 23:07:58 +01:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
2019-06-11 20:53:46 +02:00
|
|
|
serialize(a, static_cast<cryptonote::transaction_prefix &>(x), ver);
|
|
|
|
if (x.version == cryptonote::txversion::v1)
|
2016-09-14 21:23:06 +02:00
|
|
|
{
|
2016-06-16 00:37:13 +02:00
|
|
|
a & x.signatures;
|
2016-09-14 21:23:06 +02:00
|
|
|
}
|
2016-06-16 00:37:13 +02:00
|
|
|
else
|
2016-09-14 21:23:06 +02:00
|
|
|
{
|
|
|
|
a & (rct::rctSigBase&)x.rct_signatures;
|
|
|
|
if (x.rct_signatures.type != rct::RCTTypeNull)
|
|
|
|
a & x.rct_signatures.p;
|
|
|
|
}
|
2014-03-03 23:07:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, cryptonote::block &b, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & b.major_version;
|
|
|
|
a & b.minor_version;
|
|
|
|
a & b.timestamp;
|
|
|
|
a & b.prev_id;
|
|
|
|
a & b.nonce;
|
|
|
|
//------------------
|
|
|
|
a & b.miner_tx;
|
|
|
|
a & b.tx_hashes;
|
|
|
|
}
|
2016-06-14 18:25:00 +02:00
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::key &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & reinterpret_cast<char (&)[sizeof(rct::key)]>(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::ctkey &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.dest;
|
|
|
|
a & x.mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::rangeSig &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.asig;
|
|
|
|
a & x.Ci;
|
|
|
|
}
|
|
|
|
|
2017-12-02 09:32:39 +01:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::Bulletproof &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.V;
|
|
|
|
a & x.A;
|
|
|
|
a & x.S;
|
|
|
|
a & x.T1;
|
|
|
|
a & x.T2;
|
|
|
|
a & x.taux;
|
|
|
|
a & x.mu;
|
|
|
|
a & x.L;
|
|
|
|
a & x.R;
|
|
|
|
a & x.a;
|
|
|
|
a & x.b;
|
|
|
|
a & x.t;
|
|
|
|
}
|
|
|
|
|
2016-06-14 18:25:00 +02:00
|
|
|
template <class Archive>
|
2016-11-18 00:17:21 +01:00
|
|
|
inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver)
|
2016-06-14 18:25:00 +02:00
|
|
|
{
|
2016-11-18 00:17:21 +01:00
|
|
|
a & x.s0;
|
|
|
|
a & x.s1;
|
|
|
|
a & x.ee;
|
2016-06-14 18:25:00 +02:00
|
|
|
}
|
|
|
|
|
2016-08-08 13:54:00 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::mgSig &x, const boost::serialization::version_type ver)
|
2016-06-14 18:25:00 +02:00
|
|
|
{
|
|
|
|
a & x.ss;
|
|
|
|
a & x.cc;
|
2016-08-08 13:54:00 +02:00
|
|
|
// a & x.II; // not serialized, we can recover it from the tx vin
|
2016-06-14 18:25:00 +02:00
|
|
|
}
|
|
|
|
|
2019-06-09 15:02:16 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::clsag &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.s;
|
|
|
|
a & x.c1;
|
|
|
|
// a & x.I; // not serialized, we can recover it from the tx vin
|
|
|
|
a & x.D;
|
|
|
|
}
|
|
|
|
|
2016-06-14 18:25:00 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
2019-02-03 11:29:36 +01:00
|
|
|
a & x.mask;
|
|
|
|
a & x.amount;
|
2016-06-14 18:25:00 +02:00
|
|
|
}
|
|
|
|
|
Add N/N multisig tx generation and signing
Scheme by luigi1111:
Multisig for RingCT on Monero
2 of 2
User A (coordinator):
Spendkey b,B
Viewkey a,A (shared)
User B:
Spendkey c,C
Viewkey a,A (shared)
Public Address: C+B, A
Both have their own watch only wallet via C+B, a
A will coordinate spending process (though B could easily as well, coordinator is more needed for more participants)
A and B watch for incoming outputs
B creates "half" key images for discovered output D:
I2_D = (Hs(aR)+c) * Hp(D)
B also creates 1.5 random keypairs (one scalar and 2 pubkeys; one on base G and one on base Hp(D)) for each output, storing the scalar(k) (linked to D),
and sending the pubkeys with I2_D.
A also creates "half" key images:
I1_D = (Hs(aR)+b) * Hp(D)
Then I_D = I1_D + I2_D
Having I_D allows A to check spent status of course, but more importantly allows A to actually build a transaction prefix (and thus transaction).
A builds the transaction until most of the way through MLSAG_Gen, adding the 2 pubkeys (per input) provided with I2_D
to his own generated ones where they are needed (secret row L, R).
At this point, A has a mostly completed transaction (but with an invalid/incomplete signature). A sends over the tx and includes r,
which allows B (with the recipient's address) to verify the destination and amount (by reconstructing the stealth address and decoding ecdhInfo).
B then finishes the signature by computing ss[secret_index][0] = ss[secret_index][0] + k - cc[secret_index]*c (secret indices need to be passed as well).
B can then broadcast the tx, or send it back to A for broadcasting. Once B has completed the signing (and verified the tx to be valid), he can add the full I_D
to his cache, allowing him to verify spent status as well.
NOTE:
A and B *must* present key A and B to each other with a valid signature proving they know a and b respectively.
Otherwise, trickery like the following becomes possible:
A creates viewkey a,A, spendkey b,B, and sends a,A,B to B.
B creates a fake key C = zG - B. B sends C back to A.
The combined spendkey C+B then equals zG, allowing B to spend funds at any time!
The signature fixes this, because B does not know a c corresponding to C (and thus can't produce a signature).
2 of 3
User A (coordinator)
Shared viewkey a,A
"spendkey" j,J
User B
"spendkey" k,K
User C
"spendkey" m,M
A collects K and M from B and C
B collects J and M from A and C
C collects J and K from A and B
A computes N = nG, n = Hs(jK)
A computes O = oG, o = Hs(jM)
B anc C compute P = pG, p = Hs(kM) || Hs(mK)
B and C can also compute N and O respectively if they wish to be able to coordinate
Address: N+O+P, A
The rest follows as above. The coordinator possesses 2 of 3 needed keys; he can get the other
needed part of the signature/key images from either of the other two.
Alternatively, if secure communication exists between parties:
A gives j to B
B gives k to C
C gives m to A
Address: J+K+M, A
3 of 3
Identical to 2 of 2, except the coordinator must collect the key images from both of the others.
The transaction must also be passed an additional hop: A -> B -> C (or A -> C -> B), who can then broadcast it
or send it back to A.
N-1 of N
Generally the same as 2 of 3, except participants need to be arranged in a ring to pass their keys around
(using either the secure or insecure method).
For example (ignoring viewkey so letters line up):
[4 of 5]
User: spendkey
A: a
B: b
C: c
D: d
E: e
a -> B, b -> C, c -> D, d -> E, e -> A
Order of signing does not matter, it just must reach n-1 users. A "remaining keys" list must be passed around with
the transaction so the signers know if they should use 1 or both keys.
Collecting key image parts becomes a little messy, but basically every wallet sends over both of their parts with a tag for each.
Thia way the coordinating wallet can keep track of which images have been added and which wallet they come from. Reasoning:
1. The key images must be added only once (coordinator will get key images for key a from both A and B, he must add only one to get the proper key actual key image)
2. The coordinator must keep track of which helper pubkeys came from which wallet (discussed in 2 of 2 section). The coordinator
must choose only one set to use, then include his choice in the "remaining keys" list so the other wallets know which of their keys to use.
You can generalize it further to N-2 of N or even M of N, but I'm not sure there's legitimate demand to justify the complexity. It might
also be straightforward enough to support with minimal changes from N-1 format.
You basically just give each user additional keys for each additional "-1" you desire. N-2 would be 3 keys per user, N-3 4 keys, etc.
The process is somewhat cumbersome:
To create a N/N multisig wallet:
- each participant creates a normal wallet
- each participant runs "prepare_multisig", and sends the resulting string to every other participant
- each participant runs "make_multisig N A B C D...", with N being the threshold and A B C D... being the strings received from other participants (the threshold must currently equal N)
As txes are received, participants' wallets will need to synchronize so that those new outputs may be spent:
- each participant runs "export_multisig FILENAME", and sends the FILENAME file to every other participant
- each participant runs "import_multisig A B C D...", with A B C D... being the filenames received from other participants
Then, a transaction may be initiated:
- one of the participants runs "transfer ADDRESS AMOUNT"
- this partly signed transaction will be written to the "multisig_monero_tx" file
- the initiator sends this file to another participant
- that other participant runs "sign_multisig multisig_monero_tx"
- the resulting transaction is written to the "multisig_monero_tx" file again
- if the threshold was not reached, the file must be sent to another participant, until enough have signed
- the last participant to sign runs "submit_multisig multisig_monero_tx" to relay the transaction to the Monero network
2017-06-03 23:34:26 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::multisig_kLRki &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.k;
|
|
|
|
a & x.L;
|
|
|
|
a & x.R;
|
|
|
|
a & x.ki;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.c;
|
2019-06-09 15:02:16 +02:00
|
|
|
if (ver < 1)
|
|
|
|
return;
|
|
|
|
a & x.mu_p;
|
Add N/N multisig tx generation and signing
Scheme by luigi1111:
Multisig for RingCT on Monero
2 of 2
User A (coordinator):
Spendkey b,B
Viewkey a,A (shared)
User B:
Spendkey c,C
Viewkey a,A (shared)
Public Address: C+B, A
Both have their own watch only wallet via C+B, a
A will coordinate spending process (though B could easily as well, coordinator is more needed for more participants)
A and B watch for incoming outputs
B creates "half" key images for discovered output D:
I2_D = (Hs(aR)+c) * Hp(D)
B also creates 1.5 random keypairs (one scalar and 2 pubkeys; one on base G and one on base Hp(D)) for each output, storing the scalar(k) (linked to D),
and sending the pubkeys with I2_D.
A also creates "half" key images:
I1_D = (Hs(aR)+b) * Hp(D)
Then I_D = I1_D + I2_D
Having I_D allows A to check spent status of course, but more importantly allows A to actually build a transaction prefix (and thus transaction).
A builds the transaction until most of the way through MLSAG_Gen, adding the 2 pubkeys (per input) provided with I2_D
to his own generated ones where they are needed (secret row L, R).
At this point, A has a mostly completed transaction (but with an invalid/incomplete signature). A sends over the tx and includes r,
which allows B (with the recipient's address) to verify the destination and amount (by reconstructing the stealth address and decoding ecdhInfo).
B then finishes the signature by computing ss[secret_index][0] = ss[secret_index][0] + k - cc[secret_index]*c (secret indices need to be passed as well).
B can then broadcast the tx, or send it back to A for broadcasting. Once B has completed the signing (and verified the tx to be valid), he can add the full I_D
to his cache, allowing him to verify spent status as well.
NOTE:
A and B *must* present key A and B to each other with a valid signature proving they know a and b respectively.
Otherwise, trickery like the following becomes possible:
A creates viewkey a,A, spendkey b,B, and sends a,A,B to B.
B creates a fake key C = zG - B. B sends C back to A.
The combined spendkey C+B then equals zG, allowing B to spend funds at any time!
The signature fixes this, because B does not know a c corresponding to C (and thus can't produce a signature).
2 of 3
User A (coordinator)
Shared viewkey a,A
"spendkey" j,J
User B
"spendkey" k,K
User C
"spendkey" m,M
A collects K and M from B and C
B collects J and M from A and C
C collects J and K from A and B
A computes N = nG, n = Hs(jK)
A computes O = oG, o = Hs(jM)
B anc C compute P = pG, p = Hs(kM) || Hs(mK)
B and C can also compute N and O respectively if they wish to be able to coordinate
Address: N+O+P, A
The rest follows as above. The coordinator possesses 2 of 3 needed keys; he can get the other
needed part of the signature/key images from either of the other two.
Alternatively, if secure communication exists between parties:
A gives j to B
B gives k to C
C gives m to A
Address: J+K+M, A
3 of 3
Identical to 2 of 2, except the coordinator must collect the key images from both of the others.
The transaction must also be passed an additional hop: A -> B -> C (or A -> C -> B), who can then broadcast it
or send it back to A.
N-1 of N
Generally the same as 2 of 3, except participants need to be arranged in a ring to pass their keys around
(using either the secure or insecure method).
For example (ignoring viewkey so letters line up):
[4 of 5]
User: spendkey
A: a
B: b
C: c
D: d
E: e
a -> B, b -> C, c -> D, d -> E, e -> A
Order of signing does not matter, it just must reach n-1 users. A "remaining keys" list must be passed around with
the transaction so the signers know if they should use 1 or both keys.
Collecting key image parts becomes a little messy, but basically every wallet sends over both of their parts with a tag for each.
Thia way the coordinating wallet can keep track of which images have been added and which wallet they come from. Reasoning:
1. The key images must be added only once (coordinator will get key images for key a from both A and B, he must add only one to get the proper key actual key image)
2. The coordinator must keep track of which helper pubkeys came from which wallet (discussed in 2 of 2 section). The coordinator
must choose only one set to use, then include his choice in the "remaining keys" list so the other wallets know which of their keys to use.
You can generalize it further to N-2 of N or even M of N, but I'm not sure there's legitimate demand to justify the complexity. It might
also be straightforward enough to support with minimal changes from N-1 format.
You basically just give each user additional keys for each additional "-1" you desire. N-2 would be 3 keys per user, N-3 4 keys, etc.
The process is somewhat cumbersome:
To create a N/N multisig wallet:
- each participant creates a normal wallet
- each participant runs "prepare_multisig", and sends the resulting string to every other participant
- each participant runs "make_multisig N A B C D...", with N being the threshold and A B C D... being the strings received from other participants (the threshold must currently equal N)
As txes are received, participants' wallets will need to synchronize so that those new outputs may be spent:
- each participant runs "export_multisig FILENAME", and sends the FILENAME file to every other participant
- each participant runs "import_multisig A B C D...", with A B C D... being the filenames received from other participants
Then, a transaction may be initiated:
- one of the participants runs "transfer ADDRESS AMOUNT"
- this partly signed transaction will be written to the "multisig_monero_tx" file
- the initiator sends this file to another participant
- that other participant runs "sign_multisig multisig_monero_tx"
- the resulting transaction is written to the "multisig_monero_tx" file again
- if the threshold was not reached, the file must be sent to another participant, until enough have signed
- the last participant to sign runs "submit_multisig multisig_monero_tx" to relay the transaction to the Monero network
2017-06-03 23:34:26 +02:00
|
|
|
}
|
|
|
|
|
2016-12-16 10:10:03 +01:00
|
|
|
template <class Archive>
|
|
|
|
inline typename std::enable_if<Archive::is_loading::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver)
|
2016-07-23 13:09:33 +02:00
|
|
|
{
|
|
|
|
rct::keyV outPk;
|
|
|
|
a & outPk;
|
|
|
|
outPk_.resize(outPk.size());
|
|
|
|
for (size_t n = 0; n < outPk_.size(); ++n)
|
|
|
|
{
|
|
|
|
outPk_[n].dest = rct::identity();
|
|
|
|
outPk_[n].mask = outPk[n];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-16 10:10:03 +01:00
|
|
|
template <class Archive>
|
|
|
|
inline typename std::enable_if<Archive::is_saving::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver)
|
2016-07-23 13:09:33 +02:00
|
|
|
{
|
|
|
|
rct::keyV outPk(outPk_.size());
|
|
|
|
for (size_t n = 0; n < outPk_.size(); ++n)
|
|
|
|
outPk[n] = outPk_[n].mask;
|
|
|
|
a & outPk;
|
|
|
|
}
|
|
|
|
|
2016-06-14 18:25:00 +02:00
|
|
|
template <class Archive>
|
2016-08-09 22:34:09 +02:00
|
|
|
inline void serialize(Archive &a, rct::rctSigBase &x, const boost::serialization::version_type ver)
|
2016-06-14 18:25:00 +02:00
|
|
|
{
|
2016-08-08 14:49:42 +02:00
|
|
|
a & x.type;
|
2016-08-12 19:45:07 +02:00
|
|
|
if (x.type == rct::RCTTypeNull)
|
|
|
|
return;
|
2019-06-09 15:02:16 +02:00
|
|
|
if (!tools::equals_any(x.type, rct::RCTTypeFull, rct::RCTTypeSimple, rct::RCTTypeBulletproof, rct::RCTTypeBulletproof2, rct::RCTTypeCLSAG))
|
2016-08-08 14:49:42 +02:00
|
|
|
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
|
2016-07-09 20:30:28 +02:00
|
|
|
// a & x.message; message is not serialized, as it can be reconstructed from the tx data
|
2016-08-09 22:34:09 +02:00
|
|
|
// a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
|
2017-12-16 12:58:58 +01:00
|
|
|
if (x.type == rct::RCTTypeSimple) // moved to prunable with bulletproofs
|
2016-08-09 22:34:09 +02:00
|
|
|
a & x.pseudoOuts;
|
|
|
|
a & x.ecdhInfo;
|
|
|
|
serializeOutPk(a, x.outPk, ver);
|
|
|
|
a & x.txnFee;
|
|
|
|
}
|
|
|
|
|
2016-09-14 21:23:06 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::rctSigPrunable &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.rangeSigs;
|
2017-12-02 09:32:39 +01:00
|
|
|
if (x.rangeSigs.empty())
|
|
|
|
a & x.bulletproofs;
|
2016-09-14 21:23:06 +02:00
|
|
|
a & x.MGs;
|
2019-06-09 15:02:16 +02:00
|
|
|
if (ver >= 1u)
|
|
|
|
a & x.CLSAGs;
|
2017-12-16 12:58:58 +01:00
|
|
|
if (x.rangeSigs.empty())
|
|
|
|
a & x.pseudoOuts;
|
2016-09-14 21:23:06 +02:00
|
|
|
}
|
|
|
|
|
2016-08-09 22:34:09 +02:00
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::rctSig &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.type;
|
2016-08-12 19:45:07 +02:00
|
|
|
if (x.type == rct::RCTTypeNull)
|
|
|
|
return;
|
2019-06-09 15:02:16 +02:00
|
|
|
if (!tools::equals_any(x.type, rct::RCTTypeFull, rct::RCTTypeSimple, rct::RCTTypeBulletproof, rct::RCTTypeBulletproof2, rct::RCTTypeCLSAG))
|
2016-08-09 22:34:09 +02:00
|
|
|
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
|
|
|
|
// a & x.message; message is not serialized, as it can be reconstructed from the tx data
|
2016-07-09 20:30:28 +02:00
|
|
|
// a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
|
2017-12-16 12:58:58 +01:00
|
|
|
if (x.type == rct::RCTTypeSimple)
|
2016-07-10 13:57:22 +02:00
|
|
|
a & x.pseudoOuts;
|
2016-07-09 20:30:28 +02:00
|
|
|
a & x.ecdhInfo;
|
2016-07-23 13:09:33 +02:00
|
|
|
serializeOutPk(a, x.outPk, ver);
|
2016-07-09 20:30:28 +02:00
|
|
|
a & x.txnFee;
|
2016-08-09 22:34:09 +02:00
|
|
|
//--------------
|
|
|
|
a & x.p.rangeSigs;
|
2017-12-02 09:32:39 +01:00
|
|
|
if (x.p.rangeSigs.empty())
|
|
|
|
a & x.p.bulletproofs;
|
2016-08-09 22:34:09 +02:00
|
|
|
a & x.p.MGs;
|
2019-06-09 15:02:16 +02:00
|
|
|
if (ver >= 1u)
|
|
|
|
a & x.p.CLSAGs;
|
2020-09-17 01:21:48 +02:00
|
|
|
if (rct::is_rct_bulletproof(x.type))
|
2017-12-16 12:58:58 +01:00
|
|
|
a & x.p.pseudoOuts;
|
2016-07-09 20:30:28 +02:00
|
|
|
}
|
2019-03-13 12:32:44 +01:00
|
|
|
|
|
|
|
template <class Archive>
|
|
|
|
inline void serialize(Archive &a, rct::RCTConfig &x, const boost::serialization::version_type ver)
|
|
|
|
{
|
|
|
|
a & x.range_proof_type;
|
|
|
|
a & x.bp_version;
|
|
|
|
}
|
2014-03-03 23:07:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-09 15:02:16 +02:00
|
|
|
BOOST_CLASS_VERSION(rct::rctSigPrunable, 1)
|
|
|
|
BOOST_CLASS_VERSION(rct::rctSig, 1)
|
|
|
|
BOOST_CLASS_VERSION(rct::multisig_out, 1)
|