anchor: finality tags (B1), propagated-tip adoption gate (Rule C), parallel SLH-DSA signing; stage-5 re-proposal and codec fixes

- AerePqFinalityTags: eth_getBlockByNumber("finalized"/"safe") answers the parent of the last canonical anchor that carries a
  post-quantum certificate (v1 Falcon list or v2 scheme-tagged); monotonic, never moved back by a reorg. Fork choice, validity
  and import are untouched.
- PqTipAdoptionGate / PropagatedBlockAdoptionGate: from the header-seal stage, a validator does not adopt a propagated block at
  its own consensus tip without a post-quantum commit quorum for that exact hash, re-verified against the anchored registry.
  Inert below that stage; readers and the sync path are not gated.
- ParallelSigning: SLH-DSA-SHA2-128s signing spreads the message-independent work (the hypertree layers' authentication paths
  and the FORS trees) over the available cores; the output is byte-identical to Bouncy Castle 1.80 (SlhDsaParallelSigningTest
  compares against the original for several keys and message lengths and counts the tasks that ran on the pool). Kill switch:
  -Daere.pq.slhdsa.parallel=false. The engine files are regenerated from the pinned Bouncy Castle sources; the MIT licence stays
  alongside them.
- Header-seal stage: a block re-proposed in a later round carries the seal of that round's proposer; the QBFT codec requires a
  32-byte vanity field at every height. Comments translated to English throughout.
This commit is contained in:
Aere Network 2026-09-27 04:23:45 +03:00
parent 4250738290
commit 846f437724
37 changed files with 1738 additions and 142 deletions

View File

