Compare commits

..

2 Commits

Author SHA1 Message Date
Pratik Mankawde
c4162da6ab Merge branch 'develop' into pratik/openssl_354_test 2025-11-12 14:24:34 +00:00
Pratik Mankawde
07a2a4327e added openssl test
Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>
2025-11-12 14:24:09 +00:00
22 changed files with 508 additions and 148 deletions

View File

@@ -69,9 +69,9 @@ jobs:
ENABLED_VOIDSTAR: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
ENABLED_COVERAGE: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
steps:
- name: Cleanup workspace (macOS and Windows)
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
uses: XRPLF/actions/.github/actions/cleanup-workspace@01b244d2718865d427b499822fbd3f15e7197fcc
- name: Cleanup workspace
if: ${{ runner.os == 'macOS' }}
uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

View File

@@ -62,9 +62,9 @@ jobs:
runs-on: ${{ matrix.architecture.runner }}
container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || null }}
steps:
- name: Cleanup workspace (macOS and Windows)
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
uses: XRPLF/actions/.github/actions/cleanup-workspace@01b244d2718865d427b499822fbd3f15e7197fcc
- name: Cleanup workspace
if: ${{ runner.os == 'macOS' }}
uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

View File

@@ -223,14 +223,19 @@ publicKeyType(PublicKey const& publicKey)
verifyDigest(
PublicKey const& publicKey,
uint256 const& digest,
Slice const& sig) noexcept;
Slice const& sig,
bool mustBeFullyCanonical = true) noexcept;
/** Verify a signature on a message.
With secp256k1 signatures, the data is first hashed with
SHA512-Half, and the resulting digest is signed.
*/
[[nodiscard]] bool
verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept;
verify(
PublicKey const& publicKey,
Slice const& m,
Slice const& sig,
bool mustBeFullyCanonical = true) noexcept;
/** Calculate the 160-bit node ID from a node public key. */
NodeID

View File

@@ -31,8 +31,17 @@ class STTx final : public STObject, public CountedObject<STTx>
TxType tx_type_;
public:
static constexpr std::size_t minMultiSigners = 1;
static constexpr std::size_t maxMultiSigners = 32;
static std::size_t const minMultiSigners = 1;
// if rules are not supplied then the largest possible value is returned
static std::size_t
maxMultiSigners(Rules const* rules = 0)
{
if (rules && !rules->enabled(featureExpandedSignerList))
return 8;
return 32;
}
STTx() = delete;
STTx(STTx const& other) = default;
@@ -103,15 +112,22 @@ public:
std::optional<std::reference_wrapper<SField const>> signatureTarget =
{});
enum class RequireFullyCanonicalSig : bool { no, yes };
/** Check the signature.
@param requireCanonicalSig If `true`, check that the signature is fully
canonical. If `false`, only check that the signature is valid.
@param rules The current ledger rules.
@return `true` if valid signature. If invalid, the error message string.
*/
Expected<void, std::string>
checkSign(Rules const& rules) const;
checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules)
const;
Expected<void, std::string>
checkBatchSign(Rules const& rules) const;
checkBatchSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
// SQL Functions with metadata.
static std::string const&
@@ -133,25 +149,40 @@ public:
private:
/** Check the signature.
@param requireCanonicalSig If `true`, check that the signature is fully
canonical. If `false`, only check that the signature is valid.
@param rules The current ledger rules.
@param sigObject Reference to object that contains the signature fields.
Will be *this more often than not.
@return `true` if valid signature. If invalid, the error message string.
*/
Expected<void, std::string>
checkSign(Rules const& rules, STObject const& sigObject) const;
checkSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules,
STObject const& sigObject) const;
Expected<void, std::string>
checkSingleSign(STObject const& sigObject) const;
checkSingleSign(
RequireFullyCanonicalSig requireCanonicalSig,
STObject const& sigObject) const;
Expected<void, std::string>
checkMultiSign(Rules const& rules, STObject const& sigObject) const;
checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules,
STObject const& sigObject) const;
Expected<void, std::string>
checkBatchSingleSign(STObject const& batchSigner) const;
checkBatchSingleSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig) const;
Expected<void, std::string>
checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const;
checkBatchMultiSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
STBase*
copy(std::size_t n, void* buf) const override;

View File

