Simple multisigning (RIPD-182):

With this changeset two-level multisigning is removed from the
codebase and replaced with single-level multisigning.

Additionally, SignerLists in the ledger are prepared for the
possibility of multiple SignerLists per account.  This was done
by adding a defaulted 32-bit SignerListID to each SignerList.
The SignerListIndex calculation incorporates the SignerListID.

There are three known missing elements:

 1. Multisigned transactions should require higher fees than
    regular (single-signed) transaction.  That's not yet
    implemented.

 2. It should be possible to disable the master key on an account
    if that account is multisign enabled (has a signer list).
    That's not yet implemented.

 3. Documentation about multisigning needs to be improved.

Multisigning is still compiled out of the code base.  To enable
multisigning for a stand-alone rippled, change the
RIPPLE_ENABLE_MULTI_SIGN macro (in BeastConfig.h) to "1" and
rebuild.

This commit also addresses:
 o RIPD-912: Remove multisign APIs from STObject, and
 o RIPD-944: Replace common_transactor with jtx at call sites.
This commit is contained in:
Scott Schurr
2015-06-22 10:49:03 -07:00
committed by Nik Bougalis
parent ceeb36039e
commit 9e69bd5c56
33 changed files with 851 additions and 3323 deletions

View File

@@ -22,6 +22,7 @@
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/TxFlags.h>
@@ -189,8 +190,8 @@ bool STTx::checkSign(bool allowMultiSign) const
if (allowMultiSign)
{
// 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.
// at the SigningPubKey. It it's empty we must be
// multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL (sfSigningPubKey);
sigGood = signingPubKey.empty () ?
checkMultiSign () : checkSingleSign ();
@@ -269,10 +270,10 @@ STTx::getMetaSQL (Serializer rawTxn,
bool
STTx::checkSingleSign () const
{
// We don't allow both a non-empty sfSigningPubKey and an sfMultiSigners.
// We don't allow both a non-empty sfSigningPubKey and an sfSigners.
// That would allow the transaction to be signed two ways. So if both
// fields are present the signature is invalid.
if (isFieldPresent (sfMultiSigners))
if (isFieldPresent (sfSigners))
return false;
bool ret = false;
@@ -301,22 +302,19 @@ 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))
if (!isFieldPresent (sfSigners))
return false;
STArray const& multiSigners (getFieldArray (sfMultiSigners));
STArray const& signers {getFieldArray (sfSigners)};
// 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;
}
if (signers.size() < minMultiSigners || signers.size() > 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 ());
Serializer const dataStart {startMultiSigningData (*this)};
// We also use the sfAccount field inside the loop. Get it once.
auto const txnAccountID = getAccountID (sfAccount);
@@ -326,151 +324,47 @@ STTx::checkMultiSign () const
? 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.
*/
// Signers must be in sorted order by AccountID.
AccountID lastAccountID (beast::zero);
// We use this std::set to detect this form of double-signing.
std::set<AccountID> firstLevelSigners;
// SigningFors must be in sorted order by AccountID.
AccountID lastSigningForID = zero;
// Every signature must verify or we reject the transaction.
for (auto const& signingFor : multiSigners)
for (auto const& signer : signers)
{
auto const signingForID =
signingFor.getAccountID (sfAccount);
auto const accountID = signer.getAccountID (sfAccount);
// SigningFors must be in order by account ID. No duplicates allowed.
if (lastSigningForID >= signingForID)
// The account owner may not multisign for themselves.
if (accountID == txnAccountID)
return false;
// The next SigningFor must be greater than this one.
lastSigningForID = signingForID;
// Accounts must be in order by account ID. No duplicates allowed.
if (lastAccountID >= accountID)
return false;
// If signingForID is *not* txnAccountID, then look for duplicates.
bool const directSigning = (signingForID == txnAccountID);
if (! directSigning)
// The next signature must be greater than this one.
lastAccountID = accountID;
// Verify the signature.
bool validSig = false;
try
{
if (! firstLevelSigners.insert (signingForID).second)
// This is a duplicate signer. Fail.
return false;
Serializer s = dataStart;
finishMultiSigningData (accountID, s);
RippleAddress const pubKey =
RippleAddress::createAccountPublic (
signer.getFieldVL (sfSigningPubKey));
Blob const signature = signer.getFieldVL (sfTxnSignature);
validSig = pubKey.accountPublicVerify (
s.getData(), signature, fullyCanonical);
}
STArray const& signingAccounts (
signingFor.getFieldArray (sfSigningAccounts));
// There are bounds that the number of signers must be within.
catch (...)
{
std::size_t const signingAccountsCount = signingAccounts.size ();
if ((signingAccountsCount < 1) ||
(signingAccountsCount > maxMultiSigners))
{
return false;
}
}
// SingingAccounts must be in sorted order by AccountID.
AccountID lastSigningAcctID = zero;
for (auto const& signingAcct : signingAccounts)
{
auto const signingAcctID =
signingAcct.getAccountID (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;
// We assume any problem lies with the signature.
validSig = false;
}
if (!validSig)
return false;
}
// All signatures verified.