mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 18:45:55 +00:00
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:
committed by
Nik Bougalis
parent
ceeb36039e
commit
9e69bd5c56
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user