@ -5,12 +5,14 @@ computed them, and the same 276 files once produced two different aggregates her
329e4e2f93143cc8af092f0a951fa23619c12c4ec1793b053654f8e26e088da0 app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java 329e4e2f93143cc8af092f0a951fa23619c12c4ec1793b053654f8e26e088da0 app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
3f289d74bb1878280a99742320d7a27c19146a2f19cdf6a0857f2aad4361ad5e app/src/main/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptions.java 3f289d74bb1878280a99742320d7a27c19146a2f19cdf6a0857f2aad4361ad5e app/src/main/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptions.java
1615ecaf19f75af3e91c27c029177bf0faf51839f5589878b8c3562371299865 app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java 0ce3fcfdcfa7ab17f4fd1251e9066e3e1c03a59047a01fb548f490391fe4cdb4 app/src/main/java/org/hyperledger/besu/controller/PqTipAdoptionGate.java
505066636bdd2a0e6908d490ad0b3341788d270320aed81dc2923572d61216a7 app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java
129493150c0b13e8020bdf50c0c1ae07268d4f73a69a821b303bf4894451fa5e app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java 129493150c0b13e8020bdf50c0c1ae07268d4f73a69a821b303bf4894451fa5e app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java
728c29c299edf6011f2c2798cbe930a043112e0e06b924e7edb54284ca44a758 config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java 728c29c299edf6011f2c2798cbe930a043112e0e06b924e7edb54284ca44a758 config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
a3b93f2602e9755d91358c9b3f473235f4d40ae23d912f797cdb03ed7e1348f9 consensus/common/build.gradle a3b93f2602e9755d91358c9b3f473235f4d40ae23d912f797cdb03ed7e1348f9 consensus/common/build.gradle
68d635954ef33448a661256f50642eeeff2f7bb5874a54ffc2e64193c83fe391 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AereAnchorProposalDelay.java 68d635954ef33448a661256f50642eeeff2f7bb5874a54ffc2e64193c83fe391 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AereAnchorProposalDelay.java
ed2e46aaf2e74661ad831dc31df0b6f795f28f4a94496a7d226feedf2a770fde consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinality.java ed2e46aaf2e74661ad831dc31df0b6f795f28f4a94496a7d226feedf2a770fde consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinality.java
a6c2bbf05eab4173a749c67ef8b7bce6d184d8eac1b445acb847efd082dc6d62 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityTags.java
290d94bb50f23666b131ec19659982c15f1e6cbaf8f8bfe128df9acb2f2f79fd consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqHeaderSeal.java 290d94bb50f23666b131ec19659982c15f1e6cbaf8f8bfe128df9acb2f2f79fd consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqHeaderSeal.java
348135b2e4d0a6a8ce91489c94fbfa77da2536196c13823676b33ea9d08f8fb9 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqIdentity.java 348135b2e4d0a6a8ce91489c94fbfa77da2536196c13823676b33ea9d08f8fb9 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqIdentity.java
c531f9e719c48d3d5da2800fdef4506bd167af4dad4e25af96b4353a4390067d consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftBlockInterface.java c531f9e719c48d3d5da2800fdef4506bd167af4dad4e25af96b4353a4390067d consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftBlockInterface.java
@ -31,7 +33,7 @@ e16b2c654fe21893c7972dac71f3ffafd1e4e8efbecdb6f95051820e36fbd48e consensus/comm
91755c5a013b287d83d5e4d2d32ca820bced6d1d8560f0a3c76418797f0e69fe consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqAnchorV2.java 91755c5a013b287d83d5e4d2d32ca820bced6d1d8560f0a3c76418797f0e69fe consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqAnchorV2.java
78ecea21ff746564984dff40e1067808789f9bc5075ca3b5e8af92e0ea137c72 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryBinding.java 78ecea21ff746564984dff40e1067808789f9bc5075ca3b5e8af92e0ea137c72 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryBinding.java
f67a205036550f7d0f1bfed2abe7b89abd73e80614adff8d696b33c580269a6f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHash.java f67a205036550f7d0f1bfed2abe7b89abd73e80614adff8d696b33c580269a6f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHash.java
3fbd850e00e487c40c61310d894d6e1f765e9098e5c55438f9b06ab8fb92d45b consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHashTool.java 660ba13ad60c49e86cf9ee4ced2b6932a5422d988f85c5c23098a92b029f4d25 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHashTool.java
11f1c2a97a40df999471977cb3eab9af4e816081b100e345e8c962ef3bbf76f5 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryV3.java 11f1c2a97a40df999471977cb3eab9af4e816081b100e345e8c962ef3bbf76f5 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryV3.java
2ee139d21f73ff3417f6ad61b233e5ca3a95f92e68a4284eb99a47b1f8f4b58b consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSchemeSchedule.java 2ee139d21f73ff3417f6ad61b233e5ca3a95f92e68a4284eb99a47b1f8f4b58b consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSchemeSchedule.java
c0c93a096f4eceab6bf92cc3618ce7cee1e54d2d20b6ac781850a390ec6e514f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealCache.java c0c93a096f4eceab6bf92cc3618ce7cee1e54d2d20b6ac781850a390ec6e514f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealCache.java
@ -44,39 +46,41 @@ addba40c0d931a3ecfa3b2f0179311dacaa604884e3ebb1d79f958bd913f72e2 consensus/comm
444961503d515cb9f045729577e190941e59baa4bd90438a2da05f41fed7750a consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/SlhDsaSealScheme.java 444961503d515cb9f045729577e190941e59baa4bd90438a2da05f41fed7750a consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/SlhDsaSealScheme.java
454eeca487505e70c7f0000f5401b6bf4530da1d5ce6bcb803427f1d08e3cb1c consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java 454eeca487505e70c7f0000f5401b6bf4530da1d5ce6bcb803427f1d08e3cb1c consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java
e89deb6f346c221affa717eafa464d497401b7b5a01c84c144dace42032879a9 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/PqAnchorProducer.java e89deb6f346c221affa717eafa464d497401b7b5a01c84c144dace42032879a9 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/PqAnchorProducer.java
baa7f138046c501c5baecae3615d54ed0bbd6fea47d7f84a3d71eaa5698d182d consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/ADRS.java 1e0113154d79a91a07104bcc67a9a843847751b921c5f9b7cc96d7bc30a1452f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/ADRS.java
7c464b927fcb39ab017c543ca42bcd076b1dc69573c7ddefda1e685ebf9eb9ba consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/Fors.java 15ab42221f35aeb0dfd468bcb840254f08ccfccb5ff36fba559ac1017beca5ee consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/Fors.java
ddb17d7c6e1b762a9e8bcc924a17d42218cb5ec16528d80170bf2ff07e40aba2 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/HT.java 51b7d889188793a307bccb297d983f9318548e6066ad6228e37170585eda665f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/HT.java
daf4c242682df6365aa7459990a9ba7b5f97e9a06cfff95c96afe1eadc061647 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/IndexedDigest.java b4270fe63339bbef049c6b8c48bf8eec71829dac57b43a04b93ae84e29efa6f3 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/IndexedDigest.java
ddd60bf21ee7207b5b7d72fda5cd8bd9f9515536744379aa7d1bb7e3cdc9abdb consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/JdkDigest.java 56b2c1a1d862402f8e36ec9f46658b6c6ab960841b45554ff7fc80cf98ff8877 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/JdkDigest.java
108a2d252eea02e005889942966d2dcf845e1d01fc5e7bfdf85b58e1ac0697cd consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/NodeEntry.java d55e72f4a50bbd61265f062fbdc78f81e3df95e885d97b7a330767d0ac118282 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/NodeEntry.java
91ff4c4bc681bde06e72188e28177170645ca72cb72a08b08c3cf2283376a933 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/PK.java 2390c4e1582e3851206822734508bd9e1e09ac214a60ad5df4e58fbd20d62862 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/PK.java
ef1fcba8ac1d98f6bcb44616b33551cc37c21fa4fd0e5adc967bd4960c6bc184 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG.java 048b42bc48fefbe1f5078bd07c79c1dfd0681dbc9f28faca079044c5a0164446 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/ParallelSigning.java
9b9b7b171e816d6b142978ada7ea4b4491dbad025d26f431d6a6fc03130b047a consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_FORS.java bb22f53b9a859f8aaeb6218ce552b9808c75f0b3dc669cfb96b1d7f4eb2a15ef consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG.java
21371b535b9818ba0bb4b094837b11b00dc3132187a7b67f1b11196ab386f1f5 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_XMSS.java ae0dbd055801e5ee56c5a239dd4ef053865429f7cf986fc5bb133f132d75e5a5 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_FORS.java
07ed38417f5f4a4c56631c82651e70708ab47b8b6f017096ea281f0436180671 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SK.java b0052acfe28315a1ef049a67827152d78c35003188a40df279894a6310bec576 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_XMSS.java
8107ac08eabfee9016ecea9cdb53b78409f322c49706224def9c05501b297985 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAEngine.java 56f23200ef1ec3db3f2403df56400cf97520dad2766f5ade3808cfbbba21ad3a consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SK.java
efa6edc2f6cdf5e67074ef793fb15fff1bf41f5030476b7f8de991ada54b15eb consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAEngineProvider.java 20d6a4c38a925d98d89d3ce200eb726b53b9490251c799ff0ca7882b1715aad7 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAEngine.java
fa89107a6ff29b8d55d66949dd810cad36851a8ac847a0f7f33a843551f0e359 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAKeyGenerationParameters.java 14cd5293a9305f408eb4997bb2feaffeae4e3fbb150319c7cff622d35eec2cc8 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAEngineProvider.java
48e4e7448b8076376bcd5720e20dd36d07b9d7a953fdf992691d54fa20f1709d consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAKeyPairGenerator.java a8be3a968c50693252e82b705180988b50b62a360045c2463aa7bc9f1585dd13 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAKeyGenerationParameters.java
14411ba911129fb56f95c62a26aa00de8b2fa7de5994563ec10d89ada95cc697 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAKeyParameters.java d2a9bf3fedeb52712d73e1ca18536a9ea2e94f935b525b1cf31fdff09fe810d7 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAKeyPairGenerator.java
c72f197a0f8967aec5e08d1c4bb30030b9dc16c27711567ca40ad0cbe845c989 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAParameters.java 93bf21d8c5a68d29197ac8b884c49fe9180e1cd72929405c50b24530c87dbc0d consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAKeyParameters.java
3c39912f4a7f1d56ffbaf1b249ecb6726cbf98180e93c6a924fe3318552caaf0 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAPrivateKeyParameters.java dc052f82c66f852bf327115070f11bf1d7aea0acb89b52b2e4682e6f1e039a75 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAParameters.java
3a18d7af3c85ab6a9239ff52be7524d141ed7e9e6cf84d34e8e4957434596b4c consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAPublicKeyParameters.java fa85313f90f2e92b338d9c3f6df4cb57a7583fe8c82417f19928a133932dfd18 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAPrivateKeyParameters.java
636399666ed9490b29a4cb85d2b3cddf2ef4e8f8fa9a8ecf2315a8abf3e2e6de consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSASigner.java 743d78ba0f9f553b02da222cf025882e1774694b5ab72a8e2b1581ddf6035eae consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSAPublicKeyParameters.java
bd6833075646ffa2f4fca2761e3d68ccbc37f7c5bb209ae2e40f8cfd469dce14 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/WotsPlus.java 6f084a5acf5e89834218a6fae120c4542a86250e263d2fa52c6f04e5183fdd7f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSASigner.java
2cd85f1517fe9770558ffef6251600bce76609a96beb21f0a8f120412b2a935f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/WotsPlus.java
6ace00e18914a1558563e689b7427654b5e6226a31447bef1d0416d993f78840 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryHashTool.java 6ace00e18914a1558563e689b7427654b5e6226a31447bef1d0416d993f78840 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryHashTool.java
ed1cc6f617dc5a2f7801b6e8f6f039a035a2ffc9542b116262dac39bb70563dd consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryV3Tool.java ed1cc6f617dc5a2f7801b6e8f6f039a035a2ffc9542b116262dac39bb70563dd consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryV3Tool.java
cf0dcc008ce2d040590329088abdcbfc0829bf529445422deee8022d4856e08a consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AereAnchorProposalDelayTest.java cf0dcc008ce2d040590329088abdcbfc0829bf529445422deee8022d4856e08a consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AereAnchorProposalDelayTest.java
f5d6d26b9cd3babad8f7d16b9595497908b1f854d166837fbecd485d721ae856 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AereKeysFromV3Test.java f5d6d26b9cd3babad8f7d16b9595497908b1f854d166837fbecd485d721ae856 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AereKeysFromV3Test.java
1d7e3a30d3f4653b778a1bcbb81203ed8855d904017fa438a06e26baad8958c3 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AereLocalKeysV3Test.java 1d7e3a30d3f4653b778a1bcbb81203ed8855d904017fa438a06e26baad8958c3 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AereLocalKeysV3Test.java
27e5782c1742a361db7a22269ac44c1af574e25b07a87c76c10792bc9741b65c consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityAuthorTest.java 27e5782c1742a361db7a22269ac44c1af574e25b07a87c76c10792bc9741b65c consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityAuthorTest.java
ce25387272397b5aa0d53039b4cd119782081da36a282e40c23c95b7397ad62f consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityTagsTest.java
15d2d2cfc627406ae6183353a81703fd40da871b216ff8e9b1c4d15af2437805 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqIdentityTest.java 15d2d2cfc627406ae6183353a81703fd40da871b216ff8e9b1c4d15af2437805 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqIdentityTest.java
acd5c1e7f49dbbf444c19c91386bbd03b9298d952d2d13721793670103e71ee9 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/D177NeutralNamesTest.java acd5c1e7f49dbbf444c19c91386bbd03b9298d952d2d13721793670103e71ee9 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/D177NeutralNamesTest.java
c574ec22e642ac464b1728d27e4bb26202448a45c044f08935cc0a0efa0297d9 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/FalconAttachIntervalTest.java c574ec22e642ac464b1728d27e4bb26202448a45c044f08935cc0a0efa0297d9 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/FalconAttachIntervalTest.java
e96c6e79287a17eafef8f4ff5c32e539e489115b02f7afaceb8cdeff73562d69 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/HybridSealProducerTest.java e96c6e79287a17eafef8f4ff5c32e539e489115b02f7afaceb8cdeff73562d69 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/HybridSealProducerTest.java
186c4c92199400783ee3424231818dd440b0da41aeb3d8bd643cd13759998250 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/HybridSealSupportTest.java 186c4c92199400783ee3424231818dd440b0da41aeb3d8bd643cd13759998250 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/HybridSealSupportTest.java
834861639fd119c1653e6a3977ffbe4c2d4dce63a5741f1fe5a087643b86ed74 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/HybridSignerRegistryTest.java 0e89bd1444821a4b59289c45782b7c70b7621101059ea4cb8aa89bc3280b8e76 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/HybridSignerRegistryTest.java
2920f18dae0eb7ccead43d80202580b116e28c9fae5bb75d54917ddb99b9c2e9 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorConfigTest.java 2920f18dae0eb7ccead43d80202580b116e28c9fae5bb75d54917ddb99b9c2e9 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorConfigTest.java
6e2ae09f62765d6e558fdf56a124c890681c2b2c3d410bf8aa497820cd76c3a5 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorEmergencyConfigTest.java 6e2ae09f62765d6e558fdf56a124c890681c2b2c3d410bf8aa497820cd76c3a5 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorEmergencyConfigTest.java
7470d72271dd4f9de96094b3d1c6dc4b5b5ea62e46a058d4e154e3742d3ccef1 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorIntervalTest.java 7470d72271dd4f9de96094b3d1c6dc4b5b5ea62e46a058d4e154e3742d3ccef1 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorIntervalTest.java
@ -101,7 +105,7 @@ f260d284f936307ac4c31142cbea0359ded1197880b8a960ac27dd7beb5c3b53 consensus/comm
97281811ae1fe6fae8001d0d64e11d4779be153547ab24c779a54aba3e211c88 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHeightRefusalTest.java 97281811ae1fe6fae8001d0d64e11d4779be153547ab24c779a54aba3e211c88 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHeightRefusalTest.java
7ec10b4fa5ac09e4d980f5d180c75eecc892d95c2f81d97c6140e64ada34654d consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqRegistryRotationTest.java 7ec10b4fa5ac09e4d980f5d180c75eecc892d95c2f81d97c6140e64ada34654d consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqRegistryRotationTest.java
05423fc69ce0e4e94cda0d56a181a996320bd2166af3415f2d88102cf3b0ae21 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqRegistryV3Test.java 05423fc69ce0e4e94cda0d56a181a996320bd2166af3415f2d88102cf3b0ae21 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqRegistryV3Test.java
763dcb09c30c2031ac19bb62ec7a8a16a2f8042fd0e3d04840867b08df5b1ba3 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqSchemeScheduleTest.java d7d675be2a5ee598ce74f58156383589a05b8e977c2cc790671e3a39c7728309 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqSchemeScheduleTest.java
e73a075b2acfce7a6d2de63130ed55a3d8f422bdc72c51325a010852064d406d consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqSealPersistenceTest.java e73a075b2acfce7a6d2de63130ed55a3d8f422bdc72c51325a010852064d406d consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqSealPersistenceTest.java
e0bc7a465dbb2dd7efa5f1830211a1a45c6554c300b2088dd70bb9677b0ac217 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqSignedHeightTest.java e0bc7a465dbb2dd7efa5f1830211a1a45c6554c300b2088dd70bb9677b0ac217 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqSignedHeightTest.java
9a371ec367cda846640c6da18c8bf7d921a6882cfc024dc0c86d4f965cae0c63 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqStartupHistoryTest.java 9a371ec367cda846640c6da18c8bf7d921a6882cfc024dc0c86d4f965cae0c63 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqStartupHistoryTest.java
@ -112,11 +116,12 @@ e355b147fdda48c14cb6dc09b1bc1e7a7ccae3f9b709af8f8483f896086dbe4d consensus/comm
d48d62c6ea0784864a5987a804a28855265b2180c409ba3c5b4434d21b805e1d consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SealSchemeAgilityTest.java d48d62c6ea0784864a5987a804a28855265b2180c409ba3c5b4434d21b805e1d consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SealSchemeAgilityTest.java
184e3cc6f2f9beb1bd972b077ab2ced456264a55296f810ba5ed2dc976195392 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SlhDsaCrossVectorTest.java 184e3cc6f2f9beb1bd972b077ab2ced456264a55296f810ba5ed2dc976195392 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SlhDsaCrossVectorTest.java
ae37d3525697bf84defd796bb8813193c78f81e7fa24a72f8f048285d52399e2 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SlhDsaFastEngineTest.java ae37d3525697bf84defd796bb8813193c78f81e7fa24a72f8f048285d52399e2 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SlhDsaFastEngineTest.java
d0ba00d95427ae4fb1c0a1eeb37bcd039fbfa5412957dc4b2da0725c0dea5028 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SlhDsaParallelSigningTest.java
bcdd5c3c267f38eee9700bc6ba07d41b487c7889224820868e1839044108451f consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyIdentityTest.java bcdd5c3c267f38eee9700bc6ba07d41b487c7889224820868e1839044108451f consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyIdentityTest.java
6b6ecfcfb9b25bb009cb5d4df74648c729abf62626e5034af126771825b7418e consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/messagewrappers/Commit.java 6b6ecfcfb9b25bb009cb5d4df74648c729abf62626e5034af126771825b7418e consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/messagewrappers/Commit.java
597d96d64c9394c79d5084a1ab5df884b2266efe315c0f2d563bfc17b7421480 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java a1405402e891189909a436abd9f83c863349fdcf01d3011542d805e2bb3911ed consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java
35d38accf0d22b6fdadeab4f2bce0cdac708f8115591594b1bb1fa385b227c5c consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/CommitPayload.java 35d38accf0d22b6fdadeab4f2bce0cdac708f8115591594b1bb1fa385b227c5c consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/CommitPayload.java
d365bad33f3aee4597c630071ca1f727ddd8bb6ab6515fccbce0a07842037adf consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/MessageFactory.java 536375fa2d9f27107d9907f34391413e052f2a8cd89611033fefe29b9edc7891 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/MessageFactory.java
7914cc3f4a8a85dc7e2e8dad1ea9add86c4df00b04c2f7d822d4fed60a3aadcb consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/PreparePayload.java 7914cc3f4a8a85dc7e2e8dad1ea9add86c4df00b04c2f7d822d4fed60a3aadcb consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/PreparePayload.java
8f8b041e72cfaee43d2f4e61a790e603ed35b329cf431de68fc6907a18c3b824 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/ProposalPayload.java 8f8b041e72cfaee43d2f4e61a790e603ed35b329cf431de68fc6907a18c3b824 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/ProposalPayload.java
e19b12e6db33cfb9bb1c2f22e321815130dfbcc5730bb505320f08a45a371093 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/RoundChangePayload.java e19b12e6db33cfb9bb1c2f22e321815130dfbcc5730bb505320f08a45a371093 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/RoundChangePayload.java
@ -166,7 +171,7 @@ dc9f9e862a11f0135d26976176a1d1adac3e84fd5f1e06572d8727262cb3374f consensus/qbft
83dd971f66ed63103d09db5283240556bf1db1e1925c2a07808c7f59d47433ad consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/RoundChangeJustificationPqTest.java 83dd971f66ed63103d09db5283240556bf1db1e1925c2a07808c7f59d47433ad consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/RoundChangeJustificationPqTest.java
8f6abb4297df964e934cfeabe51f365478e917b73cd6909f621c9e201e280aa5 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/RoundChangePayloadValidatorPqWiringTest.java 8f6abb4297df964e934cfeabe51f365478e917b73cd6909f621c9e201e280aa5 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/RoundChangePayloadValidatorPqWiringTest.java
24ee993a415eea57c80fe2c9b839ce20140bfcc580e269e7b1182f71e960c1f0 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java 24ee993a415eea57c80fe2c9b839ce20140bfcc580e269e7b1182f71e960c1f0 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java
65ba290ae597b2217b86f2dd4b9db5faacc6e60ef7e36343a9d724c40b66dd73 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java ebf6da56130720ea95c64505959d64bfba46540ebf2139c3f71a7051d88837a1 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java
c870d39a19174328bc394658f4a365a61bd57fe7b0e28e6c5e4aaeb28bb509e5 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftBlockCreatorAdaptor.java c870d39a19174328bc394658f4a365a61bd57fe7b0e28e6c5e4aaeb28bb509e5 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftBlockCreatorAdaptor.java
cfb0aad408ab620f28b09914ece7d8d3d09f9e6bbb503070e28d9ef39c023dee consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java cfb0aad408ab620f28b09914ece7d8d3d09f9e6bbb503070e28d9ef39c023dee consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java
cbec6f4e280250ddefe5c79c453b59a07be6435e6f88d5614e92168c855e4693 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/AereBaseFeeImportRule.java cbec6f4e280250ddefe5c79c453b59a07be6435e6f88d5614e92168c855e4693 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/AereBaseFeeImportRule.java
@ -176,7 +181,7 @@ ab0fcd8722dcb76560f0ef8fda8af9c2b8b6ec9b468326aaf616c853526f7f81 consensus/qbft
0f348ac4fa519f27f52f631dd290eab6e84980739a09ec767c8ca69e4a4f8f64 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorSealsRule.java 0f348ac4fa519f27f52f631dd290eab6e84980739a09ec767c8ca69e4a4f8f64 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorSealsRule.java
a76a87987050ea6765e2a1f43d8f828ca8aec218c951e395e7acb62465e8b2fd consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRule.java a76a87987050ea6765e2a1f43d8f828ca8aec218c951e395e7acb62465e8b2fd consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRule.java
2add2a7733668e11b50ddd64b05bb37db26a18284a636bbe720c2cb1f6aa28ed consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRule.java 2add2a7733668e11b50ddd64b05bb37db26a18284a636bbe720c2cb1f6aa28ed consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRule.java
47dd0c84b2f1dba800ec843a0c06f85db86825e06493df13ffca3d5b5a9219dc consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqHeaderSealRule.java e0f8afc69fef826c5ee8a95745a967b1049ce10b9d591696a4fc095fb28062cb consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqHeaderSealRule.java
8ac99e94c89f61f0281199cf369e282fc4f9fb1a3414a1650766e1d7548e76f5 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqRegistryBindingRule.java 8ac99e94c89f61f0281199cf369e282fc4f9fb1a3414a1650766e1d7548e76f5 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqRegistryBindingRule.java
5dcf5c22389c063e12363b83366b39bd3b2e79f36c68e777fb3ef11080f53ea9 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/AereIdentityHeaderTest.java 5dcf5c22389c063e12363b83366b39bd3b2e79f36c68e777fb3ef11080f53ea9 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/AereIdentityHeaderTest.java
52b56f157500ae3527b7e55c51786915bb5d6980065668f1e7b8e297bf7125ef consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftAnchorRuleWiringTest.java 52b56f157500ae3527b7e55c51786915bb5d6980065668f1e7b8e297bf7125ef consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftAnchorRuleWiringTest.java
@ -192,7 +197,7 @@ a81da71dd34dd111a0da43c22d22345dcc595c084a2c2d36aec1f45d8b762f80 consensus/qbft
9c4e6a56ecffa70ebb03f769eda8c161d70eed47935488b88cb30c1bafa15a73 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRuleTest.java 9c4e6a56ecffa70ebb03f769eda8c161d70eed47935488b88cb30c1bafa15a73 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRuleTest.java
484bc74ff3d1e52d25631264d83af70c2b695e973c7360b0f3db471ab5c6613a consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRuleTest.java 484bc74ff3d1e52d25631264d83af70c2b695e973c7360b0f3db471ab5c6613a consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRuleTest.java
49c3fd97e7985966531928af173b6f38ac48ff9dc8b5be1106e5b7bfb9c7b2b6 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqForkGateFeedTest.java 49c3fd97e7985966531928af173b6f38ac48ff9dc8b5be1106e5b7bfb9c7b2b6 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqForkGateFeedTest.java
2db85c0c1b080123b252b6fb8896266d9d66753cf352826d3ed72c0790973abb consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqHeaderSealStage5Test.java 8d0113eec652dc5e08600fb7fb07de3e08524a500ea6bdcf3fd0f46f3f1b38e6 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqHeaderSealStage5Test.java
7ea99f24bc1c9131e8ef08f7a2c6f2a08af71ebee011a4cb88a367bd446b6c27 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java 7ea99f24bc1c9131e8ef08f7a2c6f2a08af71ebee011a4cb88a367bd446b6c27 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java
5bf41ab0be06328fa1e18bf9c98bfd89b7b16384fe185d96b3385595dabe61b3 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java 5bf41ab0be06328fa1e18bf9c98bfd89b7b16384fe185d96b3385595dabe61b3 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java
4c96b06073ff3830f4e0292fc732ec3fb9e0ed50ef741dceedf755b50f18d5ae ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java 4c96b06073ff3830f4e0292fc732ec3fb9e0ed50ef741dceedf755b50f18d5ae ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java
@ -201,3 +206,4 @@ a81da71dd34dd111a0da43c22d22345dcc595c084a2c2d36aec1f45d8b762f80 consensus/qbft
3ee087fc34b455e113ec9d2c53b5dbdb3574429fcf166f4c968a80120f70f0eb ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AereStateUnavailableIsAnErrorTest.java 3ee087fc34b455e113ec9d2c53b5dbdb3574429fcf166f4c968a80120f70f0eb ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AereStateUnavailableIsAnErrorTest.java
6e99e9f399558abbd1d4fdb4b176ba413bf31cabb3c8aba1701c17ce5828ce8a ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/AereHeadStateRetryTest.java 6e99e9f399558abbd1d4fdb4b176ba413bf31cabb3c8aba1701c17ce5828ce8a ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/AereHeadStateRetryTest.java
3b690b72e80a0f9eaf868a3fa3f99ace42dad74dc30b2c98b9f3ca5a023917c0 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStep.java 3b690b72e80a0f9eaf868a3fa3f99ace42dad74dc30b2c98b9f3ca5a023917c0 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStep.java
7f611966d74f52ef2f6d55bb5882ffb72b10abf1985df2b01f03aad4a6e2baf1 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PropagatedBlockAdoptionGate.java