@@ -65,10 +65,13 @@ XRPL_FIX (UniversalNumber, Supported::yes, VoteBehavior::DefaultNo
XRPL_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DisallowIncoming, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(ExpandedSignerList, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(FlowSortStrands, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(TicketBatch, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(NegativeUNL, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(MultiSignReserve, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes)
@@ -123,16 +126,13 @@ XRPL_RETIRE_FEATURE(DepositAuth)
XRPL_RETIRE_FEATURE(DepositPreauth)
XRPL_RETIRE_FEATURE(Escrow)
XRPL_RETIRE_FEATURE(EnforceInvariants)
XRPL_RETIRE_FEATURE(ExpandedSignerList)
XRPL_RETIRE_FEATURE(FeeEscalation)
XRPL_RETIRE_FEATURE(FlowCross)
XRPL_RETIRE_FEATURE(HardenedValidations)
XRPL_RETIRE_FEATURE(ImmediateOfferKilled)
XRPL_RETIRE_FEATURE(MultiSign)
XRPL_RETIRE_FEATURE(MultiSignReserve)
XRPL_RETIRE_FEATURE(NonFungibleTokensV1_1)
XRPL_RETIRE_FEATURE(PayChan)
XRPL_RETIRE_FEATURE(RequireFullyCanonicalSig)
XRPL_RETIRE_FEATURE(SortedDirectories)
XRPL_RETIRE_FEATURE(TickSize)
XRPL_RETIRE_FEATURE(TrustSetAuth)

View File

@@ -17,7 +17,7 @@ namespace BuildInfo {
// and follow the format described at http://semver.org/
//------------------------------------------------------------------------------
// clang-format off
char const* const versionString = "3.1.0-b0"
char const* const versionString = "3.0.0-b1"
// clang-format on
#if defined(DEBUG) || defined(SANITIZER)

View File

@@ -220,14 +220,16 @@ bool
verifyDigest(
PublicKey const& publicKey,
uint256 const& digest,
Slice const& sig) noexcept
Slice const& sig,
bool mustBeFullyCanonical) noexcept
{
if (publicKeyType(publicKey) != KeyType::secp256k1)
LogicError("sign: secp256k1 required for digest signing");
auto const canonicality = ecdsaCanonicality(sig);
if (!canonicality)
return false;
if (*canonicality != ECDSACanonicality::fullyCanonical)
if (mustBeFullyCanonical &&
(*canonicality != ECDSACanonicality::fullyCanonical))
return false;
secp256k1_pubkey pubkey_imp;
@@ -265,13 +267,18 @@ verifyDigest(
}
bool
verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept
verify(
PublicKey const& publicKey,
Slice const& m,
Slice const& sig,
bool mustBeFullyCanonical) noexcept
{
if (auto const type = publicKeyType(publicKey))
{
if (*type == KeyType::secp256k1)
{
return verifyDigest(publicKey, sha512Half(m), sig);
return verifyDigest(
publicKey, sha512Half(m), sig, mustBeFullyCanonical);
}
else if (*type == KeyType::ed25519)
{

View File

@@ -237,7 +237,10 @@ STTx::sign(
}
Expected<void, std::string>
STTx::checkSign(Rules const& rules, STObject const& sigObject) const
STTx::checkSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules,
STObject const& sigObject) const
{
try
{
@@ -246,8 +249,9 @@ STTx::checkSign(Rules const& rules, STObject const& sigObject) const
// multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = sigObject.getFieldVL(sfSigningPubKey);
return signingPubKey.empty() ? checkMultiSign(rules, sigObject)
: checkSingleSign(sigObject);
return signingPubKey.empty()
? checkMultiSign(requireCanonicalSig, rules, sigObject)
: checkSingleSign(requireCanonicalSig, sigObject);
}
catch (std::exception const&)
{
@@ -256,16 +260,18 @@ STTx::checkSign(Rules const& rules, STObject const& sigObject) const
}
Expected<void, std::string>
STTx::checkSign(Rules const& rules) const
STTx::checkSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
if (auto const ret = checkSign(rules, *this); !ret)
if (auto const ret = checkSign(requireCanonicalSig, rules, *this); !ret)
return ret;
/* Placeholder for field that will be added by Lending Protocol
if (isFieldPresent(sfCounterpartySignature))
{
auto const counterSig = getFieldObject(sfCounterpartySignature);
if (auto const ret = checkSign(rules, counterSig);
if (auto const ret = checkSign(requireCanonicalSig, rules, counterSig);
!ret)
return Unexpected("Counterparty: " + ret.error());
}
@@ -274,7 +280,9 @@ STTx::checkSign(Rules const& rules) const
}
Expected<void, std::string>
STTx::checkBatchSign(Rules const& rules) const
STTx::checkBatchSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
try
{
@@ -291,8 +299,8 @@ STTx::checkBatchSign(Rules const& rules) const
{
Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey);
auto const result = signingPubKey.empty()
? checkBatchMultiSign(signer, rules)
: checkBatchSingleSign(signer);
? checkBatchMultiSign(signer, requireCanonicalSig, rules)
: checkBatchSingleSign(signer, requireCanonicalSig);
if (!result)
return result;
@@ -387,7 +395,10 @@ STTx::getMetaSQL(
}
static Expected<void, std::string>
singleSignHelper(STObject const& sigObject, Slice const& data)
singleSignHelper(
STObject const& sigObject,
Slice const& data,
bool const fullyCanonical)
{
// 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
@@ -402,8 +413,11 @@ singleSignHelper(STObject const& sigObject, Slice const& data)
if (publicKeyType(makeSlice(spk)))
{
Blob const signature = sigObject.getFieldVL(sfTxnSignature);
validSig =
verify(PublicKey(makeSlice(spk)), data, makeSlice(signature));
validSig = verify(
PublicKey(makeSlice(spk)),
data,
makeSlice(signature),
fullyCanonical);
}
}
catch (std::exception const&)
@@ -418,24 +432,33 @@ singleSignHelper(STObject const& sigObject, Slice const& data)
}
Expected<void, std::string>
STTx::checkSingleSign(STObject const& sigObject) const
STTx::checkSingleSign(
RequireFullyCanonicalSig requireCanonicalSig,
STObject const& sigObject) const
{
auto const data = getSigningData(*this);
return singleSignHelper(sigObject, makeSlice(data));
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);
return singleSignHelper(sigObject, makeSlice(data), fullyCanonical);
}
Expected<void, std::string>
STTx::checkBatchSingleSign(STObject const& batchSigner) const
STTx::checkBatchSingleSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig) const
{
Serializer msg;
serializeBatch(msg, getFlags(), getBatchTransactionIDs());
return singleSignHelper(batchSigner, msg.slice());
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);
return singleSignHelper(batchSigner, msg.slice(), fullyCanonical);
}
Expected<void, std::string>
multiSignHelper(
STObject const& sigObject,
std::optional<AccountID> txnAccountID,
bool const fullyCanonical,
std::function<Serializer(AccountID const&)> makeMsg,
Rules const& rules)
{
@@ -453,7 +476,7 @@ multiSignHelper(
// There are well known bounds that the number of signers must be within.
if (signers.size() < STTx::minMultiSigners ||
signers.size() > STTx::maxMultiSigners)
signers.size() > STTx::maxMultiSigners(&rules))
return Unexpected("Invalid Signers array size.");
// Signers must be in sorted order by AccountID.
@@ -492,7 +515,8 @@ multiSignHelper(
validSig = verify(
PublicKey(makeSlice(spk)),
makeMsg(accountID).slice(),
makeSlice(signature));
makeSlice(signature),
fullyCanonical);
}
}
catch (std::exception const& e)
@@ -511,8 +535,14 @@ multiSignHelper(
}
Expected<void, std::string>
STTx::checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const
STTx::checkBatchMultiSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature.
@@ -521,6 +551,7 @@ STTx::checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const
return multiSignHelper(
batchSigner,
std::nullopt,
fullyCanonical,
[&dataStart](AccountID const& accountID) -> Serializer {
Serializer s = dataStart;
finishMultiSigningData(accountID, s);
@@ -530,8 +561,14 @@ STTx::checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const
}
Expected<void, std::string>
STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
STTx::checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules,
STObject const& sigObject) const
{
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
// Used inside the loop in multiSignHelper to enforce that
// the account owner may not multisign for themselves.
auto const txnAccountID = &sigObject != this
@@ -545,6 +582,7 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
return multiSignHelper(
sigObject,
txnAccountID,
fullyCanonical,
[&dataStart](AccountID const& accountID) -> Serializer {
Serializer s = dataStart;
finishMultiSigningData(accountID, s);

View File

@@ -3534,7 +3534,8 @@ private:
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
env.close();
env.require(owners(alice, 2));
int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
env.require(owners(alice, signerListOwners + 0));
msig const ms{becky, bogie};
@@ -3794,13 +3795,20 @@ private:
void
testMultisign()
{
testTxMultisign(jtx::testable_amendments());
using namespace jtx;
auto const all = testable_amendments();
testTxMultisign(
all - featureMultiSignReserve - featureExpandedSignerList);
testTxMultisign(all - featureExpandedSignerList);
testTxMultisign(all);
}
void
testPayStrand()
{
auto const all = jtx::testable_amendments();
using namespace jtx;
auto const all = testable_amendments();
testToStrand(all);
testRIPD1373(all);

View File

@@ -963,8 +963,14 @@ class Check_test : public beast::unit_test::suite
}
// Use a regular key and also multisign to cash a check.
// featureMultiSignReserve changes the reserve on a SignerList, so
// check both before and after.
for (auto const& testFeatures :
{features - featureMultiSignReserve,
features | featureMultiSignReserve})
{
Env env{*this, features};
Env env{*this, testFeatures};
env.fund(XRP(1000), gw, alice, bob);
env.close();
@@ -993,7 +999,11 @@ class Check_test : public beast::unit_test::suite
env(signers(bob, 2, {{bogie, 1}, {demon, 1}}), sig(bobby));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 2);
// If featureMultiSignReserve is enabled then bob's signer list
// has an owner count of 1, otherwise it's 4.
int const signersCount = {
testFeatures[featureMultiSignReserve] ? 1 : 4};
BEAST_EXPECT(ownerCount(env, bob) == signersCount + 1);
// bob uses his regular key to cash a check.
env(check::cash(bob, chkId1, (USD(1))), sig(bobby));
@@ -1003,7 +1013,7 @@ class Check_test : public beast::unit_test::suite
BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(ownerCount(env, bob) == 2);
BEAST_EXPECT(ownerCount(env, bob) == signersCount + 1);
// bob uses multisigning to cash a check.
XRPAmount const baseFeeDrops{env.current()->fees().base};
@@ -1016,7 +1026,7 @@ class Check_test : public beast::unit_test::suite
BEAST_EXPECT(checksOnAccount(env, alice).size() == 0);
BEAST_EXPECT(checksOnAccount(env, bob).size() == 0);
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(ownerCount(env, bob) == 2);
BEAST_EXPECT(ownerCount(env, bob) == signersCount + 1);
}
}
@@ -1595,8 +1605,13 @@ class Check_test : public beast::unit_test::suite
Account const zoe{"zoe"};
IOU const USD{gw["USD"]};
// featureMultiSignReserve changes the reserve on a SignerList, so
// check both before and after.
for (auto const& testFeatures :
{features - featureMultiSignReserve,
features | featureMultiSignReserve})
{
Env env{*this, features};
Env env{*this, testFeatures};
env.fund(XRP(1000), gw, alice, bob, zoe);
env.close();
@@ -1714,11 +1729,16 @@ class Check_test : public beast::unit_test::suite
env(signers(alice, 2, {{bogie, 1}, {demon, 1}}), sig(alie));
env.close();
// If featureMultiSignReserve is enabled then alices's signer list
// has an owner count of 1, otherwise it's 4.
int const signersCount{
testFeatures[featureMultiSignReserve] ? 1 : 4};
// alice uses her regular key to cancel a check.
env(check::cancel(alice, chkIdReg), sig(alie));
env.close();
BEAST_EXPECT(checksOnAccount(env, alice).size() == 3);
BEAST_EXPECT(ownerCount(env, alice) == 4);
BEAST_EXPECT(ownerCount(env, alice) == signersCount + 3);
// alice uses multisigning to cancel a check.
XRPAmount const baseFeeDrops{env.current()->fees().base};
@@ -1727,18 +1747,18 @@ class Check_test : public beast::unit_test::suite
fee(3 * baseFeeDrops));
env.close();
BEAST_EXPECT(checksOnAccount(env, alice).size() == 2);
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(ownerCount(env, alice) == signersCount + 2);
// Creator and destination cancel the remaining unexpired checks.
env(check::cancel(alice, chkId3), sig(alice));
env.close();
BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(ownerCount(env, alice) == signersCount + 1);
env(check::cancel(bob, chkIdNotExp3));
env.close();
BEAST_EXPECT(checksOnAccount(env, alice).size() == 0);
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(ownerCount(env, alice) == signersCount + 0);
}
}

View File

@@ -54,31 +54,37 @@ public:
Env env{*this, features};
Account const alice{"alice", KeyType::secp256k1};
// The reserve required for a signer list changes with the passage
// of featureMultiSignReserve. Make the required adjustments.
bool const reserve1{features[featureMultiSignReserve]};
// Pay alice enough to meet the initial reserve, but not enough to
// meet the reserve for a SignerListSet.
auto const fee = env.current()->fees().base;
env.fund(XRP(250) - drops(1), alice);
auto const smallSignersReserve = reserve1 ? XRP(250) : XRP(350);
env.fund(smallSignersReserve - drops(1), alice);
env.close();
env.require(owners(alice, 0));
{
// Attach a signer list to alice. Should fail.
Json::Value signersList = signers(alice, 1, {{bogie, 1}});
env(signersList, ter(tecINSUFFICIENT_RESERVE));
Json::Value smallSigners = signers(alice, 1, {{bogie, 1}});
env(smallSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require(owners(alice, 0));
// Fund alice enough to set the signer list, then attach signers.
env(pay(env.master, alice, fee + drops(1)));
env.close();
env(signersList);
env(smallSigners);
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, reserve1 ? 1 : 3));
}
{
// Pay alice enough to almost make the reserve for the biggest
// possible list.
env(pay(env.master, alice, fee - drops(1)));
auto const addReserveBigSigners = reserve1 ? XRP(0) : XRP(350);
env(pay(env.master, alice, addReserveBigSigners + fee - drops(1)));
// Replace with the biggest possible signer list. Should fail.
Json::Value bigSigners = signers(
@@ -94,14 +100,14 @@ public:
{spook, 1}});
env(bigSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, reserve1 ? 1 : 3));
// Fund alice one more drop (plus the fee) and succeed.
env(pay(env.master, alice, fee + drops(1)));
env.close();
env(bigSigners);
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, reserve1 ? 1 : 10));
}
// Remove alice's signer list and get the owner count back.
env(signers(alice, jtx::none));
@@ -164,12 +170,14 @@ public:
ter(temBAD_QUORUM));
// clang-format off
// Make a signer list that's too big. Should fail.
// Make a signer list that's too big. Should fail. (Even with
// ExpandedSignerList)
Account const spare("spare", KeyType::secp256k1);
env(signers(
alice,
1,
std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
features[featureExpandedSignerList]
? std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1},
{shade, 1}, {spook, 1}, {spare, 1},
{acc10, 1}, {acc11, 1}, {acc12, 1},
@@ -179,7 +187,10 @@ public:
{acc22, 1}, {acc23, 1}, {acc24, 1},
{acc25, 1}, {acc26, 1}, {acc27, 1},
{acc28, 1}, {acc29, 1}, {acc30, 1},
{acc31, 1}, {acc32, 1}, {acc33, 1}}),
{acc31, 1}, {acc32, 1}, {acc33, 1}}
: std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1},
{shade, 1}, {spook, 1}, {spare, 1}}),
ter(temMALFORMED));
// clang-format on
env.close();
@@ -200,7 +211,7 @@ public:
// Attach phantom signers to alice and use them for a transaction.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 4));
// This should work.
auto const baseFee = env.current()->fees().base;
@@ -277,7 +288,7 @@ public:
{shade, 1},
{spook, 1}}));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 10));
// This should work.
auto const baseFee = env.current()->fees().base;
@@ -332,7 +343,7 @@ public:
// Make sure the transaction fails if they are not.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 4));
msig phantoms{bogie, demon};
std::reverse(phantoms.signers.begin(), phantoms.signers.end());
@@ -371,7 +382,7 @@ public:
// Attach signers to alice
env(signers(alice, 4, {{becky, 3}, {cheri, 4}}), sig(alice));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 4));
// Attempt a multisigned transaction that meets the quorum.
auto const baseFee = env.current()->fees().base;
@@ -740,7 +751,7 @@ public:
env(signers(alice, 1, {{becky, 1}, {cheri, 1}, {daria, 1}, {jinni, 1}}),
sig(alie));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 6));
// Each type of signer should succeed individually.
auto const baseFee = env.current()->fees().base;
@@ -787,7 +798,7 @@ public:
{jinni, 0xFFFF}}),
sig(alie));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 6));
aliceSeq = env.seq(alice);
env(noop(alice),
@@ -818,7 +829,7 @@ public:
{spook, 0xFFFF}}),
sig(alie));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 10));
aliceSeq = env.seq(alice);
env(noop(alice),
@@ -994,7 +1005,8 @@ public:
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
env.close();
env.require(owners(alice, 1));
int const signerListOwners{features[featureMultiSignReserve] ? 1 : 4};
env.require(owners(alice, signerListOwners + 0));
// Multisign a ttPAYMENT.
auto const baseFee = env.current()->fees().base;
@@ -1024,7 +1036,7 @@ public:
fee(3 * baseFee),
require(lines("alice", 1)));
env.close();
env.require(owners(alice, 2));
env.require(owners(alice, signerListOwners + 1));
// Multisign a ttOFFER_CREATE transaction.
env(pay(gw, alice, USD(50)));
@@ -1037,7 +1049,7 @@ public:
msig(becky, bogie),
fee(3 * baseFee));
env.close();
env.require(owners(alice, 3));
env.require(owners(alice, signerListOwners + 2));
// Now multisign a ttOFFER_CANCEL canceling the offer we just created.
{
@@ -1048,7 +1060,7 @@ public:
fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
env.require(owners(alice, 2));
env.require(owners(alice, signerListOwners + 1));
}
// Multisign a ttSIGNER_LIST_SET.
@@ -1056,7 +1068,7 @@ public:
msig(becky, bogie),
fee(3 * baseFee));
env.close();
env.require(owners(alice, 2));
env.require(owners(alice, features[featureMultiSignReserve] ? 2 : 6));
}
void
@@ -1166,44 +1178,56 @@ public:
"fails local checks: Invalid Signers array size.");
}
{
// Multisign 9 (!ExpandedSignerList) | 33 (ExpandedSignerList) times
// should fail.
JTx tx = env.jt(
noop(alice),
fee(2 * baseFee),
msig(
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie));
features[featureExpandedSignerList] ? msig(
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie)
: msig(
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie));
STTx local = *(tx.stx);
auto const info = submitSTTx(local);
BEAST_EXPECT(
@@ -1422,6 +1446,101 @@ public:
.asString() == "tesSUCCESS");
}
void
testAmendmentTransition()
{
testcase("Amendment Transition");
// The OwnerCount associated with a SignerList changes once the
// featureMultiSignReserve amendment goes live. Create a couple
// of signer lists before and after the amendment goes live and
// verify that the OwnerCount is managed properly for all of them.
using namespace jtx;
Account const alice{"alice", KeyType::secp256k1};
Account const becky{"becky", KeyType::ed25519};
Account const cheri{"cheri", KeyType::secp256k1};
Account const daria{"daria", KeyType::ed25519};
Env env{*this, testable_amendments() - featureMultiSignReserve};
env.fund(XRP(1000), alice, becky, cheri, daria);
env.close();
// Give alice and becky signer lists before the amendment goes live.
env(signers(alice, 1, {{bogie, 1}}));
env(signers(
becky,
1,
{{bogie, 1},
{demon, 1},
{ghost, 1},
{haunt, 1},
{jinni, 1},
{phase, 1},
{shade, 1},
{spook, 1}}));
env.close();
env.require(owners(alice, 3));
env.require(owners(becky, 10));
// Enable the amendment.
env.enableFeature(featureMultiSignReserve);
env.close();
// Give cheri and daria signer lists after the amendment goes live.
env(signers(cheri, 1, {{bogie, 1}}));
env(signers(
daria,
1,
{{bogie, 1},
{demon, 1},
{ghost, 1},
{haunt, 1},
{jinni, 1},
{phase, 1},
{shade, 1},
{spook, 1}}));
env.close();
env.require(owners(alice, 3));
env.require(owners(becky, 10));
env.require(owners(cheri, 1));
env.require(owners(daria, 1));
// Delete becky's signer list; her OwnerCount should drop to zero.
// Replace alice's signer list; her OwnerCount should drop to one.
env(signers(becky, jtx::none));
env(signers(
alice,
1,
{{bogie, 1},
{demon, 1},
{ghost, 1},
{haunt, 1},
{jinni, 1},
{phase, 1},
{shade, 1},
{spook, 1}}));
env.close();
env.require(owners(alice, 1));
env.require(owners(becky, 0));
env.require(owners(cheri, 1));
env.require(owners(daria, 1));
// Delete the three remaining signer lists. Everybody's OwnerCount
// should now be zero.
env(signers(alice, jtx::none));
env(signers(cheri, jtx::none));
env(signers(daria, jtx::none));
env.close();
env.require(owners(alice, 0));
env.require(owners(becky, 0));
env.require(owners(cheri, 0));
env.require(owners(daria, 0));
}
void
testSignersWithTickets(FeatureBitset features)
{
@@ -1466,6 +1585,9 @@ public:
void
testSignersWithTags(FeatureBitset features)
{
if (!features[featureExpandedSignerList])
return;
testcase("Signers With Tags");
using namespace jtx;
@@ -1487,7 +1609,7 @@ public:
// Attach phantom signers to alice and use them for a transaction.
env(signers(alice, 1, {{bogie, 1, bogie_tag}, {demon, 1, demon_tag}}));
env.close();
env.require(owners(alice, 1));
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 4));
// This should work.
auto const baseFee = env.current()->fees().base;
@@ -1622,6 +1744,12 @@ public:
using namespace jtx;
auto const all = testable_amendments();
// The reserve required on a signer list changes based on
// featureMultiSignReserve. Limits on the number of signers
// changes based on featureExpandedSignerList. Test both with and
// without.
testAll(all - featureMultiSignReserve - featureExpandedSignerList);
testAll(all - featureExpandedSignerList);
testAll(all);
testSignerListSetFlags(all - fixInvalidTxFlags);
@@ -1629,6 +1757,8 @@ public:
testSignerListObject(all - fixIncludeKeyletFields);
testSignerListObject(all);
testAmendmentTransition();
}
};

View File

@@ -727,13 +727,13 @@ private:
}
void
testMultisig()
testMultisig(FeatureBitset features)
{
testcase("Multisig");
using namespace jtx;
Oracle::setFee(100'000);
Env env(*this);
Env env(*this, features);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
@@ -753,8 +753,9 @@ private:
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
env.close();
env.require(owners(alice, 1));
// if multiSignReserve disabled then its 2 + 1 per signer
int const signerListOwners{features[featureMultiSignReserve] ? 1 : 5};
env.require(owners(alice, signerListOwners));
// Create
// Force close (true) and time advancement because the close time
@@ -859,7 +860,11 @@ public:
testDelete();
testUpdate();
testAmendment();
testMultisig();
for (auto const& features :
{all,
all - featureMultiSignReserve - featureExpandedSignerList,
all - featureExpandedSignerList})
testMultisig(features);
}
};

View File

@@ -35,6 +35,23 @@ public:
SerialIter sitTrans(makeSlice(*ret));
STTx const tx = *std::make_shared<STTx const>(std::ref(sitTrans));
{
test::jtx::Env no_fully_canonical(
*this,
test::jtx::testable_amendments() -
featureRequireFullyCanonicalSig);
Validity valid = checkValidity(
no_fully_canonical.app().getHashRouter(),
tx,
no_fully_canonical.current()->rules(),
no_fully_canonical.app().config())
.first;
if (valid != Validity::Valid)
fail("Non-Fully canoncial signature was not permitted");
}
{
test::jtx::Env fully_canonical(
*this, test::jtx::testable_amendments());

View File

@@ -798,16 +798,17 @@ public:
{
// a Env FeatureBitset has *only* those features
Env env{*this, FeatureBitset{featureDynamicMPT | featureFlow}};
Env env{*this, FeatureBitset(featureMultiSignReserve, featureFlow)};
BEAST_EXPECT(env.app().config().features.size() == 2);
foreachFeature(supported, [&](uint256 const& f) {
bool const has = (f == featureDynamicMPT || f == featureFlow);
bool const has =
(f == featureMultiSignReserve || f == featureFlow);
this->BEAST_EXPECT(has == hasFeature(env, f));
});
}
auto const missingSomeFeatures =
testable_amendments() - featureDynamicMPT - featureFlow;
testable_amendments() - featureMultiSignReserve - featureFlow;
BEAST_EXPECT(missingSomeFeatures.count() == (supported.count() - 2));
{
// a Env supported_features_except is missing *only* those features
@@ -815,7 +816,8 @@ public:
BEAST_EXPECT(
env.app().config().features.size() == (supported.count() - 2));
foreachFeature(supported, [&](uint256 const& f) {
bool hasnot = (f == featureDynamicMPT || f == featureFlow);
bool hasnot =
(f == featureMultiSignReserve || f == featureFlow);
this->BEAST_EXPECT(hasnot != hasFeature(env, f));
});
}
@@ -827,8 +829,8 @@ public:
// the two supported ones
Env env{
*this,
FeatureBitset{
featureDynamicMPT, featureFlow, *neverSupportedFeat}};
FeatureBitset(
featureMultiSignReserve, featureFlow, *neverSupportedFeat)};
// this app will have just 2 supported amendments and
// one additional never supported feature flag
@@ -836,7 +838,7 @@ public:
BEAST_EXPECT(hasFeature(env, *neverSupportedFeat));
foreachFeature(supported, [&](uint256 const& f) {
bool has = (f == featureDynamicMPT || f == featureFlow);
bool has = (f == featureMultiSignReserve || f == featureFlow);
this->BEAST_EXPECT(has == hasFeature(env, f));
});
}
@@ -856,7 +858,8 @@ public:
(supported.count() - 2 + 1));
BEAST_EXPECT(hasFeature(env, *neverSupportedFeat));
foreachFeature(supported, [&](uint256 const& f) {
bool hasnot = (f == featureDynamicMPT || f == featureFlow);
bool hasnot =
(f == featureMultiSignReserve || f == featureFlow);
this->BEAST_EXPECT(hasnot != hasFeature(env, f));
});
}

View File

@@ -1612,10 +1612,11 @@ public:
// proper lifetime.
std::unordered_set<uint256, beast::uhash<>> const presets;
Rules const defaultRules{presets};
BEAST_EXPECT(!defaultRules.enabled(featureAMM));
BEAST_EXPECT(!defaultRules.enabled(featureExpandedSignerList));
unexpected(
!j.checkSign(defaultRules), "Transaction fails signature test");
!j.checkSign(STTx::RequireFullyCanonicalSig::yes, defaultRules),
"Transaction fails signature test");
Serializer rawTxn;
j.add(rawTxn);

View File

@@ -183,16 +183,16 @@ class Feature_test : public beast::unit_test::suite
using namespace test::jtx;
Env env{*this};
auto jrr = env.rpc("feature", "Flow")[jss::result];
auto jrr = env.rpc("feature", "MultiSignReserve")[jss::result];
BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
jrr.removeMember(jss::status);
BEAST_EXPECT(jrr.size() == 1);
BEAST_EXPECT(
jrr.isMember("740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D6"
"28A06927F11"));
jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
"1FC07EFE41D"));
auto feature = *(jrr.begin());
BEAST_EXPECTS(feature[jss::name] == "Flow", "name");
BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
BEAST_EXPECTS(
feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
@@ -200,7 +200,7 @@ class Feature_test : public beast::unit_test::suite
BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
// feature names are case-sensitive - expect error here
jrr = env.rpc("feature", "flow")[jss::result];
jrr = env.rpc("feature", "multiSignReserve")[jss::result];
BEAST_EXPECT(jrr[jss::error] == "badFeature");
BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
}
@@ -476,8 +476,8 @@ class Feature_test : public beast::unit_test::suite
testcase("Veto");
using namespace test::jtx;
Env env{*this, FeatureBitset{featureFlow}};
constexpr char const* featureName = "Flow";
Env env{*this, FeatureBitset(featureMultiSignReserve)};
constexpr char const* featureName = "MultiSignReserve";
auto jrr = env.rpc("feature", featureName)[jss::result];
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))

View File

@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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 <xrpl/basics/Slice.h>
#include <xrpl/protocol/digest.h>
#include <doctest/doctest.h>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <random>
#include <vector>
using namespace ripple;
static std::vector<char> const data = []() {
std::vector<char> strV(pow(10, 5));
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dis(32, 127);
for (size_t index = 0; index < strV.size(); ++index)
{
strV[index] = static_cast<char>(dis(gen));
}
return strV;
}();
TEST_SUITE_BEGIN("OpenSSL");
TEST_CASE("SingleHashFullSlice")
{
Slice const s{data.data(), data.size()};
auto hash = sha512Half(s);
}
TEST_CASE("MultihashAllSlices")
{
for (std::size_t i = 0; i < data.size(); ++i)
{
Slice s(&data[i], data.size() - i);
auto hash = sha512Half(s);
}
}
TEST_SUITE_END();

View File

@@ -451,7 +451,8 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
}
// Check the batch signers signatures.
auto const sigResult = ctx.tx.checkBatchSign(ctx.rules);
auto const sigResult = ctx.tx.checkBatchSign(
STTx::RequireFullyCanonicalSig::yes, ctx.rules);
if (!sigResult)
{

View File

@@ -142,6 +142,10 @@ SetSignerList::preCompute()
// The return type is signed so it is compatible with the 3rd argument
// of adjustOwnerCount() (which must be signed).
//
// NOTE: This way of computing the OwnerCount associated with a SignerList
// is valid until the featureMultiSignReserve amendment passes. Once it
// passes then just 1 OwnerCount is associated with a SignerList.
static int
signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
{
@@ -158,13 +162,13 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
// units. A SignerList with 8 entries would cost 10 OwnerCount units.
//
// The static_cast should always be safe since entryCount should always
// be in the range from 1 to 32.
// be in the range from 1 to 8 (or 32 if ExpandedSignerList is enabled).
// We've got a lot of room to grow.
XRPL_ASSERT(
entryCount >= STTx::minMultiSigners,
"ripple::signerCountBasedOwnerCountDelta : minimum signers");
XRPL_ASSERT(
entryCount <= STTx::maxMultiSigners,
entryCount <= STTx::maxMultiSigners(&rules),
"ripple::signerCountBasedOwnerCountDelta : maximum signers");
return 2 + static_cast<int>(entryCount);
}
@@ -246,8 +250,8 @@ SetSignerList::validateQuorumAndSignerEntries(
// Reject if there are too many or too few entries in the list.
{
std::size_t const signerCount = signers.size();
if (signerCount < STTx::minMultiSigners ||
signerCount > STTx::maxMultiSigners)
if ((signerCount < STTx::minMultiSigners) ||
(signerCount > STTx::maxMultiSigners(&rules)))
{
JLOG(j.trace()) << "Too many or too few signers in signer list.";
return temMALFORMED;
@@ -265,6 +269,9 @@ SetSignerList::validateQuorumAndSignerEntries(
return temBAD_SIGNER;
}
// Is the ExpandedSignerList amendment active?
bool const expandedSignerList = rules.enabled(featureExpandedSignerList);
// Make sure no signers reference this account. Also make sure the
// quorum can be reached.
std::uint64_t allSignersWeight(0);
@@ -284,6 +291,15 @@ SetSignerList::validateQuorumAndSignerEntries(
JLOG(j.trace()) << "A signer may not self reference account.";
return temBAD_SIGNER;
}
if (signer.tag && !expandedSignerList)
{
JLOG(j.trace()) << "Malformed transaction: sfWalletLocator "
"specified in SignerEntry "
<< "but featureExpandedSignerList is not enabled.";
return temMALFORMED;
}
// Don't verify that the signer accounts exist. Non-existent accounts
// may be phantom accounts (which are permitted).
}
@@ -321,8 +337,15 @@ SetSignerList::replaceSignerList()
// Compute new reserve. Verify the account has funds to meet the reserve.
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
constexpr int addedOwnerCount = 1;
// The required reserve changes based on featureMultiSignReserve...
int addedOwnerCount{1};
std::uint32_t flags{lsfOneOwnerCount};
if (!ctx_.view().rules().enabled(featureMultiSignReserve))
{
addedOwnerCount = signerCountBasedOwnerCountDelta(
signers_.size(), ctx_.view().rules());
flags = 0;
}
XRPAmount const newReserve{
view().fees().accountReserve(oldOwnerCount + addedOwnerCount)};
@@ -392,6 +415,9 @@ SetSignerList::writeSignersToSLE(
if (flags) // Only set flags if they are non-default (default is zero).
ledgerEntry->setFieldU32(sfFlags, flags);
bool const expandedSignerList =
ctx_.view().rules().enabled(featureExpandedSignerList);
// Create the SignerListArray one SignerEntry at a time.
STArray toLedger(signers_.size());
for (auto const& entry : signers_)
@@ -403,8 +429,8 @@ SetSignerList::writeSignersToSLE(
obj[sfSignerWeight] = entry.weight;
// This is a defensive check to make absolutely sure we will never write
// a tag into the ledger.
if (entry.tag)
// a tag into the ledger while featureExpandedSignerList is not enabled
if (expandedSignerList && entry.tag)
obj.setFieldH256(sfWalletLocator, *(entry.tag));
}

View File

@@ -25,7 +25,7 @@ SignerEntries::deserialize(
}
std::vector<SignerEntry> accountVec;
accountVec.reserve(STTx::maxMultiSigners);
accountVec.reserve(STTx::maxMultiSigners());
STArray const& sEntries(obj.getFieldArray(sfSignerEntries));
for (STObject const& sEntry : sEntries)

View File

@@ -58,7 +58,13 @@ checkValidity(
if (!any(flags & SF_SIGGOOD))
{
auto const sigVerify = tx.checkSign(rules);
// Don't know signature state. Check it.
auto const requireCanonicalSig =
rules.enabled(featureRequireFullyCanonicalSig)
? STTx::RequireFullyCanonicalSig::yes
: STTx::RequireFullyCanonicalSig::no;
auto const sigVerify = tx.checkSign(requireCanonicalSig, rules);
if (!sigVerify)
{
router.setFlags(id, SF_SIGBAD);

View File

@@ -822,7 +822,7 @@ getTxFee(Application const& app, Config const& config, Json::Value tx)
if (!tx[jss::Signers].isArray())
return config.FEES.reference_fee;
if (tx[jss::Signers].size() > STTx::maxMultiSigners)
if (tx[jss::Signers].size() > STTx::maxMultiSigners(&ledger->rules()))
return config.FEES.reference_fee;
// check multi-signed signers