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

@@ -1847,12 +1847,6 @@
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h"> <ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\common_transactor.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\tests\common_transactor.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\DeliverMin.test.cpp"> <ClCompile Include="..\..\src\ripple\app\tx\tests\DeliverMin.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2592,12 +2592,6 @@
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h"> <ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
<Filter>ripple\app\tx</Filter> <Filter>ripple\app\tx</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\common_transactor.cpp">
<Filter>ripple\app\tx\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\tests\common_transactor.h">
<Filter>ripple\app\tx\tests</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\DeliverMin.test.cpp"> <ClCompile Include="..\..\src\ripple\app\tx\tests\DeliverMin.test.cpp">
<Filter>ripple\app\tx\tests</Filter> <Filter>ripple\app\tx\tests</Filter>
</ClCompile> </ClCompile>

View File

@@ -97,7 +97,7 @@ issue a transaction that consumes that ticket.
Any account can have one SignerList attached to it. A SignerList contains the Any account can have one SignerList attached to it. A SignerList contains the
following elements: following elements:
- A list of from 2 to a protocol-defined maximum of 8 signers. Each signer in the array consists of: - A list of from 1 to a protocol-defined maximum of 8 signers. Each signer in the array consists of:
- The signer's 160-bit account ID and - The signer's 160-bit account ID and
- The signer's 16-bit weight (used to calculate whether a quorum is met). - The signer's 16-bit weight (used to calculate whether a quorum is met).
- And, for the entire list, a single 32-bit quorum value. - And, for the entire list, a single 32-bit quorum value.

View File

@@ -32,6 +32,11 @@
namespace ripple { namespace ripple {
// We're prepared for there to be multiple signer lists in the future,
// but we don't need them yet. So for the time being we're manually
// setting the sfSignerListID to zero in all cases.
static std::uint32_t const defaultSignerListID_ = 0;
std::tuple<TER, std::uint32_t, std::tuple<TER, std::uint32_t,
std::vector<SignerEntries::SignerEntry>, std::vector<SignerEntries::SignerEntry>,
SetSignerList::Operation> SetSignerList::Operation>
@@ -47,17 +52,15 @@ SetSignerList::determineOperation(STTx const& tx,
bool const hasSignerEntries(tx.isFieldPresent(sfSignerEntries)); bool const hasSignerEntries(tx.isFieldPresent(sfSignerEntries));
if (quorum && hasSignerEntries) if (quorum && hasSignerEntries)
{ {
SignerEntries::Decoded signers( auto signers = SignerEntries::deserialize(tx, j, "transaction");
SignerEntries::deserialize(tx, j, "transaction"));
if (signers.ter != tesSUCCESS) if (signers.second != tesSUCCESS)
return std::make_tuple(signers.ter, return std::make_tuple(signers.second, quorum, sign, op);
quorum, sign, op);
std::sort(signers.vec.begin(), signers.vec.end()); std::sort(signers.first.begin(), signers.first.end());
// Save deserialized list for later. // Save deserialized list for later.
sign = std::move(signers.vec); sign = std::move(signers.first);
op = set; op = set;
} }
else if ((quorum == 0) && !hasSignerEntries) else if ((quorum == 0) && !hasSignerEntries)
@@ -154,11 +157,10 @@ SetSignerList::validateQuorumAndSignerEntries (
// Reject if there are too many or too few entries in the list. // Reject if there are too many or too few entries in the list.
{ {
std::size_t const signerCount = signers.size (); std::size_t const signerCount = signers.size ();
if ((signerCount < SignerEntries::minEntries) if ((signerCount < STTx::minMultiSigners)
|| (signerCount > SignerEntries::maxEntries)) || (signerCount > STTx::maxMultiSigners))
{ {
if (j.trace) j.trace << JLOG(j.trace) << "Too many or too few signers in signer list.";
"Too many or too few signers in signer list.";
return temMALFORMED; return temMALFORMED;
} }
} }
@@ -168,8 +170,7 @@ SetSignerList::validateQuorumAndSignerEntries (
if (std::adjacent_find ( if (std::adjacent_find (
signers.begin (), signers.end ()) != signers.end ()) signers.begin (), signers.end ()) != signers.end ())
{ {
if (j.trace) j.trace << JLOG(j.trace) << "Duplicate signers in signer list";
"Duplicate signers in signer list";
return temBAD_SIGNER; return temBAD_SIGNER;
} }
@@ -178,12 +179,18 @@ SetSignerList::validateQuorumAndSignerEntries (
std::uint64_t allSignersWeight (0); std::uint64_t allSignersWeight (0);
for (auto const& signer : signers) for (auto const& signer : signers)
{ {
std::uint32_t const weight = signer.weight;
if (weight <= 0)
{
JLOG(j.trace) << "Every signer must have a positive weight.";
return temBAD_WEIGHT;
}
allSignersWeight += signer.weight; allSignersWeight += signer.weight;
if (signer.account == account) if (signer.account == account)
{ {
if (j.trace) j.trace << JLOG(j.trace) << "A signer may not self reference account.";
"A signer may not self reference account.";
return temBAD_SIGNER; return temBAD_SIGNER;
} }
@@ -192,8 +199,7 @@ SetSignerList::validateQuorumAndSignerEntries (
} }
if ((quorum <= 0) || (allSignersWeight < quorum)) if ((quorum <= 0) || (allSignersWeight < quorum))
{ {
if (j.trace) j.trace << JLOG(j.trace) << "Quorum is unreachable";
"Quorum is unreachable";
return temBAD_QUORUM; return temBAD_QUORUM;
} }
return tesSUCCESS; return tesSUCCESS;
@@ -241,9 +247,8 @@ SetSignerList::replaceSignerList (uint256 const& index)
TER result = dirAdd(ctx_.view (), TER result = dirAdd(ctx_.view (),
hint, getOwnerDirIndex (account_), index, describer); hint, getOwnerDirIndex (account_), index, describer);
if (j_.trace) j_.trace << JLOG(j_.trace) << "Create signer list for account " <<
"Create signer list for account " << toBase58(account_) << ": " << transHuman (result);
account_ << ": " << transHuman (result);
if (result != tesSUCCESS) if (result != tesSUCCESS)
return result; return result;
@@ -270,7 +275,7 @@ SetSignerList::destroySignerList (uint256 const& index)
// We have to examine the current SignerList so we know how much to // We have to examine the current SignerList so we know how much to
// reduce the OwnerCount. // reduce the OwnerCount.
std::uint32_t removeFromOwnerCount = 0; std::int32_t removeFromOwnerCount = 0;
auto const k = keylet::signers(account_); auto const k = keylet::signers(account_);
SLE::pointer accountSignersList = SLE::pointer accountSignersList =
view().peek (k); view().peek (k);
@@ -278,7 +283,7 @@ SetSignerList::destroySignerList (uint256 const& index)
{ {
STArray const& actualList = STArray const& actualList =
accountSignersList->getFieldArray (sfSignerEntries); accountSignersList->getFieldArray (sfSignerEntries);
removeFromOwnerCount = ownerCountDelta (actualList.size ()); removeFromOwnerCount = ownerCountDelta (actualList.size ()) * -1;
} }
// Remove the node from the account directory. // Remove the node from the account directory.
@@ -306,6 +311,9 @@ SetSignerList::writeSignersToLedger (SLE::pointer ledgerEntry)
// Assign the quorum. // Assign the quorum.
ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_); ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);
// For now, assign the default SignerListID.
ledgerEntry->setFieldU32 (sfSignerListID, defaultSignerListID_);
// Create the SignerListArray one SignerEntry at a time. // Create the SignerListArray one SignerEntry at a time.
STArray toLedger (signers_.size ()); STArray toLedger (signers_.size ());
for (auto const& entry : signers_) for (auto const& entry : signers_)
@@ -329,23 +337,12 @@ SetSignerList::ownerCountDelta (std::size_t entryCount)
// o Accounting for the number of entries in the list. // o Accounting for the number of entries in the list.
// We can get away with that because lists are not adjusted incrementally; // We can get away with that because lists are not adjusted incrementally;
// we add or remove an entire list. // we add or remove an entire list.
// The wiki (https://wiki.ripple.com/Multisign#Fees_2) currently says
// (December 2014) the reserve should be
// Reserve * (N + 1) / 2
// That's not making sense to me right now, since I'm working in
// integral OwnerCount units. If, say, N is 4 I don't know how to return
// 4.5 units as an integer.
// //
// So, just to get started, I'm saying that: // The rule is:
// o Simply having a SignerList costs 2 OwnerCount units. // o Simply having a SignerList costs 2 OwnerCount units.
// o And each signer in the list costs 1 more OwnerCount unit. // o And each signer in the list costs 1 more OwnerCount unit.
// So, at a minimum, adding a SignerList with 2 entries costs 4 OwnerCount // So, at a minimum, adding a SignerList with 1 entry costs 3 OwnerCount
// units. A SignerList with 8 entries would cost 10 OwnerCount units. // units. A SignerList with 8 entries would cost 10 OwnerCount units.
//
// It's worth noting that once this reserve policy has gotten into the
// wild it will be very difficult to change. So think hard about what
// we want for the long term.
return 2 + entryCount; return 2 + entryCount;
} }

View File

@@ -26,22 +26,23 @@
namespace ripple { namespace ripple {
SignerEntries::Decoded std::pair<std::vector<SignerEntries::SignerEntry>, TER>
SignerEntries::deserialize ( SignerEntries::deserialize (
STObject const& obj, beast::Journal journal, std::string const& annotation) STObject const& obj, beast::Journal journal, std::string const& annotation)
{ {
Decoded s; std::pair<std::vector<SignerEntry>, TER> s;
auto& accountVec (s.vec);
accountVec.reserve (maxEntries);
if (!obj.isFieldPresent (sfSignerEntries)) if (!obj.isFieldPresent (sfSignerEntries))
{ {
if (journal.trace) journal.trace << if (journal.trace) journal.trace <<
"Malformed " << annotation << ": Need signer entry array."; "Malformed " << annotation << ": Need signer entry array.";
s.ter = temMALFORMED; s.second = temMALFORMED;
return s; return s;
} }
auto& accountVec = s.first;
accountVec.reserve (STTx::maxMultiSigners);
STArray const& sEntries (obj.getFieldArray (sfSignerEntries)); STArray const& sEntries (obj.getFieldArray (sfSignerEntries));
for (STObject const& sEntry : sEntries) for (STObject const& sEntry : sEntries)
{ {
@@ -50,7 +51,7 @@ SignerEntries::deserialize (
{ {
journal.trace << journal.trace <<
"Malformed " << annotation << ": Expected SignerEntry."; "Malformed " << annotation << ": Expected SignerEntry.";
s.ter = temMALFORMED; s.second = temMALFORMED;
return s; return s;
} }
@@ -60,7 +61,7 @@ SignerEntries::deserialize (
accountVec.emplace_back (account, weight); accountVec.emplace_back (account, weight);
} }
s.ter = tesSUCCESS; s.second = tesSUCCESS;
return s; return s;
} }

View File

@@ -56,20 +56,13 @@ public:
} }
}; };
struct Decoded
{
std::vector<SignerEntry> vec;
TER ter = temMALFORMED;
};
// Deserialize a SignerEntries array from the network or from the ledger. // Deserialize a SignerEntries array from the network or from the ledger.
static Decoded deserialize ( static
std::pair<std::vector<SignerEntry>, TER>
deserialize (
STObject const& obj, STObject const& obj,
beast::Journal journal, beast::Journal journal,
std::string const& annotation); std::string const& annotation);
static std::size_t const minEntries = 2;
static std::size_t const maxEntries = STTx::maxMultiSigners;
}; };
} // ripple } // ripple

View File

