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:
Scott Schurr
2015-02-09 10:35:24 -08:00
committed by Vinnie Falco
parent cf1638e6de
commit d6ef66646f
29 changed files with 3552 additions and 59 deletions

View File

@@ -28,6 +28,17 @@ InnerObjectFormats::InnerObjectFormats ()
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSignerWeight, SOE_REQUIRED)
;
add (sfSigningFor.getJsonName ().c_str (), sfSigningFor.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSigningAccounts, SOE_REQUIRED)
;
add (sfSigningAccount.getJsonName ().c_str (), sfSigningAccount.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSigningPubKey, SOE_REQUIRED)
<< SOElement (sfMultiSignature, SOE_REQUIRED)
;
}
void InnerObjectFormats::addCommonFields (Item& item)

View File

@@ -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

View File

@@ -73,6 +73,8 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },
{ tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." },
{ tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." },
{ tefBAD_QUORUM, "tefBAD_QUORUM", "Signatures provided do not meet the quorum." },
{ tefBAD_SIGNATURE, "tefBAD_SIGNATURE", "A signature is provided for a non-signer." },
{ tefCREATED, "tefCREATED", "Can't add an already created account." },
{ tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." },
{ tefFAILURE, "tefFAILURE", "Failed to apply." },
@@ -80,8 +82,10 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ tefMASTER_DISABLED, "tefMASTER_DISABLED", "Master key is disabled." },
{ tefMAX_LEDGER, "tefMAX_LEDGER", "Ledger sequence too high." },
{ tefNO_AUTH_REQUIRED, "tefNO_AUTH_REQUIRED", "Auth is not required." },
{ tefNOT_MULTI_SIGNING, "tefNOT_MULTI_SIGNING", "Account has no appropriate list of multi-signers." },
{ tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past." },
{ tefWRONG_PRIOR, "tefWRONG_PRIOR", "This previous transaction does not match." },
{ tefBAD_AUTH_MASTER, "tefBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." },
{ telLOCAL_ERROR, "telLOCAL_ERROR", "Local failure." },
{ telBAD_DOMAIN, "telBAD_DOMAIN", "Domain too long." },
@@ -93,7 +97,6 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ temMALFORMED, "temMALFORMED", "Malformed transaction." },
{ temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." },
{ temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." },
{ temBAD_CURRENCY, "temBAD_CURRENCY", "Malformed: Bad currency." },
{ temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed: Bad expiration." },
{ temBAD_FEE, "temBAD_FEE", "Invalid fee, negative or not XRP." },

View File

@@ -112,6 +112,7 @@ void TxFormats::addCommonFields (Item& item)
<< SOElement(sfMemos, SOE_OPTIONAL)
<< SOElement(sfSigningPubKey, SOE_REQUIRED)
<< SOElement(sfTxnSignature, SOE_OPTIONAL)
<< SOElement(sfMultiSigners, SOE_OPTIONAL) // submit_multisigned
;
}