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:
parent
4250738290
commit
846f437724
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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');
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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(
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user