fix(consensus): reject noncanonical extended positions

Require legacy consensus positions to use the exact 32-byte form. Extended encodings now need a nonzero known flag byte, preventing alternate wire encodings of the same tx-set hash.
This commit is contained in:
Nicholas Dudfield
2026-06-26 17:57:55 +07:00
parent 932ccbe0e5
commit 38672fba9f
2 changed files with 19 additions and 6 deletions

View File

@@ -522,17 +522,26 @@ class ExtendedPosition_test : public beast::unit_test::suite
BEAST_EXPECT(!result.has_value());
}
// Size says extended payload, but no flag byte remains. This accepts
// the legacy hash and ignores the inconsistent advertised size.
// Size says extended payload, but no flag byte remains.
{
auto const txSet = makeHash("txset-missing-flags");
Serializer s;
s.addBitString(txSet);
SerialIter sit(s.slice());
auto result = ExtendedPosition::fromSerialIter(sit, 33);
BEAST_EXPECT(result.has_value());
if (result)
BEAST_EXPECT(result->txSetHash == txSet);
BEAST_EXPECT(!result.has_value());
}
// Zero flags are a non-canonical duplicate of the 32-byte legacy form.
{
auto const txSet = makeHash("txset-zero-flags");
Serializer s;
s.addBitString(txSet);
s.add8(0);
SerialIter sit(s.slice());
auto result =
ExtendedPosition::fromSerialIter(sit, s.getDataLength());
BEAST_EXPECT(!result.has_value());
}
// Trailing extra bytes after valid fields

View File

@@ -205,10 +205,14 @@ struct ExtendedPosition
// Extended format: flags byte + optional uint256 fields
if (sit.empty())
return pos;
return std::nullopt;
std::uint8_t flags = sit.get8();
// A 32-byte payload is the only canonical no-extension encoding.
if (flags == 0)
return std::nullopt;
// Reject unknown flag bits (reduces wire malleability)
if (flags & 0x80)
return std::nullopt;