mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
2-level transaction multi-signatures (RIPD-182):
This commit provides support for 2-level multi-signing of transactions. The ability is usually compiled out, since other aspects of multi-signing are not yet complete. Here are the missing parts: o Full support for Tickets in transactions. o Variable fees based on the number of signers, o Multiple SignerLists with access control flags on accounts, o Enable / disable operations based on access control flags, o Enable / disable all of multi-signing based on an amendment, o Integration tests, and o Documentation.
This commit is contained in:
committed by
Vinnie Falco
parent
cf1638e6de
commit
d6ef66646f
@@ -18,17 +18,18 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/Protocol.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/STArray.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
#include <beast/cxx14/memory.h> // <memory>
|
||||
#include <boost/format.hpp>
|
||||
#include <array>
|
||||
|
||||
@@ -195,15 +196,16 @@ bool STTx::checkSign () const
|
||||
{
|
||||
try
|
||||
{
|
||||
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
|
||||
? ECDSA::strict
|
||||
: ECDSA::not_strict;
|
||||
|
||||
RippleAddress n;
|
||||
n.setAccountPublic (getFieldVL (sfSigningPubKey));
|
||||
|
||||
sig_state_ = n.accountPublicVerify (getSigningData (*this),
|
||||
getFieldVL (sfTxnSignature), fullyCanonical);
|
||||
#if RIPPLE_ENABLE_MULTI_SIGN
|
||||
// Determine whether we're single- or multi-signing by looking
|
||||
// at the SigningPubKey. It it's empty we must be multi-signing.
|
||||
// Otherwise we're single-signing.
|
||||
Blob const& signingPubKey = getFieldVL (sfSigningPubKey);
|
||||
sig_state_ = signingPubKey.empty () ?
|
||||
checkMultiSign () : checkSingleSign ();
|
||||
#else
|
||||
sig_state_ = checkSingleSign ();
|
||||
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -280,6 +282,217 @@ STTx::getMetaSQL (Serializer rawTxn,
|
||||
% getSequence () % inLedger % status % rTxn % escapedMetaData);
|
||||
}
|
||||
|
||||
bool
|
||||
STTx::checkSingleSign () const
|
||||
{
|
||||
// We don't allow both a non-empty sfSigningPubKey and an sfMultiSigners.
|
||||
// That would allow the transaction to be signed two ways. So if both
|
||||
// fields are present the signature is invalid.
|
||||
if (isFieldPresent (sfMultiSigners))
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
try
|
||||
{
|
||||
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
|
||||
? ECDSA::strict
|
||||
: ECDSA::not_strict;
|
||||
|
||||
RippleAddress n;
|
||||
n.setAccountPublic (getFieldVL (sfSigningPubKey));
|
||||
|
||||
ret = n.accountPublicVerify (getSigningData (*this),
|
||||
getFieldVL (sfTxnSignature), fullyCanonical);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Assume it was a signature failure.
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
STTx::checkMultiSign () const
|
||||
{
|
||||
// Make sure the MultiSigners are present. Otherwise they are not
|
||||
// attempting multi-signing and we just have a bad SigningPubKey.
|
||||
if (!isFieldPresent (sfMultiSigners))
|
||||
return false;
|
||||
|
||||
STArray const& multiSigners (getFieldArray (sfMultiSigners));
|
||||
|
||||
// There are well known bounds that the number of signers must be within.
|
||||
{
|
||||
std::size_t const multiSignerCount = multiSigners.size ();
|
||||
if ((multiSignerCount < 1) || (multiSignerCount > maxMultiSigners))
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can ease the computational load inside the loop a bit by
|
||||
// pre-constructing part of the data that we hash. Fill a Serializer
|
||||
// with the stuff that stays constant from signature to signature.
|
||||
Serializer const dataStart (startMultiSigningData ());
|
||||
|
||||
// We also use the sfAccount field inside the loop. Get it once.
|
||||
RippleAddress const txnAccountID = getFieldAccount (sfAccount);
|
||||
|
||||
// Determine whether signatures must be full canonical.
|
||||
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
|
||||
? ECDSA::strict
|
||||
: ECDSA::not_strict;
|
||||
|
||||
// We need to detect (and reject) if a multi-signer is both signing
|
||||
// directly and using a SigningFor. Here's an example:
|
||||
//
|
||||
// {
|
||||
// ...
|
||||
// "MultiSigners": [
|
||||
// {
|
||||
// "SigningFor": {
|
||||
// "Account": "<alice>",
|
||||
// "SigningAccounts": [
|
||||
// {
|
||||
// "SigningAccount": {
|
||||
// // * becky says that becky signs for alice. *
|
||||
// "Account": "<becky>",
|
||||
// ...
|
||||
// "SigningFor": {
|
||||
// "Account": "<becky>",
|
||||
// "SigningAccounts": [
|
||||
// {
|
||||
// "SigningAccount": {
|
||||
// // * cheri says that becky signs for alice. *
|
||||
// "Account": "<cheri>",
|
||||
// ...
|
||||
// "tx_json": {
|
||||
// "Account": "<alice>",
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Why is this way of signing a problem? Alice has a signer list, and
|
||||
// Becky can show up in that list only once. By design. So if Becky
|
||||
// signs twice -- once directly and once indirectly -- we have three
|
||||
// options:
|
||||
//
|
||||
// 1. We can add Becky's weight toward Alice's quorum twice, once for
|
||||
// each signature. This seems both unexpected and counter to Alice's
|
||||
// intention.
|
||||
//
|
||||
// 2. We could allow both signatures, but only add Becky's weight
|
||||
// toward Alice's quorum once. This seems a bit better. But it allows
|
||||
// our clients to ask rippled to do more work than necessary. We
|
||||
// should also let the client know that only one of the signatures
|
||||
// was necessary.
|
||||
//
|
||||
// 3. The only way to tell the client that they have done more work
|
||||
// than necessary (and that one of the signatures will be ignored) is
|
||||
// to declare the transaction malformed. This behavior also aligns
|
||||
// well with rippled's behavior if Becky had signed directly twice:
|
||||
// the transaction would be marked as malformed.
|
||||
//
|
||||
// We use this std::set to detect this form of double-signing.
|
||||
std::set<RippleAddress> firstLevelSigners;
|
||||
|
||||
// SigningFors must be in sorted order by AccountID.
|
||||
RippleAddress lastSigningForID;
|
||||
lastSigningForID.setAccountID ("");
|
||||
|
||||
// Every signature must verify or we reject the transaction.
|
||||
for (auto const& signingFor : multiSigners)
|
||||
{
|
||||
RippleAddress const signingForID =
|
||||
signingFor.getFieldAccount (sfAccount);
|
||||
|
||||
// SigningFors must be in order by account ID. No duplicates allowed.
|
||||
if (lastSigningForID >= signingForID)
|
||||
return false;
|
||||
|
||||
// The next SigningFor must be greater than this one.
|
||||
lastSigningForID = signingForID;
|
||||
|
||||
// If signingForID is *not* txnAccountID, then look for duplicates.
|
||||
bool const directSigning = (signingForID == txnAccountID);
|
||||
if (! directSigning)
|
||||
{
|
||||
if (! firstLevelSigners.insert (signingForID).second)
|
||||
// This is a duplicate signer. Fail.
|
||||
return false;
|
||||
}
|
||||
|
||||
STArray const& signingAccounts (
|
||||
signingFor.getFieldArray (sfSigningAccounts));
|
||||
|
||||
// There are bounds that the number of signers must be within.
|
||||
{
|
||||
std::size_t const signingAccountsCount = signingAccounts.size ();
|
||||
if ((signingAccountsCount < 1) ||
|
||||
(signingAccountsCount > maxMultiSigners))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// SingingAccounts must be in sorted order by AccountID.
|
||||
RippleAddress lastSigningAcctID;
|
||||
lastSigningAcctID.setAccountID ("");
|
||||
|
||||
for (auto const& signingAcct : signingAccounts)
|
||||
{
|
||||
RippleAddress const signingAcctID =
|
||||
signingAcct.getFieldAccount (sfAccount);
|
||||
|
||||
// None of the multi-signers may sign for themselves.
|
||||
if (signingForID == signingAcctID)
|
||||
return false;
|
||||
|
||||
// Accounts must be in order by account ID. No duplicates allowed.
|
||||
if (lastSigningAcctID >= signingAcctID)
|
||||
return false;
|
||||
|
||||
// The next signature must be greater than this one.
|
||||
lastSigningAcctID = signingAcctID;
|
||||
|
||||
// If signingForID *is* txnAccountID, then look for duplicates.
|
||||
if (directSigning)
|
||||
{
|
||||
if (! firstLevelSigners.insert (signingAcctID).second)
|
||||
// This is a duplicate signer. Fail.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the signature.
|
||||
bool validSig = false;
|
||||
try
|
||||
{
|
||||
Serializer s = dataStart;
|
||||
finishMultiSigningData (signingForID, signingAcctID, s);
|
||||
|
||||
RippleAddress const pubKey =
|
||||
RippleAddress::createAccountPublic (
|
||||
signingAcct.getFieldVL (sfSigningPubKey));
|
||||
|
||||
Blob const signature =
|
||||
signingAcct.getFieldVL (sfMultiSignature);
|
||||
|
||||
validSig = pubKey.accountPublicVerify (
|
||||
s.getData(), signature, fullyCanonical);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// We assume any problem lies with the signature.
|
||||
validSig = false;
|
||||
}
|
||||
if (!validSig)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// All signatures verified.
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static
|
||||
|
||||
Reference in New Issue
Block a user