@@ -107,7 +107,7 @@ TER Transactor::payFee ()
// Only check fee is sufficient when the ledger is open. // Only check fee is sufficient when the ledger is open.
if (view().open() && saPaid < mFeeDue) if (view().open() && saPaid < mFeeDue)
{ {
j_.trace << "Insufficient fee paid: " << JLOG(j_.trace) << "Insufficient fee paid: " <<
saPaid.getText () << "/" << mFeeDue.getText (); saPaid.getText () << "/" << mFeeDue.getText ();
return telINSUF_FEE_P; return telINSUF_FEE_P;
@@ -124,7 +124,7 @@ TER Transactor::payFee ()
if (mSourceBalance < saPaid) if (mSourceBalance < saPaid)
{ {
j_.trace << "Insufficient balance:" << JLOG(j_.trace) << "Insufficient balance:" <<
" balance=" << mSourceBalance.getText () << " balance=" << mSourceBalance.getText () <<
" paid=" << saPaid.getText (); " paid=" << saPaid.getText ();
@@ -162,7 +162,7 @@ TER Transactor::checkSeq ()
{ {
if (a_seq < t_seq) if (a_seq < t_seq)
{ {
j_.trace << JLOG(j_.trace) <<
"applyTransaction: has future sequence number " << "applyTransaction: has future sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq; "a_seq=" << a_seq << " t_seq=" << t_seq;
return terPRE_SEQ; return terPRE_SEQ;
@@ -171,7 +171,7 @@ TER Transactor::checkSeq ()
if (view().txExists(tx().getTransactionID ())) if (view().txExists(tx().getTransactionID ()))
return tefALREADY; return tefALREADY;
j_.trace << "applyTransaction: has past sequence number " << JLOG(j_.trace) << "applyTransaction: has past sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq; "a_seq=" << a_seq << " t_seq=" << t_seq;
return tefPAST_SEQ; return tefPAST_SEQ;
} }
@@ -271,37 +271,33 @@ TER Transactor::checkSign ()
TER Transactor::checkSingleSign () TER Transactor::checkSingleSign ()
{ {
// VFALCO NOTE This is needlessly calculating the auto const sle = view().peek(keylet::account(account_));
// AccountID multiple times. if (! sle)
return tefFAILURE; // We really expected to find the account.
// VFALCO What if sle is nullptr?
auto const sle = view().peek(
keylet::account(account_));
// Consistency: Check signature // Consistency: Check signature
// Verify the transaction's signing public key is authorized for signing. // Verify the transaction's signing public key is authorized for signing.
if (calcAccountID(mSigningPubKey) == account_) AccountID const idFromPubKey = calcAccountID(mSigningPubKey);
if (idFromPubKey == account_)
{ {
// Authorized to continue. // Authorized to continue.
mSigMaster = true; mSigMaster = true;
if (sle->isFlag(lsfDisableMaster)) if (sle->isFlag(lsfDisableMaster))
return tefMASTER_DISABLED; return tefMASTER_DISABLED;
} }
else if (mHasAuthKey && else if (mHasAuthKey && (idFromPubKey == sle->getAccountID (sfRegularKey)))
(calcAccountID(mSigningPubKey) ==
sle->getAccountID (sfRegularKey)))
{ {
// Authorized to continue. // Authorized to continue.
} }
else if (mHasAuthKey) else if (mHasAuthKey)
{ {
j_.trace << JLOG(j_.trace) <<
"applyTransaction: Delay: Not authorized to use account."; "applyTransaction: Delay: Not authorized to use account.";
return tefBAD_AUTH; return tefBAD_AUTH;
} }
else else
{ {
j_.trace << JLOG(j_.trace) <<
"applyTransaction: Invalid: Not authorized to use account."; "applyTransaction: Invalid: Not authorized to use account.";
return tefBAD_AUTH_MASTER; return tefBAD_AUTH_MASTER;
} }
@@ -309,101 +305,60 @@ TER Transactor::checkSingleSign ()
return tesSUCCESS; return tesSUCCESS;
} }
namespace TransactorDetail TER Transactor::checkMultiSign ()
{ {
// Get mTxnAccountID's SignerList and Quorum.
struct GetSignerListResult std::shared_ptr<STLedgerEntry const> sleAccountSigners =
{ view().read (keylet::signers(account_));
TER ter = tefFAILURE;
std::uint32_t quorum = std::numeric_limits <std::uint32_t>::max ();
std::vector<SignerEntries::SignerEntry> signerEntries;
};
// We need the SignerList for every SigningFor while multi-signing.
static
GetSignerListResult
getSignerList (AccountID signingForAcctID,
ReadView const& view, beast::Journal journal)
{
GetSignerListResult ret;
auto const k = keylet::signers(signingForAcctID);
auto const accountSignersList =
view.read (k);
// If the signer list doesn't exist the account is not multi-signing. // If the signer list doesn't exist the account is not multi-signing.
if (!accountSignersList) if (!sleAccountSigners)
{ {
journal.trace << JLOG(j_.trace) <<
"applyTransaction: Invalid: Not a multi-signing account."; "applyTransaction: Invalid: Not a multi-signing account.";
ret.ter = tefNOT_MULTI_SIGNING; return tefNOT_MULTI_SIGNING;
return ret;
} }
ret.quorum = accountSignersList->getFieldU32 (sfSignerQuorum);
SignerEntries::Decoded signersOnAccountDecode = // We have plans to support multiple SignerLists in the future. The
SignerEntries::deserialize (*accountSignersList, journal, "ledger"); // presence and defaulted value of the SignerListID field will enable that.
ret.ter = signersOnAccountDecode.ter; assert (sleAccountSigners->isFieldPresent (sfSignerListID));
assert (sleAccountSigners->getFieldU32 (sfSignerListID) == 0);
if (signersOnAccountDecode.ter == tesSUCCESS) auto accountSigners =
{ SignerEntries::deserialize (*sleAccountSigners, j_, "ledger");
ret.signerEntries = std::move (signersOnAccountDecode.vec); if (accountSigners.second != tesSUCCESS)
} return accountSigners.second;
return ret;
}
struct CheckSigningAccountsResult // Get the array of transaction signers.
{ STArray const& txSigners (tx().getFieldArray (sfSigners));
TER ter = tefFAILURE;
std::uint32_t weightSum = 0;
};
// Verify that every SigningAccount is a valid SignerEntry. Sum signer weights. // Walk the accountSigners performing a variety of checks and see if
CheckSigningAccountsResult // the quorum is met.
checkSigningAccounts (
std::vector<SignerEntries::SignerEntry> signerEntries,
STArray const& signingAccounts,
ApplyContext& ctx,
beast::Journal journal)
{
CheckSigningAccountsResult ret;
// Both the signerEntries and signingAccounts are sorted by account. So // Both the multiSigners and accountSigners are sorted by account. So
// matching signerEntries to signingAccounts should be a simple // matching multi-signers to account signers should be a simple
// linear walk. *All* signers must be valid or the transaction fails. // linear walk. *All* signers must be valid or the transaction fails.
std::uint32_t weightSum = 0; std::uint32_t weightSum = 0;
auto signerEntriesItr = signerEntries.begin (); auto iter = accountSigners.first.begin ();
for (auto const& signingAccount : signingAccounts) for (auto const& txSigner : txSigners)
{ {
auto const signingAcctID = AccountID const txSignerAcctID = txSigner.getAccountID (sfAccount);
signingAccount.getAccountID(sfAccount);
// Attempt to match the SignerEntry with a SigningAccount; // Attempt to match the SignerEntry with a Signer;
while (signerEntriesItr->account < signingAcctID) while (iter->account < txSignerAcctID)
{ {
if (++signerEntriesItr == signerEntries.end ()) if (++iter == accountSigners.first.end ())
{ {
journal.trace << JLOG(j_.trace) <<
"applyTransaction: Invalid SigningAccount.Account."; "applyTransaction: Invalid SigningAccount.Account.";
ret.ter = tefBAD_SIGNATURE; return tefBAD_SIGNATURE;
return ret;
} }
} }
if (signerEntriesItr->account != signingAcctID) if (iter->account != txSignerAcctID)
{ {
// The SigningAccount is not in the SignerEntries. // The SigningAccount is not in the SignerEntries.
journal.trace << JLOG(j_.trace) <<
"applyTransaction: Invalid SigningAccount.Account."; "applyTransaction: Invalid SigningAccount.Account.";
ret.ter = tefBAD_SIGNATURE; return tefBAD_SIGNATURE;
return ret;
}
if (signerEntriesItr->weight <= 0)
{
// The SigningAccount has a weight of zero and may not sign.
journal.trace <<
"applyTransaction: SigningAccount.Account needs weight > 0.";
ret.ter = tefBAD_SIGNATURE;
return ret;
} }
// We found the SigningAccount in the list of valid signers. Now we // We found the SigningAccount in the list of valid signers. Now we
@@ -411,7 +366,7 @@ checkSigningAccounts (
// public key. // public key.
AccountID const signingAcctIDFromPubKey = AccountID const signingAcctIDFromPubKey =
calcAccountID(RippleAddress::createAccountPublic ( calcAccountID(RippleAddress::createAccountPublic (
signingAccount.getFieldVL (sfSigningPubKey))); txSigner.getFieldVL (sfSigningPubKey)));
// Verify that the signingAcctID and the signingAcctIDFromPubKey // Verify that the signingAcctID and the signingAcctIDFromPubKey
// belong together. Here is are the rules: // belong together. Here is are the rules:
@@ -438,24 +393,23 @@ checkSigningAccounts (
// In any of these cases we need to know whether the account is in // In any of these cases we need to know whether the account is in
// the ledger. Determine that now. // the ledger. Determine that now.
auto signersAccountRoot = std::shared_ptr<STLedgerEntry const> sleTxSignerRoot =
ctx.view().read (keylet::account(signingAcctID)); view().read (keylet::account(txSignerAcctID));
if (signingAcctIDFromPubKey == signingAcctID) if (signingAcctIDFromPubKey == txSignerAcctID)
{ {
// Either Phantom or Master. Phantom's automatically pass. // Either Phantom or Master. Phantoms automatically pass.
if (signersAccountRoot) if (sleTxSignerRoot)
{ {
// Master Key. Account may not have asfDisableMaster set. // Master Key. Account may not have asfDisableMaster set.
std::uint32_t const signerAccountFlags = std::uint32_t const signerAccountFlags =
signersAccountRoot->getFieldU32 (sfFlags); sleTxSignerRoot->getFieldU32 (sfFlags);
if (signerAccountFlags & lsfDisableMaster) if (signerAccountFlags & lsfDisableMaster)
{ {
journal.trace << JLOG(j_.trace) <<
"applyTransaction: MultiSignature lsfDisableMaster."; "applyTransaction: Signer:Account lsfDisableMaster.";
ret.ter = tefMASTER_DISABLED; return tefMASTER_DISABLED;
return ret;
} }
} }
} }
@@ -463,158 +417,36 @@ checkSigningAccounts (
{ {
// May be a Regular Key. Let's find out. // May be a Regular Key. Let's find out.
// Public key must hash to the account's regular key. // Public key must hash to the account's regular key.
if (!signersAccountRoot) if (!sleTxSignerRoot)
{ {
journal.trace << JLOG(j_.trace) <<
"applyTransaction: Non-phantom signer lacks account root."; "applyTransaction: Non-phantom signer lacks account root.";
ret.ter = tefBAD_SIGNATURE; return tefBAD_SIGNATURE;
return ret;
} }
if (!signersAccountRoot->isFieldPresent (sfRegularKey)) if (!sleTxSignerRoot->isFieldPresent (sfRegularKey))
{ {
journal.trace << JLOG(j_.trace) <<
"applyTransaction: Account lacks RegularKey."; "applyTransaction: Account lacks RegularKey.";
ret.ter = tefBAD_SIGNATURE; return tefBAD_SIGNATURE;
return ret;
} }
if (signingAcctIDFromPubKey != if (signingAcctIDFromPubKey !=
signersAccountRoot->getAccountID (sfRegularKey)) sleTxSignerRoot->getAccountID (sfRegularKey))
{ {
journal.trace << JLOG(j_.trace) <<
"applyTransaction: Account doesn't match RegularKey."; "applyTransaction: Account doesn't match RegularKey.";
ret.ter = tefBAD_SIGNATURE; return tefBAD_SIGNATURE;
return ret;
} }
} }
// The signer is legitimate. Add their weight toward the quorum. // The signer is legitimate. Add their weight toward the quorum.
weightSum += signerEntriesItr->weight; weightSum += iter->weight;
}
ret.weightSum = weightSum;
ret.ter = tesSUCCESS;
return ret;
}
} // namespace TransactorDetail
TER Transactor::checkMultiSign ()
{
// Get account_'s SignerList and Quorum.
using namespace TransactorDetail;
GetSignerListResult const outer =
getSignerList (account_, view(), j_);
if (outer.ter != tesSUCCESS)
return outer.ter;
// Get the actual array of transaction signers.
STArray const& multiSigners (tx().getFieldArray (sfMultiSigners));
// Walk the accountSigners performing a variety of checks and see if
// the quorum is met.
// Both the multiSigners and accountSigners are sorted by account. So
// matching multi-signers to account signers should be a simple
// linear walk. *All* signers must be valid or the transaction fails.
std::uint32_t weightSum = 0;
auto signerEntriesItr = outer.signerEntries.begin ();
for (auto const& signingFor : multiSigners)
{
auto const signingForID =
signingFor.getAccountID(sfAccount);
auto const& signingAccounts =
signingFor.getFieldArray (sfSigningAccounts);
// There are two possibilities:
// o The signers are direct multi-signers for this account.
// o The signers are signing for a multi-signer on this account.
// Handle those two cases separately.
if (signingForID == account_)
{
// The signers are direct multi-signers for this account. Results
// from these signers directly effect the quorum.
CheckSigningAccountsResult const outerSigningAccountsResult =
checkSigningAccounts (
outer.signerEntries, signingAccounts, ctx_, j_);
if (outerSigningAccountsResult.ter != tesSUCCESS)
return outerSigningAccountsResult.ter;
weightSum += outerSigningAccountsResult.weightSum;
}
else
{
// The signers are signing for a multi-signer on this account.
// Attempt to match the signingForID with a SignerEntry
while (signerEntriesItr->account < signingForID)
{
if (++signerEntriesItr == outer.signerEntries.end ())
{
j_.trace <<
"applyTransaction: Invalid SigningFor.Account.";
return tefBAD_SIGNATURE;
}
}
if (signerEntriesItr->account != signingForID)
{
// The signingForID is not in the SignerEntries.
j_.trace <<
"applyTransaction: Invalid SigningFor.Account.";
return tefBAD_SIGNATURE;
}
if (signerEntriesItr->weight <= 0)
{
// The SigningFor entry needs a weight greater than zero.
j_.trace <<
"applyTransaction: SigningFor.Account needs weight > 0.";
return tefBAD_SIGNATURE;
}
// See if the signingForID has a SignerList.
GetSignerListResult const inner =
getSignerList (signingForID, view(), j_);
if (inner.ter != tesSUCCESS)
return inner.ter;
// Results from these signers indirectly effect the quorum.
CheckSigningAccountsResult const innerSigningAccountsResult =
checkSigningAccounts (
inner.signerEntries, signingAccounts, ctx_, j_);
if (innerSigningAccountsResult.ter != tesSUCCESS)
return innerSigningAccountsResult.ter;
// There's a policy question here. If the SigningAccounts are
// all valid but fail to reach this signingFor's Quorum do we:
//
// 1. Say the signature is valid but contributes 0 toward the
// quorum?
//
// 2. Say that any SigningFor that doesn't meet quorum is an
// invalid signature and fails?
//
// The choice is not obvious to me. I'm picking 2 for now, since
// it's more restrictive. We can switch to policy 1 later without
// causing transactions that would have worked before to fail.
// -- January 2015
if (innerSigningAccountsResult.weightSum < inner.quorum)
{
j_.trace <<
"applyTransaction: Level-2 SigningFor did not make quorum.";
return tefBAD_QUORUM;
}
// This SigningFor met quorum. Add its weight.
weightSum += signerEntriesItr->weight;
}
} }
// Cannot perform transaction if quorum is not met. // Cannot perform transaction if quorum is not met.
if (weightSum < outer.quorum) if (weightSum < sleAccountSigners->getFieldU32 (sfSignerQuorum))
{ {
j_.trace << JLOG(j_.trace) <<
"applyTransaction: MultiSignature failed to meet quorum."; "applyTransaction: Signers failed to meet quorum.";
return tefBAD_QUORUM; return tefBAD_QUORUM;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,435 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/tx/tests/common_transactor.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/ledger/LedgerConsensus.h>
#include <ripple/app/ledger/LedgerTiming.h>
#include <ripple/app/ledger/tests/common_ledger.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/chrono.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/TxFlags.h>
#if 0
namespace ripple {
namespace test {
UserAccount::UserAccount (KeyType kType, std::string const& passphrase)
{
RippleAddress const seed = RippleAddress::createSeedGeneric (passphrase);
master_ = generateKeysFromSeed (kType, seed);
acctID_ = calcAccountID(master_.publicKey);
}
void UserAccount::setRegKey (
TestLedger& ledger, KeyType kType, std::string const& passphrase)
{
// Get information for the new regular key.
RippleAddress const seed = RippleAddress::createSeedGeneric(passphrase);
KeyPair regular = generateKeysFromSeed (kType, seed);
// Tell the ledger what we're up to.
STTx tx = getSetRegularKeyTx (*this,
calcAccountID(regular.publicKey));
singleSign (tx, *this);
ledger.applyGoodTransaction (tx);
// Remember what changed.
regular_ = regular;
}
void UserAccount::clrRegKey (TestLedger& ledger)
{
// Tell the ledger what we're up to.
STTx tx = getClearRegularKeyTx (*this);
singleSign (tx, *this);
ledger.applyGoodTransaction (tx);
// Remember what changed.
RippleAddress temp;
regular_.secretKey = temp;
regular_.publicKey = temp;
}
void UserAccount::disableMaster (TestLedger& ledger, bool doDisable)
{
STTx tx = getAccountSetTx (*this);
SField const& field = doDisable ? sfSetFlag : sfClearFlag;
tx.setFieldU32 (field, asfDisableMaster);
singleSign (tx, *this);
ledger.applyGoodTransaction (tx);
}
//------------------------------------------------------------------------------
TestLedger::TestLedger (
std::uint64_t startAmountDrops,
UserAccount const& master,
beast::unit_test::suite& suite)
: lastClosedLedger()
, openLedger_()
, suite_(suite)
{
// To leverage createGenesisLedger from the Ledger tests, we must match
// its interface.
TestAccount const masterAcct {master.publicKey(), master.secretKey(), 0};
std::tie (lastClosedLedger, openLedger_) =
createGenesisLedger(startAmountDrops, masterAcct);
}
std::pair<TER, bool>
TestLedger::applyTransaction (STTx const& tx, bool check)
{
// Apply the transaction to the open ledger.
auto const r = apply(
*openLedger_, tx, check ? tapNONE : tapNO_CHECK_SIGN,
getConfig(), beast::Journal{});
// Close the open ledger to see if the transaction was real committed.
//
// In part we close the open ledger so we don't have to think about the
// time sequencing of transactions. Every transaction applied by a
// call to this method gets applied individually. So this transaction
// is guaranteed to be applied before the next one.
close_and_advance(openLedger_, lastClosedLedger);
// Check for the transaction in the closed ledger.
bool const foundTx =
lastClosedLedger->txExists(tx.getTransactionID());
suite_.expect (r.second == foundTx);
return {r.first, r.second && foundTx};
}
void TestLedger::applyGoodTransaction (STTx const& tx, bool check)
{
auto ret = applyTransaction (tx, check);
suite_.expect (ret.first == tesSUCCESS);
suite_.expect (ret.second == true);
}
void TestLedger::applyBadTransaction (STTx const& tx, TER err, bool check)
{
auto ret = applyTransaction (tx, check);
suite_.expect (ret.first == err);
suite_.expect (ret.second == false);
}
void TestLedger::applyTecTransaction (STTx const& tx, TER err, bool check)
{
auto ret = applyTransaction (tx, check);
suite_.expect (ret.first == err);
suite_.expect (ret.second == true);
}
//------------------------------------------------------------------------------
MultiSig::MultiSig(UserAccount const& signingFor,
UserAccount const& signer, STTx const& tx)
: signingFor_(&signingFor)
, signer_(&signer)
, multiSig_()
{
Serializer s = tx.getMultiSigningData (
calcAccountID(signingFor.acctPublicKey()),
calcAccountID(signer.acctPublicKey()));
multiSig_ = signer.secretKey().accountPrivateSign (s.getData());
}
//------------------------------------------------------------------------------
void SignerList::injectInto (STTx& tx) const
{
// Create the SignerListArray one STObject at a time.
STArray list (list_.size ());
for (auto const& entry : list_)
{
list.emplace_back(sfSignerEntry);
STObject& obj = list.back();
obj.reserve (2);
obj.setAccountID (sfAccount, entry.acct->getID ());
obj.setFieldU16 (sfSignerWeight, entry.weight);
obj.setTypeFromSField (sfSignerEntry);
}
// Insert the SignerEntries.
tx.setFieldArray (sfSignerEntries, list);
}
//------------------------------------------------------------------------------
// Single-sign the passed transaction using acct.
void singleSign (STTx& tx, UserAccount& acct)
{
tx.setFieldVL (sfSigningPubKey, acct.publicKey().getAccountPublic ());
tx.sign (acct.secretKey ());
}
// Multi-sign the passed transaction using multiSigs.
void multiSign (STTx& tx, std::vector<MultiSig>& multiSigs)
{
// multiSigs must be sorted or the signature will fail.
std::sort(multiSigs.begin(), multiSigs.end());
insertMultiSigs(tx, multiSigs);
}
// Insert the multiSigs into tx without sorting. Allows testing error cases.
void insertMultiSigs (STTx& tx, std::vector<MultiSig> const& multiSigs)
{
// Know when to change out SigningFor containers.
AccountID prevSigningForID;
// Create the MultiSigners array one STObject at a time.
STArray multiSigners;
boost::optional <STObject> signingFor;
for (auto const& entry : multiSigs)
{
if (entry.signingForAccount() != prevSigningForID)
{
if (signingFor != boost::none)
multiSigners.push_back (std::move(signingFor.get()));
// Construct the next SigningFor object and fill it in.
prevSigningForID = entry.signingForAccount();
signingFor.emplace (sfSigningFor);
signingFor->reserve (2);
signingFor->setAccountID (sfAccount, entry.signingForAccount());
signingFor->setFieldArray (sfSigningAccounts, STArray());
}
assert(signingFor);
// Construct this SigningAccount object and fill it in.
STArray& signingAccounts = signingFor->peekFieldArray(
sfSigningAccounts);
signingAccounts.emplace_back (sfSigningAccount);
STObject& signingAccount (signingAccounts.back());
signingAccount.reserve (3);
signingAccount.setAccountID (sfAccount, entry.signingAccount());
signingAccount.setFieldVL (sfMultiSignature, entry.multiSignature());
signingAccount.setFieldVL (sfSigningPubKey, entry.signingPubKey());
}
// Remember to put in the final SigningFor object.
if (signingFor)
multiSigners.push_back (std::move(signingFor.get()));
// Inject the multiSigners into tx.
tx.setFieldArray (sfMultiSigners, multiSigners);
}
//------------------------------------------------------------------------------
// Return a transaction with an SOTemplate, sfTransactionType, sfAccount,
// sfFee, sfFlags, and sfSequence.
STTx getSeqTx (UserAccount& acct, TxType type)
{
STTx tx (type); // Sets SOTemplate and sfTransactionType.
tx.setAccountID (sfAccount, acct.getID());
tx.setFieldAmount (sfFee, STAmount (10));
tx.setFieldU32 (sfFlags, tfUniversal);
tx.setFieldU32 (sfSequence, acct.consumeSeq());
return tx;
}
// Return an unsigned AccountSet transaction.
STTx getAccountSetTx (UserAccount& acct)
{
STTx tx = getSeqTx (acct, ttACCOUNT_SET);
return tx;
}
// Return an unsigned OfferCreate transaction.
STTx getOfferCreateTx (UserAccount& acct,
STAmount const& takerGets, STAmount const& takerPays)
{
STTx tx = getSeqTx (acct, ttOFFER_CREATE);
tx.setFieldAmount (sfTakerGets, takerGets);
tx.setFieldAmount (sfTakerPays, takerPays);
return tx;
}
// Return an unsigned OfferCancel transaction.
STTx getOfferCancelTx (UserAccount& acct, std::uint32_t offerSeq)
{
STTx tx = getSeqTx (acct, ttOFFER_CANCEL);
tx.setFieldU32 (sfOfferSequence, offerSeq);
return tx;
}
// Return an unsigned transaction good for making a payment.
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops)
{
STTx tx = getSeqTx (from, ttPAYMENT);
tx.setAccountID (sfDestination, to.getID());
tx.setFieldAmount (sfAmount, amountDrops);
return tx;
}
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, STAmount const& amount)
{
STTx tx = getSeqTx (from, ttPAYMENT);
tx.setAccountID (sfDestination, to.getID());
tx.setFieldAmount (sfAmount, amount);
return tx;
}
// Return a transaction that sets a regular key
STTx getSetRegularKeyTx (UserAccount& acct, AccountID const& regKey)
{
STTx tx = getSeqTx (acct, ttREGULAR_KEY_SET);
tx.setAccountID (sfRegularKey, regKey);
return tx;
}
// Return a transaction that clears a regular key
STTx getClearRegularKeyTx (UserAccount& acct)
{
STTx tx = getSeqTx (acct, ttREGULAR_KEY_SET);
return tx;
}
// Return a SignerListSet transaction.
STTx getSignerListSetTx (
UserAccount& acct, SignerList const& signers, std::uint32_t quorum)
{
STTx tx = getSeqTx (acct, ttSIGNER_LIST_SET);
tx.setFieldU32 (sfSignerQuorum, quorum);
if (! signers.empty ())
{
signers.injectInto (tx);
}
return tx;
}
// Return a transaction that creates an un-targeted ticket.
STTx getCreateTicketTx (UserAccount& acct)
{
STTx tx = getSeqTx (acct, ttTICKET_CREATE);
return tx;
}
// Return a transaction that creates a targeted ticket.
STTx getCreateTicketTx (UserAccount& acct, UserAccount const& target)
{
STTx tx = getSeqTx (acct, ttTICKET_CREATE);
tx.setAccountID (sfTarget, target.getID());
return tx;
}
// Return a transaction that cancels a ticket.
STTx getCancelTicketTx (UserAccount& acct, uint256 const& ticketID)
{
STTx tx = getSeqTx (acct, ttTICKET_CANCEL);
tx.setFieldH256 (sfTicketID, ticketID);
return tx;
}
// Return a trust set transaction.
STTx getTrustSetTx (UserAccount& from, Issue const& issuer, int limit)
{
STTx tx = getSeqTx (from, ttTRUST_SET);
STAmount const stLimit (issuer, limit);
tx.setFieldAmount (sfLimitAmount, stLimit);
return tx;
}
//------------------------------------------------------------------------------
void payInDrops (TestLedger& ledger,
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops)
{
STTx tx = getPaymentTx (from, to, amountDrops);
singleSign (tx, from);
ledger.applyGoodTransaction (tx);
}
std::uint64_t getNativeBalance(TestLedger& ledger, UserAccount& acct)
{
return ledger.lastClosedLedger->read(
keylet::account(acct.getID()))->getFieldAmount(
sfBalance).mantissa();
}
std::uint32_t getOwnerCount(TestLedger& ledger, UserAccount& acct)
{
return ledger.lastClosedLedger->read(
keylet::account(acct.getID()))->getFieldU32(sfOwnerCount);
}
std::vector<RippleState::pointer> getRippleStates (
TestLedger& ledger, UserAccount const& acct, UserAccount const& peer)
{
std::vector <RippleState::pointer> states;
forEachItem(*ledger.openLedger(), acct.getID(),
[&states, &acct, &peer](
std::shared_ptr<SLE const> const& sleCur)
{
// See whether this SLE is a lt_RIPPLE_STATE
if (!sleCur || sleCur->getType () != ltRIPPLE_STATE)
return;
// It's a lt_RIPPLE_STATE. See if it's one we want to return.
RippleState::pointer const state (
RippleState::makeItem (acct.getID(), sleCur));
if ((state) && (state->getAccountIDPeer() == peer.getID()))
states.emplace_back (std::move (state));
});
return states;
}
std::vector <std::shared_ptr<SLE const>>
getOffersOnAccount (TestLedger& ledger, UserAccount const& acct)
{
std::vector <std::shared_ptr<SLE const>> offers;
forEachItem(*ledger.openLedger(), acct.getID(),
[&offers, &acct](
std::shared_ptr<SLE const> const& sleCur)
{
// If sleCur is an ltOFFER save it.
if (sleCur && sleCur->getType () == ltOFFER)
offers.emplace_back (sleCur);
});
return offers;
}
std::vector <std::shared_ptr<SLE const>>
getTicketsOnAccount (TestLedger& ledger, UserAccount const& acct)
{
std::vector <std::shared_ptr<SLE const>> offers;
forEachItem(*ledger.openLedger(), acct.getID(),
[&offers, &acct](
std::shared_ptr<SLE const> const& sleCur)
{
// If sleCur is an ltTICKET save it.
if (sleCur && sleCur->getType () == ltTICKET)
offers.emplace_back (sleCur);
});
return offers;
}
} // test
} // ripple
#endif

View File

@@ -1,359 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_TRANSACTORS_TESTS_COMMON_TRANSACTOR_H_INCLUDED
#define RIPPLE_APP_TRANSACTORS_TESTS_COMMON_TRANSACTOR_H_INCLUDED
#include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/RippleState.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/STAmount.h>
#include <beast/unit_test/suite.h>
#include <cstdint>
namespace ripple {
namespace test {
// Forward declare TestLedger so UserAccount can take it as a parameter.
class TestLedger;
// This class makes it easier to write unit tests that involve user accounts.
class UserAccount
{
private:
KeyPair master_;
AccountID acctID_;
KeyPair regular_;
bool useRegKey_ = false;
std::uint32_t sequence_ = 0;
public:
UserAccount () = delete;
UserAccount (UserAccount const& rhs) = default;
UserAccount& operator= (UserAccount const& rhs) = default;
UserAccount (KeyType kType, std::string const& passphrase);
AccountID const& getID () const
{
return acctID_;
}
// Sets the regular key on the account, but does not disable master.
void setRegKey (
TestLedger& ledger, KeyType kType, std::string const& passphrase);
// Removes the regular key.
void clrRegKey (TestLedger& ledger);
// Either disables or enables the master key.
void disableMaster (TestLedger& ledger, bool doDisable);
// Select to use either the regular (true) or master (false) key.
void useRegKey (bool useReg)
{
useRegKey_ = useReg;
}
std::uint32_t consumeSeq ()
{
return ++sequence_;
}
// If a transaction fails we have to back up the sequence number, since.
// the last sequence wasn't consumed.
void decrSeq ()
{
--sequence_;
}
RippleAddress const& acctPublicKey () const
{
return master_.publicKey;
}
RippleAddress const& publicKey () const
{
return useRegKey_ ? regular_.publicKey : master_.publicKey;
}
RippleAddress const& secretKey () const
{
return useRegKey_ ? regular_.secretKey : master_.secretKey;
}
};
// This class collects a bunch of the ledger shenanigans tests have to do.
class TestLedger
{
public:
std::shared_ptr<Ledger const> lastClosedLedger;
private:
Ledger::pointer openLedger_;
beast::unit_test::suite& suite_;
public:
TestLedger () = delete;
TestLedger (const TestLedger& lhs) = delete;
TestLedger (
std::uint64_t startAmountDrops,
UserAccount const& master,
beast::unit_test::suite& suite);
// Return.first : transaction's TEC
// Return.second : transaction successfully applied and in ledger.
std::pair<TER, bool> applyTransaction (STTx const& tx, bool check = true);
// Apply a transaction that we expect to succeed.
void applyGoodTransaction (STTx const& tx, bool check = true);
// Apply a transaction that we expect to fail. Pass the expected error code.
void applyBadTransaction (STTx const& tx, TER err, bool check = true);
// apply a transaction that we expect to fail but charges a fee. Pass the
// expected error code.
void applyTecTransaction (STTx const& tx, TER err, bool check = true);
// Return the current open ledger.
Ledger::pointer openLedger ()
{
return openLedger_;
}
};
// This class makes it easier to construct unit tests with SignerLists.
//
// Typically construct this class with an initializer list. Like this:
//
// UserAccount alice;
// UserAccount becky;
// SignerList s {{alice, 5}, {becky, 2}};
//
// where '5' and '2' are the weights of the accounts in the SignerList.
class SignerList
{
private:
// Our initializer list takes references that we turn into pointers.
struct InitListEntry
{
UserAccount& acct;
std::uint16_t weight;
};
struct SignerAndWeight
{
UserAccount* acct;
std::uint16_t weight;
SignerAndWeight(InitListEntry& entry)
: acct (&entry.acct)
, weight (entry.weight) { }
};
std::vector<SignerAndWeight> list_;
public:
SignerList() = default;
SignerList(SignerList const& rhs) = default;
SignerList(SignerList&& rhs) // Visual Studio 2013 won't default move ctor
: list_(std::move(rhs.list_))
{ }
SignerList(std::initializer_list<InitListEntry> args)
{
list_.reserve(args.size());
for (auto arg : args)
{
list_.emplace_back(arg);
}
}
SignerList& operator=(SignerList const& rhs) = default;
SignerList& operator=(SignerList&& rhs) // VS 2013 won't default move assign
{
list_ = std::move(rhs.list_);
return *this;
}
bool empty() const
{
return list_.empty ();
}
// Inject this SignerList into the passed transaction.
void injectInto (STTx& tx) const;
};
// This type makes it easier to write unit tests that use multi-signatures.
//
// The MultiSig type is intended to be short-lived (any user account it
// references should out-live it). It is also intended to be easily
// moved in a std::vector, so it uses pointers not references.
class MultiSig
{
UserAccount const* signingFor_;
UserAccount const* signer_;
Blob multiSig_;
public:
MultiSig() = delete;
MultiSig(MultiSig const& rhs) = default;
MultiSig(MultiSig&& rhs) // Visual Studio 2013 won't default move ctor
: signingFor_ (std::move(rhs.signingFor_))
, signer_ (std::move(rhs.signer_))
, multiSig_ (std::move(rhs.multiSig_))
{ }
MultiSig& operator=(MultiSig const& rhs) = delete;
MultiSig& operator=(MultiSig&& rhs) // VS 2013 won't default move assign
{
signingFor_ = std::move(rhs.signingFor_);
signer_ = std::move(rhs.signer_);
multiSig_ = std::move(rhs.multiSig_);
return *this;
}
MultiSig(UserAccount const& signingFor,
UserAccount const& signer, STTx const& tx);
friend bool operator==(MultiSig const& lhs, MultiSig const& rhs)
{
return ((lhs.signingFor_->getID() == rhs.signingFor_->getID()) &&
(lhs.signer_->getID() == rhs.signer_->getID()));
}
friend bool operator<(MultiSig const& lhs, MultiSig const& rhs)
{
AccountID const& lhsSigingFor = lhs.signingFor_->getID();
AccountID const& rhsSigningFor = rhs.signingFor_->getID();
if (lhsSigingFor < rhsSigningFor)
return true;
if (lhsSigingFor > rhsSigningFor)
return false;
if (lhs.signer_->getID() < rhs.signer_->getID())
return true;
return false;
}
AccountID const& signingForAccount() const
{
return signingFor_->getID();
}
AccountID const& signingAccount() const
{
return signer_->getID();
}
Blob const& multiSignature() const
{
return multiSig_;
}
Blob const& signingPubKey() const
{
return signer_->publicKey().getAccountPublic();
}
};
//------------------------------------------------------------------------------
// Single-sign the passed transaction using acct.
void singleSign (STTx& tx, UserAccount& acct);
// Multi-sign the passed transaction using multiSigs.
void multiSign (STTx& tx, std::vector<MultiSig>& multiSigs);
// Insert the multiSigs into tx without sorting. Allows testing error cases.
void insertMultiSigs (STTx& tx, std::vector<MultiSig> const& multiSigs);
//------------------------------------------------------------------------------
// Return a transaction with an SOTemplate, sfTransactionType, sfAccount,
// sfFee, sfFlags, and sfSequence.
STTx getSeqTx (UserAccount& acct, TxType type);
// Get an AccountSet transaction.
STTx getAccountSetTx (UserAccount& acct);
// Return an unsigned OfferCreate transaction.
STTx getOfferCreateTx (UserAccount& acct,
STAmount const& takerGets, STAmount const& takerPays);
// Return an unsigned OfferCancel transaction.
STTx getOfferCancelTx (UserAccount& acct, std::uint32_t offerSeq);
// Return an unsigned transaction good for making a payment in XRP.
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops);
// Return an unsigned transaction good for making a payment.
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, STAmount const& amount);
// Return a transaction that sets a regular key.
STTx getSetRegularKeyTx (UserAccount& acct, AccountID const& regKey);
// Return a transaction that clears a regular key.
STTx getClearRegularKeyTx (UserAccount& acct);
// Return a SignerListSet transaction. If the quorum is zero and signers
// is empty, then any signer list is removed from the account.
STTx getSignerListSetTx (
UserAccount& acct, SignerList const& signers, std::uint32_t quorum);
// Return a transaction that creates an un-targeted ticket.
STTx getCreateTicketTx (UserAccount& acct);
// Return a transaction that creates an targeted ticket.
STTx getCreateTicketTx (UserAccount& acct, UserAccount const& target);
// Return a transaction that cancels a ticket.
STTx getCancelTicketTx (UserAccount& acct, uint256 const& ticketID);
// Return an unsigned trust set transaction.
STTx getTrustSetTx (UserAccount& from, Issue const& issuer, int limit);
//------------------------------------------------------------------------------
// Complete the a simple Payment transaction in drops. Expected to succeed.
void payInDrops (TestLedger& ledger,
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops);
// Return the native balance on an account.
std::uint64_t getNativeBalance(TestLedger& ledger, UserAccount& acct);
// Return the owner count of an account.
std::uint32_t getOwnerCount(TestLedger& ledger, UserAccount& acct);
// Get all RippleStates between two accounts.
std::vector<RippleState::pointer> getRippleStates (
TestLedger& ledger, UserAccount const& from, UserAccount const& peer);
// Get all Offers on an account.
std::vector <std::shared_ptr<SLE const>>
getOffersOnAccount (TestLedger& ledger, UserAccount const& acct);
// Get all Tickets on an account.
std::vector <std::shared_ptr<SLE const>>
getTicketsOnAccount (TestLedger& ledger, UserAccount const& acct);
} // test
} // ripple
#endif // RIPPLE_APP_TRANSACTORS_TESTS_COMMON_TRANSACTOR_H_INCLUDED

View File

@@ -390,6 +390,7 @@ extern SF_U32 const sfClearFlag;
extern SF_U32 const sfSignerQuorum; extern SF_U32 const sfSignerQuorum;
extern SF_U32 const sfCancelAfter; extern SF_U32 const sfCancelAfter;
extern SF_U32 const sfFinishAfter; extern SF_U32 const sfFinishAfter;
extern SF_U32 const sfSignerListID;
// 64-bit integers // 64-bit integers
extern SF_U64 const sfIndexNext; extern SF_U64 const sfIndexNext;
@@ -462,9 +463,7 @@ extern SF_Blob const sfMemoData;
extern SF_Blob const sfMemoFormat; extern SF_Blob const sfMemoFormat;
// variable length (uncommon) // variable length (uncommon)
extern SF_Blob const sfMultiSignature;
extern SF_Blob const sfProof; extern SF_Blob const sfProof;
// account // account
extern SF_Account const sfAccount; extern SF_Account const sfAccount;
extern SF_Account const sfOwner; extern SF_Account const sfOwner;
@@ -493,14 +492,13 @@ extern SField const sfNewFields;
extern SField const sfTemplateEntry; extern SField const sfTemplateEntry;
extern SField const sfMemo; extern SField const sfMemo;
extern SField const sfSignerEntry; extern SField const sfSignerEntry;
extern SField const sfSigningAccount; extern SField const sfSigner;
extern SField const sfSigningFor;
extern SField const sfMajority; extern SField const sfMajority;
// array of objects // array of objects
// ARRAY/1 is reserved for end of array // ARRAY/1 is reserved for end of array
extern SField const sfSigningAccounts; // extern SField const sfSigningAccounts; // Never been used.
extern SField const sfMultiSigners; extern SField const sfSigners;
extern SField const sfSignerEntries; extern SField const sfSignerEntries;
extern SField const sfTemplate; extern SField const sfTemplate;
extern SField const sfNecessary; extern SField const sfNecessary;

View File

@@ -423,20 +423,6 @@ public:
uint256 getHash (std::uint32_t prefix) const; uint256 getHash (std::uint32_t prefix) const;
uint256 getSigningHash (std::uint32_t prefix) const; uint256 getSigningHash (std::uint32_t prefix) const;
// Break the multi-signing hash computation into 2 parts for optimization.
Serializer startMultiSigningData () const;
void finishMultiSigningData (
AccountID const& signingForID,
AccountID const& signingID, Serializer& s) const;
// VFALCO Get functions are usually simple observers but
// this one performs an expensive construction.
//
// Get data to compute a complete multi-signature.
Serializer getMultiSigningData (
AccountID const& signingForID,
AccountID const& signingID) const;
const STBase& peekAtIndex (int offset) const const STBase& peekAtIndex (int offset) const
{ {
return v_[offset].get(); return v_[offset].get();

View File

@@ -45,6 +45,7 @@ public:
using pointer = std::shared_ptr<STTx>; using pointer = std::shared_ptr<STTx>;
using ref = const std::shared_ptr<STTx>&; using ref = const std::shared_ptr<STTx>&;
static std::size_t const minMultiSigners = 1;
static std::size_t const maxMultiSigners = 8; static std::size_t const maxMultiSigners = 8;
public: public:

View File

@@ -46,6 +46,31 @@ verify (STObject const& st,
PublicKey const& pk, PublicKey const& pk,
bool mustBeFullyCanonical); bool mustBeFullyCanonical);
/** Return a Serializer suitable for computing a multisigning TxnSignature. */
Serializer
buildMultiSigningData (STObject const& obj, AccountID const& signingID);
/** Break the multi-signing hash computation into 2 parts for optimization.
We can optimize verifying multiple multisignatures by splitting the
data building into two parts;
o A large part that is shared by all of the computations.
o A small part that is unique to each signer in the multisignature.
The following methods support that optimization:
1. startMultiSigningData provides the large part which can be shared.
2. finishMuiltiSigningData caps the passed in serializer with each
signer's unique data.
*/
Serializer
startMultiSigningData (STObject const& obj);
inline void
finishMultiSigningData (AccountID const& signingID, Serializer& s)
{
s.add160 (signingID);
}
} // ripple } // ripple
#endif #endif

View File

@@ -83,6 +83,7 @@ enum TER
temDISABLED, temDISABLED,
temBAD_SIGNER, temBAD_SIGNER,
temBAD_QUORUM, temBAD_QUORUM,
temBAD_WEIGHT,
// An intermediate result used internally, should never be returned. // An intermediate result used internally, should never be returned.
temUNCERTAIN, temUNCERTAIN,

View File

@@ -181,9 +181,14 @@ getRippleStateIndex (AccountID const& a, Issue const& issue)
uint256 uint256
getSignerListIndex (AccountID const& account) getSignerListIndex (AccountID const& account)
{ {
// We are prepared for there to be multiple SignerLists in the future,
// but we don't have them yet. In anticipation of multiple SignerLists
// We supply a 32-bit ID to locate the SignerList. Until we actually
// *have* multiple signer lists, we can default that ID to zero.
return sha512Half( return sha512Half(
std::uint16_t(spaceSignerList), std::uint16_t(spaceSignerList),
account); account,
std::uint32_t (0)); // 0 == default SignerList ID.
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -29,15 +29,10 @@ InnerObjectFormats::InnerObjectFormats ()
<< SOElement (sfSignerWeight, SOE_REQUIRED) << SOElement (sfSignerWeight, SOE_REQUIRED)
; ;
add (sfSigningFor.getJsonName ().c_str (), sfSigningFor.getCode ()) add (sfSigner.getJsonName ().c_str (), sfSigner.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSigningAccounts, SOE_REQUIRED)
;
add (sfSigningAccount.getJsonName ().c_str (), sfSigningAccount.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED) << SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSigningPubKey, SOE_REQUIRED) << SOElement (sfSigningPubKey, SOE_REQUIRED)
<< SOElement (sfMultiSignature, SOE_REQUIRED) << SOElement (sfTxnSignature, SOE_REQUIRED)
; ;
} }

View File

@@ -122,12 +122,15 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfExpiration, SOE_OPTIONAL) << SOElement (sfExpiration, SOE_OPTIONAL)
; ;
// All three fields are SOE_REQUIRED because there is always a // All fields are SOE_REQUIRED because there is always a
// SignerEntries. If there are no SignerEntries the node is deleted. // SignerEntries. If there are no SignerEntries the node is deleted.
add ("SignerList", ltSIGNER_LIST) add ("SignerList", ltSIGNER_LIST)
<< SOElement (sfOwnerNode, SOE_REQUIRED) << SOElement (sfOwnerNode, SOE_REQUIRED)
<< SOElement (sfSignerQuorum, SOE_REQUIRED) << SOElement (sfSignerQuorum, SOE_REQUIRED)
<< SOElement (sfSignerEntries, SOE_REQUIRED) << SOElement (sfSignerEntries, SOE_REQUIRED)
<< SOElement (sfSignerListID, SOE_REQUIRED)
<< SOElement (sfPreviousTxnID, SOE_REQUIRED)
<< SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED)
; ;
} }

View File

@@ -123,6 +123,7 @@ SF_U32 const sfClearFlag = make::one<SF_U32::type>(&sfClearFlag,
SF_U32 const sfSignerQuorum = make::one<SF_U32::type>(&sfSignerQuorum, STI_UINT32, 35, "SignerQuorum"); SF_U32 const sfSignerQuorum = make::one<SF_U32::type>(&sfSignerQuorum, STI_UINT32, 35, "SignerQuorum");
SF_U32 const sfCancelAfter = make::one<SF_U32::type>(&sfCancelAfter, STI_UINT32, 36, "CancelAfter"); SF_U32 const sfCancelAfter = make::one<SF_U32::type>(&sfCancelAfter, STI_UINT32, 36, "CancelAfter");
SF_U32 const sfFinishAfter = make::one<SF_U32::type>(&sfFinishAfter, STI_UINT32, 37, "FinishAfter"); SF_U32 const sfFinishAfter = make::one<SF_U32::type>(&sfFinishAfter, STI_UINT32, 37, "FinishAfter");
SF_U32 const sfSignerListID = make::one<SF_U32::type>(&sfSignerListID, STI_UINT32, 38, "SignerListID");
// 64-bit integers // 64-bit integers
SF_U64 const sfIndexNext = make::one<SF_U64::type>(&sfIndexNext, STI_UINT64, 1, "IndexNext"); SF_U64 const sfIndexNext = make::one<SF_U64::type>(&sfIndexNext, STI_UINT64, 1, "IndexNext");
@@ -195,7 +196,7 @@ SF_Blob const sfMemoData = make::one<SF_Blob::type>(&sfMemoData, STI
SF_Blob const sfMemoFormat = make::one<SF_Blob::type>(&sfMemoFormat, STI_VL, 14, "MemoFormat"); SF_Blob const sfMemoFormat = make::one<SF_Blob::type>(&sfMemoFormat, STI_VL, 14, "MemoFormat");
// variable length (uncommon) // variable length (uncommon)
SF_Blob const sfMultiSignature = make::one<SF_Blob::type>(&sfMultiSignature, STI_VL, 16, "MultiSignature"); // 16 has not been used yet...
SF_Blob const sfProof = make::one<SF_Blob::type>(&sfProof, STI_VL, 17, "Proof"); SF_Blob const sfProof = make::one<SF_Blob::type>(&sfProof, STI_VL, 17, "Proof");
// account // account
@@ -228,14 +229,14 @@ SField const sfMemo = make::one(&sfMemo, STI_OBJEC
SField const sfSignerEntry = make::one(&sfSignerEntry, STI_OBJECT, 11, "SignerEntry"); SField const sfSignerEntry = make::one(&sfSignerEntry, STI_OBJECT, 11, "SignerEntry");
// inner object (uncommon) // inner object (uncommon)
SField const sfSigningAccount = make::one(&sfSigningAccount, STI_OBJECT, 16, "SigningAccount"); SField const sfSigner = make::one(&sfSigner, STI_OBJECT, 16, "Signer");
SField const sfSigningFor = make::one(&sfSigningFor, STI_OBJECT, 17, "SigningFor"); // 17 has not been used yet...
SField const sfMajority = make::one(&sfMajority, STI_OBJECT, 18, "Majority"); SField const sfMajority = make::one(&sfMajority, STI_OBJECT, 18, "Majority");
// array of objects // array of objects
// ARRAY/1 is reserved for end of array // ARRAY/1 is reserved for end of array
SField const sfSigningAccounts = make::one(&sfSigningAccounts, STI_ARRAY, 2, "SigningAccounts"); // SField const sfSigningAccounts = make::one(&sfSigningAccounts, STI_ARRAY, 2, "SigningAccounts"); // Never been used.
SField const sfMultiSigners = make::one(&sfMultiSigners, STI_ARRAY, 3, "MultiSigners", SField::sMD_Default, SField::notSigning); SField const sfSigners = make::one(&sfSigners, STI_ARRAY, 3, "Signers", SField::sMD_Default, SField::notSigning);
SField const sfSignerEntries = make::one(&sfSignerEntries, STI_ARRAY, 4, "SignerEntries"); SField const sfSignerEntries = make::one(&sfSignerEntries, STI_ARRAY, 4, "SignerEntries");
SField const sfTemplate = make::one(&sfTemplate, STI_ARRAY, 5, "Template"); SField const sfTemplate = make::one(&sfTemplate, STI_ARRAY, 5, "Template");
SField const sfNecessary = make::one(&sfNecessary, STI_ARRAY, 6, "Necessary"); SField const sfNecessary = make::one(&sfNecessary, STI_ARRAY, 6, "Necessary");

View File

@@ -338,36 +338,6 @@ uint256 STObject::getSigningHash (std::uint32_t prefix) const
return s.getSHA512Half (); return s.getSHA512Half ();
} }
Serializer
STObject::startMultiSigningData () const
{
Serializer s;
s.add32 (HashPrefix::txMultiSign);
add (s, false);
return s;
}
// VFALCO This should not be a member,
// and the function shouldn't even exist
void
STObject::finishMultiSigningData (
AccountID const& signingForID,
AccountID const& signingID,
Serializer& s) const
{
s.add160 (signingForID);
s.add160 (signingID);
}
Serializer
STObject::getMultiSigningData (
AccountID const& signingForID, AccountID const& signingID) const
{
Serializer s (startMultiSigningData ());
finishMultiSigningData (signingForID, signingID, s);
return s;
}
int STObject::getFieldIndex (SField const& field) const int STObject::getFieldIndex (SField const& field) const
{ {
if (mType != nullptr) if (mType != nullptr)

View File

@@ -22,6 +22,7 @@
#include <ripple/protocol/HashPrefix.h> #include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/Protocol.h> #include <ripple/protocol/Protocol.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/STAccount.h> #include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STArray.h> #include <ripple/protocol/STArray.h>
#include <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
@@ -189,8 +190,8 @@ bool STTx::checkSign(bool allowMultiSign) const
if (allowMultiSign) if (allowMultiSign)
{ {
// Determine whether we're single- or multi-signing by looking // Determine whether we're single- or multi-signing by looking
// at the SigningPubKey. It it's empty we must be multi-signing. // at the SigningPubKey. It it's empty we must be
// Otherwise we're single-signing. // multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL (sfSigningPubKey); Blob const& signingPubKey = getFieldVL (sfSigningPubKey);
sigGood = signingPubKey.empty () ? sigGood = signingPubKey.empty () ?
checkMultiSign () : checkSingleSign (); checkMultiSign () : checkSingleSign ();
@@ -269,10 +270,10 @@ STTx::getMetaSQL (Serializer rawTxn,
bool bool
STTx::checkSingleSign () const 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 // That would allow the transaction to be signed two ways. So if both
// fields are present the signature is invalid. // fields are present the signature is invalid.
if (isFieldPresent (sfMultiSigners)) if (isFieldPresent (sfSigners))
return false; return false;
bool ret = false; bool ret = false;
@@ -301,22 +302,19 @@ STTx::checkMultiSign () const
{ {
// Make sure the MultiSigners are present. Otherwise they are not // Make sure the MultiSigners are present. Otherwise they are not
// attempting multi-signing and we just have a bad SigningPubKey. // attempting multi-signing and we just have a bad SigningPubKey.
if (!isFieldPresent (sfMultiSigners)) if (!isFieldPresent (sfSigners))
return false; 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. // There are well known bounds that the number of signers must be within.
{ if (signers.size() < minMultiSigners || signers.size() > maxMultiSigners)
std::size_t const multiSignerCount = multiSigners.size ();
if ((multiSignerCount < 1) || (multiSignerCount > maxMultiSigners))
return false; return false;
}
// We can ease the computational load inside the loop a bit by // We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer // pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature. // 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. // We also use the sfAccount field inside the loop. Get it once.
auto const txnAccountID = getAccountID (sfAccount); auto const txnAccountID = getAccountID (sfAccount);
@@ -326,139 +324,36 @@ STTx::checkMultiSign () const
? ECDSA::strict ? ECDSA::strict
: ECDSA::not_strict; : ECDSA::not_strict;
/* // Signers must be in sorted order by AccountID.
We need to detect (and reject) if a multi-signer is both signing AccountID lastAccountID (beast::zero);
directly and using a SigningFor. Here's an example:
for (auto const& signer : signers)
{ {
... auto const accountID = signer.getAccountID (sfAccount);
"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 // The account owner may not multisign for themselves.
Becky can show up in that list only once. By design. So if Becky if (accountID == txnAccountID)
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<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)
{
auto const signingForID =
signingFor.getAccountID (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.
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; return false;
// Accounts must be in order by account ID. No duplicates allowed. // Accounts must be in order by account ID. No duplicates allowed.
if (lastSigningAcctID >= signingAcctID) if (lastAccountID >= accountID)
return false; return false;
// The next signature must be greater than this one. // The next signature must be greater than this one.
lastSigningAcctID = signingAcctID; lastAccountID = accountID;
// 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. // Verify the signature.
bool validSig = false; bool validSig = false;
try try
{ {
Serializer s = dataStart; Serializer s = dataStart;
finishMultiSigningData (signingForID, signingAcctID, s); finishMultiSigningData (accountID, s);
RippleAddress const pubKey = RippleAddress const pubKey =
RippleAddress::createAccountPublic ( RippleAddress::createAccountPublic (
signingAcct.getFieldVL (sfSigningPubKey)); signer.getFieldVL (sfSigningPubKey));
Blob const signature = Blob const signature = signer.getFieldVL (sfTxnSignature);
signingAcct.getFieldVL (sfMultiSignature);
validSig = pubKey.accountPublicVerify ( validSig = pubKey.accountPublicVerify (
s.getData(), signature, fullyCanonical); s.getData(), signature, fullyCanonical);
@@ -471,7 +366,6 @@ STTx::checkMultiSign () const
if (!validSig) if (!validSig)
return false; return false;
} }
}
// All signatures verified. // All signatures verified.
return true; return true;

View File

@@ -51,4 +51,21 @@ verify (STObject const& st,
true); true);
} }
Serializer
buildMultiSigningData (STObject const& obj, AccountID const& signingID)
{
Serializer s {startMultiSigningData (obj)};
finishMultiSigningData (signingID, s);
return s;
}
Serializer
startMultiSigningData (STObject const& obj)
{
Serializer s;
s.add32 (HashPrefix::txMultiSign);
obj.addWithoutSigningFields (s);
return s;
}
} // ripple } // ripple

View File

@@ -113,9 +113,10 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ temBAD_SEND_XRP_PATHS, "temBAD_SEND_XRP_PATHS", "Malformed: Paths are not allowed for XRP to XRP." }, { temBAD_SEND_XRP_PATHS, "temBAD_SEND_XRP_PATHS", "Malformed: Paths are not allowed for XRP to XRP." },
{ temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence is not in the past." }, { temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence is not in the past." },
{ temBAD_SIGNATURE, "temBAD_SIGNATURE", "Malformed: Bad signature." }, { temBAD_SIGNATURE, "temBAD_SIGNATURE", "Malformed: Bad signature." },
{ temBAD_SIGNER, "temBAD_SIGNER", "Malformed: A signer may not duplicate account or other signers"}, { temBAD_SIGNER, "temBAD_SIGNER", "Malformed: No signer may duplicate account or other signers." },
{ temBAD_SRC_ACCOUNT, "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." }, { temBAD_SRC_ACCOUNT, "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." },
{ temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" }, { temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" },
{ temBAD_WEIGHT, "temBAD_WEIGHT", "Malformed: Weight must be a positive value." },
{ temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." },
{ temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." },
{ temINVALID, "temINVALID", "The transaction is ill-formed." }, { temINVALID, "temINVALID", "The transaction is ill-formed." },

View File

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

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
#include <ripple/protocol/STParsedJSON.h> #include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/types.h> #include <ripple/protocol/types.h>
@@ -113,44 +114,34 @@ public:
saSeed.createAccountPrivate(saGenerator, saSeed, 0); saSeed.createAccountPrivate(saGenerator, saSeed, 0);
// Get the stream of the transaction for use in multi-signing. // Get the stream of the transaction for use in multi-signing.
Serializer s = txn.getMultiSigningData( Serializer s = buildMultiSigningData (txn, saID);
calcAccountID(saPublicAcct), calcAccountID(saPublicAcct));
Blob saMultiSignature = Blob saMultiSignature =
saPrivateAcct.accountPrivateSign (s.getData()); saPrivateAcct.accountPrivateSign (s.getData());
// The InnerObjectFormats say a SigningAccount is supposed to look // The InnerObjectFormats say a Signer is supposed to look
// like this: // like this:
// SigningAccount { // Signer {
// Account: "...", // Account: "...",
// MultiSignature: "...", // TxnSignature: "...",
// PublicKey: "..."" // PublicKey: "...""
// } // }
// Make one well formed SigningAccount and several mal-formed ones. // Make one well formed Signer and several mal-formed ones. See
// See whether the serializer lets the good one through and catches // whether the serializer lets the good one through and catches
// the bad ones. // the bad ones.
// This lambda contains the bulk of the test code. // This lambda contains the bulk of the test code.
auto testMalformedSigningAccount = auto testMalformedSigningAccount =
[this, &txn, &signingForID] [this, &txn, &signingForID]
(STObject const& signingAcct, bool expectPass) (STObject const& signer, bool expectPass)
{ {
// Create SigningAccounts array. // Create SigningAccounts array.
STArray signingAccts (sfSigningAccounts, 1); STArray signers (sfSigners, 1);
signingAccts.push_back (signingAcct); signers.push_back (signer);
// Insert SigningAccounts array into SigningFor object. // Insert signers into transaction.
STObject signingFor (sfSigningFor);
signingFor.setAccountID (sfAccount, signingForID);
signingFor.setFieldArray (sfSigningAccounts, signingAccts);
// Insert SigningFor into MultiSigners.
STArray multiSigners (sfMultiSigners, 1);
multiSigners.push_back (signingFor);
// Insert MultiSigners into transaction.
STTx tempTxn (txn); STTx tempTxn (txn);
tempTxn.setFieldArray (sfMultiSigners, multiSigners); tempTxn.setFieldArray (sfSigners, signers);
Serializer rawTxn; Serializer rawTxn;
tempTxn.add (rawTxn); tempTxn.add (rawTxn);
@@ -167,41 +158,41 @@ public:
} }
expect (serialized == expectPass, expect (serialized == expectPass,
"Unexpected serialized = " + std::to_string (serialized) + "Unexpected serialized = " + std::to_string (serialized) +
". Object:\n" + signingAcct.getFullText () + "\n"); ". Object:\n" + signer.getFullText () + "\n");
}; };
{ {
// Test case 1. Make a valid SigningAccount object. // Test case 1. Make a valid Signer object.
STObject soTest1 (sfSigningAccount); STObject soTest1 (sfSigner);
soTest1.setAccountID (sfAccount, saID); soTest1.setAccountID (sfAccount, saID);
soTest1.setFieldVL (sfSigningPubKey, soTest1.setFieldVL (sfSigningPubKey,
txnPublicAcct.getAccountPublic ()); txnPublicAcct.getAccountPublic ());
soTest1.setFieldVL (sfMultiSignature, saMultiSignature); soTest1.setFieldVL (sfTxnSignature, saMultiSignature);
testMalformedSigningAccount (soTest1, true); testMalformedSigningAccount (soTest1, true);
} }
{ {
// Test case 2. Omit sfSigningPubKey from SigningAccount. // Test case 2. Omit sfSigningPubKey from SigningAccount.
STObject soTest2 (sfSigningAccount); STObject soTest2 (sfSigner);
soTest2.setAccountID (sfAccount, saID); soTest2.setAccountID (sfAccount, saID);
soTest2.setFieldVL (sfMultiSignature, saMultiSignature); soTest2.setFieldVL (sfTxnSignature, saMultiSignature);
testMalformedSigningAccount (soTest2, false); testMalformedSigningAccount (soTest2, false);
} }
{ {
// Test case 3. Extra sfAmount in SigningAccount. // Test case 3. Extra sfAmount in SigningAccount.
STObject soTest3 (sfSigningAccount); STObject soTest3 (sfSigner);
soTest3.setAccountID (sfAccount, saID); soTest3.setAccountID (sfAccount, saID);
soTest3.setFieldVL (sfSigningPubKey, soTest3.setFieldVL (sfSigningPubKey,
txnPublicAcct.getAccountPublic ()); txnPublicAcct.getAccountPublic ());
soTest3.setFieldVL (sfMultiSignature, saMultiSignature); soTest3.setFieldVL (sfTxnSignature, saMultiSignature);
soTest3.setFieldAmount (sfAmount, STAmount (10000)); soTest3.setFieldAmount (sfAmount, STAmount (10000));
testMalformedSigningAccount (soTest3, false); testMalformedSigningAccount (soTest3, false);
} }
{ {
// Test case 4. Right number of fields, but wrong ones. // Test case 4. Right number of fields, but wrong ones.
STObject soTest4 (sfSigningAccount); STObject soTest4 (sfSigner);
soTest4.setFieldVL (sfSigningPubKey, soTest4.setFieldVL (sfSigningPubKey,
txnPublicAcct.getAccountPublic ()); txnPublicAcct.getAccountPublic ());
soTest4.setFieldVL (sfMultiSignature, saMultiSignature); soTest4.setFieldVL (sfTxnSignature, saMultiSignature);
soTest4.setFieldAmount (sfAmount, STAmount (10000)); soTest4.setFieldAmount (sfAmount, STAmount (10000));
testMalformedSigningAccount (soTest4, false); testMalformedSigningAccount (soTest4, false);
} }

View File

@@ -71,23 +71,6 @@ Json::Value doAccountInfo (RPC::Context& context)
if (sleAccepted) if (sleAccepted)
{ {
RPC::injectSLE(jvAccepted, *sleAccepted); RPC::injectSLE(jvAccepted, *sleAccepted);
// See if there's a SignerEntries for this account.
auto const signerList = ledger->read (keylet::signers(accountID));
if (signerList)
{
// Return multi-signing information if there are multi-signers.
static const Json::StaticString multiSignersName("multisigners");
jvAccepted[multiSignersName] = signerList->getJson (0);
Json::Value& multiSignerJson = jvAccepted[multiSignersName];
// Remove unwanted fields.
multiSignerJson.removeMember (sfFlags.getName ());
multiSignerJson.removeMember (sfLedgerEntryType.getName ());
multiSignerJson.removeMember (sfOwnerNode.getName ());
multiSignerJson.removeMember ("index");
}
result[jss::account_data] = jvAccepted; result[jss::account_data] = jvAccepted;
} }
else else

View File

@@ -68,6 +68,7 @@ Json::Value doSign (RPC::Context&);
Json::Value doSignFor (RPC::Context&); Json::Value doSignFor (RPC::Context&);
Json::Value doStop (RPC::Context&); Json::Value doStop (RPC::Context&);
Json::Value doSubmit (RPC::Context&); Json::Value doSubmit (RPC::Context&);
Json::Value doSubmitMultiSigned (RPC::Context&);
Json::Value doSubscribe (RPC::Context&); Json::Value doSubscribe (RPC::Context&);
Json::Value doTransactionEntry (RPC::Context&); Json::Value doTransactionEntry (RPC::Context&);
Json::Value doTx (RPC::Context&); Json::Value doTx (RPC::Context&);

View File

@@ -27,9 +27,11 @@
#include <ripple/json/json_reader.h> #include <ripple/json/json_reader.h>
#include <ripple/json/json_writer.h> #include <ripple/json/json_writer.h>
#include <ripple/net/RPCErr.h> #include <ripple/net/RPCErr.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/STParsedJSON.h> #include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/rpc/impl/KeypairForSignature.h> #include <ripple/rpc/impl/KeypairForSignature.h>
#include <ripple/rpc/impl/LegacyPathFind.h> #include <ripple/rpc/impl/LegacyPathFind.h>
#include <ripple/rpc/impl/TransactionSign.h> #include <ripple/rpc/impl/TransactionSign.h>
@@ -44,14 +46,12 @@ namespace detail {
class SigningForParams class SigningForParams
{ {
private: private:
AccountID const* const signingForAcctID_;
AccountID const* const multiSigningAcctID_; AccountID const* const multiSigningAcctID_;
RippleAddress* const multiSignPublicKey_; RippleAddress* const multiSignPublicKey_;
Blob* const multiSignature_; Blob* const multiSignature_;
public: public:
explicit SigningForParams () explicit SigningForParams ()
: signingForAcctID_ (nullptr) : multiSigningAcctID_ (nullptr)
, multiSigningAcctID_ (nullptr)
, multiSignPublicKey_ (nullptr) , multiSignPublicKey_ (nullptr)
, multiSignature_ (nullptr) , multiSignature_ (nullptr)
{ } { }
@@ -59,12 +59,10 @@ public:
SigningForParams (SigningForParams const& rhs) = delete; SigningForParams (SigningForParams const& rhs) = delete;
SigningForParams ( SigningForParams (
AccountID const& signingForAcctID,
AccountID const& multiSigningAcctID, AccountID const& multiSigningAcctID,
RippleAddress& multiSignPublicKey, RippleAddress& multiSignPublicKey,
Blob& multiSignature) Blob& multiSignature)
: signingForAcctID_ (&signingForAcctID) : multiSigningAcctID_ (&multiSigningAcctID)
, multiSigningAcctID_ (&multiSigningAcctID)
, multiSignPublicKey_ (&multiSignPublicKey) , multiSignPublicKey_ (&multiSignPublicKey)
, multiSignature_ (&multiSignature) , multiSignature_ (&multiSignature)
{ } { }
@@ -72,7 +70,6 @@ public:
bool isMultiSigning () const bool isMultiSigning () const
{ {
return ((multiSigningAcctID_ != nullptr) && return ((multiSigningAcctID_ != nullptr) &&
(signingForAcctID_ != nullptr) &&
(multiSignPublicKey_ != nullptr) && (multiSignPublicKey_ != nullptr) &&
(multiSignature_ != nullptr)); (multiSignature_ != nullptr));
} }
@@ -84,11 +81,6 @@ public:
} }
// Don't call this method unless isMultiSigning() returns true. // Don't call this method unless isMultiSigning() returns true.
AccountID const& getSigningFor ()
{
return *signingForAcctID_;
}
AccountID const& getSigner () AccountID const& getSigner ()
{ {
return *multiSigningAcctID_; return *multiSigningAcctID_;
@@ -345,9 +337,8 @@ Json::Value checkFee (
if (fee > limit) if (fee > limit)
{ {
std::stringstream ss; std::stringstream ss;
ss << ss << "Fee of " << fee
"Fee of " << fee << << " exceeds the requested tx limit of " << limit;
" exceeds the requested tx limit of " << limit;
return RPC::make_error (rpcHIGH_FEE, ss.str()); return RPC::make_error (rpcHIGH_FEE, ss.str());
} }
@@ -364,7 +355,7 @@ enum class PathFind : unsigned char
static Json::Value checkPayment( static Json::Value checkPayment(
Json::Value const& params, Json::Value const& params,
Json::Value& tx_json, Json::Value& tx_json,
AccountID const& raSrcAddressID, AccountID const& srcAddressID,
TxnSignApiFacade const& apiFacade, TxnSignApiFacade const& apiFacade,
Role const role, Role const role,
PathFind const doPath) PathFind const doPath)
@@ -399,21 +390,21 @@ static Json::Value checkPayment(
if (!tx_json.isMember (jss::Paths) && params.isMember (jss::build_path)) if (!tx_json.isMember (jss::Paths) && params.isMember (jss::build_path))
{ {
STAmount saSendMax; STAmount sendMax;
if (tx_json.isMember (jss::SendMax)) if (tx_json.isMember (jss::SendMax))
{ {
if (! amountFromJsonNoThrow (saSendMax, tx_json [jss::SendMax])) if (! amountFromJsonNoThrow (sendMax, tx_json [jss::SendMax]))
return RPC::invalid_field_error ("tx_json.SendMax"); return RPC::invalid_field_error ("tx_json.SendMax");
} }
else else
{ {
// If no SendMax, default to Amount with sender as issuer. // If no SendMax, default to Amount with sender as issuer.
saSendMax = amount; sendMax = amount;
saSendMax.setIssuer (raSrcAddressID); sendMax.setIssuer (srcAddressID);
} }
if (saSendMax.native () && amount.native ()) if (sendMax.native () && amount.native ())
return RPC::make_error (rpcINVALID_PARAMS, return RPC::make_error (rpcINVALID_PARAMS,
"Cannot build XRP to XRP paths."); "Cannot build XRP to XRP paths.");
@@ -426,7 +417,7 @@ static Json::Value checkPayment(
STPath fullLiquidityPath; STPath fullLiquidityPath;
bool valid = apiFacade.findPathsForOneIssuer ( bool valid = apiFacade.findPathsForOneIssuer (
*dstAccountID, *dstAccountID,
saSendMax.issue (), sendMax.issue (),
amount, amount,
getConfig ().PATH_SEARCH_OLD, getConfig ().PATH_SEARCH_OLD,
4, // iMaxPaths 4, // iMaxPaths
@@ -514,7 +505,7 @@ checkTxJsonFields (
return ret; return ret;
} }
// It's all good. Return the AddressID. // It's all good. Return the AccountID.
ret.second = *srcAddressID; ret.second = *srcAddressID;
return ret; return ret;
} }
@@ -580,7 +571,7 @@ transactionPreProcessImpl (
if (RPC::contains_error (txJsonResult.first)) if (RPC::contains_error (txJsonResult.first))
return std::move (txJsonResult.first); return std::move (txJsonResult.first);
auto const raSrcAddressID = txJsonResult.second; auto const srcAddressID = txJsonResult.second;
// This test covers the case where we're offline so the sequence number // This test covers the case where we're offline so the sequence number
// cannot be determined locally. If we're offline then the caller must // cannot be determined locally. If we're offline then the caller must
@@ -588,7 +579,7 @@ transactionPreProcessImpl (
if (!verify && !tx_json.isMember (jss::Sequence)) if (!verify && !tx_json.isMember (jss::Sequence))
return RPC::missing_field_error ("tx_json.Sequence"); return RPC::missing_field_error ("tx_json.Sequence");
apiFacade.snapshotAccountState (raSrcAddressID); apiFacade.snapshotAccountState (srcAddressID);
if (verify) { if (verify) {
if (!apiFacade.isValidAccount()) if (!apiFacade.isValidAccount())
@@ -597,7 +588,7 @@ transactionPreProcessImpl (
WriteLog (lsDEBUG, RPCHandler) WriteLog (lsDEBUG, RPCHandler)
<< "transactionSign: Failed to find source account " << "transactionSign: Failed to find source account "
<< "in current ledger: " << "in current ledger: "
<< toBase58(raSrcAddressID); << toBase58(srcAddressID);
return rpcError (rpcSRC_ACT_NOT_FOUND); return rpcError (rpcSRC_ACT_NOT_FOUND);
} }
@@ -616,7 +607,7 @@ transactionPreProcessImpl (
err = checkPayment ( err = checkPayment (
params, params,
tx_json, tx_json,
raSrcAddressID, srcAddressID,
apiFacade, apiFacade,
role, role,
signingArgs.editFields() ? PathFind::might : PathFind::dont); signingArgs.editFields() ? PathFind::might : PathFind::dont);
@@ -640,9 +631,9 @@ transactionPreProcessImpl (
// XXX Ignore transactions for accounts not created. // XXX Ignore transactions for accounts not created.
return rpcError (rpcSRC_ACT_NOT_FOUND); return rpcError (rpcSRC_ACT_NOT_FOUND);
WriteLog (lsTRACE, RPCHandler) << WriteLog (lsTRACE, RPCHandler)
"verify: " << toBase58(calcAccountID(keypair.publicKey)) << << "verify: " << toBase58(calcAccountID(keypair.publicKey))
" : " << toBase58(raSrcAddressID); << " : " << toBase58(srcAddressID);
// If multisigning then we need to return the public key. // If multisigning then we need to return the public key.
if (signingArgs.isMultiSigning()) if (signingArgs.isMultiSigning())
@@ -698,8 +689,8 @@ transactionPreProcessImpl (
// If multisign then return multiSignature, else set TxnSignature field. // If multisign then return multiSignature, else set TxnSignature field.
if (signingArgs.isMultiSigning ()) if (signingArgs.isMultiSigning ())
{ {
Serializer s = stpTrans->getMultiSigningData( Serializer s =
signingArgs.getSigningFor(), signingArgs.getSigner()); buildMultiSigningData (*stpTrans, signingArgs.getSigner ());
Blob multiSignature = keypair.secretKey.accountPrivateSign(s.getData()); Blob multiSignature = keypair.secretKey.accountPrivateSign(s.getData());
signingArgs.moveMultiSignature (std::move (multiSignature)); signingArgs.moveMultiSignature (std::move (multiSignature));
} }
@@ -797,51 +788,6 @@ Json::Value transactionFormatResultImpl (Transaction::pointer tpTrans)
return jvResult; return jvResult;
} }
void insertMultiSigners (
Json::Value& jvResult,
AccountID const& signingForAcctID,
AccountID const& signingAcctID,
RippleAddress const& accountPublic,
Blob const& signature)
{
// Build a SigningAccount object to inject into the SigningAccounts.
Json::Value signingAccount (Json::objectValue);
signingAccount[sfAccount.getJsonName ()] =
getApp().accountIDCache().toBase58(signingAcctID);
signingAccount[sfSigningPubKey.getJsonName ()] =
strHex (accountPublic.getAccountPublic ());
signingAccount[sfMultiSignature.getJsonName ()] = strHex (signature);
// Give the SigningAccount an object name and put it in the
// SigningAccounts array.
Json::Value nameSigningAccount (Json::objectValue);
nameSigningAccount[sfSigningAccount.getJsonName ()] = signingAccount;
Json::Value signingAccounts (Json::arrayValue);
signingAccounts.append (nameSigningAccount);
// Put the signingForAcctID and the SigningAccounts in the SigningFor.
Json::Value signingFor (Json::objectValue);
signingFor[sfAccount.getJsonName ()] =
getApp().accountIDCache().toBase58(signingForAcctID);
signingFor[sfSigningAccounts.getJsonName ()] = signingAccounts;
// Give the SigningFor an object name and put it in the MultiSigners array.
Json::Value nameSigningFor (Json::objectValue);
nameSigningFor[sfSigningFor.getJsonName ()] = signingFor;
Json::Value multiSigners (Json::arrayValue);
multiSigners.append (nameSigningFor);
// Inject the MultiSigners into the jvResult.
jvResult[sfMultiSigners.getName ()] = multiSigners;
}
} // detail } // detail
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -953,8 +899,7 @@ Json::Value transactionSignFor (
detail::TxnSignApiFacade& apiFacade, detail::TxnSignApiFacade& apiFacade,
Role role) Role role)
{ {
WriteLog (lsDEBUG, RPCHandler) << WriteLog (lsDEBUG, RPCHandler) << "transactionSignFor: " << jvRequest;
"transactionSignFor: " << jvRequest;
// Verify presence of the signer's account field. // Verify presence of the signer's account field.
const char accountField[] = "account"; const char accountField[] = "account";
@@ -962,30 +907,15 @@ Json::Value transactionSignFor (
if (! jvRequest.isMember (accountField)) if (! jvRequest.isMember (accountField))
return RPC::missing_field_error (accountField); return RPC::missing_field_error (accountField);
// Turn the signer's account into a RippleAddress for multi-sign. // Turn the signer's account into an AccountID for multi-sign.
auto const multiSignAddrID = parseBase58<AccountID>( auto const signerAccountID = parseBase58<AccountID>(
jvRequest[accountField].asString()); jvRequest[accountField].asString());
if (! multiSignAddrID) if (! signerAccountID)
{ {
return RPC::make_error (rpcSRC_ACT_MALFORMED, return RPC::make_error (rpcSRC_ACT_MALFORMED,
RPC::invalid_field_message (accountField)); RPC::invalid_field_message (accountField));
} }
// Verify the presence of the "signing_for" field.
const char signing_forField[] = "signing_for";
if (! jvRequest.isMember (signing_forField))
return RPC::missing_field_error (signing_forField);
// Turn the signing_for account into a RippleAddress for multi-sign.
auto const multiSignForAddrID = parseBase58<AccountID>(
jvRequest[signing_forField].asString ());
if (! multiSignForAddrID)
{
return RPC::make_error (rpcSIGN_FOR_MALFORMED,
RPC::invalid_field_message (signing_forField));
}
// When multi-signing, the "Sequence" and "SigningPubKey" fields must // When multi-signing, the "Sequence" and "SigningPubKey" fields must
// be passed in by the caller. // be passed in by the caller.
using namespace detail; using namespace detail;
@@ -999,8 +929,7 @@ Json::Value transactionSignFor (
Blob multiSignature; Blob multiSignature;
RippleAddress multiSignPubKey; RippleAddress multiSignPubKey;
SigningForParams signForParams( SigningForParams signForParams(
*multiSignForAddrID, *multiSignAddrID, *signerAccountID, multiSignPubKey, multiSignature);
multiSignPubKey, multiSignature);
transactionPreProcessResult preprocResult = transactionPreProcessResult preprocResult =
transactionPreProcessImpl ( transactionPreProcessImpl (
@@ -1015,7 +944,7 @@ Json::Value transactionSignFor (
// Make sure the multiSignAddrID can legitimately multi-sign. // Make sure the multiSignAddrID can legitimately multi-sign.
{ {
error_code_i const err = error_code_i const err =
apiFacade.multiAcctMatchesPubKey(*multiSignAddrID, multiSignPubKey); apiFacade.multiAcctMatchesPubKey (*signerAccountID, multiSignPubKey);
if (err != rpcSUCCESS) if (err != rpcSUCCESS)
return rpcError (err); return rpcError (err);
@@ -1032,9 +961,26 @@ Json::Value transactionSignFor (
if (RPC::contains_error (json)) if (RPC::contains_error (json))
return json; return json;
// Finally, do what we were called for: return a SigningFor object. // Finally, do what we were called for: return a Signers array. Build
insertMultiSigners (json, // a Signer object to insert into the Signers array.
*multiSignForAddrID, *multiSignAddrID, multiSignPubKey, multiSignature); Json::Value signer (Json::objectValue);
signer[sfAccount.getJsonName ()] = toBase58 (*signerAccountID);
signer[sfSigningPubKey.getJsonName ()] =
strHex (multiSignPubKey.getAccountPublic ());
signer[sfTxnSignature.getJsonName ()] = strHex (multiSignature);
// Give the Signer an object name and put it in the Signers array.
Json::Value nameSigner (Json::objectValue);
nameSigner[sfSigner.getJsonName ()] = std::move (signer);
Json::Value signers (Json::arrayValue);
signers.append (std::move (nameSigner));
// Inject the Signers into the json.
json[sfSigners.getName ()] = std::move(signers);
return json; return json;
} }
@@ -1064,16 +1010,16 @@ Json::Value transactionSubmitMultiSigned (
if (RPC::contains_error (txJsonResult.first)) if (RPC::contains_error (txJsonResult.first))
return std::move (txJsonResult.first); return std::move (txJsonResult.first);
auto const raSrcAddressID = txJsonResult.second; auto const srcAddressID = txJsonResult.second;
apiFacade.snapshotAccountState (raSrcAddressID); apiFacade.snapshotAccountState (srcAddressID);
if (!apiFacade.isValidAccount ()) if (!apiFacade.isValidAccount ())
{ {
// If not offline and did not find account, error. // If not offline and did not find account, error.
WriteLog (lsDEBUG, RPCHandler) WriteLog (lsDEBUG, RPCHandler)
<< "transactionSubmitMultiSigned: Failed to find source account " << "transactionSubmitMultiSigned: Failed to find source account "
<< "in current ledger: " << "in current ledger: "
<< toBase58(raSrcAddressID); << toBase58(srcAddressID);
return rpcError (rpcSRC_ACT_NOT_FOUND); return rpcError (rpcSRC_ACT_NOT_FOUND);
} }
@@ -1086,7 +1032,7 @@ Json::Value transactionSubmitMultiSigned (
err = checkPayment ( err = checkPayment (
jvRequest, jvRequest,
tx_json, tx_json,
raSrcAddressID, srcAddressID,
apiFacade, apiFacade,
role, role,
PathFind::dont); PathFind::dont);
@@ -1147,146 +1093,81 @@ Json::Value transactionSubmitMultiSigned (
} }
} }
// Check MultiSigners for valid entries. // Check Signers for valid entries.
const char* multiSignersArrayName {sfMultiSigners.getJsonName ().c_str ()}; STArray signers;
STArray multiSigners;
{ {
// Verify that the MultiSigners field is present and an array. // Verify that the Signers field is present and an array.
if (! jvRequest.isMember (multiSignersArrayName)) char const* signersArrayName {sfSigners.getJsonName ().c_str ()};
return RPC::missing_field_error (multiSignersArrayName); if (! jvRequest.isMember (signersArrayName))
return RPC::missing_field_error (signersArrayName);
Json::Value& multiSignersValue ( Json::Value& signersValue (
jvRequest [multiSignersArrayName]); jvRequest [signersArrayName]);
if (! multiSignersValue.isArray ()) if (! signersValue.isArray ())
{ {
std::ostringstream err; std::ostringstream err;
err << "Expected " err << "Expected "
<< multiSignersArrayName << " to be an array"; << signersArrayName << " to be an array";
return RPC::make_param_error (err.str ()); return RPC::make_param_error (err.str ());
} }
// Convert the MultiSigners into SerializedTypes. // Convert signers into SerializedTypes.
STParsedJSONArray parsedMultiSigners ( STParsedJSONArray parsedSigners (signersArrayName, signersValue);
multiSignersArrayName, multiSignersValue);
if (!parsedMultiSigners.array) if (!parsedSigners.array)
{ {
Json::Value jvResult; Json::Value jvResult;
jvResult ["error"] = parsedMultiSigners.error ["error"]; jvResult ["error"] = parsedSigners.error ["error"];
jvResult ["error_code"] = jvResult ["error_code"] = parsedSigners.error ["error_code"];
parsedMultiSigners.error ["error_code"]; jvResult ["error_message"] = parsedSigners.error ["error_message"];
jvResult ["error_message"] =
parsedMultiSigners.error ["error_message"];
return jvResult; return jvResult;
} }
multiSigners = std::move (parsedMultiSigners.array.get()); signers = std::move (parsedSigners.array.get());
} }
if (multiSigners.empty ()) if (signers.empty ())
return RPC::make_param_error ("MultiSigners array may not be empty."); return RPC::make_param_error ("Signers array may not be empty.");
for (STObject const& signingFor : multiSigners) // Signers must be sorted by Account.
signers.sort ([] (STObject const& a, STObject const& b)
{ {
if (signingFor.getFName () != sfSigningFor) return (a.getAccountID (sfAccount) < b.getAccountID (sfAccount));
return RPC::make_param_error ( });
"MultiSigners array has a non-SigningFor entry");
// Each SigningAccounts array must have at least one entry. // Signers may not contain any duplicates.
STArray const& signingAccounts = auto const dupIter = std::adjacent_find (
signingFor.getFieldArray (sfSigningAccounts); signers.begin(), signers.end(),
[] (STObject const& a, STObject const& b)
if (signingAccounts.empty ())
return RPC::make_param_error (
"A SigningAccounts array may not be empty");
// Each SigningAccounts array may only contain SigningAccount objects.
auto const signingAccountsEnd = signingAccounts.end ();
auto const findItr = std::find_if (
signingAccounts.begin (), signingAccountsEnd,
[](STObject const& obj)
{ return obj.getFName () != sfSigningAccount; });
if (findItr != signingAccountsEnd)
return RPC::make_param_error (
"SigningAccounts may only contain SigningAccount objects.");
}
// Lambdas for sorting arrays and finding duplicates.
auto byFieldAccountID = [](STObject const& a, STObject const& b)
{ {
return a.getAccountID(sfAccount) < return (a.getAccountID (sfAccount) == b.getAccountID (sfAccount));
b.getAccountID(sfAccount); });
};
auto ifDuplicateAccountID = if (dupIter != signers.end())
[](STObject const& a, STObject const& b)
{
return a.getAccountID(sfAccount) ==
b.getAccountID(sfAccount);
};
{
// MultiSigners are submitted sorted in AccountID order. This
// assures that the same list will always have the same hash.
multiSigners.sort (byFieldAccountID);
// There may be no duplicate Accounts in MultiSigners
auto const multiSignersEnd = multiSigners.end ();
auto const dupAccountItr = std::adjacent_find (
multiSigners.begin (), multiSignersEnd, ifDuplicateAccountID);
if (dupAccountItr != multiSignersEnd)
{ {
std::ostringstream err; std::ostringstream err;
err << "Duplicate SigningFor:Account entries (" err << "Duplicate Signers:Signer:Account entries ("
<< getApp().accountIDCache().toBase58(dupAccountItr->getAccountID(sfAccount)) << getApp().accountIDCache().toBase58(
<< ") are not allowed."; dupIter->getAccountID(sfAccount))
return RPC::make_param_error(err.str ());
}
}
// All SigningAccounts inside the MultiSigners must also be sorted and
// contain no duplicates.
for (STObject& signingFor : multiSigners)
{
STArray& signingAccts = signingFor.peekFieldArray (sfSigningAccounts);
signingAccts.sort (byFieldAccountID);
auto const signingAcctsEnd = signingAccts.end ();
auto const dupAccountItr = std::adjacent_find (
signingAccts.begin (), signingAcctsEnd, ifDuplicateAccountID);
if (dupAccountItr != signingAcctsEnd)
{
std::ostringstream err;
err << "Duplicate SigningAccounts:SigningAccount:Account entries ("
<< getApp().accountIDCache().toBase58(dupAccountItr->getAccountID(sfAccount))
<< ") are not allowed."; << ") are not allowed.";
return RPC::make_param_error(err.str ()); return RPC::make_param_error(err.str ());
} }
// An account may not sign for itself. // An account may not sign for itself.
auto const account = if (signers.end() != std::find_if (signers.begin(), signers.end(),
signingFor.getAccountID(sfAccount); [&srcAddressID](STObject const& elem)
if (std::find_if (signingAccts.begin(), signingAcctsEnd,
[&account](STObject const& elem)
{ {
return elem.getAccountID(sfAccount) == account; return elem.getAccountID (sfAccount) == srcAddressID;
}) != signingAcctsEnd) }))
{ {
std::ostringstream err; std::ostringstream err;
err << "A SigningAccount may not SignFor itself (" err << "A Signer may not be the transaction's Account ("
<< getApp().accountIDCache().toBase58(account) << ")."; << getApp().accountIDCache().toBase58(srcAddressID) << ").";
return RPC::make_param_error(err.str ()); return RPC::make_param_error(err.str ());
} }
}
// Insert the MultiSigners into the transaction. // Insert signers into the transaction.
stpTrans->setFieldArray (sfMultiSigners, std::move(multiSigners)); stpTrans->setFieldArray (sfSigners, std::move(signers));
// Make sure the SerializedTransaction makes a legitimate Transaction. // Make sure the SerializedTransaction makes a legitimate Transaction.
std::pair <Json::Value, Transaction::pointer> txn = std::pair <Json::Value, Transaction::pointer> txn =

View File

@@ -80,14 +80,13 @@ R"({
{ {
"", "",
"", "",
"Missing field 'signing_for'.", "Missing field 'tx_json.Sequence'.",
"Missing field 'tx_json.Sequence'."}}, "Missing field 'tx_json.Sequence'."}},
{ "Pass in Sequence.", { "Pass in Sequence.",
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Sequence": 0, "Sequence": 0,
@@ -107,7 +106,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Sequence": 0, "Sequence": 0,
@@ -128,7 +126,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"fee_mult_max": 7, "fee_mult_max": 7,
"tx_json": { "tx_json": {
@@ -149,7 +146,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"fee_mult_max": 0, "fee_mult_max": 0,
"tx_json": { "tx_json": {
@@ -171,7 +167,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"fee_mult_max": "NotAFeeMultiplier", "fee_mult_max": "NotAFeeMultiplier",
"tx_json": { "tx_json": {
@@ -192,7 +187,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"fee_mult_max": 0, "fee_mult_max": 0,
"tx_json": { "tx_json": {
@@ -213,7 +207,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -231,7 +224,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -250,7 +242,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -268,7 +259,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -287,7 +277,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"build_path": 1, "build_path": 1,
"tx_json": { "tx_json": {
@@ -307,7 +296,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"build_path": 1, "build_path": 1,
"tx_json": { "tx_json": {
@@ -331,7 +319,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"build_path": 1, "build_path": 1,
"tx_json": { "tx_json": {
@@ -356,7 +343,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"build_path": 1, "build_path": 1,
"tx_json": { "tx_json": {
@@ -385,7 +371,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"build_path": 1, "build_path": 1,
"tx_json": { "tx_json": {
@@ -410,7 +395,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000", "Amount": "1000000000",
@@ -428,7 +412,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "", "secret": "",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -447,7 +430,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"rx_json": { "rx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -466,7 +448,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -484,7 +465,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -503,7 +483,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
@@ -522,7 +501,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Amount": "1000000000", "Amount": "1000000000",
@@ -540,7 +518,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "NotAnAccount", "Account": "NotAnAccount",
@@ -559,7 +536,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"offline": 0, "offline": 0,
"tx_json": { "tx_json": {
@@ -579,7 +555,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"offline": 1, "offline": 1,
"tx_json": { "tx_json": {
@@ -599,7 +574,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"offline": 1, "offline": 1,
"tx_json": { "tx_json": {
@@ -620,7 +594,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Flags": 0, "Flags": 0,
@@ -640,7 +613,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Flags": "NotGoodFlags", "Flags": "NotGoodFlags",
@@ -660,7 +632,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"debug_signing": 0, "debug_signing": 0,
"tx_json": { "tx_json": {
@@ -680,7 +651,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -696,13 +666,12 @@ R"({
"", "",
"", "",
"", "",
"Missing field 'MultiSigners'."}}, "Missing field 'Signers'."}},
{ "Missing 'Account' in sign_for.", { "Missing 'Account' in sign_for.",
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Amount": "1000000000", "Amount": "1000000000",
@@ -723,7 +692,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -744,7 +712,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -765,7 +732,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -786,7 +752,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -807,7 +772,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -828,7 +792,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -850,7 +813,6 @@ R"({
R"({ R"({
"command": "doesnt_matter", "command": "doesnt_matter",
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"signing_for": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"secret": "masterpassphrase", "secret": "masterpassphrase",
"tx_json": { "tx_json": {
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -870,21 +832,14 @@ R"({
{ "Minimal submit_multisigned.", { "Minimal submit_multisigned.",
R"({ R"({
"command": "submit_multisigned", "command": "submit_multisigned",
"MultiSigners": [ "Signers": [
{ {
"SigningFor": { "Signer": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"SigningAccounts": [
{
"SigningAccount": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux", "Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"MultiSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE", "TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8" "SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
} }
} }
]
}
}
], ],
"tx_json": { "tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",

View File

@@ -364,39 +364,8 @@ public:
env(noop("alice"), msig("carol")); env(noop("alice"), msig("carol"));
env(noop("alice"), msig("bob", "carol")); env(noop("alice"), msig("bob", "carol"));
env(noop("alice"), msig("bob", "carol", "dilbert"), ter(tefBAD_SIGNATURE)); env(noop("alice"), msig("bob", "carol", "dilbert"), ter(tefBAD_SIGNATURE));
}
// Two level Multi-sign env(signers("alice", none));
void
testMultiSign2()
{
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol");
env.fund(XRP(10000), "david", "eric", "frank", "greg");
env(signers("alice", 2, { { "bob", 1 }, { "carol", 1 } }));
env(signers("bob", 1, { { "david", 1 }, { "eric", 1 } }));
env(signers("carol", 1, { { "frank", 1 }, { "greg", 1 } }));
env(noop("alice"), msig2(
{ { "bob", "david" } }), ter(tefBAD_QUORUM));
env(noop("alice"), msig2(
{ { "bob", "david" }, { "bob", "eric" } }), ter(tefBAD_QUORUM));
env(noop("alice"), msig2(
{ { "carol", "frank" } }), ter(tefBAD_QUORUM));
env(noop("alice"), msig2(
{ { "carol", "frank" }, { "carol", "greg" } }), ter(tefBAD_QUORUM));
env(noop("alice"), msig2(
{ { "bob", "david" }, { "carol", "frank" } }));
env(noop("alice"), msig2(
{ { "bob", "david" }, { "bob", "eric" },
{ "carol", "frank" }, { "carol", "greg" } }));
} }
void void
@@ -581,7 +550,6 @@ public:
testKeyType(); testKeyType();
testPayments(); testPayments();
testMultiSign(); testMultiSign();
testMultiSign2();
testTicket(); testTicket();
testJTxProperties(); testJTxProperties();
testProp(); testProp();

View File

@@ -22,6 +22,7 @@
#include <ripple/test/jtx/utility.h> #include <ripple/test/jtx/utility.h>
#include <ripple/protocol/HashPrefix.h> #include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/types.h> #include <ripple/protocol/types.h>
namespace ripple { namespace ripple {
@@ -36,15 +37,15 @@ signers (Account const& account,
Json::Value jv; Json::Value jv;
jv[jss::Account] = account.human(); jv[jss::Account] = account.human();
jv[jss::TransactionType] = "SignerListSet"; jv[jss::TransactionType] = "SignerListSet";
jv["SignerQuorum"] = quorum; jv[sfSignerQuorum.getJsonName()] = quorum;
auto& ja = jv["SignerEntries"]; auto& ja = jv[sfSignerEntries.getJsonName()];
ja.resize(v.size()); ja.resize(v.size());
for(std::size_t i = 0; i < v.size(); ++i) for(std::size_t i = 0; i < v.size(); ++i)
{ {
auto const& e = v[i]; auto const& e = v[i];
auto& je = ja[i]["SignerEntry"]; auto& je = ja[i][sfSignerEntry.getJsonName()];
je[jss::Account] = e.account.human(); je[jss::Account] = e.account.human();
je["SignerWeight"] = e.weight; je[sfSignerWeight.getJsonName()] = e.weight;
} }
return jv; return jv;
} }
@@ -55,24 +56,30 @@ signers (Account const& account, none_t)
Json::Value jv; Json::Value jv;
jv[jss::Account] = account.human(); jv[jss::Account] = account.human();
jv[jss::TransactionType] = "SignerListSet"; jv[jss::TransactionType] = "SignerListSet";
jv[sfSignerQuorum.getJsonName()] = 0;
return jv; return jv;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
msig::msig (std::vector<msig::Reg> signers_)
: signers(std::move(signers_))
{
// Signatures must be applied in sorted order.
std::sort(signers.begin(), signers.end(),
[](msig::Reg const& lhs, msig::Reg const& rhs)
{
return lhs.acct.id() < rhs.acct.id();
});
}
void void
msig::operator()(Env const& env, JTx& jt) const msig::operator()(Env const& env, JTx& jt) const
{ {
// VFALCO Inefficient pre-C++14 auto const mySigners = signers;
auto accounts = accounts_; jt.signer = [mySigners, &env](Env&, JTx& jt)
std::sort(accounts.begin(), accounts.end(),
[](Account const& lhs, Account const& rhs)
{ {
return lhs.id() < rhs.id(); jt[sfSigningPubKey.getJsonName()] = "";
});
jt.signer = [accounts, &env](Env&, JTx& jt)
{
jt["SigningPubKey"] = "";
boost::optional<STObject> st; boost::optional<STObject> st;
try try
{ {
@@ -83,107 +90,24 @@ msig::operator()(Env const& env, JTx& jt) const
env.test.log << pretty(jt.jv); env.test.log << pretty(jt.jv);
throw; throw;
} }
auto const signFor = auto& js = jt[sfSigners.getJsonName()];
parseBase58<AccountID>( js.resize(mySigners.size());
jt.jv[jss::Account].asString()); for(std::size_t i = 0; i < mySigners.size(); ++i)
if (! signFor)
{ {
env.test.log << auto const& e = mySigners[i];
"invalid AccountID: '" << auto& jo = js[i][sfSigner.getJsonName()];
jt.jv[jss::Account].asString() << "'"; jo[jss::Account] = e.acct.human();
throw parse_error("msig: bad Account"); jo[jss::SigningPubKey] = strHex(e.sig.pk().slice());
}
auto& jv = jt["MultiSigners"][0u]["SigningFor"];
jv[jss::Account] = jt[jss::Account];
auto& js = jv["SigningAccounts"];
js.resize(accounts.size());
for(std::size_t i = 0; i < accounts.size(); ++i)
{
auto const& e = accounts[i];
auto& jo = js[i]["SigningAccount"];
jo[jss::Account] = e.human();
jo[jss::SigningPubKey] = strHex(e.pk().slice());
Serializer ss; Serializer ss {buildMultiSigningData (*st, e.acct.id())};
ss.add32 (HashPrefix::txMultiSign); auto const sig = ripple::sign (
st->addWithoutSigningFields(ss); *publicKeyType(e.sig.pk().slice()), e.sig.sk(), ss.slice());
ss.add160(*signFor); jo[sfTxnSignature.getJsonName()] =
ss.add160(e.id());
auto const sig = ripple::sign(
*publicKeyType(e.pk().slice()),
e.sk(), ss.slice());
jo["MultiSignature"] =
strHex(Slice{ sig.data(), sig.size() }); strHex(Slice{ sig.data(), sig.size() });
} }
}; };
} }
msig2_t::msig2_t (std::vector<std::pair<
Account, Account>> sigs)
{
for (auto& sig : sigs)
{
auto result = sigs_.emplace(
std::piecewise_construct,
std::make_tuple(std::move(sig.first)),
std::make_tuple());
result.first->second.emplace(
std::move(sig.second));
}
}
void
msig2_t::operator()(Env const& env, JTx& jt) const
{
// VFALCO Inefficient pre-C++14
auto const sigs = sigs_;
jt.signer = [sigs, &env](Env&, JTx& jt)
{
jt["SigningPubKey"] = "";
boost::optional<STObject> st;
try
{
st = parse(jt.jv);
}
catch(parse_error const&)
{
env.test.log << pretty(jt.jv);
throw;
}
auto& ja = jt["MultiSigners"];
ja.resize(sigs.size());
for (auto i = std::make_pair(0, sigs.begin());
i.first < sigs.size(); ++i.first, ++i.second)
{
auto const& sign_for = i.second->first;
auto const& list = i.second->second;
auto& ji = ja[i.first]["SigningFor"];
ji[jss::Account] = sign_for.human();
auto& js = ji["SigningAccounts"];
js.resize(list.size());
for (auto j = std::make_pair(0, list.begin());
j.first < list.size(); ++j.first, ++j.second)
{
auto& jj = js[j.first]["SigningAccount"];
jj[jss::Account] = j.second->human();
jj[jss::SigningPubKey] = strHex(
j.second->pk().slice());
Serializer ss;
ss.add32 (HashPrefix::txMultiSign);
st->addWithoutSigningFields(ss);
ss.add160(sign_for.id());
ss.add160(j.second->id());
auto const sig = ripple::sign(
*publicKeyType(j.second->pk().slice()),
j.second->sk(), ss.slice());
jj["MultiSignature"] =
strHex(Slice{ sig.data(), sig.size() });
}
}
};
}
} // jtx } // jtx
} // test } // test
} // ripple } // ripple

View File

@@ -58,14 +58,42 @@ signers (Account const& account, none_t);
/** Set a multisignature on a JTx. */ /** Set a multisignature on a JTx. */
class msig class msig
{ {
private: public:
std::vector<Account> accounts_; struct Reg
{
Account acct;
Account sig;
Reg (Account const& masterSig)
: acct (masterSig)
, sig (masterSig)
{ }
Reg (Account const& acct_, Account const& regularSig)
: acct (acct_)
, sig (regularSig)
{ }
Reg (char const* masterSig)
: acct (masterSig)
, sig (masterSig)
{ }
Reg (char const* acct_, char const* regularSig)
: acct (acct_)
, sig (regularSig)
{ }
bool operator< (Reg const& rhs) const
{
return acct < rhs.acct;
}
};
std::vector<Reg> signers;
public: public:
msig (std::vector<Account> accounts) msig (std::vector<Reg> signers_);
: accounts_(std::move(accounts))
{
}
template <class AccountType, class... Accounts> template <class AccountType, class... Accounts>
msig (AccountType&& a0, Accounts&&... aN) msig (AccountType&& a0, Accounts&&... aN)
@@ -82,17 +110,17 @@ private:
template <class AccountType> template <class AccountType>
static static
void void
helper (std::vector<Account>& v, helper (std::vector<Reg>& v,
AccountType&& account) AccountType&& account)
{ {
v.emplace_back(std::forward< v.emplace_back(std::forward<
Account>(account)); Reg>(account));
} }
template <class AccountType, class... Accounts> template <class AccountType, class... Accounts>
static static
void void
helper (std::vector<Account>& v, helper (std::vector<Reg>& v,
AccountType&& a0, Accounts&&... aN) AccountType&& a0, Accounts&&... aN)
{ {
helper(v, std::forward<AccountType>(a0)); helper(v, std::forward<AccountType>(a0));
@@ -101,44 +129,19 @@ private:
template <class... Accounts> template <class... Accounts>
static static
std::vector<Account> std::vector<Reg>
make_vector(Accounts&&... accounts) make_vector(Accounts&&... signers)
{ {
std::vector<Account> v; std::vector<Reg> v;
v.reserve(sizeof...(accounts)); v.reserve(sizeof...(signers));
helper(v, std::forward< helper(v, std::forward<
Accounts>(accounts)...); Accounts>(signers)...);
return v; return v;
} }
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** Set a multisignature on a JTx. */
class msig2_t
{
private:
std::map<Account,
std::set<Account>> sigs_;
public:
msig2_t (std::vector<std::pair<
Account, Account>> sigs);
void
operator()(Env const&, JTx& jt) const;
};
inline
msig2_t
msig2 (std::vector<std::pair<
Account, Account>> sigs)
{
return msig2_t(std::move(sigs));
}
//------------------------------------------------------------------------------
/** The number of signer lists matches. */ /** The number of signer lists matches. */
using siglists = owner_count<ltSIGNER_LIST>; using siglists = owner_count<ltSIGNER_LIST>;

View File

@@ -43,7 +43,6 @@
#include <ripple/app/tx/impl/TransactionAcquire.cpp> #include <ripple/app/tx/impl/TransactionAcquire.cpp>
#include <ripple/app/tx/impl/Transactor.cpp> #include <ripple/app/tx/impl/Transactor.cpp>
#include <ripple/app/tx/tests/common_transactor.cpp>
#include <ripple/app/tx/tests/DeliverMin.test.cpp> #include <ripple/app/tx/tests/DeliverMin.test.cpp>
#include <ripple/app/tx/tests/MultiSign.test.cpp> #include <ripple/app/tx/tests/MultiSign.test.cpp>
#include <ripple/app/tx/tests/OfferStream.test.cpp> #include <ripple/app/tx/tests/OfferStream.test.cpp>