View File

@ -0,0 +1,187 @@
/*
* Copyright contributors to Besu / AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.controller;
import org.hyperledger.besu.consensus.common.bft.AerePqHeaderSeal;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.BftHelpers;
import org.hyperledger.besu.consensus.common.bft.FalconSeal;
import org.hyperledger.besu.consensus.common.bft.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqSealCache;
import org.hyperledger.besu.consensus.common.bft.PqSignerRegistry;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.eth.sync.PropagatedBlockAdoptionGate;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AERE D-412 (2026-09-26), Rule C, the consensus-aware implementation of {@link
* PropagatedBlockAdoptionGate.Gate}.
*
* <p>A validator does not ADOPT, as the new canonical head, a block that arrived by propagation at the
* tip of its own consensus height unless it holds a post-quantum COMMIT quorum for that exact block
* hash - the Falcon-512 commit seals it heard on AIP-21 Commit messages, re-verified now against the
* anchored registry. From H_hdr the header carries only the proposer's single seal, so a valid header no
* longer proves quorum agreement; that agreement lives in the seal cache, and a single-key forgery has
* none. The block stays VALID (this is fork choice, not validation), so:
*
* <ul>
* <li>Below H_hdr the gate is inert (the header's ECDSA committed-seals rule already proves a quorum).
* <li>A reader (not in the validator set) is not gated: it hears no commit seals, so it could never
* clear the gate and would stall; its canonicity is Rule B, out of this milestone's scope.
* <li>The sync downloader is a different code path and does not consult this gate, so a validator that
* fell behind catches up freely and only rejoins the gated tip once it hears commits again.
* </ul>
*
* <p>The quorum proof is cryptographic: each cached seal is re-verified with Falcon over {@link
* PqAnchor#commitMessage} (the exact message a commit seal signs from the anchor form, which holds at
* every height at or above H_hdr), and only distinct verified signer indexes are counted, so junk in the
* cache cannot forge the proof. To clear the gate an adversary would need the commit quorum's distinct
* Falcon signatures over the block's commit message - which is exactly the agreement D-412 shows a single
* key cannot produce.
*/
public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
private static final Logger LOG = LoggerFactory.getLogger(PqTipAdoptionGate.class);
/** Verifies one Falcon seal over the commit message of a block; injectable for tests. */
@FunctionalInterface
public interface SealVerifier {
/**
* Verify.
*
* @param blockNumber the height
* @param validatorIndex the signer index in the anchored registry
* @param message the commit message that was signed
* @param signature the Falcon signature
* @return true iff it verifies against the key bound at that height
*/
boolean verify(long blockNumber, int validatorIndex, Bytes message, Bytes signature);
}
private final ValidatorProvider validatorProvider;
private final Address localEcdsaAddress;
private final long chainId;
private final PqSealCache cache;
private final SealVerifier verifier;
/**
* Production wiring.
*
* @param validatorProvider the validator provider (the set at a height; ids from H_id, addresses below)
* @param localEcdsaAddress this node's ECDSA address (from its node key)
* @param chainId the chain id every commit message carries
*/
public PqTipAdoptionGate(
final ValidatorProvider validatorProvider, final Address localEcdsaAddress, final long chainId) {
this(
validatorProvider,
localEcdsaAddress,
chainId,
PqSealCache.instance(),
(n, i, m, s) -> PqSignerRegistry.falconSealSupport().verifyAtHistoric(n, i, m, s));
}
/**
* Full wiring, for tests.
*
* @param validatorProvider the validator provider
* @param localEcdsaAddress this node's ECDSA address
* @param chainId the chain id
* @param cache the seal cache to read
* @param verifier the seal verifier
*/
PqTipAdoptionGate(
final ValidatorProvider validatorProvider,
final Address localEcdsaAddress,
final long chainId,
final PqSealCache cache,
final SealVerifier verifier) {
this.validatorProvider = validatorProvider;
this.localEcdsaAddress = localEcdsaAddress;
this.chainId = chainId;
this.cache = cache;
this.verifier = verifier;
}
@Override
public boolean mayAdopt(final BlockHeader header, final BlockHeader parent) {
final long h = header.getNumber();
// Below H_hdr the header still carries a quorum of ECDSA committed seals, whose upstream rule
// already refuses a single-key block; nothing to add here.
if (!AerePqHeaderSeal.active(h)) {
return true;
}
final Collection<Address> validators = validatorProvider.getValidatorsForBlock(parent);
if (validators == null || validators.isEmpty()) {
return true;
}
// Only a validator hears commit seals; a reader would never clear the gate, so it is not gated
// (its canonicity is Rule B). isLocal matches this node as an ECDSA address OR its v3 id, so it is
// correct below H_id (addresses) and from stage 5 (ids), where the set carries ids.
boolean localIsValidator = false;
for (final Address v : validators) {
if (AerePqIdentity.isLocal(v, localEcdsaAddress)) {
localIsValidator = true;
break;
}
}
if (!localIsValidator) {
return true;
}
final int quorum = BftHelpers.calculateRequiredValidatorQuorum(validators.size());
if (quorum <= 0) {
return true;
}
final Bytes32 message = PqAnchor.commitMessage(chainId, h, header.getHash().getBytes());
final List<FalconSeal> heard = cache.sealsFor(h, header.getHash());
final Set<Integer> verified = new HashSet<>();
for (final FalconSeal seal : heard) {
if (seal == null || seal.getValidatorIndex() < 0 || seal.getSignature() == null) {
continue;
}
try {
if (verifier.verify(h, seal.getValidatorIndex(), message, seal.getSignature())) {
verified.add(seal.getValidatorIndex());
}
} catch (final RuntimeException e) {
// a seal this node cannot judge is not counted; never a pass
LOG.debug("AERE D-412: seal of index {} for block {} could not be verified ({})", seal.getValidatorIndex(), h, e.toString());
}
}
final boolean ok = verified.size() >= quorum;
if (!ok) {
LOG.info(
"AERE D-412: propagated block {} at tip height {} holds {} verified post-quantum commit "
+ "seal(s), quorum is {}; NOT adopting it via propagation - this node's own consensus "
+ "decides this height.",
header.getHash(),
h,
verified.size(),
quorum);
}
return ok;
}
}

View File

@ -31,6 +31,7 @@ import org.hyperledger.besu.consensus.common.BftValidatorOverrides;
import org.hyperledger.besu.consensus.common.EpochManager; import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.bft.AerePqFinality; import org.hyperledger.besu.consensus.common.bft.AerePqFinality;
import org.hyperledger.besu.consensus.common.bft.AerePqFinalityTags;
import org.hyperledger.besu.consensus.common.bft.AerePqHeaderSeal; import org.hyperledger.besu.consensus.common.bft.AerePqHeaderSeal;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity; import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; import org.hyperledger.besu.consensus.common.bft.BftBlockInterface;
@ -113,6 +114,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocol;
import org.hyperledger.besu.ethereum.eth.SnapProtocol; import org.hyperledger.besu.ethereum.eth.SnapProtocol;
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager;
import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager;
import org.hyperledger.besu.ethereum.eth.sync.PropagatedBlockAdoptionGate;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
@ -608,6 +610,44 @@ public class QbftBesuControllerBuilder extends BesuControllerBuilder {
protocolContext.getConsensusContext(BftContext.class).getValidatorProvider(); protocolContext.getConsensusContext(BftContext.class).getValidatorProvider();
} }
// AERE D-412 (2026-09-26), Rule C: arm the propagation adoption gate. From AIP-22 stage 5 a valid
// header carries only the proposer's single Falcon seal, so it no longer proves a quorum agreed the
// block; a single key could forge one and, until this gate, honest validators adopted it via NewBlock
// and built on top (measured on rep28098). The gate lets this node refuse to ADOPT such a propagated
// block at the tip of its own consensus height unless it holds a post-quantum commit quorum for it
// (heard on AIP-21 Commit seals). Block VALIDITY is unchanged (sync still imports it); only
// ADOPTION-at-the-tip is gated, only for a validator, only from H_hdr. Inert here on any chain whose
// H_hdr is unset, which is chain 2800 today. Placed after the validator set is resolved (localAddress
// and validatorProvider both exist) and before the network and the QBFT machine start.
if (aereAnchorConfig.everActive()) {
PropagatedBlockAdoptionGate.install(
new PqTipAdoptionGate(validatorProvider, localAddress, aereAnchorConfig.chainId()));
LOG.info(
"AERE D-412: propagation adoption gate armed; a propagated block at the tip is adopted only "
+ "with a post-quantum commit quorum from H_hdr={} (unset means never on this node).",
AerePqHeaderSeal.headerBlock());
}
// AERE D-412 (2026-09-26), Rule B1: post-quantum FINALITY TAGS (see AerePqFinalityTags). finalized/safe = the parent of the
// last canonical anchor that carries a certificate (v1 Falcon list OR v2 scheme-tagged certificate); monotonic; never moved
// back by a reorg (AERE-PQC-FIN-01). No validity, fork-choice or import is touched. Proven live on rep28098 2026-09-26.
if (aereAnchorConfig.everActive()) {
final QbftExtraDataCodec codecForTags = qbftExtraDataCodec;
AerePqFinalityTags.install(
aereAnchorConfig,
blockchain,
h -> {
try {
return AerePqFinalityTags.carriesCertificate(codecForTags.decodeRaw(h.getExtraData()));
} catch (final RuntimeException e) {
return false; // an undecodable header carries nothing this rule can rely on
}
});
LOG.info(
"AERE D-412 B1: finalized/safe tags follow the last certified anchor's parent (interval {} at the head).",
aereAnchorConfig.intervalAt(blockchain.getChainHeadBlockNumber()));
}
// AERE GARDA-PRAG (2026-08-02): refuse to start on a seal threshold that STOPS the chain. // AERE GARDA-PRAG (2026-08-02): refuse to start on a seal threshold that STOPS the chain.
// //
// Measured before this line was written: nothing anywhere refused a threshold value. Searched // Measured before this line was written: nothing anywhere refused a threshold value. Searched

View File

