anchor: the corpus carries AIP-22 stage 5 (headers sealed by the proposer's post-quantum seal, no ECDSA committed seals) and the stage-3b correction (a commit's hybrid seal judged with the v3 epoch's key)

This commit is contained in:
Aere Network 2026-09-24 02:29:37 +03:00
parent ab90cc5b06
commit 344ba39314
17 changed files with 876 additions and 23 deletions

View File

@ -123,7 +123,7 @@ the section 4(b) notice of changed files), and each keeps its original notice pe
<!-- GENERAT: lista de mai jos se rescrie de unealta de licentiere din depozitul de constructie --> <!-- GENERAT: lista de mai jos se rescrie de unealta de licentiere din depozitul de constructie -->
## anchor/: every file that carries someone else's copyright (generated 2026-09-23) ## anchor/: every file that carries someone else's copyright (generated 2026-09-24)
This list is **generated from the files this package actually ships**, not written by hand: the This list is **generated from the files this package actually ships**, not written by hand: the
previous hand-written table fell behind the tree on 2026-09-12 and seven upstream files went previous hand-written table fell behind the tree on 2026-09-12 and seven upstream files went
@ -132,9 +132,9 @@ repository, and the publisher refuses to ship a package whose list disagrees wit
You do not need that tool to check the list: every row below names a file in this package, and You do not need that tool to check the list: every row below names a file in this package, and
`grep -i copyright` on it shows the same line. `grep -i copyright` on it shows the same line.
Measured 2026-09-23 over `anchor/`: **198 files**. **90** carry someone Measured 2026-09-24 over `anchor/`: **202 files**. **90** carry someone
else's copyright alone, **54** carry a shared notice naming both upstream and us, and **11** name only else's copyright alone, **57** carry a shared notice naming both upstream and us, and **11** name only
`contributors to the AERE Network`; 43 carry no copyright line at all. `contributors to the AERE Network`; 44 carry no copyright line at all.
The first two groups are the table below. A shared notice is listed too: those are precisely the The first two groups are the table below. A shared notice is listed too: those are precisely the
upstream files we changed, so section 4(b) has something to say about each of them. upstream files we changed, so section 4(b) has something to say about each of them.
Every file below is Apache-2.0 material redistributed under section 4: it keeps its original Every file below is Apache-2.0 material redistributed under section 4: it keeps its original
@ -150,6 +150,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/consensus/common/build.gradle` | `Copyright ConsenSys AG.` | upstream | | `anchor/consensus/common/build.gradle` | `Copyright ConsenSys AG.` | upstream |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinality.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinality.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqHeaderSeal.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftBlockInterface.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftBlockInterface.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java` | `Copyright ConsenSys AG.` | upstream |
@ -268,6 +269,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorSealsRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorSealsRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqHeaderSealRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqRegistryBindingRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqRegistryBindingRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/FalconSealLogThrottleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/FalconSealLogThrottleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/FalconSealValidationRuleRetirementTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/FalconSealValidationRuleRetirementTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
@ -279,6 +281,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRuleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqCheckpointRuleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRuleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqEmergencyShoutRuleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqForkGateFeedTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqForkGateFeedTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqHeaderSealStage5Test.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java` | `Copyright ConsenSys AG.` | upstream |

View File

@ -5,15 +5,16 @@ 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
6bc5a9ae4dbeda9a26e3157cb61c938cc306c859ee597526107199e0c88166ae app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java 1615ecaf19f75af3e91c27c029177bf0faf51839f5589878b8c3562371299865 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
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
c28e3bf26d68ab7b764f6381bc678d5f7b60bfa7d02796e3ac34521c5c5bc6ed 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
2e84bb48611d3da7c46bcd4018ae746cbf785dfcb16f495674a0f6d542e71e8e consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java 9fcf44789ddf6d5b80be7d44f0bec58af6ba8316733d49817e890f0e17b71223 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java
70fa067469a084ac225765da6a01619dec51130acc255b7194a1ad274d327141 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java 70fa067469a084ac225765da6a01619dec51130acc255b7194a1ad274d327141 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java
3ab425b5b5f7d7c2199065a3905623d5c1c4c5922d892c6c4275415b0c69a2b4 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FalconSeal.java 3ab425b5b5f7d7c2199065a3905623d5c1c4c5922d892c6c4275415b0c69a2b4 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FalconSeal.java
db1e80115ce59c8281fbf046cb37e153a49fe017c1675afd200e8125d3f6b538 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FalconSealScheme.java db1e80115ce59c8281fbf046cb37e153a49fe017c1675afd200e8125d3f6b538 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FalconSealScheme.java
@ -121,12 +122,12 @@ d365bad33f3aee4597c630071ca1f727ddd8bb6ab6515fccbce0a07842037adf consensus/qbft
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
51eddf3cb8e37489abd35e6f4c36294a13ec76337bbbbfd54c07584085dceaac consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManager.java 51eddf3cb8e37489abd35e6f4c36294a13ec76337bbbbfd54c07584085dceaac consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManager.java
0a450699c702104dcef6bf8912b4d180f05d647288c75a9f78da6af3bd3d90f1 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftController.java 0a450699c702104dcef6bf8912b4d180f05d647288c75a9f78da6af3bd3d90f1 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftController.java
9c7e2698016f8cf19d3b6d78c6256ad5e8eabec06d9a877eee4e8c2dde423839 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftRound.java 6a34f5e87b5c5c629f1f08d0dc88c336a3ab3c0ba8797d3b0ee477246f10f4c6 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftRound.java
00c8204e4b44acd469973d6bbc07992382fdd2d692f079a55de76f944a7432f8 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/RoundState.java 4494f33ca028b60eaa093e6c24dd13706461e228f7f5a29e40e8917d1ef3b373 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/RoundState.java
601b0cccf32f8ebe327c7581e0e9b3f9f67de49d9fb646326d6b49ef2138c641 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftBlockCreator.java eadc4a14e2f0fdf7c1d9667d67c59f709af7a86fccf51923de0248b73e223c21 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftBlockCreator.java
4c0811d182f5252bdde31560f514a518db58c8d0c82c748a6c0f5d68472470c6 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidator.java 4c0811d182f5252bdde31560f514a518db58c8d0c82c748a6c0f5d68472470c6 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidator.java
a40621537e2b2024ab5586c9001f96fd81ffe3c3d5ce1e867a07a77584cf2173 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/MessageValidator.java a40621537e2b2024ab5586c9001f96fd81ffe3c3d5ce1e867a07a77584cf2173 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/MessageValidator.java
22c73f9cdfe5626347f34f173b7b60312e00970ccba49862686051b2dd84fe36 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqCommitEnforcement.java 60c0c68de44d277456f84452d2acaf93a4a239728f65af2fb55be49bbe417123 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqCommitEnforcement.java
bacf232d9f3a9163ce6df3091c5e9161efcb26a73aecaead910e6952ee27bf86 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqFinalityLayersGuard.java bacf232d9f3a9163ce6df3091c5e9161efcb26a73aecaead910e6952ee27bf86 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqFinalityLayersGuard.java
5ab52433f770537e838b15e23abfc7b24eaa733ea26577451d5e32226d617879 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcement.java 5ab52433f770537e838b15e23abfc7b24eaa733ea26577451d5e32226d617879 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcement.java
742611d52091a6c138cdff60c5f7f0bfb2fc33e567f520f3e86878fc086e77d9 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqProposalEnforcement.java 742611d52091a6c138cdff60c5f7f0bfb2fc33e567f520f3e86878fc086e77d9 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqProposalEnforcement.java
@ -153,6 +154,7 @@ c1b31f5c357a1966cdd1e963369a1eb3078a381679c6adc8fb5206f754eca84b consensus/qbft
c0853ed97c53d54951e25ad6d0b70c0299dedd859cc7c44da64e3751d0e0de33 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqCommitPlumbingTest.java c0853ed97c53d54951e25ad6d0b70c0299dedd859cc7c44da64e3751d0e0de33 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqCommitPlumbingTest.java
01cefcd272ca54e1ebd37f606ec87cc884918c1d72289347a7c234cdca2840ff consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqFinalityLayersGuardTest.java 01cefcd272ca54e1ebd37f606ec87cc884918c1d72289347a7c234cdca2840ff consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqFinalityLayersGuardTest.java
52e75fd98b9d2ab3d6f87fc8ea1a638321e70d5eec5f14db615ee13755ba1287 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqHybridEnforcementTest.java 52e75fd98b9d2ab3d6f87fc8ea1a638321e70d5eec5f14db615ee13755ba1287 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqHybridEnforcementTest.java
1834ed0950cdac7620091315e1dfd60f61cecd10def1196c69bf8a22db08f943 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqHybridRotationV3Test.java
bca46ca91803c89eb18d670c740edb4e47ef0bca53a9b4b82ce0c8446e0d6c0e consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqMessageAgilityTest.java bca46ca91803c89eb18d670c740edb4e47ef0bca53a9b4b82ce0c8446e0d6c0e consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqMessageAgilityTest.java
5a47d247d7bcda77b57f5c906c3cee1af826785416012fafe9cfa49d63671388 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareAgilityTest.java 5a47d247d7bcda77b57f5c906c3cee1af826785416012fafe9cfa49d63671388 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareAgilityTest.java
1939e33cc8ea81782e5e17d68e2b59c001e4207ed8ef07acd1d2a166a047dcb3 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcementTest.java 1939e33cc8ea81782e5e17d68e2b59c001e4207ed8ef07acd1d2a166a047dcb3 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcementTest.java
@ -163,9 +165,9 @@ dc9f9e862a11f0135d26976176a1d1adac3e84fd5f1e06572d8727262cb3374f consensus/qbft
2c612868dfdd61d86cadc4ca9b34faacf4e43aca88878feb72ac140e1321ef5d consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/ProposalPayloadValidatorPqWiringTest.java 2c612868dfdd61d86cadc4ca9b34faacf4e43aca88878feb72ac140e1321ef5d consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/ProposalPayloadValidatorPqWiringTest.java
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
77568cb12200fd653772cd5bb93f989b012909fdb645d68fa39e8251138ec89e 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
42b86cd888efe1aa00f9c9477566e353649c609c13a042e514185b57a6674a33 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java 65ba290ae597b2217b86f2dd4b9db5faacc6e60ef7e36343a9d724c40b66dd73 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java
103d46eecfc11c97fb59a1e0237a6b5b6d384a43ec45f5028bd225b8bf8e3f6c 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
e4e75c28ff15d058176145b1ae32606bf91b22112d1668a718f1a6ae4add0e71 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/FalconSealValidationRule.java e4e75c28ff15d058176145b1ae32606bf91b22112d1668a718f1a6ae4add0e71 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/FalconSealValidationRule.java
@ -174,6 +176,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
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
@ -189,6 +192,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
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

View File

@ -181,6 +181,8 @@ This matters more than the file list, so it is stated before it.
| Sealing keys read from the v3 epochs, key rotation under an id (AIP-22 stage 3b, `aere.pq.registryV3.keysFrom`, `aere.falcon.keys.next`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only | | Sealing keys read from the v3 epochs, key rotation under an id (AIP-22 stage 3b, `aere.pq.registryV3.keysFrom`, `aere.falcon.keys.next`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only |
| Message authorship from VERIFIED post-quantum seals, with the startup guards AERE-PQC-FIN-01/02/03 (AIP-22 stage 4, `aere.pq.finalityBlock`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only (H_fin 3,117,000) | | Message authorship from VERIFIED post-quantum seals, with the startup guards AERE-PQC-FIN-01/02/03 (AIP-22 stage 4, `aere.pq.finalityBlock`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only (H_fin 3,117,000) |
| Checkpoint option, a header rule that refuses another hash at a checkpoint height (AIP-22 stage 4, `aere.pq.checkpoint`) | yes (added 2026-09-23) | **no**; not configured anywhere | | Checkpoint option, a header rule that refuses another hash at a checkpoint height (AIP-22 stage 4, `aere.pq.checkpoint`) | yes (added 2026-09-23) | **no**; not configured anywhere |
| Headers without ECDSA committed seals, sealed by the proposer's post-quantum seal as extraData element 6 (AIP-22 stage 5, `aere.pq.headerBlock`, guards AERE-PQC-HDR-01/02) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only (H_hdr 3,131,000) |
| The hybrid (SLH-DSA) seal of a commit judged, from the v3 key height, with the key of the v3 epoch in force (a rotated row signs with its new key) | yes (corrected 2026-09-23) | **no** (no v3 epoch on chain 2800); on testnet 28001 since 2026-09-23 22:51Z |
Everything not yet in force is **disarmed by absence or by height, never by a flag**: the properties that arm them Everything not yet in force is **disarmed by absence or by height, never by a flag**: the properties that arm them
are unset, and unset means never. Each refuses loudly on a value it cannot parse rather than booting are unset, and unset means never. Each refuses loudly on a value it cannot parse rather than booting

View File

@ -31,6 +31,7 @@ import org.hyperledger.besu.consensus.common.BftValidatorOverrides;
import org.hyperledger.besu.consensus.common.EpochManager; import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.bft.AerePqFinality; import org.hyperledger.besu.consensus.common.bft.AerePqFinality;
import org.hyperledger.besu.consensus.common.bft.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;
import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftContext;
@ -480,6 +481,15 @@ public class QbftBesuControllerBuilder extends BesuControllerBuilder {
FalconSealSupport.proposalAttachBlock(), FalconSealSupport.proposalAttachBlock(),
FalconSealSupport.prepareAttachBlock(), FalconSealSupport.prepareAttachBlock(),
FalconSealSupport.roundChangeAttachBlock()); FalconSealSupport.roundChangeAttachBlock());
// AERE AIP-22 stage 5: H_hdr (aere.pq.headerBlock), from which a header carries the proposer's post-quantum seal
// and no ECDSA committed seal; refuses unless H_fin is at or below it (AERE-PQC-HDR-01). Inert when unset.
AerePqHeaderSeal.installFromSystemProperties();
if (AerePqHeaderSeal.headerBlock() != AerePqIdentity.NEVER) {
LOG.info(
"AERE PQ HEADER: H_hdr armed at {}; from it a header carries the proposer's post-quantum seal and NO ECDSA"
+ " committed seal",
AerePqHeaderSeal.headerBlock());
}
if (AerePqFinality.finalityBlock() != AerePqIdentity.NEVER) { if (AerePqFinality.finalityBlock() != AerePqIdentity.NEVER) {
LOG.info( LOG.info(
"AERE PQ FINALITY: H_fin armed at {}; from it a message's author is the id of its VERIFIED post-quantum seal and the" "AERE PQ FINALITY: H_fin armed at {}; from it a message's author is the id of its VERIFIED post-quantum seal and the"

View File

@ -0,0 +1,95 @@
/*
* 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;
/**
* AERE AIP-22 stage 5 (plan 9.7): from H_hdr ({@code aere.pq.headerBlock}) a header carries no ECDSA
* committed seal; its seventh extraData element is the proposer's Falcon-512 seal, the PROPOSAL layer's
* seal over the proposal digest, and a header is admitted between anchors iff that seal verifies for
* its {@code coinbase} (an id) at its height. Finality stays with the anchors (stage 4). H_hdr must be
* at or above H_fin, because the seal it relies on is only an AUTHOR from H_fin (AERE-PQC-HDR-01).
* Unset: never, which is every chain today.
*/
public final class AerePqHeaderSeal {
/** The system property naming H_hdr. */
public static final String PROPERTY = "aere.pq.headerBlock";
private static volatile long headerBlock = AerePqIdentity.NEVER;
private AerePqHeaderSeal() {}
/**
* Arms H_hdr.
*
* @param hHdr H_hdr, or {@link AerePqIdentity#NEVER}
*/
public static void install(final long hHdr) {
if (hHdr != AerePqIdentity.NEVER) {
final long hFin = AerePqFinality.finalityBlock();
if (hFin == AerePqIdentity.NEVER || hHdr < hFin) {
throw new IllegalStateException(
"AERE-PQC-HDR-01: "
+ PROPERTY
+ "="
+ hHdr
+ " needs H_fin (aere.pq.finalityBlock) armed at or below it (H_fin "
+ (hFin == AerePqIdentity.NEVER ? "unset" : Long.toString(hFin))
+ "): the proposer's seal in the header is an author only from H_fin");
}
}
headerBlock = hHdr;
}
/** Disarms (tests). */
public static void uninstall() {
headerBlock = AerePqIdentity.NEVER;
}
/**
* H_hdr as armed.
*
* @return H_hdr, or {@link AerePqIdentity#NEVER}
*/
public static long headerBlock() {
return headerBlock;
}
/** Arms from {@link #PROPERTY}; unset means never. Call after H_fin is installed. */
public static void installFromSystemProperties() {
final String v = System.getProperty(PROPERTY);
if (v == null || v.isBlank()) {
uninstall();
return;
}
final long h;
try {
h = Long.parseLong(v.trim());
} catch (final NumberFormatException e) {
throw new IllegalStateException("AERE-PQC-HDR-01: " + PROPERTY + "='" + v + "' is not a height", e);
}
install(h);
}
/**
* Whether a header at this height carries the proposer's seal instead of ECDSA committed seals.
*
* @param height the header's height
* @return true from H_hdr
*/
public static boolean active(final long height) {
return height >= headerBlock;
}
}

View File

@ -117,7 +117,10 @@ public class BftBlockInterface implements BlockInterface {
// AERE ANCHOR V2 (2026-09-03, D-328): the scheme-tagged certificate rides along too. Without // AERE ANCHOR V2 (2026-09-03, D-328): the scheme-tagged certificate rides along too. Without
// this line every v2 header lost its certificate at the round substitution and the fleet // this line every v2 header lost its certificate at the round substitution and the fleet
// refused its own first v2 anchor (testnet 28001, block 168032, 3 hours stalled). // refused its own first v2 anchor (testnet 28001, block 168032, 3 hours stalled).
prevExtraData.getHybridSeals()); prevExtraData.getHybridSeals(),
// AERE AIP-22 stage 5: and the proposer's seal (element 6), for the same reason; it sits outside
// both hash pre-images, so carrying it changes no hash
prevExtraData.getPqProposerSeal());
final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(block.getHeader()); final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(block.getHeader());
headerBuilder headerBuilder

View File

@ -49,6 +49,12 @@ public class BftExtraData implements ParsedExtraData {
private final Collection<FalconSeal> falconSeals; private final Collection<FalconSeal> falconSeals;
/** AERE ANCHOR V2: the scheme-tagged certificate; empty on every header below the v2 height. */ /** AERE ANCHOR V2: the scheme-tagged certificate; empty on every header below the v2 height. */
private final List<SchemeSeal> hybridSeals; private final List<SchemeSeal> hybridSeals;
/**
* AERE AIP-22 stage 5: the proposer's post-quantum seal (element 6 of the stored extraData, from
* H_hdr), the PROPOSAL layer's seal over the proposal digest; outside every hash, like the
* certificate. Empty on every header below H_hdr.
*/
private final Optional<FalconSeal> proposerSeal;
/** /**
* Instantiates a new Bft extra data (no Falcon seals). Retained for callers, notably the IBFT * Instantiates a new Bft extra data (no Falcon seals). Retained for callers, notably the IBFT
@ -103,7 +109,33 @@ public class BftExtraData implements ParsedExtraData {
final Collection<Address> validators, final Collection<Address> validators,
final Collection<FalconSeal> falconSeals, final Collection<FalconSeal> falconSeals,
final List<SchemeSeal> hybridSeals) { final List<SchemeSeal> hybridSeals) {
this(vanityData, seals, vote, round, validators, falconSeals, hybridSeals, Optional.empty());
}
/**
* Full constructor with the proposer's post-quantum seal (AIP-22 stage 5).
*
* @param vanityData the vanity data
* @param seals the ECDSA committed seals (from H_hdr: empty)
* @param vote the vote
* @param round the round
* @param validators the validators
* @param falconSeals the v1 Falcon certificate, or empty
* @param hybridSeals the v2 certificate, or empty
* @param proposerSeal the proposer's seal (element 6), or empty
*/
public BftExtraData(
final Bytes vanityData,
final Collection<SECPSignature> seals,
final Optional<Vote> vote,
final int round,
final Collection<Address> validators,
final Collection<FalconSeal> falconSeals,
final List<SchemeSeal> hybridSeals,
final Optional<FalconSeal> proposerSeal) {
checkNotNull(vanityData); checkNotNull(vanityData);
checkNotNull(proposerSeal);
this.proposerSeal = proposerSeal;
checkNotNull(hybridSeals); checkNotNull(hybridSeals);
if (!hybridSeals.isEmpty() && falconSeals != null && !falconSeals.isEmpty()) { if (!hybridSeals.isEmpty() && falconSeals != null && !falconSeals.isEmpty()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -184,6 +216,30 @@ public class BftExtraData implements ParsedExtraData {
return hybridSeals; return hybridSeals;
} }
/**
* The proposer's post-quantum seal (AIP-22 stage 5, element 6).
*
* @return the seal, or empty below H_hdr
*/
public Optional<FalconSeal> getPqProposerSeal() {
return proposerSeal;
}
/**
* The same extra data with another proposer seal and ECDSA seals, everything else carried over
* (the certificate included): the one way to seal a stage-5 header, so no caller can drop the
* certificate while adding the seal (the lesson of 2026-09-03, D-328).
*
* @param newSeals the ECDSA committed seals (from H_hdr: empty)
* @param newProposerSeal the proposer's seal
* @return the new extra data
*/
public BftExtraData withSeals(
final Collection<SECPSignature> newSeals, final Optional<FalconSeal> newProposerSeal) {
return new BftExtraData(
vanityData, newSeals, vote, round, validators, falconSeals, hybridSeals, newProposerSeal);
}
@Override @Override
public String toString() { public String toString() {
return "BftExtraData{" return "BftExtraData{"
@ -201,6 +257,8 @@ public class BftExtraData implements ParsedExtraData {
+ falconSeals + falconSeals
+ ", hybridSeals=" + ", hybridSeals="
+ hybridSeals + hybridSeals
+ ", proposerSeal="
+ proposerSeal
+ '}'; + '}';
} }
} }

View File

@ -22,6 +22,7 @@ package org.hyperledger.besu.consensus.qbft.core.statemachine;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import org.hyperledger.besu.consensus.common.bft.AerePqHeaderSeal;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; 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.FalconSealSupport; import org.hyperledger.besu.consensus.common.bft.FalconSealSupport;
@ -539,6 +540,27 @@ public class QbftRound {
// least K valid seals, K being the configured schedule (6 of 9 on chain 2800 today). Between // least K valid seals, K being the configured schedule (6 of 9 on chain 2800 today). Between
// anchor heights nothing is written. The older wording here said "a >= 2f+1 Falcon quorum // anchor heights nothing is written. The older wording here said "a >= 2f+1 Falcon quorum
// certificate" and described the legacy per-block rule, which is retired at the anchor block. // certificate" and described the legacy per-block rule, which is retired at the anchor block.
// AERE AIP-22 stage 5: from H_hdr the sealed header carries no ECDSA committed seal and the
// accepted proposal's post-quantum seal as element 6 (the same bytes on every node). A proposal
// without one cannot have been accepted from H_fin <= H_hdr; if it happens anyway nothing is
// imported, because the only other header this node could build is one every node refuses.
final long aereHeight = roundState.getRoundIdentifier().getSequenceNumber();
if (AerePqHeaderSeal.active(aereHeight)) {
final Optional<FalconSeal> proposerSeal = roundState.getProposalFalconSeal();
if (proposerSeal.isEmpty()) {
LOG.error(
"AERE-PQC-HDR-02: committed at {} but the accepted proposal carries no post-quantum seal;"
+ " a header from H_hdr cannot be sealed without it. Not importing.",
aereHeight);
return;
}
importSealedBlock(
blockCreator.createSealedBlockWithProposerSeal(
roundState.getProposedBlock().get(),
roundState.getRoundIdentifier().getRoundNumber(),
proposerSeal.get()));
return;
}
final Collection<FalconSeal> falconSeals = roundState.getFalconSeals(); final Collection<FalconSeal> falconSeals = roundState.getFalconSeals();
final QbftBlock blockToImport = final QbftBlock blockToImport =
falconSeals.isEmpty() falconSeals.isEmpty()
@ -552,6 +574,11 @@ public class QbftRound {
roundState.getCommitSeals(), roundState.getCommitSeals(),
falconSeals); falconSeals);
importSealedBlock(blockToImport);
}
/** Imports a sealed block into the chain (shared by the stage-5 and the ECDSA sealing paths). */
private void importSealedBlock(final QbftBlock blockToImport) {
final long blockNumber = blockToImport.getHeader().getNumber(); final long blockNumber = blockToImport.getHeader().getNumber();
if (getRoundIdentifier().getRoundNumber() > 0) { if (getRoundIdentifier().getRoundNumber() > 0) {
LOG.info( LOG.info(

View File

@ -166,6 +166,16 @@ public class RoundState {
return proposalMessage.map(p -> p.getSignedPayload().getPayload().getProposedBlock()); return proposalMessage.map(p -> p.getSignedPayload().getPayload().getProposedBlock());
} }
/**
* AIP-22 stage 5: the post-quantum seal of the accepted proposal, which becomes element 6 of the
* sealed header from H_hdr (every node writes the same one, the proposer's).
*
* @return the proposal's seal, or empty
*/
public Optional<FalconSeal> getProposalFalconSeal() {
return proposalMessage.flatMap(p -> p.getSignedPayload().getPayload().getFalconSeal());
}
/** /**
* Gets proposed block access list. * Gets proposed block access list.
* *

View File

@ -79,6 +79,22 @@ public interface QbftBlockCreator {
return createSealedBlock(block, roundNumber, commitSeals); return createSealedBlock(block, roundNumber, commitSeals);
} }
/**
* AIP-22 stage 5: seal a block from H_hdr, with NO ECDSA committed seal and the proposer's
* post-quantum seal as element 6, everything else of the proposed header carried over (the anchor
* certificate included). An implementation that cannot do this must say so, never seal the old way.
*
* @param block the proposed block
* @param roundNumber the round number
* @param proposerSeal the proposal's post-quantum seal
* @return the sealed block
*/
default QbftBlock createSealedBlockWithProposerSeal(
final QbftBlock block, final int roundNumber, final FalconSeal proposerSeal) {
throw new UnsupportedOperationException(
"AERE-PQC-HDR-02: this block creator cannot seal a stage-5 header (proposer seal, no ECDSA seals)");
}
/** /**
* Convenience empty Falcon seal collection. * Convenience empty Falcon seal collection.
* *

View File

@ -312,15 +312,28 @@ public final class PqCommitEnforcement {
+ " but the Falcon seal on the same commit is index " + " but the Falcon seal on the same commit is index "
+ falconSeal.getValidatorIndex() + " - one commit, one signer"); + falconSeal.getValidatorIndex() + " - one commit, one signer");
} }
final Optional<byte[]> bound = hybridRegistry.address(found.getValidatorIndex()); // AIP-22 stage 3b: from H_k the keys and the index->author binding are those of the v3 epoch in force, as in the anchor
if (bound.isEmpty() // producer and PqAnchorSealsRule. Paid 2026-09-23 on testnet 28001: this check alone still read the static v2 hybrid
|| !AerePqIdentity.author(Address.wrap(Bytes.wrap(bound.get())), height).equals(author)) { // registry, so after the rotation at 3,097,600 the ROTATED row (index 2) signed with its new SLH-DSA key and every one of its
// commits at an anchor's parent was refused here; latent while four other validators sufficed, it stopped the chain at
// 3,135,679 when the Nethermind validator fell behind.
final boolean v3 = AerePqIdentity.keysFromV3(height);
final Optional<Address> boundId =
v3
? AerePqIdentity.v3IdAt(height, found.getValidatorIndex())
: hybridRegistry
.address(found.getValidatorIndex())
.map(a -> AerePqIdentity.author(Address.wrap(Bytes.wrap(a)), height));
if (boundId.isEmpty() || !boundId.get().equals(author)) {
return Optional.of( return Optional.of(
"AERE HIBRID: index " + found.getValidatorIndex() "AERE HIBRID: index " + found.getValidatorIndex()
+ " is not bound to the commit author " + author + " in the hybrid registry"); + " is not bound to the commit author " + author
+ (v3 ? " in the v3 epoch in force" : " in the hybrid registry"));
} }
final Optional<byte[]> publicKey = final Optional<byte[]> publicKey =
hybridRegistry.publicKey(found.getValidatorIndex(), schemeId); v3
? AerePqIdentity.v3KeyAt(height, found.getValidatorIndex(), schemeId)
: hybridRegistry.publicKey(found.getValidatorIndex(), schemeId);
if (publicKey.isEmpty()) { if (publicKey.isEmpty()) {
return Optional.of( return Optional.of(
"AERE HIBRID: the hybrid registry holds no " + schemeId + " key for index " "AERE HIBRID: the hybrid registry holds no " + schemeId + " key for index "

View File

@ -0,0 +1,171 @@
/* AIP-22 stage 3b: the hybrid (SLH-DSA) seal of a COMMIT is judged, from H_k, with the key of the v3 epoch in force, not with the
* static v2 hybrid registry.
*
* Paid 2026-09-23 on testnet 28001: after the rotation at 3,097,600 the rotated row (index 2) signed its commits with its NEW
* SLH-DSA key, as the v3 epoch says, and PqCommitEnforcement still read the OLD key from the v2 hybrid registry, so every one of its
* commits at an anchor's parent was refused for six hours, invisibly while four other validators sufficed, until the chain stopped at
* 3,135,679 when the Nethermind validator fell behind. The anchor producer and PqAnchorSealsRule were already on v3.
*
* Real cryptography (Falcon-512 and SLH-DSA-SHA2-128s generated on every run, TEST keys); a v3 epoch built from the canonical row
* bytes; the v2 registry built from properties exactly as in production. The negative control plants, at run time, "keys never from
* v3" in PqCommitEnforcement: the two cases from H_k must turn red. */
package org.hyperledger.besu.consensus.qbft.core.validation;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.FalconSeal;
import org.hyperledger.besu.consensus.common.bft.HybridSignerRegistry;
import org.hyperledger.besu.consensus.common.bft.PqRegistryV3;
import org.hyperledger.besu.consensus.common.bft.PqSchemeSchedule;
import org.hyperledger.besu.consensus.common.bft.PqSignerRegistry;
import org.hyperledger.besu.consensus.common.bft.SchemeSeal;
import org.hyperledger.besu.consensus.common.bft.SealScheme;
import org.hyperledger.besu.consensus.common.bft.SealSchemes;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SecureRandomProvider;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.cryptoservices.NodeKeyUtils;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class PqHybridRotationV3Test {
private static final long CHAIN = 28001L;
private static final long H_ARMARE = 1_000L;
private static final long H_HIBRID = 2_000L;
private static final long H_K = 3_000L;
private static final Hash DIGEST = Hash.hash(Bytes.of(7, 7));
private static final SealScheme FALCON = SealSchemes.FALCON_512;
private static final SealScheme SLH = SealSchemes.SLH_DSA_128S;
private final SecureRandom random = SecureRandomProvider.createSecureRandom();
private SealScheme.GeneratedPair falcon0;
private SealScheme.GeneratedPair slhOld;
private SealScheme.GeneratedPair slhNew;
private Address addr0;
private Address id0;
private PqCommitEnforcement enforcement;
@BeforeEach
public void setup() {
falcon0 = FALCON.generate(random);
slhOld = SLH.generate(random);
slhNew = SLH.generate(random);
final NodeKey nk = NodeKeyUtils.generate();
addr0 = org.hyperledger.besu.ethereum.core.Util.publicKeyToAddress(nk.getPublicKey());
// the v2 hybrid registry: index 0 is addr0, with the OLD SLH-DSA key (what the static registry keeps after a v3 rotation)
final Properties p = new Properties();
p.setProperty("formatVersion", HybridSignerRegistry.FORMAT_VERSION);
p.setProperty("chainId", String.valueOf(CHAIN));
p.setProperty("count", "1");
p.setProperty("0.addr", addr0.toHexString());
p.setProperty("0.key." + FALCON.id(), Bytes.wrap(falcon0.publicRegistryForm()).toHexString());
p.setProperty("0.key." + SLH.id(), Bytes.wrap(slhOld.publicRegistryForm()).toHexString());
final HybridSignerRegistry v2 = HybridSignerRegistry.fromProperties(p, "proba");
// the v3 epoch at H_k: row 0 keeps its Falcon key and carries the NEW SLH-DSA key (a rotation of the hybrid key)
final byte[] std = new byte[897];
std[0] = 0x09;
System.arraycopy(falcon0.publicRegistryForm(), 0, std, 1, 896);
final byte[] id = PqRegistryV3.idOf(std);
id0 = Address.wrap(Bytes.wrap(id));
final byte[] slhPub = slhNew.publicRegistryForm();
final byte[] popF = FALCON.sign(falcon0.privateKey(),
PqRegistryV3.popMessage(CHAIN, H_K, 1, 0, id, FALCON.wireId(), std).toArrayUnsafe()).orElseThrow();
final byte[] popS = SLH.sign(slhNew.privateKey(),
PqRegistryV3.popMessage(CHAIN, H_K, 1, 0, id, SLH.wireId(), slhPub).toArrayUnsafe()).orElseThrow();
final TreeMap<Integer, PqRegistryV3.Key> keys = new TreeMap<>();
keys.put(FALCON.wireId() & 0xff, new PqRegistryV3.Key(FALCON, std, popF));
keys.put(SLH.wireId() & 0xff, new PqRegistryV3.Key(SLH, slhPub, popS));
final PqRegistryV3.Row bare = new PqRegistryV3.Row(0, id, keys, null, Map.of(), null, null);
final SECPSignature claim = nk.sign(PqRegistryV3.claimDigest(CHAIN, PqRegistryV3.rowHash(CHAIN, H_K, 1, bare)));
final PqRegistryV3.Row row =
new PqRegistryV3.Row(0, id, keys, null, Map.of(), addr0.getBytes().toArrayUnsafe(), claim.encodedBytes().toArrayUnsafe());
final Bytes32 rh = PqRegistryV3.rowHash(CHAIN, H_K, 1, row);
final Bytes32 root = PqRegistryV3.merkleRoot(List.of(rh));
final TreeMap<Long, PqRegistryV3.Registry> epochs = new TreeMap<>();
epochs.put(H_K, new PqRegistryV3.Registry(CHAIN, H_K, 1, List.of(row), List.of(rh), root,
PqRegistryV3.registryHash(CHAIN, H_K, 1, root)));
AerePqIdentity.uninstall();
AerePqIdentity.install(new AerePqIdentity.Config(H_K, epochs, H_K));
// the Falcon part of the commit check: index 0 is addr0 below H_k and its id from H_k, the Falcon key unchanged
final PqSignerRegistry falconReg =
new PqSignerRegistry() {
@Override
public Address addressForIndexAtHistoric(final long h, final int i) {
return addressForIndexAtOwnHead(h, i);
}
@Override
public Address addressForIndexAtOwnHead(final long h, final int i) {
return i != 0 ? null : (h >= H_K ? id0 : addr0);
}
@Override
public boolean verifyAtHistoric(final long h, final int i, final Bytes m, final Bytes s) {
return verifyAtOwnHead(h, i, m, s);
}
@Override
public boolean verifyAtOwnHead(final long h, final int i, final Bytes m, final Bytes s) {
return i == 0 && FALCON.verifyRaw(falcon0.publicRegistryForm(), m.toArray(), s.toArray());
}
};
final PqSchemeSchedule orar =
PqSchemeSchedule.parse(H_ARMARE + ":" + FALCON.id() + "," + H_HIBRID + ":" + FALCON.id() + "+" + SLH.id());
enforcement = new PqCommitEnforcement(H_ARMARE, falconReg, orar, v2, h -> true);
}
@AfterEach
public void disarm() {
AerePqIdentity.uninstall();
}
private Optional<FalconSeal> falconSeal() {
return Optional.of(new FalconSeal(0, Bytes.wrap(FALCON.sign(falcon0.privateKey(), DIGEST.getBytes().toArray()).orElseThrow())));
}
private List<SchemeSeal> slhSeal(final SealScheme.GeneratedPair key) {
return List.of(new SchemeSeal(SLH.wireId(), 0, Bytes.wrap(SLH.sign(key.privateKey(), DIGEST.getBytes().toArray()).orElseThrow())));
}
@Test
public void fromHkTheRotatedSlhKeyOfTheV3EpochCounts() {
assertThat(enforcement.refusal(H_K + 5, id0, DIGEST, falconSeal(), slhSeal(slhNew))).isEmpty();
}
@Test
public void fromHkTheOldSlhKeyOfTheStaticRegistryNoLongerCounts() {
final Optional<String> r = enforcement.refusal(H_K + 5, id0, DIGEST, falconSeal(), slhSeal(slhOld));
assertThat(r).isPresent();
assertThat(r.get()).contains("does NOT verify").contains(SLH.id());
}
@Test
public void belowHkTheStaticRegistryDecidesAsBefore() {
assertThat(enforcement.refusal(H_K - 5, addr0, DIGEST, falconSeal(), slhSeal(slhOld))).isEmpty();
assertThat(enforcement.refusal(H_K - 5, addr0, DIGEST, falconSeal(), slhSeal(slhNew))).isPresent();
}
@Test
public void fromHkTheHybridSealCannotVouchForAnotherAuthor() {
final Optional<String> r =
enforcement.refusal(H_K + 5, Address.fromHexString("0x" + "cd".repeat(20)), DIGEST, falconSeal(), slhSeal(slhNew));
assertThat(r).isPresent();
}
}

View File

@ -27,13 +27,13 @@ import org.hyperledger.besu.consensus.common.bft.BftHelpers;
import org.hyperledger.besu.consensus.common.bft.FalconSealSupport; import org.hyperledger.besu.consensus.common.bft.FalconSealSupport;
import org.hyperledger.besu.consensus.common.bft.PqAnchorConfig; import org.hyperledger.besu.consensus.common.bft.PqAnchorConfig;
import org.hyperledger.besu.consensus.common.bft.headervalidationrules.BftCoinbaseValidationRule; import org.hyperledger.besu.consensus.common.bft.headervalidationrules.BftCoinbaseValidationRule;
import org.hyperledger.besu.consensus.common.bft.headervalidationrules.BftCommitSealsValidationRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.AereBaseFeeImportRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.AereBaseFeeImportRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.FalconSealValidationRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.FalconSealValidationRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqAnchorDigestAttachedRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqAnchorDigestAttachedRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqAnchorDigestRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqAnchorDigestRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqAnchorSealsRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqAnchorSealsRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqCheckpointRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqCheckpointRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqHeaderSealRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqEmergencyShoutRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqEmergencyShoutRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqRegistryBindingRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.PqRegistryBindingRule;
import org.hyperledger.besu.consensus.qbft.headervalidationrules.QbftValidatorsValidationRule; import org.hyperledger.besu.consensus.qbft.headervalidationrules.QbftValidatorsValidationRule;
@ -162,7 +162,9 @@ public class QbftBlockHeaderValidationRulesetFactory {
"Difficulty", BlockHeader::getDifficulty, UInt256.ONE)) "Difficulty", BlockHeader::getDifficulty, UInt256.ONE))
.addRule(new QbftValidatorsValidationRule(useValidatorContract)) .addRule(new QbftValidatorsValidationRule(useValidatorContract))
.addRule(new BftCoinbaseValidationRule()) .addRule(new BftCoinbaseValidationRule())
.addRule(new BftCommitSealsValidationRule()) // AERE AIP-22 stage 5: upstream's committed seals rule below H_hdr, unchanged; from H_hdr the proposer's
// post-quantum seal (element 6) and an EMPTY committed-seal element (PqHeaderSealRule)
.addRule(PqHeaderSealRule.forFleet(pqAnchorConfig.chainId()))
// AERE ANCORA-V2: the legacy log-only Falcon rule now stands down at H. With an // AERE ANCORA-V2: the legacy log-only Falcon rule now stands down at H. With an
// unconfigured anchor this is Long.MAX_VALUE, i.e. it never stands down and behaviour is // unconfigured anchor this is Long.MAX_VALUE, i.e. it never stands down and behaviour is
// unchanged. // unchanged.

View File

@ -164,6 +164,20 @@ public class QbftExtraDataCodec extends BftExtraDataCodec {
hybridSeals = Collections.emptyList(); hybridSeals = Collections.emptyList();
} }
// AERE AIP-22 stage 5: an OPTIONAL seventh element, the proposer's seal [index, signature], after
// the certificate slot. When it is present the certificate slot is ALWAYS written (an empty list
// when the header carries no certificate), so the POSITION tells the two apart, never the shape
// (a proposer seal from index 2 would otherwise look like the head of a v2 certificate), and the
// canonical round trip below keeps every value to exactly one encoding.
Optional<FalconSeal> proposerSeal = Optional.empty();
if (!rlpInput.isEndOfCurrentList()) {
rlpInput.enterList();
final int idx = rlpInput.readIntScalar();
final Bytes sig = rlpInput.readBytes();
rlpInput.leaveList();
proposerSeal = Optional.of(new FalconSeal(idx, sig));
}
// AERE FIX-MALEABILITATE-BLOC (2026-08-01), gate 1 of 2: the leave is STRICT. // AERE FIX-MALEABILITATE-BLOC (2026-08-01), gate 1 of 2: the leave is STRICT.
// //
// Upstream ends this decode with leaveListLenient(), which silently discards anything still // Upstream ends this decode with leaveListLenient(), which silently discards anything still
@ -177,7 +191,8 @@ public class QbftExtraDataCodec extends BftExtraDataCodec {
rlpInput.leaveList(); rlpInput.leaveList();
final BftExtraData decoded = final BftExtraData decoded =
new BftExtraData(vanityData, seals, vote, round, validators, falconSeals, hybridSeals); new BftExtraData(
vanityData, seals, vote, round, validators, falconSeals, hybridSeals, proposerSeal);
// AERE FIX-MALEABILITATE-BLOC (2026-08-01), gate 2 of 2: CANONICAL ROUND TRIP. // AERE FIX-MALEABILITATE-BLOC (2026-08-01), gate 2 of 2: CANONICAL ROUND TRIP.
// //
@ -243,6 +258,16 @@ public class QbftExtraDataCodec extends BftExtraDataCodec {
rlp.writeBytes(falconSeal.getSignature()); rlp.writeBytes(falconSeal.getSignature());
rlp.endList(); rlp.endList();
}); });
} else if (bftExtraData.getPqProposerSeal().isPresent()) {
// AERE AIP-22 stage 5: the certificate slot stays in place, empty, so the seal is element 6
encoder.writeEmptyList();
}
if (bftExtraData.getPqProposerSeal().isPresent()) {
final FalconSeal ps = bftExtraData.getPqProposerSeal().get();
encoder.startList();
encoder.writeIntScalar(ps.getValidatorIndex());
encoder.writeBytes(ps.getSignature());
encoder.endList();
} }
} else { } else {
encoder.writeEmptyList(); encoder.writeEmptyList();

View File

@ -87,6 +87,38 @@ public class QbftBlockCreatorAdaptor implements QbftBlockCreator {
return createSealedBlock(block, roundNumber, commitSeals, Collections.emptyList()); return createSealedBlock(block, roundNumber, commitSeals, Collections.emptyList());
} }
/**
* AIP-22 stage 5: the stage-5 header, from H_hdr. The proposed header's extraData is carried over
* whole (vanity with the anchor digest, validators, vote, the anchor certificate verbatim) through
* {@link BftExtraData#withSeals}, so nothing the proposer chose can be dropped here; only the round
* is set to the commit round, the ECDSA committed seals are EMPTY and element 6 is the proposal's
* post-quantum seal. Every node seals the same bytes: the header is no longer assembled from what
* each node heard.
*/
@Override
public QbftBlock createSealedBlockWithProposerSeal(
final QbftBlock block, final int roundNumber, final FalconSeal proposerSeal) {
final Block besuBlock = AdaptorUtil.toBesuBlock(block);
final BftExtraData initial = bftExtraDataCodec.decode(AdaptorUtil.toBesuBlockHeader(block.getHeader()));
final BftExtraData withRound =
new BftExtraData(
initial.getVanityData(),
Collections.emptyList(),
initial.getVote(),
roundNumber,
initial.getValidators(),
initial.getFalconSeals(),
initial.getHybridSeals(),
initial.getPqProposerSeal());
final BftExtraData sealed = withRound.withSeals(Collections.emptyList(), Optional.of(proposerSeal));
final BlockHeader header =
BlockHeaderBuilder.fromHeader(AdaptorUtil.toBesuBlockHeader(block.getHeader()))
.extraData(bftExtraDataCodec.encode(sealed))
.blockHeaderFunctions(BftBlockHeaderFunctions.forOnchainBlock(bftExtraDataCodec))
.buildBlockHeader();
return new QbftBlockAdaptor(new Block(header, besuBlock.getBody()));
}
@Override @Override
public QbftBlock createSealedBlock( public QbftBlock createSealedBlock(
final QbftBlock block, final QbftBlock block,

View File

@ -0,0 +1,155 @@
/*
* 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.qbft.headervalidationrules;
import org.hyperledger.besu.consensus.common.bft.AerePqFinality;
import org.hyperledger.besu.consensus.common.bft.AerePqHeaderSeal;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.BftBlockHashing;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.FalconSeal;
import org.hyperledger.besu.consensus.common.bft.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqSignerRegistry;
import org.hyperledger.besu.consensus.common.bft.headervalidationrules.BftCommitSealsValidationRule;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AIP-22 stage 5: the seal rule of a header. Below H_hdr it IS the upstream rule (a quorum of ECDSA
* committed seals of the parent's validators), unchanged. From H_hdr a header is admitted iff its ECDSA
* committed-seal element is EMPTY, its seventh element carries the proposer's Falcon-512 seal, the seal's
* index names the header's {@code coinbase} (an id) in the v3 epoch in force at its height, and the seal
* 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
* anchors (AIP-22 section 4); this rule is what admits a header between them.
*/
public class PqHeaderSealRule implements AttachedBlockHeaderValidationRule {
private static final Logger LOG = LoggerFactory.getLogger(PqHeaderSealRule.class);
private final AttachedBlockHeaderValidationRule belowHdr;
private final BftExtraDataCodec codec;
private final long chainId;
private final AerePqFinality.SealVerifier verifier;
/**
* The rule.
*
* @param belowHdr the rule below H_hdr (upstream's committed seals rule in production)
* @param codec the extraData codec
* @param chainId the chain id every seal message carries
* @param verifier the seal verifier (the anchor rules' historic door in production)
*/
public PqHeaderSealRule(
final AttachedBlockHeaderValidationRule belowHdr,
final BftExtraDataCodec codec,
final long chainId,
final AerePqFinality.SealVerifier verifier) {
this.belowHdr = belowHdr;
this.codec = codec;
this.chainId = chainId;
this.verifier = verifier;
}
/**
* The fleet's rule: upstream's committed seals rule below H_hdr, the verifier of the header rules above.
*
* @param chainId the chain id
* @return the rule
*/
public static PqHeaderSealRule forFleet(final long chainId) {
final PqSignerRegistry registry = PqSignerRegistry.falconSealSupport();
return new PqHeaderSealRule(
new BftCommitSealsValidationRule(), new QbftExtraDataCodec(), chainId, registry::verifyAtHistoric);
}
@Override
public boolean validate(
final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) {
if (!AerePqHeaderSeal.active(header.getNumber())) {
return belowHdr.validate(header, parent, protocolContext);
}
final Optional<String> r = refusal(header);
r.ifPresent(LOG::info);
return r.isEmpty();
}
/**
* Why a header at or above H_hdr is refused, if it is.
*
* @param header the header
* @return the refusal, or empty
*/
public Optional<String> refusal(final BlockHeader header) {
final long h = header.getNumber();
final BftExtraData ed;
try {
ed = codec.decode(header);
} catch (final RuntimeException e) {
return Optional.of("AERE PQ HEADER: block " + h + " extraData does not decode: " + e.getMessage());
}
if (!ed.getSeals().isEmpty()) {
return Optional.of(
"AERE PQ HEADER: block " + h + " carries " + ed.getSeals().size()
+ " ECDSA committed seal(s); from H_hdr " + AerePqHeaderSeal.headerBlock() + " the element MUST be empty");
}
if (ed.getPqProposerSeal().isEmpty()) {
return Optional.of("AERE PQ HEADER: block " + h + " carries no proposer seal (element 6) from H_hdr");
}
final FalconSeal ps = ed.getPqProposerSeal().get();
final Optional<Address> id = AerePqIdentity.v3IdAt(h, ps.getValidatorIndex());
if (id.isEmpty()) {
return Optional.of(
"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())) {
return Optional.of(
"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");
}
final Bytes32 message;
try {
message =
PqAnchor.proposalMessage(
chainId, h, ed.getRound(), new BftBlockHashing(codec).calculateDataHashForCommittedSeal(header).getBytes());
} catch (final RuntimeException e) {
return Optional.of("AERE PQ HEADER: block " + h + " proposal message cannot be built: " + e.getMessage());
}
final boolean ok;
try {
ok = verifier.verify(h, ps.getValidatorIndex(), message, ps.getSignature());
} catch (final RuntimeException e) {
return Optional.of("AERE PQ HEADER: block " + h + " proposer seal verification threw: " + e.getMessage());
}
return ok
? Optional.empty()
: Optional.of("AERE PQ HEADER: block " + h + " proposer seal of index " + ps.getValidatorIndex() + " does not verify");
}
@Override
public boolean includeInLightValidation() {
return belowHdr.includeInLightValidation();
}
}

View File

@ -0,0 +1,227 @@
/*
* 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.qbft.headervalidationrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.consensus.common.bft.AerePqFinality;
import org.hyperledger.besu.consensus.common.bft.AerePqHeaderSeal;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.BftBlockHashing;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.FalconSeal;
import org.hyperledger.besu.consensus.common.bft.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqRegistryV3;
import org.hyperledger.besu.consensus.common.bft.SealScheme;
import org.hyperledger.besu.consensus.common.bft.SealSchemes;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SecureRandomProvider;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.cryptoservices.NodeKeyUtils;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* AIP-22 stage 5 in the Besu-derived client, with real Falcon-512 keys generated for the run: the
* seventh extraData element (the proposer's seal) round-trips canonically, stays outside BOTH hashes,
* and the header rule admits a header from H_hdr iff its ECDSA committed seals are empty and the seal
* of its coinbase's index verifies over the proposal message recomputed from the stored header. Each
* refusal is asserted BY ITS REASON. Below H_hdr the upstream rule decides. The runner's negative
* control plants "never refuse" in the rule at run time and these probes turn red.
*/
public class PqHeaderSealStage5Test {
private static final long CHAIN = 28001;
private static final long BIND = 10;
private static final long H_ID = 10;
private static final long H_FIN = 20;
private static final long H_HDR = 30;
private static final int N = 4;
private static final SealScheme FALCON = SealSchemes.FALCON_512;
private static final QbftExtraDataCodec CODEC = new QbftExtraDataCodec();
private final List<SealScheme.PrivateHandle> falcon = new ArrayList<>();
private final List<Address> ids = new ArrayList<>();
private PqHeaderSealRule rule;
@BeforeEach
public void arm() {
final List<PqRegistryV3.Row> rows = new ArrayList<>();
for (int i = 0; i < N; i++) {
final NodeKey nk = NodeKeyUtils.generate();
final SealScheme.GeneratedPair p = FALCON.generate(SecureRandomProvider.createSecureRandom());
falcon.add(p.privateKey());
final byte[] std = new byte[897];
std[0] = 0x09;
System.arraycopy(p.publicRegistryForm(), 0, std, 1, 896);
final byte[] id = PqRegistryV3.idOf(std);
final Bytes32 popMsg = PqRegistryV3.popMessage(CHAIN, BIND, N, i, id, FALCON.wireId(), std);
final byte[] pop = FALCON.sign(p.privateKey(), popMsg.toArrayUnsafe()).orElseThrow();
final TreeMap<Integer, PqRegistryV3.Key> keys = new TreeMap<>();
keys.put(FALCON.wireId() & 0xff, new PqRegistryV3.Key(FALCON, std, pop));
final PqRegistryV3.Row bare = new PqRegistryV3.Row(i, id, keys, null, Map.of(), null, null);
final SECPSignature claim = nk.sign(PqRegistryV3.claimDigest(CHAIN, PqRegistryV3.rowHash(CHAIN, BIND, N, bare)));
rows.add(
new PqRegistryV3.Row(
i, id, keys, null, Map.of(),
Util.publicKeyToAddress(nk.getPublicKey()).getBytes().toArrayUnsafe(),
claim.encodedBytes().toArrayUnsafe()));
ids.add(Address.wrap(Bytes.wrap(id)));
}
final List<Bytes32> hashes = new ArrayList<>();
for (final PqRegistryV3.Row r : rows) {
hashes.add(PqRegistryV3.rowHash(CHAIN, BIND, N, r));
}
final Bytes32 root = PqRegistryV3.merkleRoot(hashes);
final TreeMap<Long, PqRegistryV3.Registry> epochs = new TreeMap<>();
epochs.put(
BIND,
new PqRegistryV3.Registry(CHAIN, BIND, N, rows, hashes, root, PqRegistryV3.registryHash(CHAIN, BIND, N, root)));
AerePqHeaderSeal.uninstall();
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
AerePqIdentity.install(new AerePqIdentity.Config(H_ID, epochs, H_ID));
final AerePqFinality.SealVerifier v =
(h, i, m, s) ->
AerePqIdentity.v3KeyAt(h, i, "falcon-512")
.map(k -> FALCON.verifyRaw(k, m.toArrayUnsafe(), s.toArrayUnsafe()))
.orElse(false);
AerePqFinality.install(H_FIN, CHAIN, v);
AerePqHeaderSeal.install(H_HDR);
rule = new PqHeaderSealRule(sentinel(false), CODEC, CHAIN, v);
}
@AfterEach
public void disarm() {
AerePqHeaderSeal.uninstall();
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
}
private static AttachedBlockHeaderValidationRule sentinel(final boolean answer) {
return (header, parent, ctx) -> answer;
}
private BftExtraData extra(final int round, final List<SECPSignature> seals, final Optional<FalconSeal> ps) {
return new BftExtraData(
Bytes.wrap(new byte[32]), seals, Optional.empty(), round, ids, Collections.emptyList(), Collections.emptyList(), ps);
}
private static BlockHeader header(final long h, final Address coinbase, final BftExtraData ed) {
return new BlockHeaderTestFixture().number(h).coinbase(coinbase).extraData(CODEC.encode(ed)).buildHeader();
}
/** The proposer seal of {@code signer} over the header's own proposal message (for the index {@code index}). */
private Optional<FalconSeal> sealOver(final BlockHeader unsealed, final int round, final int index, final int signer) {
final Bytes32 m =
PqAnchor.proposalMessage(
CHAIN, unsealed.getNumber(), round, new BftBlockHashing(CODEC).calculateDataHashForCommittedSeal(unsealed).getBytes());
return Optional.of(new FalconSeal(index, Bytes.wrap(FALCON.sign(falcon.get(signer), m.toArrayUnsafe()).orElseThrow())));
}
private BlockHeader sealedHeader(final long h, final int round, final int coinbase, final int index, final int signer) {
final BlockHeader unsealed = header(h, ids.get(coinbase), extra(round, List.of(), Optional.empty()));
return header(h, ids.get(coinbase), extra(round, List.of(), sealOver(unsealed, round, index, signer)));
}
@Test
public void theProposerSealIsTheSeventhElementCanonicalAndOutsideBothHashes() {
final BlockHeader unsealed = header(H_HDR, ids.get(1), extra(2, List.of(), Optional.empty()));
final BlockHeader sealed = sealedHeader(H_HDR, 2, 1, 1, 1);
final BftExtraData back = CODEC.decode(sealed);
assertThat(back.getPqProposerSeal()).isPresent();
assertThat(back.getPqProposerSeal().get().getValidatorIndex()).isEqualTo(1);
assertThat(back.getSeals()).isEmpty();
assertThat(back.getRound()).isEqualTo(2);
final BftBlockHashing hashing = new BftBlockHashing(CODEC);
assertThat(hashing.calculateHashOfBftBlockOnchain(sealed)).isEqualTo(hashing.calculateHashOfBftBlockOnchain(unsealed));
assertThat(hashing.calculateDataHashForCommittedSeal(sealed)).isEqualTo(hashing.calculateDataHashForCommittedSeal(unsealed));
assertThat(sealed.getExtraData().size()).isGreaterThan(unsealed.getExtraData().size() + 600);
}
@Test
public void anEmptyCertificateSlotWithoutTheProposerSealIsNonCanonicalAndRefused() {
// six elements whose sixth is an explicit empty list: two encodings of one value, refused as before
final BytesValueRLPOutput o = new BytesValueRLPOutput();
o.startList();
o.writeBytes(Bytes.wrap(new byte[32]));
o.writeList(ids, (a, rlp) -> rlp.writeBytes(a.getBytes()));
o.writeEmptyList();
o.writeIntScalar(0);
o.writeEmptyList();
o.writeEmptyList();
o.endList();
assertThatThrownBy(() -> CODEC.decodeRaw(o.encoded())).hasMessageContaining("Non-canonical");
}
@Test
public void fromHhdrAHeaderWithAVerifiedProposerSealOfItsCoinbaseAndNoEcdsaSealIsAdmitted() {
for (int i = 0; i < N; i++) {
assertThat(rule.refusal(sealedHeader(H_HDR, 0, i, i, i))).as("proposer %s", i).isEmpty();
}
assertThat(rule.validate(sealedHeader(H_HDR + 7, 3, 2, 2, 2), null, null)).isTrue();
}
@Test
public void fromHhdrEachWayOfBeingWrongIsRefusedByItsReason() {
// an ECDSA committed seal present
final SECPSignature ecdsa = NodeKeyUtils.generate().sign(Bytes32.ZERO);
final BlockHeader unsealed = header(H_HDR, ids.get(0), extra(0, List.of(), Optional.empty()));
final BlockHeader withEcdsa = header(H_HDR, ids.get(0), extra(0, List.of(ecdsa), sealOver(unsealed, 0, 0, 0)));
assertThat(rule.refusal(withEcdsa)).get().asString().contains("MUST be empty");
// no proposer seal
assertThat(rule.refusal(unsealed)).get().asString().contains("no proposer seal");
// index 0 sealed by key 1
assertThat(rule.refusal(sealedHeader(H_HDR, 0, 0, 0, 1))).get().asString().contains("does not verify");
// a valid seal of index 1 on a header whose coinbase is id 0
assertThat(rule.refusal(sealedHeader(H_HDR, 0, 0, 1, 1))).get().asString().contains("cannot vouch");
// an index with no row
assertThat(rule.refusal(sealedHeader(H_HDR, 0, 0, N, 0))).get().asString().contains("names no row");
// a seal over another round than the header's
final BlockHeader r1 = header(H_HDR, ids.get(0), extra(1, List.of(), Optional.empty()));
final BlockHeader sealedAtRound0 = header(H_HDR, ids.get(0), extra(1, List.of(), sealOver(unsealed, 0, 0, 0)));
assertThat(rule.refusal(sealedAtRound0)).get().asString().contains("does not verify");
assertThat(rule.refusal(header(H_HDR, ids.get(0), extra(1, List.of(), sealOver(r1, 1, 0, 0))))).isEmpty();
}
@Test
public void belowHhdrTheUpstreamRuleDecidesAndHhdrNeedsHfin() {
final BlockHeader below = sealedHeader(H_HDR - 1, 0, 0, 0, 0);
assertThat(new PqHeaderSealRule(sentinel(false), CODEC, CHAIN, (h, i, m, s) -> true).validate(below, null, null)).isFalse();
assertThat(new PqHeaderSealRule(sentinel(true), CODEC, CHAIN, (h, i, m, s) -> false).validate(below, null, null)).isTrue();
assertThatThrownBy(() -> AerePqHeaderSeal.install(H_FIN - 1)).hasMessageStartingWith("AERE-PQC-HDR-01");
AerePqFinality.uninstall();
assertThatThrownBy(() -> AerePqHeaderSeal.install(H_HDR)).hasMessageStartingWith("AERE-PQC-HDR-01");
}
}