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