@ -0,0 +1,202 @@
/*
* Copyright contributors to Besu / AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.common.bft;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
// in this Besu base (26.4.0-dev, d2032017b) the event type enum lives in the plugin API, not nested in BlockAddedEvent
// (measured 2026-09-26: "cannot find symbol EventType in class BlockAddedEvent" on the first harness run)
import org.hyperledger.besu.plugin.data.AddedBlockContext.EventType;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AERE D-412 Rule B1 (2026-09-26): the post-quantum FINALITY TAGS.
*
* <p><b>What this closes, read in the code and measured on the chain.</b> From AIP-22 stage 5 a valid
* between-anchor header carries only the proposer's Falcon-512 seal, so a single validator key can
* forge a header a reader imports as valid and, if that reader is syncing or eclipsed, adopts as its
* canonical tip (D-412, corrected severity). Such a forged tail is bounded: at the next anchor height
* {@code PqAnchorSealsRule} (R2, FULL validation, which {@code PqAnchorSyncModeGuard} makes the only
* sync mode) requires K distinct valid Falcon-512 seals over the parent's commit message, which one key
* cannot produce, so the tail dies within one anchor interval. What remains is that the reader's
* {@code latest} can transiently name a forged block, and a consumer who asks for {@code finalized} or
* {@code safe} - the tags bridges, exchanges and rollups use - received, on every node of chain 2800 and
* 28001 measured 2026-09-26, the error {@code Unknown block}: QBFT never sets them.
*
* <p><b>The rule.</b> {@code finalized} and {@code safe} name the last block COVERED BY A VALID ANCHOR
* CERTIFICATE: when a canonical head at an anchor height carries a certificate, the anchor's PARENT
* becomes finalized (the certificate's K seals sign the parent's commit message, AIP-15, and R1/R2 have
* already refused the header if that certificate does not bind or does not verify). Between anchors
* the tags do not move. A forged between-anchor block can therefore never be reported final: it is
* never an anchor's certified parent. This is exactly the finality AIP-22 section 1 defines ("the
* finality of the block is established only by the covering anchor"), exposed on the API the world
* already reads.
*
* <p><b>What it deliberately does NOT do.</b> It does not touch fork-choice, block validity or import:
* nothing here can stop the chain or refuse a block, and a node that lags still catches up exactly as
* before. It only writes the two tags, monotonically: a later event can only move them forward. If a
* canonical reorg ever drops below the finalized block (which would mean K validators signed two
* conflicting anchors, outside this rule's adversary), it is logged as {@code AERE-PQC-FIN-01} at
* ERROR and the tags are left where they were, never moved back in silence.
*
* <p><b>At startup</b> the tags are initialised by scanning back from the head to the most recent
* anchor height that carries a certificate (at most two intervals), so a freshly started node answers
* {@code finalized} before it imports its next anchor.
*
* <p>Inert on a chain where the anchor was never active (the node never installs it); inert below
* the activation height (no anchor height applies there). Stated, not measured here: the behaviour on
* the live fleet, which is the rehearsal's job.
*/
public final class AerePqFinalityTags {
private static final Logger LOG = LoggerFactory.getLogger(AerePqFinalityTags.class);
/** Logged when a canonical reorg would move finality backwards; the tags are not moved. */
public static final String CODE_REORG_BELOW_FINALIZED = "AERE-PQC-FIN-01";
private final PqAnchorConfig config;
private final MutableBlockchain blockchain;
private final Predicate<BlockHeader> carriesCertificate;
private final AtomicLong finalizedNumber = new AtomicLong(-1L);
AerePqFinalityTags(
final PqAnchorConfig config,
final MutableBlockchain blockchain,
final Predicate<BlockHeader> carriesCertificate) {
this.config = config;
this.blockchain = blockchain;
this.carriesCertificate = carriesCertificate;
}
/**
* Install the tags on a blockchain: initialise from the current head, then follow every block added.
*
* @param config the anchor configuration (activation height, interval schedule)
* @param blockchain the chain whose finalized/safe tags are written
* @param carriesCertificate whether a header carries a (validated) anchor certificate
* @return the installed instance, for inspection
*/
public static AerePqFinalityTags install(
final PqAnchorConfig config,
final MutableBlockchain blockchain,
final Predicate<BlockHeader> carriesCertificate) {
final AerePqFinalityTags tags = new AerePqFinalityTags(config, blockchain, carriesCertificate);
tags.initialiseFromHead();
blockchain.observeBlockAdded(tags::onBlockAdded);
return tags;
}
/** The height currently reported as finalized, or -1 when none has been set by this rule. */
public long finalizedNumber() {
return finalizedNumber.get();
}
/**
* Whether a decoded header carries an anchor certificate. A header carries the v1 Falcon list OR
* the v2 scheme-tagged certificate, never both ({@link BftExtraData}), and from AIP-22 stage 5
* every anchor carries the v2 certificate while {@code getFalconSeals()} is empty. So a predicate
* that only looked at the v1 list ({@code getFalconSeals()}) never fired on a stage-5 chain -
* measured 2026-09-26 on the rep28098 rehearsal, where {@code finalized} stayed 0 while the chain
* ran past certified anchors 1920 and 1952. Both fields are checked here, in one place both the
* controller and the test read, so the two cannot drift.
*
* @param extraData the decoded BFT extra data of the header
* @return true when the header carries a (v1 or v2) anchor certificate
*/
public static boolean carriesCertificate(final BftExtraData extraData) {
return !extraData.getFalconSeals().isEmpty() || !extraData.getHybridSeals().isEmpty();
}
/** Scan back from the head, at most two anchor intervals, to the last certified anchor. */
void initialiseFromHead() {
final BlockHeader head = blockchain.getChainHeadHeader();
if (head == null) {
return;
}
final long top = head.getNumber();
final long floor = Math.max(0L, top - 2L * Math.max(1, config.intervalAt(top)));
for (long h = top; h >= floor; h--) {
if (!config.anchorAppliesAt(h)) {
continue;
}
final Optional<BlockHeader> header = blockchain.getBlockHeader(h);
if (header.isPresent() && carriesCertificate.test(header.get())) {
set(header.get().getParentHash(), h - 1L, "startup scan");
return;
}
}
LOG.info(
"AERE PQ FINALITY (B1): no certified anchor between {} and the head {}; finalized/safe stay "
+ "unset until the next anchor is imported.",
floor,
top);
}
/**
* Follow a block added to the chain.
*
* @param event the event
*/
void onBlockAdded(final BlockAddedEvent event) {
if (event == null || !event.isNewCanonicalHead()) {
return;
}
final BlockHeader header = event.getHeader();
if (header == null) {
return;
}
if (event.getEventType() == EventType.CHAIN_REORG && finalizedNumber.get() >= 0) {
final Hash ancestorHash = event.getCommonAncestorHash();
final Optional<BlockHeader> ancestor =
ancestorHash == null ? Optional.empty() : blockchain.getBlockHeader(ancestorHash);
if (ancestor.isPresent() && ancestor.get().getNumber() < finalizedNumber.get()) {
LOG.error(
"{}: a canonical reorg to {} (common ancestor {}) went BELOW the finalized block {}. "
+ "Finality tags are NOT moved back. Under the anchor rules this needs K validators to "
+ "have certified two conflicting anchors; investigate before anything else.",
CODE_REORG_BELOW_FINALIZED,
header.getNumber(),
ancestor.get().getNumber(),
finalizedNumber.get());
}
}
final long h = header.getNumber();
if (config.anchorAppliesAt(h) && carriesCertificate.test(header)) {
set(header.getParentHash(), h - 1L, "anchor " + h);
}
}
private void set(final Hash finalHash, final long finalNumber, final String why) {
if (finalNumber <= finalizedNumber.get()) {
return; // monotonic: never backwards, never twice for the same height
}
blockchain.setFinalized(finalHash);
blockchain.setSafeBlock(finalHash);
finalizedNumber.set(finalNumber);
LOG.info(
"AERE PQ FINALITY (B1): finalized/safe = {} ({}), covered by the certificate of {}.",
finalNumber,
finalHash,
why);
}
}

View File

