Skip to content

Commit 0b5220b

Browse files
committed
rippled backport of nestedsig
1 parent 3a17230 commit 0b5220b

File tree

10 files changed

+1024
-76
lines changed

10 files changed

+1024
-76
lines changed

include/xrpl/protocol/detail/features.macro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ XRPL_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo
6969
XRPL_FIX (UniversalNumber, Supported::yes, VoteBehavior::DefaultNo)
7070
XRPL_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo)
7171
XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultYes)
72+
XRPL_FEATURE(NestedMultiSign, Supported::yes, VoteBehavior::DefaultNo)
7273

7374
// The following amendments are obsolete, but must remain supported
7475
// because they could potentially get enabled.

src/libxrpl/protocol/InnerObjectFormats.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ InnerObjectFormats::InnerObjectFormats()
2121
sfSigner.getCode(),
2222
{
2323
{sfAccount, soeREQUIRED},
24-
{sfSigningPubKey, soeREQUIRED},
25-
{sfTxnSignature, soeREQUIRED},
24+
{sfSigningPubKey, soeOPTIONAL},
25+
{sfTxnSignature, soeOPTIONAL},
26+
{sfSigners, soeOPTIONAL},
2627
});
2728

2829
add(sfMajority.jsonName,

src/libxrpl/protocol/STTx.cpp

Lines changed: 91 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -431,58 +431,104 @@ multiSignHelper(
431431

432432
STArray const& signers{sigObject.getFieldArray(sfSigners)};
433433

434-
// There are well known bounds that the number of signers must be within.
435-
if (signers.size() < STTx::minMultiSigners || signers.size() > STTx::maxMultiSigners)
436-
return Unexpected("Invalid Signers array size.");
434+
// Set max depth based on feature flag
435+
int const maxDepth = rules.enabled(featureNestedMultiSign) ? 4 : 1;
437436

438-
// Signers must be in sorted order by AccountID.
439-
AccountID lastAccountID(beast::zero);
437+
// Define recursive lambda for checking signatures at any depth
438+
std::function<Expected<void, std::string>(STArray const&, int)> checkSignersArray;
440439

441-
for (auto const& signer : signers)
442-
{
443-
auto const accountID = signer.getAccountID(sfAccount);
440+
checkSignersArray = [&](STArray const& signersArray, int depth) -> Expected<void, std::string> {
441+
// Check depth limit
442+
if (depth > maxDepth)
443+
return Unexpected("Multi-signing depth limit exceeded.");
444444

445-
// The account owner may not usually multisign for themselves.
446-
// If they can, txnAccountID will be unseated, which is not equal to any
447-
// value.
448-
if (txnAccountID == accountID)
449-
return Unexpected("Invalid multisigner.");
445+
// There are well known bounds that the number of signers must be
446+
// within.
447+
if (signers.size() < STTx::minMultiSigners || signers.size() > STTx::maxMultiSigners)
448+
return Unexpected("Invalid Signers array size.");
450449

451-
// No duplicate signers allowed.
452-
if (lastAccountID == accountID)
453-
return Unexpected("Duplicate Signers not allowed.");
450+
// Signers must be in sorted order by AccountID.
451+
AccountID lastAccountID(beast::zero);
454452

455-
// Accounts must be in order by account ID. No duplicates allowed.
456-
if (lastAccountID > accountID)
457-
return Unexpected("Unsorted Signers array.");
453+
for (auto const& signer : signersArray)
454+
{
455+
auto const accountID = signer.getAccountID(sfAccount);
458456

459-
// The next signature must be greater than this one.
460-
lastAccountID = accountID;
457+
// The account owner may not multisign for themselves.
458+
if (accountID == txnAccountID)
459+
return Unexpected("Invalid multisigner.");
461460

462-
// Verify the signature.
463-
bool validSig = false;
464-
std::optional<std::string> errorWhat;
465-
try
466-
{
467-
auto spk = signer.getFieldVL(sfSigningPubKey);
468-
if (publicKeyType(makeSlice(spk)))
461+
// No duplicate signers allowed.
462+
if (lastAccountID == accountID)
463+
return Unexpected("Duplicate Signers not allowed.");
464+
465+
// Accounts must be in order by account ID. No duplicates allowed.
466+
if (lastAccountID > accountID)
467+
return Unexpected("Unsorted Signers array.");
468+
469+
// The next signature must be greater than this one.
470+
lastAccountID = accountID;
471+
472+
// Check if this signer has nested signers
473+
if (signer.isFieldPresent(sfSigners))
469474
{
470-
Blob const signature = signer.getFieldVL(sfTxnSignature);
471-
validSig = verify(PublicKey(makeSlice(spk)), makeMsg(accountID).slice(), makeSlice(signature));
475+
// This is a nested multi-signer
476+
if (maxDepth == 1)
477+
{
478+
// amendment is not enabled, this is an error
479+
return Unexpected("FeatureNestedMultiSign is disabled");
480+
}
481+
482+
// Ensure it doesn't also have signature fields
483+
if (signer.isFieldPresent(sfSigningPubKey) || signer.isFieldPresent(sfTxnSignature))
484+
return Unexpected(
485+
"Signer cannot have both nested signers and signature "
486+
"fields.");
487+
488+
// Recursively check nested signers
489+
STArray const& nestedSigners = signer.getFieldArray(sfSigners);
490+
auto result = checkSignersArray(nestedSigners, depth + 1);
491+
if (!result)
492+
return result;
493+
}
494+
else
495+
{
496+
// This is a leaf node - must have signature
497+
if (!signer.isFieldPresent(sfSigningPubKey) || !signer.isFieldPresent(sfTxnSignature))
498+
return Unexpected(
499+
"Leaf signer must have SigningPubKey and "
500+
"TxnSignature.");
501+
502+
// Verify the signature
503+
bool validSig = false;
504+
std::optional<std::string> errorWhat;
505+
try
506+
{
507+
auto spk = signer.getFieldVL(sfSigningPubKey);
508+
if (publicKeyType(makeSlice(spk)))
509+
{
510+
Blob const signature = signer.getFieldVL(sfTxnSignature);
511+
validSig = verify(PublicKey(makeSlice(spk)), makeMsg(accountID).slice(), makeSlice(signature));
512+
}
513+
}
514+
catch (std::exception const& e)
515+
{
516+
// We assume any problem lies with the signature.
517+
validSig = false;
518+
errorWhat = e.what();
519+
}
520+
if (!validSig)
521+
return Unexpected(
522+
std::string("Invalid signature on account ") + toBase58(accountID) + errorWhat.value_or("") +
523+
".");
472524
}
473525
}
474-
catch (std::exception const& e)
475-
{
476-
// We assume any problem lies with the signature.
477-
validSig = false;
478-
errorWhat = e.what();
479-
}
480-
if (!validSig)
481-
return Unexpected(
482-
std::string("Invalid signature on account ") + toBase58(accountID) + errorWhat.value_or("") + ".");
483-
}
484-
// All signatures verified.
485-
return {};
526+
527+
return {};
528+
};
529+
530+
// Start the recursive check at depth 1
531+
return checkSignersArray(signers, 1);
486532
}
487533

488534
Expected<void, std::string>
@@ -511,6 +557,9 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
511557
// the account owner may not multisign for themselves.
512558
auto const txnAccountID = &sigObject != this ? std::nullopt : std::optional<AccountID>(getAccountID(sfAccount));
513559

560+
// Set max depth based on feature flag
561+
int const maxDepth = rules.enabled(featureNestedMultiSign) ? 4 : 1;
562+
514563
// We can ease the computational load inside the loop a bit by
515564
// pre-constructing part of the data that we hash. Fill a Serializer
516565
// with the stuff that stays constant from signature to signature.

0 commit comments

Comments
 (0)