diff --git a/src/test/consensus/ProposalPrecheck_test.cpp b/src/test/consensus/ProposalPrecheck_test.cpp index 9fb451de5..2c5a9343b 100644 --- a/src/test/consensus/ProposalPrecheck_test.cpp +++ b/src/test/consensus/ProposalPrecheck_test.cpp @@ -63,6 +63,16 @@ public: { using enum detail::ProposalPrecheckResult; + //@@start test-peer-proposal-authentication + testcase("cluster transport does not bypass proposal authentication"); + { + BEAST_EXPECT(detail::proposalSignatureAccepted(false, true)); + BEAST_EXPECT(detail::proposalSignatureAccepted(true, true)); + BEAST_EXPECT(!detail::proposalSignatureAccepted(false, false)); + BEAST_EXPECT(!detail::proposalSignatureAccepted(true, false)); + } + //@@end test-peer-proposal-authentication + //@@start test-proposal-precheck-legacy-ok testcase("legacy and extended ok"); { diff --git a/src/xrpld/app/consensus/ProposalPrecheck.h b/src/xrpld/app/consensus/ProposalPrecheck.h index 0e875255b..8b78a8a68 100644 --- a/src/xrpld/app/consensus/ProposalPrecheck.h +++ b/src/xrpld/app/consensus/ProposalPrecheck.h @@ -37,6 +37,13 @@ struct ProposalPrecheckRejection char const* feeReason; }; +inline bool +proposalSignatureAccepted(bool /* clusterPeer */, bool signatureValid) +{ + // Cluster trust affects relay and charging, never validator attribution. + return signatureValid; +} + inline bool proposalHasMalformedHashes(protocol::TMProposeSet const& set) { diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 07379b176..132e8e690 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -3120,19 +3120,19 @@ PeerImp::checkPropose( XRPL_ASSERT(packet, "ripple::PeerImp::checkPropose : non-null packet"); - // Always verify the proposal signature before harvesting export - // sigs, even in cluster mode. Cluster peers are trusted for relay - // and resource charging, but export sigs produce on-chain artifacts - // (multisigned blobs in metadata) so they require cryptographic - // proof of validator identity regardless. + // Always authenticate validator positions, including those relayed by a + // cluster peer. Sidecar alignment counts these positions as validator + // statements, so cluster transport trust cannot replace the signature. + //@@start peer-proposal-authentication bool const sigValid = peerPos.checkSign(); - if (!cluster() && !sigValid) + if (!detail::proposalSignatureAccepted(cluster(), sigValid)) { std::string desc{"Proposal fails sig check"}; JLOG(p_journal_.warn()) << desc; charge(Resource::feeInvalidSignature, desc); return; } + //@@end peer-proposal-authentication //@@start peer-harvest-export-sigs // Harvest export sigs AFTER checkSign() so only cryptographically