@ -168,10 +168,10 @@ public final class PqRegistryHashTool {
epoci.put(Long.parseLong(rot.substring(0, eq).trim()), Path.of(rot.substring(eq + 1).trim())); epoci.put(Long.parseLong(rot.substring(0, eq).trim()), Path.of(rot.substring(eq + 1).trim()));
} }
final List<Long> inaltimi = new ArrayList<>(epoci.keySet()); final List<Long> epochHeights = new ArrayList<>(epoci.keySet());
inaltimi.sort(Long::compare); epochHeights.sort(Long::compare);
if (inaltimi.get(0) != h) { if (epochHeights.get(0) != h) {
System.out.println("RED: the first epoch is at " + inaltimi.get(0) + ", not at the arming height " + h); System.out.println("RED: the first epoch is at " + epochHeights.get(0) + ", not at the arming height " + h);
return 1; return 1;
} }
@ -186,8 +186,8 @@ public final class PqRegistryHashTool {
json.append(" \"pqRegistryHash\": [\n"); json.append(" \"pqRegistryHash\": [\n");
final StringBuilder history = new StringBuilder(); final StringBuilder history = new StringBuilder();
final StringBuilder harta = new StringBuilder(); final StringBuilder harta = new StringBuilder();
for (int i = 0; i < inaltimi.size(); i++) { for (int i = 0; i < epochHeights.size(); i++) {
final long b = inaltimi.get(i); final long b = epochHeights.get(i);
final Path p = epoci.get(b); final Path p = epoci.get(b);
final PqRegistryHash.Registry reg = PqRegistryHash.loadAuto(p); final PqRegistryHash.Registry reg = PqRegistryHash.loadAuto(p);
// AERE D-C (2026-08-06). THIS IS THE DANGEROUS VERB: what is printed here gets pasted into // AERE D-C (2026-08-06). THIS IS THE DANGEROUS VERB: what is printed here gets pasted into
@ -222,7 +222,7 @@ public final class PqRegistryHashTool {
return 1; return 1;
} }
json.append(" {\"block\": ").append(b).append(", \"hash\": \"0x").append(hash).append("\"}"); json.append(" {\"block\": ").append(b).append(", \"hash\": \"0x").append(hash).append("\"}");
if (i < inaltimi.size() - 1) { if (i < epochHeights.size() - 1) {
json.append(','); json.append(',');
} }
json.append('\n'); json.append('\n');

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;
@ -24,6 +26,18 @@ class HT
final byte[] htPubKey; final byte[] htPubKey;
// AERE (D-337, 2026-09-26): the same object without htPubKey. The constructor below computes the
// top-layer public key (a full treehash of 2^H' WOTS+ leaves) on every signature, and signing never
// reads it. Used by ParallelSigning; key generation keeps the constructor below.
HT(SLHDSAEngine engine, byte[] skSeed, byte[] pkSeed, boolean withoutPublicKey)
{
this.skSeed = skSeed;
this.pkSeed = pkSeed;
this.engine = engine;
this.wots = new WotsPlus(engine);
this.htPubKey = null;
}
public HT(SLHDSAEngine engine, byte[] skSeed, byte[] pkSeed) public HT(SLHDSAEngine engine, byte[] skSeed, byte[] pkSeed)
{ {
this.skSeed = skSeed; this.skSeed = skSeed;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -0,0 +1,257 @@
/*
* Copyright contributors to Besu / AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
* Written by AERE Network (not derived from Bouncy Castle); emitted into this package by
* consensus-pqc/slhdsa-fork/genereaza-motorul.py from ParallelSigning.java.sablon. DO NOT EDIT BY HAND.
*/
package org.hyperledger.besu.consensus.common.bft.slhdsa;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import org.bouncycastle.util.Arrays;
/**
* SLH-DSA signing with the message-independent work spread over the cores; the SAME bytes as the
* sequential signer, which remains the fallback.
*
* <p>D-337, measured 2026-09-26 on chain 2800: the parent of every anchor took 1.69 s against 0.60 s
* for an ordinary block (import times on two validators, fifteen anchors each), because each
* validator signs its Commit with SLH-DSA-SHA2-128s there, on the QBFT thread, on one core. One
* signature on a validator's CPU (EPYC-Rome, one core): 0.47 s warm, 0.88-1.02 s cold, and a
* validator signs once every ~77 s. Almost all of it is message-independent: the authentication
* path of each of the D hypertree layers (2^H' - 1 WOTS+ leaves each) and the k FORS trees. Only
* the WOTS+ signature of each layer depends on the root of the layer below, and that chain is cheap.
* So the auth paths and FORS run as separate tasks on a small daemon pool, each with its own engine
* (the engines hold digest state and are not thread-safe), and the chain is then walked in order.
*
* <p>Also dropped: the HT constructor used by the sequential signer computes the top layer's public
* key (a full 2^H'-leaf treehash) on every signature, and signing never reads it.
*
* <p>Taken only with more than one core and unless {@code -Daere.pq.slhdsa.parallel=false}; any
* failure returns null and the caller runs the original Bouncy Castle code unchanged.
*/
@SuppressWarnings("all")
final class ParallelSigning {
static final String SWITCH_PROPERTY = "aere.pq.slhdsa.parallel";
private static final int THREADS =
Math.max(1, Math.min(8, Runtime.getRuntime().availableProcessors()));
// counts tasks executed on the pool; the proof reads it to show the parallel path was the one taken
private static final AtomicInteger TASKS_RUN = new AtomicInteger();
private static volatile ExecutorService pool;
private ParallelSigning() {}
static boolean enabled() {
return THREADS > 1
&& !"false".equalsIgnoreCase(System.getProperty(SWITCH_PROPERTY, "true").trim());
}
static int tasksRun() {
return TASKS_RUN.get();
}
static int tasksPerSignature(final SLHDSAParameters params) {
return 1 + 2 * params.getEngine().D;
}
private static ExecutorService pool() {
ExecutorService p = pool;
if (p == null) {
synchronized (ParallelSigning.class) {
p = pool;
if (p == null) {
final AtomicInteger n = new AtomicInteger();
p =
Executors.newFixedThreadPool(
THREADS,
r -> {
final Thread t = new Thread(r, "aere-slhdsa-sign-" + n.incrementAndGet());
t.setDaemon(true);
return t;
});
pool = p;
}
}
}
return p;
}
/** The signature, or null when this path is not taken; the caller then runs the original code. */
static byte[] trySign(
final SLHDSAPrivateKeyParameters privKey,
final byte[] msgPrefix,
final byte[] msg,
final byte[] optRand) {
if (!enabled()) {
return null;
}
try {
return sign(privKey, msgPrefix, msg, optRand);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} catch (final ExecutionException | RuntimeException e) {
return null;
}
}
static byte[] sign(
final SLHDSAPrivateKeyParameters privKey,
final byte[] msgPrefix,
final byte[] msg,
final byte[] optRand)
throws InterruptedException, ExecutionException {
final SLHDSAParameters params = privKey.getParameters();
final byte[] skSeed = privKey.sk.seed;
final byte[] pkSeed = privKey.pk.seed;
final SLHDSAEngine engine = params.getEngine();
engine.init(pkSeed);
// the randomiser and the message digest, exactly as SLHDSASigner.internalGenerateSignature
final byte[] r = engine.PRF_msg(privKey.sk.prf, optRand, msgPrefix, msg);
final IndexedDigest idxDigest = engine.H_msg(r, pkSeed, privKey.pk.root, msgPrefix, msg);
final byte[] mHash = idxDigest.digest;
final long idxTree = idxDigest.idx_tree;
final int idxLeaf = idxDigest.idx_leaf;
final int d = engine.D;
final int hPrime = engine.H_PRIME;
// the (tree, leaf) each layer signs with: the walk HT.sign does, layer 0 first
final long[] tree = new long[d];
final int[] leaf = new int[d];
long t = idxTree;
int l = idxLeaf;
for (int j = 0; j < d; j++) {
if (j > 0) {
l = (int) (t & ((1 << hPrime) - 1));
t >>>= hPrime;
}
tree[j] = t;
leaf[j] = l;
}
final List<Callable<Object>> tasks = new ArrayList<Callable<Object>>();
tasks.add(forsTask(params, skSeed, pkSeed, mHash, idxTree, idxLeaf));
final byte[][][] auth = new byte[d][hPrime][];
for (int j = 0; j < d; j++) {
// two halves per layer: the top auth node alone is half of the tree's leaves, the rest the other half
tasks.add(authTask(params, skSeed, pkSeed, j, tree[j], leaf[j], hPrime - 1, hPrime, auth));
tasks.add(authTask(params, skSeed, pkSeed, j, tree[j], leaf[j], 0, hPrime - 1, auth));
}
final List<Future<Object>> done = pool().invokeAll(tasks);
final Object[] fors = (Object[]) done.get(0).get();
for (int i = 1; i < done.size(); i++) {
done.get(i).get(); // surfaces a failed task, which sends the caller to the original code
}
for (int j = 0; j < d; j++) {
for (int h = 0; h < hPrime; h++) {
if (auth[j][h] == null) {
throw new IllegalStateException("auth path of layer " + j + " height " + h + " missing");
}
}
}
final SIG_FORS[] sigFors = (SIG_FORS[]) fors[0];
final byte[] pkFors = (byte[]) fors[1];
// the hypertree chain, in order: each layer's WOTS+ signs the root of the layer below (FORS for layer 0)
final HT ht = new HT(engine, skSeed, pkSeed, true);
byte[] m = pkFors;
final byte[][] htParts = new byte[d][];
for (int j = 0; j < d; j++) {
final ADRS adrs = new ADRS();
adrs.setLayerAddress(j);
adrs.setTreeAddress(tree[j]);
final ADRS wotsAdrs = new ADRS(adrs);
wotsAdrs.setTypeAndClear(ADRS.WOTS_HASH);
wotsAdrs.setKeyPairAddress(leaf[j]);
final SIG_XMSS xmss = new SIG_XMSS(ht.wots.sign(m, skSeed, pkSeed, wotsAdrs), auth[j]);
htParts[j] = Arrays.concatenate(xmss.sig, Arrays.concatenate(xmss.auth));
if (j < d - 1) {
m = ht.xmss_pkFromSig(leaf[j], xmss, m, pkSeed, adrs);
}
}
final byte[][] parts = new byte[sigFors.length + 2][];
parts[0] = r;
for (int i = 0; i != sigFors.length; i++) {
parts[1 + i] = Arrays.concatenate(sigFors[i].sk, Arrays.concatenate(sigFors[i].authPath));
}
parts[parts.length - 1] = Arrays.concatenate(htParts);
return Arrays.concatenate(parts);
}
private static Callable<Object> forsTask(
final SLHDSAParameters params,
final byte[] skSeed,
final byte[] pkSeed,
final byte[] mHash,
final long idxTree,
final int idxLeaf) {
return () -> {
TASKS_RUN.incrementAndGet();
final SLHDSAEngine e = params.getEngine();
e.init(pkSeed);
final Fors fors = new Fors(e);
ADRS adrs = new ADRS();
adrs.setTypeAndClear(ADRS.FORS_TREE);
adrs.setTreeAddress(idxTree);
adrs.setKeyPairAddress(idxLeaf);
final SIG_FORS[] sigFors = fors.sign(mHash, skSeed, pkSeed, adrs);
adrs = new ADRS();
adrs.setTypeAndClear(ADRS.FORS_TREE);
adrs.setTreeAddress(idxTree);
adrs.setKeyPairAddress(idxLeaf);
return new Object[] {sigFors, fors.pkFromSig(sigFors, mHash, pkSeed, adrs)};
};
}
// the auth-path loop of HT.xmss_sign, for heights [fromHeight, toHeight) of one layer's tree
private static Callable<Object> authTask(
final SLHDSAParameters params,
final byte[] skSeed,
final byte[] pkSeed,
final int layer,
final long treeAddress,
final int idx,
final int fromHeight,
final int toHeight,
final byte[][][] auth) {
return () -> {
TASKS_RUN.incrementAndGet();
final SLHDSAEngine e = params.getEngine();
e.init(pkSeed);
final HT ht = new HT(e, skSeed, pkSeed, true);
final ADRS a = new ADRS();
a.setTypeAndClear(ADRS.TREE);
a.setLayerAddress(layer);
a.setTreeAddress(treeAddress);
for (int j = fromHeight; j < toHeight; j++) {
final int k = (idx >>> j) ^ 1;
auth[layer][j] = ht.treehash(skSeed, k << j, j, pkSeed, a);
}
return null;
};
}
}

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;
@ -191,6 +193,13 @@ public class SLHDSASigner
byte[] optRand) byte[] optRand)
{ {
// TODO Check init via privKey != null // TODO Check init via privKey != null
// AERE (D-337, 2026-09-26): the same signature, bit for bit, with the message-independent work on
// the free cores (ParallelSigning). null = not taken, and the original code below runs unchanged.
byte[] aere = ParallelSigning.trySign(privKey, msgPrefix, msg, optRand);
if (aere != null)
{
return aere;
}
SLHDSAEngine engine = privKey.getParameters().getEngine(); SLHDSAEngine engine = privKey.getParameters().getEngine();
engine.init(privKey.pk.seed); engine.init(privKey.pk.seed);

View File

@ -2,10 +2,12 @@
* Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence, * Derived from Bouncy Castle 1.80, package org.bouncycastle.pqc.crypto.slhdsa (MIT licence,
* Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt), * Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),
* regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337). * regenerated into Aere Network by consensus-pqc/slhdsa-fork/genereaza-motorul.py on 2026-09-04 (D-337).
* The ONLY change of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI * Changes of substance: the SHA-2 engine hashes through the JDK's MessageDigest (SHA-NI intrinsics)
* intrinsics) instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature * instead of Bouncy Castle's pure-Java digests, which made one SLH-DSA-SHA2-128s signature cost seconds on
* cost seconds on the QBFT thread at every anchor parent. Algorithm, key formats and signature bytes are * the QBFT thread at every anchor parent; and (2026-09-26) signing first tries ParallelSigning, which
* FIPS 205 as before; SlhDsaFastEngineTest pins that against the original, in both directions. * spreads the message-independent work over the cores and gives the same bytes (HT gains a constructor
* without the unused top-layer public key). Algorithm, key formats and signature bytes are FIPS 205 as
* before; SlhDsaFastEngineTest and SlhDsaParallelSigningTest pin that against the original.
* DO NOT EDIT BY HAND: regenerate with the script above. * DO NOT EDIT BY HAND: regenerate with the script above.
*/ */
package org.hyperledger.besu.consensus.common.bft.slhdsa; package org.hyperledger.besu.consensus.common.bft.slhdsa;

View File

@ -0,0 +1,235 @@
/*
* Copyright contributors to Besu / AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.common.bft;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.plugin.data.AddedBlockContext.EventType;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* AERE D-412 Rule B1: finalized/safe follow the last certified anchor's parent, monotonically, and a
* forged between-anchor block can never be reported final. Anchors every 32 from 96 (K=3); the
* certificate predicate is a set of heights that "carry" one, so a planted defect in the rule (an
* anchor without certificate moving the tag, a lower height moving it back, a non-anchor moving it)
* turns exactly one of these red.
*/
class AerePqFinalityTagsTest {
private static final long ANCHOR_BLOCK = 96L;
private static final int INTERVAL = 32;
private PqAnchorConfig config;
private MutableBlockchain blockchain;
private final Map<Long, BlockHeader> byNumber = new HashMap<>();
private final Map<Hash, BlockHeader> byHash = new HashMap<>();
private final Set<Long> certified = new HashSet<>();
@BeforeEach
void setUp() {
config =
new PqAnchorConfig(2800L, ANCHOR_BLOCK, Map.of(ANCHOR_BLOCK, 3), OptionalInt.empty(), false)
.withAnchorInterval(OptionalInt.of(INTERVAL));
blockchain = mock(MutableBlockchain.class);
// a linear chain 0..200 with deterministic hashes; header(n).parentHash == hash(header(n-1))
Hash prev = Hash.ZERO;
for (long n = 0; n <= 200; n++) {
final BlockHeader h = new BlockHeaderTestFixture().number(n).parentHash(prev).buildHeader();
byNumber.put(n, h);
byHash.put(h.getHash(), h);
prev = h.getHash();
}
when(blockchain.getBlockHeader(anyLong())).thenAnswer(i -> Optional.ofNullable(byNumber.get(i.getArgument(0, Long.class))));
when(blockchain.getBlockHeader(any(Hash.class))).thenAnswer(i -> Optional.ofNullable(byHash.get(i.getArgument(0, Hash.class))));
}
private AerePqFinalityTags tagsWithHead(final long head) {
when(blockchain.getChainHeadHeader()).thenReturn(byNumber.get(head));
return AerePqFinalityTags.install(config, blockchain, h -> certified.contains(h.getNumber()));
}
private BlockAddedEvent headAdvanced(final long n) {
final BlockAddedEvent e = mock(BlockAddedEvent.class);
when(e.isNewCanonicalHead()).thenReturn(true);
when(e.getHeader()).thenReturn(byNumber.get(n));
when(e.getEventType()).thenReturn(EventType.HEAD_ADVANCED);
return e;
}
private BlockAddedEvent reorgTo(final long n, final long commonAncestor) {
final BlockAddedEvent e = mock(BlockAddedEvent.class);
when(e.isNewCanonicalHead()).thenReturn(true);
when(e.getHeader()).thenReturn(byNumber.get(n));
when(e.getEventType()).thenReturn(EventType.CHAIN_REORG);
when(e.getCommonAncestorHash()).thenReturn(byNumber.get(commonAncestor).getHash());
return e;
}
@Test
void theConfigurationUnderTestHasAnchorsEveryThirtyTwoFromNinetySix() {
assertThat(config.anchorAppliesAt(96)).isTrue();
assertThat(config.anchorAppliesAt(128)).isTrue();
assertThat(config.anchorAppliesAt(129)).isFalse();
assertThat(config.anchorAppliesAt(95)).isFalse();
}
@Test
void startupScanFinalizesTheParentOfTheLastCertifiedAnchorBelowTheHead() {
certified.add(96L);
certified.add(128L);
final AerePqFinalityTags tags = tagsWithHead(140);
verify(blockchain, times(1)).setFinalized(byNumber.get(127L).getHash());
verify(blockchain, times(1)).setSafeBlock(byNumber.get(127L).getHash());
assertThat(tags.finalizedNumber()).isEqualTo(127L);
}
@Test
void startupWithNoCertifiedAnchorInTwoIntervalsSetsNothing() {
final AerePqFinalityTags tags = tagsWithHead(140); // 96 and 128 are anchor heights, none certified
verify(blockchain, never()).setFinalized(any());
assertThat(tags.finalizedNumber()).isEqualTo(-1L);
}
@Test
void aCertifiedAnchorAtTheHeadFinalizesItsParent() {
certified.add(96L);
final AerePqFinalityTags tags = tagsWithHead(100); // finalized 95
certified.add(128L);
tags.onBlockAdded(headAdvanced(128));
verify(blockchain).setFinalized(byNumber.get(127L).getHash());
verify(blockchain).setSafeBlock(byNumber.get(127L).getHash());
assertThat(tags.finalizedNumber()).isEqualTo(127L);
}
@Test
void aBetweenAnchorHeadNeverMovesTheTags_thisIsWhatKeepsAForgedTailOutOfFinalized() {
certified.add(96L);
final AerePqFinalityTags tags = tagsWithHead(100);
for (long n = 101; n < 128; n++) {
certified.add(n); // even if a between-anchor header somehow "carried" a certificate
tags.onBlockAdded(headAdvanced(n));
}
verify(blockchain, times(1)).setFinalized(any()); // only the startup one (95)
assertThat(tags.finalizedNumber()).isEqualTo(95L);
}
@Test
void anAnchorHeightWithoutACertificateDoesNotMoveTheTags() {
certified.add(96L);
final AerePqFinalityTags tags = tagsWithHead(100);
tags.onBlockAdded(headAdvanced(128)); // 128 not certified
verify(blockchain, times(1)).setFinalized(any());
assertThat(tags.finalizedNumber()).isEqualTo(95L);
}
@Test
void theTagsAreMonotonic_aLowerCertifiedAnchorNeverMovesThemBack() {
certified.add(96L);
certified.add(128L);
final AerePqFinalityTags tags = tagsWithHead(140); // finalized 127
tags.onBlockAdded(headAdvanced(96)); // certified, but lower
verify(blockchain, times(1)).setFinalized(any());
assertThat(tags.finalizedNumber()).isEqualTo(127L);
}
@Test
void aReorgBelowTheFinalizedBlockIsNotFollowedBackwards() {
certified.add(96L);
certified.add(128L);
final AerePqFinalityTags tags = tagsWithHead(140); // finalized 127
tags.onBlockAdded(reorgTo(130, 100)); // common ancestor 100 < 127: logged, not moved
verify(blockchain, times(1)).setFinalized(any());
assertThat(tags.finalizedNumber()).isEqualTo(127L);
}
@Test
void aNonCanonicalEventIsIgnored() {
certified.add(96L);
certified.add(128L);
final AerePqFinalityTags tags = tagsWithHead(100); // finalized 95
final BlockAddedEvent fork = mock(BlockAddedEvent.class);
when(fork.isNewCanonicalHead()).thenReturn(false);
when(fork.getHeader()).thenReturn(byNumber.get(128L));
tags.onBlockAdded(fork);
assertThat(tags.finalizedNumber()).isEqualTo(95L);
}
@Test
void headersAreLinkedByParentHashInTheFixture() {
assertThat(byNumber.get(128L).getParentHash()).isEqualTo(byNumber.get(127L).getHash());
assertThat(byNumber.get(127L).getHash().size()).isEqualTo(32);
}
// --- the certificate predicate: the controller feeds carriesCertificate(decode(header)) into install(),
// so it decides which anchors move the tags. On a stage-5 chain the certificate is the v2 (hybrid) list
// and getFalconSeals() is empty; a predicate that read only the v1 list never fired (rep28098 rehearsal,
// 2026-09-26). These three cases are the control that both fields are read.
private static BftExtraData extraWith(final List<FalconSeal> v1, final List<SchemeSeal> v2) {
return new BftExtraData(
Bytes.EMPTY, Collections.emptyList(), Optional.empty(), 0, Collections.emptyList(), v1, v2);
}
@Test
void carriesCertificate_trueForAV1FalconList() {
assertThat(
AerePqFinalityTags.carriesCertificate(
extraWith(List.of(new FalconSeal(0, Bytes.of(1, 2, 3))), Collections.emptyList())))
.isTrue();
}
@Test
void carriesCertificate_trueForAV2HybridCertificate_thisIsTheStage5AnchorThatWasBeingMissed() {
assertThat(
AerePqFinalityTags.carriesCertificate(
extraWith(
Collections.emptyList(),
List.of(new SchemeSeal((byte) 1, 0, Bytes.of(1, 2, 3))))))
.isTrue();
}
@Test
void carriesCertificate_falseWhenNeitherListIsPresent() {
assertThat(
AerePqFinalityTags.carriesCertificate(
extraWith(Collections.emptyList(), Collections.emptyList())))
.isFalse();
}
}

View File

@ -134,7 +134,7 @@ class HybridSignerRegistryTest {
@Test @Test
void unrecognisedEntryRefuses() { void unrecognisedEntryRefuses() {
final Properties p = sanatos(); final Properties p = sanatos();
p.setProperty("1.cheie", "0x1234"); // aproape corect, dar nu e nici addr nici key.<schema> p.setProperty("1.cheie", "0x1234"); // almost right, but it is neither addr nor key.<scheme>
assertThatThrownBy(() -> HybridSignerRegistry.fromProperties(p, "test")) assertThatThrownBy(() -> HybridSignerRegistry.fromProperties(p, "test"))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("unrecognised"); .hasMessageContaining("unrecognised");
@ -149,7 +149,7 @@ class HybridSignerRegistryTest {
final HybridSignerRegistry b = HybridSignerRegistry.fromProperties(p, "b"); final HybridSignerRegistry b = HybridSignerRegistry.fromProperties(p, "b");
assertThat(a.canonicalHash()).isEqualTo(b.canonicalHash()); // determinist assertThat(a.canonicalHash()).isEqualTo(b.canonicalHash()); // determinist
// schimb O cheie: hash-ul se misca // change ONE key: the hash moves
final Properties altKey = sanatos(); final Properties altKey = sanatos();
altKey.setProperty( altKey.setProperty(
"2.key." + SLHDSA, "2.key." + SLHDSA,
@ -157,7 +157,7 @@ class HybridSignerRegistryTest {
assertThat(HybridSignerRegistry.fromProperties(altKey, "c").canonicalHash()) assertThat(HybridSignerRegistry.fromProperties(altKey, "c").canonicalHash())
.isNotEqualTo(a.canonicalHash()); .isNotEqualTo(a.canonicalHash());
// scot o schema de la un validator: hash-ul se misca // remove one scheme from a validator: the hash moves
final Properties altScheme = sanatos(); final Properties altScheme = sanatos();
altScheme.remove("2.key." + SLHDSA); altScheme.remove("2.key." + SLHDSA);
assertThat(HybridSignerRegistry.fromProperties(altScheme, "d").canonicalHash()) assertThat(HybridSignerRegistry.fromProperties(altScheme, "d").canonicalHash())
@ -200,7 +200,7 @@ class HybridSignerRegistryTest {
p.setProperty("1.key." + FALCON, Bytes.wrap(f1.publicRegistryForm()).toHexString()); p.setProperty("1.key." + FALCON, Bytes.wrap(f1.publicRegistryForm()).toHexString());
final HybridSignerRegistry reg = HybridSignerRegistry.fromProperties(p, "test"); final HybridSignerRegistry reg = HybridSignerRegistry.fromProperties(p, "test");
// certificatul hibrid: validatorul 0 cu amandoua schemele, 1 doar Falcon // the hybrid certificate: validator 0 with both schemes, 1 with Falcon only
final java.util.List<SchemeSeal> cert = final java.util.List<SchemeSeal> cert =
java.util.List.of( java.util.List.of(
new SchemeSeal((byte) 0x01, 0, Bytes.wrap( new SchemeSeal((byte) 0x01, 0, Bytes.wrap(

View File

@ -77,7 +77,7 @@ class PqSchemeScheduleTest {
p.setProperty("count", String.valueOf(count)); p.setProperty("count", String.valueOf(count));
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
p.setProperty(i + ".addr", "0x" + String.format("%040x", 0xB0 + i)); p.setProperty(i + ".addr", "0x" + String.format("%040x", 0xB0 + i));
// fiecare index primeste macar o cheie; acoperirea per schema e controlata mai jos // every index gets at least one key; per-scheme coverage is controlled below
if (i < falconi) { if (i < falconi) {
p.setProperty(i + ".key." + FALCON, p.setProperty(i + ".key." + FALCON,
Bytes.wrap(SealSchemes.FALCON_512.generate(random).publicRegistryForm()).toHexString()); Bytes.wrap(SealSchemes.FALCON_512.generate(random).publicRegistryForm()).toHexString());
@ -98,7 +98,7 @@ class PqSchemeScheduleTest {
void armabilityGateWalksTheWholeScheduleNotJustTheFirstStep() { void armabilityGateWalksTheWholeScheduleNotJustTheFirstStep() {
// the registry: 3 validators with Falcon, only 1 with SLH-DSA // the registry: 3 validators with Falcon, only 1 with SLH-DSA
final HybridSignerRegistry reg = registruCuAcoperire(3, 1); final HybridSignerRegistry reg = registruCuAcoperire(3, 1);
// treapta PERICULOASA e ULTIMA: hibridul cere SLH-DSA cu acoperire 1 < K=3 // the DANGEROUS step is the LAST: the hybrid requires SLH-DSA with coverage 1 < K=3
final PqSchemeSchedule orar = final PqSchemeSchedule orar =
PqSchemeSchedule.parse("100:falcon-512,999999:falcon-512+slh-dsa-128s"); PqSchemeSchedule.parse("100:falcon-512,999999:falcon-512+slh-dsa-128s");
final var refusal = orar.firstUnsatisfied(reg, 3); final var refusal = orar.firstUnsatisfied(reg, 3);
@ -122,20 +122,20 @@ class PqSchemeScheduleTest {
@Test @Test
void perSchemeIntervalsParseAndOnlyBiteOnTheirOwnGrid() { void perSchemeIntervalsParseAndOnlyBiteOnTheirOwnGrid() {
// Falcon la fiecare al 32-lea bloc, SLH-DSA doar la fiecare al 128-lea, grilele numarate de la H0=64 // Falcon every 32nd block, SLH-DSA only every 128th, grids counted from H0=64
final PqSchemeSchedule orar = final PqSchemeSchedule orar =
PqSchemeSchedule.parse("1000:" + FALCON + "/32+" + SLHDSA_CANONIC + "/128"); PqSchemeSchedule.parse("1000:" + FALCON + "/32+" + SLHDSA_CANONIC + "/128");
assertThat(orar.hasIntervals()).isTrue(); assertThat(orar.hasIntervals()).isTrue();
// schemesAt ramane setul intreg: cheile si armabilitatea privesc toate schemele // schemesAt ramane setul intreg: cheile si armabilitatea privesc toate schemele
assertThat(orar.schemesAt(1000)).containsExactlyInAnyOrder(FALCON, SLHDSA_CANONIC); assertThat(orar.schemesAt(1000)).containsExactlyInAnyOrder(FALCON, SLHDSA_CANONIC);
// 64 + 32k dar nu 64 + 128k: doar Falcon // 64 + 32k but not 64 + 128k: Falcon only
assertThat(orar.schemesRequiredForAnchor(1056, 64)).containsExactly(FALCON); // 1056-64 = 992 = 31*32 assertThat(orar.schemesRequiredForAnchor(1056, 64)).containsExactly(FALCON); // 1056-64 = 992 = 31*32
assertThat(orar.schemesRequiredForAnchor(1120, 64)).containsExactly(FALCON); // 1056 = 33*32, nu multiplu de 128 assertThat(orar.schemesRequiredForAnchor(1120, 64)).containsExactly(FALCON); // 1056 = 33*32, not a multiple of 128
// 64 + 128k: amandoua // 64 + 128k: amandoua
assertThat(orar.schemesRequiredForAnchor(1216, 64)).containsExactlyInAnyOrder(FALCON, SLHDSA_CANONIC); // 1152 = 9*128 assertThat(orar.schemesRequiredForAnchor(1216, 64)).containsExactlyInAnyOrder(FALCON, SLHDSA_CANONIC); // 1152 = 9*128
// sub prima treapta (parintele 999 e sub 1000): nimic // sub prima treapta (parintele 999 e sub 1000): nimic
assertThat(orar.schemesRequiredForAnchor(1000, 64)).isEmpty(); assertThat(orar.schemesRequiredForAnchor(1000, 64)).isEmpty();
// pe granita: treapta se citeste la PARINTE (1000), ancora e 1001... care nu e pe grila 32 de la 64 // at the boundary: the step is read at the PARENT (1000), the anchor is 1001... which is not on the 32 grid from 64
assertThat(orar.schemesRequiredForAnchor(1001, 64)).isEmpty(); assertThat(orar.schemesRequiredForAnchor(1001, 64)).isEmpty();
} }

View File

@ -0,0 +1,137 @@
/*
* Copyright contributors to Besu / AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.common.bft.slhdsa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.hyperledger.besu.crypto.SecureRandomProvider;
import org.junit.jupiter.api.Test;
/**
* D-337 (2026-09-26): the parallel SLH-DSA signer must be the SAME signature as the Bouncy Castle
* original, bit for bit, and must actually be the path taken. Otherwise the anchor certificate would
* carry a different SLH-DSA seal than the second client and the public verifier expect, or the
* speed-up would be claimed while the sequential code still ran. Pinned: deterministic signing over
* several keys and message lengths equals the original; every signature ran exactly 1 + 2D tasks on
* the pool; the kill switch falls back to the original code with the same bytes and no task; and a
* randomised signature from the parallel path verifies under the original.
*/
class SlhDsaParallelSigningTest {
private static final org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters ORIGINAL =
org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters.sha2_128s;
private static AsymmetricCipherKeyPair keyPair() {
final SLHDSAKeyPairGenerator gen = new SLHDSAKeyPairGenerator();
gen.init(
new SLHDSAKeyGenerationParameters(
SecureRandomProvider.createSecureRandom(), SLHDSAParameters.sha2_128s));
return gen.generateKeyPair();
}
private static org.bouncycastle.pqc.crypto.slhdsa.SLHDSASigner original(
final AsymmetricCipherKeyPair kp, final boolean forSigning) {
final org.bouncycastle.pqc.crypto.slhdsa.SLHDSASigner s =
new org.bouncycastle.pqc.crypto.slhdsa.SLHDSASigner();
if (forSigning) {
s.init(
true,
new org.bouncycastle.pqc.crypto.slhdsa.SLHDSAPrivateKeyParameters(
ORIGINAL, ((SLHDSAPrivateKeyParameters) kp.getPrivate()).getEncoded()));
} else {
s.init(
false,
new org.bouncycastle.pqc.crypto.slhdsa.SLHDSAPublicKeyParameters(
ORIGINAL, ((SLHDSAPublicKeyParameters) kp.getPublic()).getEncoded()));
}
return s;
}
private static byte[] ours(final AsymmetricCipherKeyPair kp, final byte[] msg) {
final SLHDSASigner s = new SLHDSASigner();
s.init(true, kp.getPrivate());
return s.generateSignature(msg);
}
@Test
void parallelSignaturesAreBitIdenticalToTheOriginalAndTakeTheParallelPath() throws Exception {
assumeThat(ParallelSigning.enabled()).as("needs more than one core").isTrue();
final int perSignature = ParallelSigning.tasksPerSignature(SLHDSAParameters.sha2_128s);
assertThat(perSignature).isEqualTo(15); // FORS + two halves of each of the 7 hypertree layers
final byte[][] messages = {
new byte[0],
"a".getBytes(StandardCharsets.UTF_8),
new byte[32],
"anchor parent 20333167 on chain 2800, a longer message".getBytes(StandardCharsets.UTF_8)
};
int signatures = 0;
for (int key = 0; key < 3; key++) {
final AsymmetricCipherKeyPair kp = keyPair();
for (final byte[] msg : messages) {
final int before = ParallelSigning.tasksRun();
final byte[] fast = ours(kp, msg);
final int ran = ParallelSigning.tasksRun() - before;
assertThat(ran).as("tasks run on the pool for one signature").isEqualTo(perSignature);
assertThat(fast).hasSize(7856);
assertThat(fast).isEqualTo(original(kp, true).generateSignature(msg));
assertThat(original(kp, false).verifySignature(msg, fast)).isTrue();
signatures++;
}
}
assertThat(signatures).isEqualTo(12);
}
@Test
void theKillSwitchRunsTheOriginalCodeWithTheSameBytes() throws Exception {
final AsymmetricCipherKeyPair kp = keyPair();
final byte[] msg = "kill switch".getBytes(StandardCharsets.UTF_8);
final String before = System.getProperty(ParallelSigning.SWITCH_PROPERTY);
System.setProperty(ParallelSigning.SWITCH_PROPERTY, "false");
try {
assertThat(ParallelSigning.enabled()).isFalse();
final int tasksBefore = ParallelSigning.tasksRun();
final byte[] sig = ours(kp, msg);
assertThat(ParallelSigning.tasksRun()).as("no task on the pool").isEqualTo(tasksBefore);
assertThat(sig).isEqualTo(original(kp, true).generateSignature(msg));
} finally {
if (before == null) {
System.clearProperty(ParallelSigning.SWITCH_PROPERTY);
} else {
System.setProperty(ParallelSigning.SWITCH_PROPERTY, before);
}
}
}
@Test
void aRandomisedParallelSignatureVerifiesUnderTheOriginalAndATamperedOneDoesNot()
throws Exception {
assumeThat(ParallelSigning.enabled()).as("needs more than one core").isTrue();
final AsymmetricCipherKeyPair kp = keyPair();
final byte[] msg = "randomised".getBytes(StandardCharsets.UTF_8);
final SLHDSASigner s = new SLHDSASigner();
s.init(true, new ParametersWithRandom(kp.getPrivate(), SecureRandomProvider.createSecureRandom()));
final int before = ParallelSigning.tasksRun();
final byte[] sig = s.generateSignature(msg);
assertThat(ParallelSigning.tasksRun() - before).isEqualTo(15);
assertThat(original(kp, false).verifySignature(msg, sig)).isTrue();
final byte[] bad = sig.clone();
bad[4000] ^= 0x01;
assertThat(original(kp, false).verifySignature(msg, bad)).isFalse();
}
}

View File

@ -142,10 +142,10 @@ public class QbftMessageTransmitter {
/** /**
* Multicast a prepare carrying an OPTIONAL post-quantum seal of this node. * Multicast a prepare carrying an OPTIONAL post-quantum seal of this node.
* *
* <p>Sigiliul vine GATA CALCULAT de la apelant, si asta nu e comoditate: semnaturile Falcon sunt * <p>The seal arrives ALREADY COMPUTED from the caller, and that is not a convenience: Falcon signatures are
* randomized, so signing the same message twice yields two different byte strings. If the local * randomized, so signing the same message twice yields two different byte strings. If the local
* copy and the one on the wire each signed their own, the same validator would produce two * copy and the one on the wire each signed their own, the same validator would produce two
* valide si DIFERITE pentru aceeasi runda. Se calculeaza o data, sus, si se trece prin amandoua. * valid and DIFFERENT for the same round. It is computed once, above, and passed through both.
* *
* @param roundIdentifier the round identifier * @param roundIdentifier the round identifier
* @param digest the digest * @param digest the digest

View File

@ -142,10 +142,10 @@ public class MessageFactory {
/** /**
* Create a Prepare carrying an OPTIONAL post-quantum seal of its author. * Create a Prepare carrying an OPTIONAL post-quantum seal of its author.
* *
* <p>AERE PQ (2026-08-28), pasul 1: firul poate purta sigiliul, si nimic nu il emite inca - * <p>AERE PQ (2026-08-28), step 1: the wire can carry the seal, and nothing emits it yet -
* fiecare apel de azi trece prin varianta fara sigiliu de mai sus. Ca la commit, semnatura ECDSA * every call today goes through the seal-less variant above. As with commit, the author's ECDSA
* a autorului acopera INTREG payload-ul, deci si sigiliul, ceea ce leaga indexul revendicat de * signature covers the WHOLE payload, and so the seal too, which binds the claimed index to
* identitatea celui care trimite mesajul. * the identity of whoever sends the message.
* *
* @param roundIdentifier the round identifier * @param roundIdentifier the round identifier
* @param digest the digest * @param digest the digest

View File

@ -106,6 +106,19 @@ public class QbftExtraDataCodec extends BftExtraDataCodec {
rlpInput.enterList(); // This accounts for the "root node" which contains BFT data items. rlpInput.enterList(); // This accounts for the "root node" which contains BFT data items.
final Bytes vanityData = rlpInput.readBytes(); final Bytes vanityData = rlpInput.readBytes();
// AERE-D415 (2026-09-25): the vanity field is exactly 32 bytes at EVERY height, not only where
// the anchor rule looks (PqAnchorDigestRule checks it at anchor heights alone). The second
// client's decoder has required 32 from the start, so every header of chains 2800 and 28001,
// both geneses included, satisfies it; the differential fuzzer of the stage-5 header rule found
// the two clients judging a 0-, 31-, 33- or 64-byte vanity between anchors differently (this
// codec admitted it, the second client refused it). One rule, in the codec, for both clients.
if (vanityData.size() != EXTRA_VANITY_LENGTH) {
throw new RLPException(
"AERE-D415: QBFT vanity must be exactly "
+ EXTRA_VANITY_LENGTH
+ " bytes, the header carries "
+ vanityData.size());
}
final List<Address> validators = rlpInput.readList(Address::readFrom); final List<Address> validators = rlpInput.readList(Address::readFrom);
final Optional<Vote> vote; final Optional<Vote> vote;

View File

@ -20,9 +20,11 @@ import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; import org.hyperledger.besu.consensus.common.bft.BftBlockHashing;
import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.FalconSeal; import org.hyperledger.besu.consensus.common.bft.FalconSeal;
import org.hyperledger.besu.consensus.common.bft.PqAnchor; import org.hyperledger.besu.consensus.common.bft.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqSignerRegistry; import org.hyperledger.besu.consensus.common.bft.PqSignerRegistry;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftProposerSelector;
import org.hyperledger.besu.consensus.common.bft.headervalidationrules.BftCommitSealsValidationRule; import org.hyperledger.besu.consensus.common.bft.headervalidationrules.BftCommitSealsValidationRule;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Address;
@ -30,6 +32,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule; import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.Bytes32;
@ -44,6 +47,18 @@ import org.slf4j.LoggerFactory;
* verifies over the PROPOSAL message recomputed from the stored header (chain id, height, the round in * verifies over the PROPOSAL message recomputed from the stored header (chain id, height, the round in
* element 3, the proposal digest, i.e. the committed-seal hash of the header). Finality stays with the * element 3, the proposal digest, i.e. the committed-seal hash of the header). Finality stays with the
* anchors (AIP-22 section 4); this rule is what admits a header between them. * anchors (AIP-22 section 4); this rule is what admits a header between them.
*
* <p>AERE D-406 (2026-09-25, measured on testnet 28001): a block PREPARED in one round and not
* committed before its timer is RE-PROPOSED unchanged in a later round r by the proposer of round r
* (QBFT's locked value; only element 3 changes, the coinbase stays the builder's), and every node seals
* the header with the accepted proposal's seal, which is the round-r proposer's. Requiring the coinbase
* refused every such header on every node: block 3,314,873 stood 128 s (rounds 1 to 4 refused with
* "a seal cannot vouch for another proposer", round 5 was the builder's own turn again, N = 5); at
* N = 10 the same wait is ten rounds, and with the builder down after the prepare it does not end. So
* the seal's id must be the coinbase OR, at a round above zero, the proposer of the round in element 3
* as upstream's proposer selection computes it from the parent's proposer (its coinbase) and the
* header's own validator list (the set after the parent, which BftValidatorsValidationRule enforces).
* Every header the old rule admitted is admitted unchanged; the new branch only runs where it refused.
*/ */
public class PqHeaderSealRule implements AttachedBlockHeaderValidationRule { public class PqHeaderSealRule implements AttachedBlockHeaderValidationRule {
@ -91,18 +106,86 @@ public class PqHeaderSealRule implements AttachedBlockHeaderValidationRule {
if (!AerePqHeaderSeal.active(header.getNumber())) { if (!AerePqHeaderSeal.active(header.getNumber())) {
return belowHdr.validate(header, parent, protocolContext); return belowHdr.validate(header, parent, protocolContext);
} }
final Optional<String> r = refusal(header); final Optional<String> r = refusal(header, parent);
r.ifPresent(LOG::info); r.ifPresent(LOG::info);
return r.isEmpty(); return r.isEmpty();
} }
/**
* Why a header at or above H_hdr is refused, if it is, judged WITHOUT its parent: only a seal of the
* coinbase's index can be admitted (a re-proposed header needs the parent, {@link #refusal(BlockHeader,
* BlockHeader)}).
*
* @param header the header
* @return the refusal, or empty
*/
public Optional<String> refusal(final BlockHeader header) {
return refusal(header, null);
}
/**
* AERE D-406: the proposer of the round in element 3, as upstream's selection computes it (changeEachBlock,
* as QBFT), from the parent's proposer and the header's validator list. Empty at round 0, without the
* parent, or when the list cannot seed a selection.
*
* @param h the height
* @param round the round in element 3
* @param parent the parent header, or null
* @param validators the header's validator list
* @return the proposer of that round, or empty
*/
static Optional<Address> proposerOfRound(
final long h, final int round, final BlockHeader parent, final Collection<Address> validators) {
if (round <= 0 || round > MAX_ROUND || parent == null || validators == null || validators.isEmpty()) {
return Optional.empty();
}
try {
return Optional.of(
BftProposerSelector.selectProposerForRound(
new ConsensusRoundIdentifier(h, round),
parent.getCoinbase(),
validators,
true));
} catch (final RuntimeException e) {
return Optional.empty();
}
}
/**
* AERE D-406: the highest round element 3 may carry from H_hdr. No honest round gets near it (the QBFT round timer doubles each
* round), and it keeps the rotation arithmetic, which upstream does in int, in range. The second client applies the same bound.
*/
static final int MAX_ROUND = 1024;
/**
* AERE D-406: whether {@code coinbase} is the proposer of some round 0..round at this height, i.e. could have BUILT the block
* (a block is built by the proposer of the round it is first proposed in, and a re-proposal keeps its coinbase). The rotation is
* periodic in n, so rounds 0..min(round, n - 1) cover the set. The second client has required exactly this since D-331.
*/
static boolean builtByAProposerUpTo(
final long h, final int round, final BlockHeader parent, final Collection<Address> validators, final Address coinbase) {
final int last = Math.min(round, validators.size() - 1);
for (int r = 0; r <= last; r++) {
try {
if (BftProposerSelector.selectProposerForRound(new ConsensusRoundIdentifier(h, r), parent.getCoinbase(), validators, true)
.equals(coinbase)) {
return true;
}
} catch (final RuntimeException e) {
return false;
}
}
return false;
}
/** /**
* Why a header at or above H_hdr is refused, if it is. * Why a header at or above H_hdr is refused, if it is.
* *
* @param header the header * @param header the header
* @param parent its parent, or null (then only the coinbase's seal can be admitted)
* @return the refusal, or empty * @return the refusal, or empty
*/ */
public Optional<String> refusal(final BlockHeader header) { public Optional<String> refusal(final BlockHeader header, final BlockHeader parent) {
final long h = header.getNumber(); final long h = header.getNumber();
final BftExtraData ed; final BftExtraData ed;
try { try {
@ -118,6 +201,17 @@ public class PqHeaderSealRule implements AttachedBlockHeaderValidationRule {
if (ed.getPqProposerSeal().isEmpty()) { if (ed.getPqProposerSeal().isEmpty()) {
return Optional.of("AERE PQ HEADER: block " + h + " carries no proposer seal (element 6) from H_hdr"); return Optional.of("AERE PQ HEADER: block " + h + " carries no proposer seal (element 6) from H_hdr");
} }
// AERE D-406: the round bound, and with the parent, the builder: the coinbase is the proposer of some round up to element 3
if (ed.getRound() < 0 || ed.getRound() > MAX_ROUND) {
return Optional.of("AERE PQ HEADER: block " + h + " round " + ed.getRound() + " in element 3 is outside 0.." + MAX_ROUND);
}
if (parent != null
&& !ed.getValidators().isEmpty()
&& !builtByAProposerUpTo(h, ed.getRound(), parent, ed.getValidators(), header.getCoinbase())) {
return Optional.of(
"AERE PQ HEADER: block " + h + " coinbase " + header.getCoinbase() + " is not the proposer of any round 0.."
+ ed.getRound() + " at this height, so it did not build the block");
}
final FalconSeal ps = ed.getPqProposerSeal().get(); final FalconSeal ps = ed.getPqProposerSeal().get();
final Optional<Address> id = AerePqIdentity.v3IdAt(h, ps.getValidatorIndex()); final Optional<Address> id = AerePqIdentity.v3IdAt(h, ps.getValidatorIndex());
if (id.isEmpty()) { if (id.isEmpty()) {
@ -125,9 +219,19 @@ public class PqHeaderSealRule implements AttachedBlockHeaderValidationRule {
"AERE PQ HEADER: block " + h + " proposer seal index " + ps.getValidatorIndex() + " names no row of the v3 epoch in force"); "AERE PQ HEADER: block " + h + " proposer seal index " + ps.getValidatorIndex() + " names no row of the v3 epoch in force");
} }
if (!id.get().equals(header.getCoinbase())) { if (!id.get().equals(header.getCoinbase())) {
// AERE D-406: a re-proposed block carries the seal of the proposer of the round it was committed in
final Optional<Address> roundProposer = proposerOfRound(h, ed.getRound(), parent, ed.getValidators());
if (roundProposer.isEmpty() || !roundProposer.get().equals(id.get())) {
return Optional.of( return Optional.of(
"AERE PQ HEADER: block " + h + " proposer seal of index " + ps.getValidatorIndex() + " (id " + id.get() "AERE PQ HEADER: block " + h + " proposer seal of index " + ps.getValidatorIndex() + " (id " + id.get()
+ ") but coinbase " + header.getCoinbase() + " - a seal cannot vouch for another proposer"); + ") but coinbase " + header.getCoinbase()
+ (ed.getRound() > 0
? roundProposer
.map(p -> " and the proposer of round " + ed.getRound() + " is " + p)
.orElse(" and the proposer of round " + ed.getRound() + " is not known without the parent")
: "")
+ " - a seal cannot vouch for another proposer");
}
} }
final Bytes32 message; final Bytes32 message;
try { try {

View File

@ -38,6 +38,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule; import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -215,6 +216,256 @@ public class PqHeaderSealStage5Test {
assertThat(rule.refusal(header(H_HDR, ids.get(0), extra(1, List.of(), sealOver(r1, 1, 0, 0))))).isEmpty(); assertThat(rule.refusal(header(H_HDR, ids.get(0), extra(1, List.of(), sealOver(r1, 1, 0, 0))))).isEmpty();
} }
// ---- AERE D-406 (2026-09-25): a block re-proposed at round r carries the seal of round r's proposer ----
/** The validator list as a real header stores it: ascending. */
private List<Address> sortedIds() {
final List<Address> s = new ArrayList<>(ids);
Collections.sort(s);
return s;
}
/**
* The proposer of round r written HERE, independently of the rule (which calls upstream's selector): the
* existing-proposer case is list[(indexOf(prev) + 1 + r) mod n], the missing-proposer case seeds from the first
* validator strictly above prev (the first if none) with no +1.
*/
private static Address expectedProposer(final List<Address> sorted, final Address prev, final int r) {
final int n = sorted.size();
final int i = sorted.indexOf(prev);
if (i >= 0) {
return sorted.get((i + 1 + r) % n);
}
int seed = 0;
for (int k = 0; k < n; k++) {
if (sorted.get(k).compareTo(prev) > 0) {
seed = k;
break;
}
}
return sorted.get((seed + r) % n);
}
private BlockHeader parentWithCoinbase(final long h, final Address coinbase) {
return new BlockHeaderTestFixture().number(h - 1).coinbase(coinbase).buildHeader();
}
/** A header at h, round r, built by {@code coinbase}, sealed as index {@code index} by key {@code signer}. */
private BlockHeader reproposed(final long h, final int round, final Address coinbase, final int index, final int signer) {
final List<Address> sorted = sortedIds();
final BftExtraData bare =
new BftExtraData(
Bytes.wrap(new byte[32]), List.of(), Optional.empty(), round, sorted, Collections.emptyList(),
Collections.emptyList(), Optional.empty());
final BlockHeader unsealed = header(h, coinbase, bare);
final BftExtraData sealed =
new BftExtraData(
Bytes.wrap(new byte[32]), List.of(), Optional.empty(), round, sorted, Collections.emptyList(),
Collections.emptyList(), sealOver(unsealed, round, index, signer));
return header(h, coinbase, sealed);
}
@Test
public void aReProposedHeaderSealedByTheProposerOfItsRoundIsAdmittedOnlyWithItsParent() {
final List<Address> sorted = sortedIds();
final long h = H_HDR + 5;
int judged = 0;
for (int p = 0; p < N; p++) {
final Address prev = sorted.get(p);
final Address builder = expectedProposer(sorted, prev, 0);
for (int r = 1; r < 2 * N; r++) {
final Address roundProposer = expectedProposer(sorted, prev, r);
if (roundProposer.equals(builder)) {
continue;
}
final int idx = ids.indexOf(roundProposer);
final BlockHeader hd = reproposed(h, r, builder, idx, idx);
final BlockHeader parent = parentWithCoinbase(h, prev);
assertThat(rule.refusal(hd, parent)).as("prev %s round %s", p, r).isEmpty();
assertThat(rule.validate(hd, parent, null)).as("prev %s round %s", p, r).isTrue();
// without the parent the round's proposer is not known: refused, and the reason says why
assertThat(rule.refusal(hd)).get().asString()
.contains("cannot vouch").contains("not known without the parent");
judged++;
}
}
// every (prev, round) pair whose round proposer differs from the builder was judged: 4 x (7 - 1) at N = 4
assertThat(judged).isEqualTo(N * (2 * N - 1 - 1));
}
@Test
public void aReProposedHeaderSealedByAThirdValidatorIsRefusedAndTheReasonNamesTheRoundsProposer() {
final List<Address> sorted = sortedIds();
final long h = H_HDR + 6;
final Address prev = sorted.get(0);
final Address builder = expectedProposer(sorted, prev, 0);
final int r = 1;
final Address roundProposer = expectedProposer(sorted, prev, r);
Address third = null;
for (final Address a : sorted) {
if (!a.equals(builder) && !a.equals(roundProposer)) {
third = a;
break;
}
}
final int idx = ids.indexOf(third);
final Optional<String> why = rule.refusal(reproposed(h, r, builder, idx, idx), parentWithCoinbase(h, prev));
assertThat(why).get().asString()
.contains("cannot vouch")
.contains("the proposer of round " + r + " is " + roundProposer);
// the round proposer's key over ANOTHER round than element 3: the id passes, the signature does not
final int rp = ids.indexOf(roundProposer);
final List<Address> s2 = sortedIds();
final BlockHeader atRound0 =
header(h, builder, new BftExtraData(Bytes.wrap(new byte[32]), List.of(), Optional.empty(), 0, s2,
Collections.emptyList(), Collections.emptyList(), Optional.empty()));
final BlockHeader wrongRound =
header(h, builder, new BftExtraData(Bytes.wrap(new byte[32]), List.of(), Optional.empty(), r, s2,
Collections.emptyList(), Collections.emptyList(), sealOver(atRound0, 0, rp, rp)));
assertThat(rule.refusal(wrongRound, parentWithCoinbase(h, prev))).get().asString().contains("does not verify");
}
@Test
public void theSealMustBeExactlyTheProposerOfRoundRNotOfAnEarlierRound() {
// found by the adversarial review of 2026-09-25: an implementation admitting ANY P(r') with r' <= r passed every probe above
final List<Address> sorted = sortedIds();
final long h = H_HDR + 9;
final Address prev = sorted.get(0);
final Address builder = expectedProposer(sorted, prev, 0);
final Address p1 = expectedProposer(sorted, prev, 1);
final Address p2 = expectedProposer(sorted, prev, 2);
assertThat(p1).isNotEqualTo(p2);
final int i1 = ids.indexOf(p1);
assertThat(rule.refusal(reproposed(h, 2, builder, i1, i1), parentWithCoinbase(h, prev))).get().asString()
.contains("cannot vouch").contains("the proposer of round 2 is " + p2);
final int i2 = ids.indexOf(p2);
assertThat(rule.refusal(reproposed(h, 2, builder, i2, i2), parentWithCoinbase(h, prev))).isEmpty();
}
@Test
public void withTheParentTheCoinbaseMustBeTheProposerOfSomeRoundUpToElement3() {
// the second client has refused this since D-331; the Besu-derived rule did not, so the two judged such a header differently
final List<Address> sorted = sortedIds();
final long h = H_HDR + 10;
final Address prev = sorted.get(0);
final Address p1 = expectedProposer(sorted, prev, 1);
final Address p3 = expectedProposer(sorted, prev, 3);
final int i1 = ids.indexOf(p1);
// round 1, built by P(3) (a proposer of a LATER round), sealed by P(1): the seal is the round's, the builder is impossible
assertThat(rule.refusal(reproposed(h, 1, p3, i1, i1), parentWithCoinbase(h, prev))).get().asString()
.contains("is not the proposer of any round 0..1");
// and a coinbase-sealed header at round 0 whose coinbase is not P(0)
final int i3 = ids.indexOf(p3);
assertThat(rule.refusal(reproposed(h, 0, p3, i3, i3), parentWithCoinbase(h, prev))).get().asString()
.contains("is not the proposer of any round 0..0");
// without the parent the builder cannot be judged, and the coinbase's own seal is admitted as before
assertThat(rule.refusal(reproposed(h, 0, p3, i3, i3))).isEmpty();
}
@Test
public void theRoundInElement3IsBoundedAndTheBoundIsInclusive() {
final List<Address> sorted = sortedIds();
final long h = H_HDR + 11;
final Address prev = sorted.get(2);
final int max = PqHeaderSealRule.MAX_ROUND;
// at the bound: a re-proposal sealed by P(max) is admitted (upstream's int arithmetic stays in range)
final Address builder = expectedProposer(sorted, prev, 0);
final Address pm = expectedProposer(sorted, prev, max);
final int im = ids.indexOf(pm);
final int ib = ids.indexOf(builder);
final Address sealer = pm.equals(builder) ? builder : pm;
final int is = sealer.equals(builder) ? ib : im;
assertThat(rule.refusal(reproposed(h, max, builder, is, is), parentWithCoinbase(h, prev))).isEmpty();
// above it: refused by the bound, with or without the parent, whoever sealed
assertThat(rule.refusal(reproposed(h, max + 1, builder, ib, ib), parentWithCoinbase(h, prev))).get().asString()
.contains("is outside 0.." + max);
assertThat(rule.refusal(reproposed(h, max + 1, builder, ib, ib))).get().asString().contains("is outside 0.." + max);
}
@Test
public void atRoundZeroOnlyTheCoinbaseSealIsAdmittedEvenWithTheParent() {
final List<Address> sorted = sortedIds();
final long h = H_HDR + 7;
final Address prev = sorted.get(1);
final Address builder = expectedProposer(sorted, prev, 0);
for (final Address other : sorted) {
if (other.equals(builder)) {
continue;
}
final int idx = ids.indexOf(other);
final Optional<String> why = rule.refusal(reproposed(h, 0, builder, idx, idx), parentWithCoinbase(h, prev));
assertThat(why).get().asString().contains("cannot vouch").doesNotContain("proposer of round");
}
final int b = ids.indexOf(builder);
assertThat(rule.refusal(reproposed(h, 0, builder, b, b), parentWithCoinbase(h, prev))).isEmpty();
}
@Test
public void whenTheParentsProposerLeftTheSetTheRoundsProposerFollowsUpstreamsMissingProposerSeed() {
final List<Address> sorted = sortedIds();
final long h = H_HDR + 8;
// a parent proposer below every id (the seed is the first validator) and one above every id (it wraps to the first)
final Address below = Address.fromHexString("0x0000000000000000000000000000000000000001");
final Address above = Address.fromHexString("0xffffffffffffffffffffffffffffffffffffffff");
// and one strictly between the second and the third id, so the seed is the third (index 2), not the first
final byte[] mid = sorted.get(1).getBytes().toArrayUnsafe().clone();
int k = mid.length - 1;
while (k >= 0 && mid[k] == (byte) 0xff) {
mid[k--] = 0;
}
mid[k]++;
final Address middle = Address.wrap(Bytes.wrap(mid));
assertThat(middle.compareTo(sorted.get(1)) > 0 && middle.compareTo(sorted.get(2)) < 0).isTrue();
assertThat(expectedProposer(sorted, middle, 0)).isEqualTo(sorted.get(2));
int judged = 0;
for (final Address prev : List.of(below, above, middle)) {
final Address builder = expectedProposer(sorted, prev, 0);
for (int r = 1; r < N; r++) {
final Address rp = expectedProposer(sorted, prev, r);
final int idx = ids.indexOf(rp);
assertThat(rule.refusal(reproposed(h, r, builder, idx, idx), parentWithCoinbase(h, prev)))
.as("prev %s round %s", prev, r).isEmpty();
judged++;
}
}
assertThat(judged).isEqualTo(3 * (N - 1));
}
// ---- AERE D-415 (2026-09-25): the vanity field is exactly 32 bytes at EVERY height, in the codec, as in the second client ----
/** The genesis extraData of chains 2800 and 28001 (both a 32-zero vanity), as published. */
private static final String GENESIS_2800_EXTRA =
"0xf865a00000000000000000000000000000000000000000000000000000000000000000f83f94b5e768cb3117ea25175f51aa956b48691025c2a894f73bbbd26a82dbc769066550d8d4585eab2fc00d944bf6f91d84b37454544279cba152581ed6820044c080c0";
private static final String GENESIS_28001_EXTRA =
"0xf87aa00000000000000000000000000000000000000000000000000000000000000000f8549404211fd71c1eae94f560e7e173399c91c15fd9b394070d5e1534a367430307a6e56bef29ddff79c0e394b84ec13c5811264f60a8bc392634709558223ea694fb6be1504acf8262cf98669f7833b9cf1cbfabb9c080c0";
/** A real 32-byte vanity between anchors on 28001 today: the client version string, not a digest. */
private static final String VANITY_VERSIUNE = "0x626573752032362e342e302d616572652e312d643230333230312d7774396239";
private BlockHeader cuVanity(final long h, final Bytes vanity) {
final BftExtraData ed =
new BftExtraData(vanity, List.of(), Optional.empty(), 0, ids, Collections.emptyList(), Collections.emptyList(), Optional.empty());
return new BlockHeaderTestFixture().number(h).coinbase(ids.get(0)).extraData(CODEC.encode(ed)).buildHeader();
}
@Test
public void theVanityFieldIsExactly32BytesAtEveryHeightAndBothGenesesStillDecode() {
// the two real geneses and a real between-anchors vanity decode as before (the check cannot touch the chain)
for (final String g : List.of(GENESIS_2800_EXTRA, GENESIS_28001_EXTRA)) {
assertThat(CODEC.decodeRaw(Bytes.fromHexString(g)).getVanityData().size()).as("genesis").isEqualTo(32);
}
assertThat(CODEC.decode(cuVanity(H_HDR - 5, Bytes.fromHexString(VANITY_VERSIUNE))).getVanityData().size()).isEqualTo(32);
// found by the differential fuzzer: a 0-, 31-, 33- or 64-byte vanity between anchors was admitted here and refused by the
// second client; now the codec refuses it at any height, above and below H_hdr, and the header rule reports the reason
for (final int n : new int[] {0, 31, 33, 64}) {
final Bytes v = Bytes.wrap(new byte[n]);
assertThatThrownBy(() -> CODEC.decode(cuVanity(H_HDR + 12, v))).as("vanity %s above H_hdr", n)
.isInstanceOf(RLPException.class).hasMessageContaining("AERE-D415");
assertThatThrownBy(() -> CODEC.decode(cuVanity(H_HDR - 5, v))).as("vanity %s below H_hdr", n)
.isInstanceOf(RLPException.class).hasMessageContaining("AERE-D415");
assertThat(rule.refusal(cuVanity(H_HDR + 12, v))).get().asString().contains("does not decode").contains("AERE-D415");
}
}
@Test @Test
public void belowHhdrTheUpstreamRuleDecidesAndHhdrNeedsHfin() { public void belowHhdrTheUpstreamRuleDecidesAndHhdrNeedsHfin() {
final BlockHeader below = sealedHeader(H_HDR - 1, 0, 0, 0, 0); final BlockHeader below = sealedHeader(H_HDR - 1, 0, 0, 0, 0);

View File

@ -0,0 +1,103 @@
/*
* Copyright contributors to Besu / AERE Network.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.eth.sync;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AERE D-412 (2026-09-26), Rule C. A process-wide gate the block PROPAGATION path asks before it adopts
* a block arriving from the network as the new canonical head.
*
* <p>WHY IT EXISTS. From AIP-22 stage 5 (H_hdr) a header carries only the proposer's single Falcon-512
* seal, so a valid header no longer proves a quorum agreed the block: an honest committed block and a
* block a single key forged are indistinguishable to header validation. The quorum agreement lives OFF
* the header, in the commit seals a validator heard (AIP-21, {@code PqSealCache}). Measured on rep28098,
* a single validator forged its own between-anchor block, gossiped it, and honest validators imported it
* as head and built on top. This gate lets a validator refuse to ADOPT such a propagated block at the tip
* of its own consensus height unless it holds a post-quantum commit quorum for it. Block VALIDITY is
* unchanged (this is fork choice, not validation), so the sync downloader - a different code path that
* does not consult this gate - still imports the same chain and catch-up is unaffected.
*
* <p>WHY A STATIC REGISTRY. This module ({@code ethereum/eth}) sits below the consensus modules and
* cannot depend on them, so the smart part of the decision (the seal cache, the registry, the validator
* set) lives in a consensus-aware implementation that registers itself here at startup, exactly as
* {@code PqSealCache.instance()} and the other AERE consensus singletons are process-wide. The interface
* takes only {@code ethereum/core} types, so there is no backward dependency. Unset, the gate is
* {@link #ALLOW_ALL} and behaviour is byte-for-byte as before.
*
* <p>FAIL OPEN. {@link #mayAdopt} never throws: a bug in the installed gate reverts to the current
* behaviour (adopt), never to refusing every block, because a repair that could stop the chain is worse
* than the exposure it closes.
*/
public final class PropagatedBlockAdoptionGate {
private static final Logger LOG = LoggerFactory.getLogger(PropagatedBlockAdoptionGate.class);
/** The decision a consensus-aware implementation supplies. */
@FunctionalInterface
public interface Gate {
/**
* Whether this node may adopt a block that arrived by propagation as the new canonical head.
*
* @param header the propagated block's header, already header-validated
* @param parent the parent header, already on this node's chain
* @return true to adopt (import as head), false to leave this height to this node's own consensus
*/
boolean mayAdopt(BlockHeader header, BlockHeader parent);
}
/** The default: adopt everything, i.e. exactly the behaviour before this gate existed. */
public static final Gate ALLOW_ALL = (header, parent) -> true;
private static volatile Gate gate = ALLOW_ALL;
private PropagatedBlockAdoptionGate() {}
/**
* Install the gate for this process. A consensus builder calls this at startup.
*
* @param g the gate, or null to reset to {@link #ALLOW_ALL}
*/
public static void install(final Gate g) {
gate = (g == null) ? ALLOW_ALL : g;
}
/** Reset to {@link #ALLOW_ALL}. For tests and a deliberate operator reset. */
public static void reset() {
gate = ALLOW_ALL;
}
/**
* Whether the installed gate allows adopting this propagated block. Never throws: on any error it
* allows (fail open), so a gate bug can never stop the chain.
*
* @param header the propagated block's header
* @param parent the parent header
* @return true to adopt
*/
public static boolean mayAdopt(final BlockHeader header, final BlockHeader parent) {
try {
return gate.mayAdopt(header, parent);
} catch (final RuntimeException e) {
LOG.warn(
"AERE D-412: propagation adoption gate threw ({}); allowing the block, as before the gate.",
e.toString());
return true;
}
}
}