anchor: the corpus carries AIP-22 stage 4 (message authorship from VERIFIED post-quantum seals, the startup guards AERE-PQC-FIN-01/02/03, the checkpoint option), unarmed on chain 2800 and proven on testnet 28001 (H_fin 3,117,000); the armed-on-2800 table says so row by row; LICENSING.md lists the new files

This commit is contained in:
Aere Network 2026-09-23 22:48:38 +03:00
parent 099908a4ca
commit 71412b9792
18 changed files with 1553 additions and 13 deletions

View File

@ -132,8 +132,8 @@ 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/`: **189 files**. **90** carry someone Measured 2026-09-23 over `anchor/`: **198 files**. **90** carry someone
else's copyright alone, **45** carry a shared notice naming both upstream and us, and **11** name only else's copyright alone, **54** 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`; 43 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.
@ -149,6 +149,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `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/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 |
@ -165,6 +166,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHashTool.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqRegistryHashTool.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealCache.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealCache.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealStore.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealStore.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealed.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSignerRegistry.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSignerRegistry.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/PqAnchorProducer.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/PqAnchorProducer.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
@ -191,6 +193,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSASigner.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SLHDSASigner.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/WotsPlus.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/WotsPlus.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryHashTool.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryHashTool.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityAuthorTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorEmergencyConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorEmergencyConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorLapseTest.java` | `Copyright contributors to Besu / Aere Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorLapseTest.java` | `Copyright contributors to Besu / Aere Network.` | upstream, **modified by Aere Network** |
@ -228,6 +231,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftBlockCreator.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftBlockCreator.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidator.java` | `Copyright 2020 ConsenSys AG.` | upstream | | `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidator.java` | `Copyright 2020 ConsenSys AG.` | upstream |
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/MessageValidator.java` | `Copyright ConsenSys AG.` | upstream | | `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/MessageValidator.java` | `Copyright ConsenSys AG.` | upstream |
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqFinalityLayersGuard.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcement.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcement.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqProposalEnforcement.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqProposalEnforcement.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqRoundChangeEnforcement.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqRoundChangeEnforcement.java` | `Copyright contributors to Besu.` | upstream |
@ -241,6 +245,9 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/payload/RoundChangePayloadPqTest.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/payload/RoundChangePayloadPqTest.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqHybridExtrasNeverThrowTest.java` | `Copyright contributors to Hyperledger Besu.` | upstream | | `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqHybridExtrasNeverThrowTest.java` | `Copyright contributors to Hyperledger Besu.` | upstream |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqLateSealSalvageTest.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqLateSealSalvageTest.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/AereFinalityMessageTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/AereFinalityVectorsWriter.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqFinalityLayersGuardTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqMessageAgilityTest.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqMessageAgilityTest.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareAgilityTest.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareAgilityTest.java` | `Copyright contributors to Besu.` | upstream |
| `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcementTest.java` | `Copyright contributors to Besu.` | upstream | | `anchor/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqPrepareEnforcementTest.java` | `Copyright contributors to Besu.` | upstream |
@ -259,6 +266,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/PqAnchorDigestAttachedRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestAttachedRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestRule.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestRule.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/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/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/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** |
@ -268,6 +276,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/PqAnchorSealsRuleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorSealsRuleTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorTestSupport.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorTestSupport.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
| `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqArmedWithoutRegistryTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** | | `anchor/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqArmedWithoutRegistryTest.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/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 |

View File

@ -5,11 +5,12 @@ 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
6a324fd14b4dea445ac08a7ef43bedb800c64c11c4bcbaefb58f98ec9051e60a app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java 6bc5a9ae4dbeda9a26e3157cb61c938cc306c859ee597526107199e0c88166ae 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
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 c28e3bf26d68ab7b764f6381bc678d5f7b60bfa7d02796e3ac34521c5c5bc6ed 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 2e84bb48611d3da7c46bcd4018ae746cbf785dfcb16f495674a0f6d542e71e8e consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java
@ -34,6 +35,7 @@ f67a205036550f7d0f1bfed2abe7b89abd73e80614adff8d696b33c580269a6f consensus/comm
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
73abbaddcec1533a7a758f1c4a26ae68a83ddbb96fd98ad147e74d7386bbbf3f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealStore.java 73abbaddcec1533a7a758f1c4a26ae68a83ddbb96fd98ad147e74d7386bbbf3f consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealStore.java
fad72fb52b6f6198113b80f5f2729002066a10decf596cfe26e602b058d9c603 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSealed.java
a235b3ec8c69665c525d7c74a084d589904879f5cd33cb87e1d1f38eabba8a15 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSignerRegistry.java a235b3ec8c69665c525d7c74a084d589904879f5cd33cb87e1d1f38eabba8a15 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/PqSignerRegistry.java
addba40c0d931a3ecfa3b2f0179311dacaa604884e3ebb1d79f958bd913f72e2 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/SchemeSeal.java addba40c0d931a3ecfa3b2f0179311dacaa604884e3ebb1d79f958bd913f72e2 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/SchemeSeal.java
285f6a7c1188a387fb1ac21f5ab809595ef48d936b40aced94047525e6def0a6 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/SealScheme.java 285f6a7c1188a387fb1ac21f5ab809595ef48d936b40aced94047525e6def0a6 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/SealScheme.java
@ -67,6 +69,7 @@ ed1cc6f617dc5a2f7801b6e8f6f039a035a2ffc9542b116262dac39bb70563dd consensus/comm
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
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
@ -111,11 +114,11 @@ ae37d3525697bf84defd796bb8813193c78f81e7fa24a72f8f048285d52399e2 consensus/comm
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 597d96d64c9394c79d5084a1ab5df884b2266efe315c0f2d563bfc17b7421480 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java
03173f2029f767e10cc978b5576b9557efa783dab2ef105aa856f6d5e08b9352 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 d365bad33f3aee4597c630071ca1f727ddd8bb6ab6515fccbce0a07842037adf consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/MessageFactory.java
e5c34515bebb3e53d790c2ca5c4f195db0ca022ec2603c28d47ac171cb74f2cc 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
efb0e336e19a16ef192e3d1f91f1e5488f632e17b5748654e75008ddfcdda932 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
02d31e71cc50b1a2330c7a928c833d8b4b5f969228f085d54d099eb5d29eb65f 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 9c7e2698016f8cf19d3b6d78c6256ad5e8eabec06d9a877eee4e8c2dde423839 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftRound.java
@ -124,6 +127,7 @@ efb0e336e19a16ef192e3d1f91f1e5488f632e17b5748654e75008ddfcdda932 consensus/qbft
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 22c73f9cdfe5626347f34f173b7b60312e00970ccba49862686051b2dd84fe36 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
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
afb487f8c1dd87e323fdc1153ecfdf8ec6cb5a10e0726783d4214c07a3fa9a95 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqRoundChangeEnforcement.java afb487f8c1dd87e323fdc1153ecfdf8ec6cb5a10e0726783d4214c07a3fa9a95 consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/validation/PqRoundChangeEnforcement.java
@ -139,12 +143,15 @@ df5d7d2119cf0864a8c2dce50f4f69bd8b64804cfda7f7366c013b304af0a6e7 consensus/qbft
97ac1e46ab37238a2b6232eccdd4636ac6fa928c8e3d4f8a473c083c5d5c3b39 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqHybridExtrasNeverThrowTest.java 97ac1e46ab37238a2b6232eccdd4636ac6fa928c8e3d4f8a473c083c5d5c3b39 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqHybridExtrasNeverThrowTest.java
0bb2c1e31a7f29e9b07fb7030567746fc6d79460ed4739d5ecafac44501f7bb5 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqLateSealSalvageTest.java 0bb2c1e31a7f29e9b07fb7030567746fc6d79460ed4739d5ecafac44501f7bb5 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqLateSealSalvageTest.java
127de5fbe8014768a657f31bc77d7e66557273febdf3412a5536e8628fd70c5c consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqSealsWithoutProposalTest.java 127de5fbe8014768a657f31bc77d7e66557273febdf3412a5536e8628fd70c5c consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/PqSealsWithoutProposalTest.java
c1b31f5c357a1966cdd1e963369a1eb3078a381679c6adc8fb5206f754eca84b consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/AereFinalityMessageTest.java
70bc4d5242e5f5560333c862bef0b2d5167649aabb3cd66fe61a101678d30b4d consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/AereFinalityVectorsWriter.java
11c968ba3eb4adabb548af5a0f6192b21ec75ff01431cc678fdfc6c13f0d4937 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/AereIdentityMessageTest.java 11c968ba3eb4adabb548af5a0f6192b21ec75ff01431cc678fdfc6c13f0d4937 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/AereIdentityMessageTest.java
3f945a0a73288e42c82026ead5d118ab6b7a8200aacc881855aff442d0954c1c consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidatorAnchorFormTest.java 3f945a0a73288e42c82026ead5d118ab6b7a8200aacc881855aff442d0954c1c consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidatorAnchorFormTest.java
8c40173c7b248edec7c29ee9e049f9e269f4725b637bceb8734735fc73eb9ca2 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidatorPqEnforcementTest.java 8c40173c7b248edec7c29ee9e049f9e269f4725b637bceb8734735fc73eb9ca2 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/CommitValidatorPqEnforcementTest.java
3374a7ee317a807528c79b8218dbb34b308de8b7a908cb6680b83356641fcade consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/MessageValidatorAnchorFormPlumbingTest.java 3374a7ee317a807528c79b8218dbb34b308de8b7a908cb6680b83356641fcade consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/MessageValidatorAnchorFormPlumbingTest.java
401c25f63abb248dfe60b69676a34ea255bb669b4bc58e45916f6eecea3cb870 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqCommitEnforcementTest.java 401c25f63abb248dfe60b69676a34ea255bb669b4bc58e45916f6eecea3cb870 consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/validation/PqCommitEnforcementTest.java
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
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
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
@ -156,7 +163,7 @@ 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
79dea5e85b968af696bf57d51d0de175a4c025537200402e3594a4a8290d1a29 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java 77568cb12200fd653772cd5bb93f989b012909fdb645d68fa39e8251138ec89e 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 42b86cd888efe1aa00f9c9477566e353649c609c13a042e514185b57a6674a33 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 103d46eecfc11c97fb59a1e0237a6b5b6d384a43ec45f5028bd225b8bf8e3f6c 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
@ -165,6 +172,7 @@ e4e75c28ff15d058176145b1ae32606bf91b22112d1668a718f1a6ae4add0e71 consensus/qbft
ab0fcd8722dcb76560f0ef8fda8af9c2b8b6ec9b468326aaf616c853526f7f81 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestAttachedRule.java ab0fcd8722dcb76560f0ef8fda8af9c2b8b6ec9b468326aaf616c853526f7f81 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestAttachedRule.java
63313f944fcda0db45c40d4df840a465e6147cc85997b8ca832f0cfefea97987 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestRule.java 63313f944fcda0db45c40d4df840a465e6147cc85997b8ca832f0cfefea97987 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorDigestRule.java
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
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
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
@ -178,6 +186,7 @@ a81da71dd34dd111a0da43c22d22345dcc595c084a2c2d36aec1f45d8b762f80 consensus/qbft
535cca4f1083c8a25bd29877e988b99704666853c1fe8b0ba32d71efe4e15f37 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorTestSupport.java 535cca4f1083c8a25bd29877e988b99704666853c1fe8b0ba32d71efe4e15f37 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorTestSupport.java
2a76e2500e7d26fc2443f4a37211d59eb1e80e736430dca0785dd2f3f6ea1daf consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorV2RulesTest.java 2a76e2500e7d26fc2443f4a37211d59eb1e80e736430dca0785dd2f3f6ea1daf consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqAnchorV2RulesTest.java
321cb7e6923f77e078b523316650ac49136cf8d4df506b9bb235a32795c69611 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqArmedWithoutRegistryTest.java 321cb7e6923f77e078b523316650ac49136cf8d4df506b9bb235a32795c69611 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/PqArmedWithoutRegistryTest.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
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

View File

@ -179,6 +179,8 @@ This matters more than the file list, so it is stated before it.
| Per-scheme anchor intervals, Falcon-512 every 32 and SLH-DSA every 128 (AIP-22 stage 2, `scheme/interval` steps in `aere.pq.schemeSchedule`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only | | Per-scheme anchor intervals, Falcon-512 every 32 and SLH-DSA every 128 (AIP-22 stage 2, `scheme/interval` steps in `aere.pq.schemeSchedule`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only |
| Post-quantum validator identity and the v3 key registry (AIP-22 stage 3, `aere.pq.identityBlock`, `aere.pq.registryV3`) | yes (added 2026-09-23) | **no**; proven on testnet 28001 only | | Post-quantum validator identity and the v3 key registry (AIP-22 stage 3, `aere.pq.identityBlock`, `aere.pq.registryV3`) | 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 | | 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) |
| 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 |
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

@ -30,6 +30,7 @@ import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.consensus.common.BftValidatorOverrides; 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.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;
@ -89,6 +90,7 @@ import org.hyperledger.besu.consensus.qbft.core.types.QbftMinedBlockObserver;
import org.hyperledger.besu.consensus.qbft.core.types.QbftProtocolSchedule; import org.hyperledger.besu.consensus.qbft.core.types.QbftProtocolSchedule;
import org.hyperledger.besu.consensus.qbft.core.types.QbftValidatorProvider; import org.hyperledger.besu.consensus.qbft.core.types.QbftValidatorProvider;
import org.hyperledger.besu.consensus.qbft.core.validation.MessageValidatorFactory; import org.hyperledger.besu.consensus.qbft.core.validation.MessageValidatorFactory;
import org.hyperledger.besu.consensus.qbft.core.validation.PqFinalityLayersGuard;
import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods; import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods;
import org.hyperledger.besu.consensus.qbft.network.QbftGossiperImpl; import org.hyperledger.besu.consensus.qbft.network.QbftGossiperImpl;
import org.hyperledger.besu.consensus.qbft.protocol.Istanbul100SubProtocol; import org.hyperledger.besu.consensus.qbft.protocol.Istanbul100SubProtocol;
@ -463,6 +465,28 @@ public class QbftBesuControllerBuilder extends BesuControllerBuilder {
.verifyLocalKeysCoverV3EpochsOrAbort(Util.publicKeyToAddress(nodeKey.getPublicKey())); .verifyLocalKeysCoverV3EpochsOrAbort(Util.publicKeyToAddress(nodeKey.getPublicKey()));
HybridSealSupport.instance() HybridSealSupport.instance()
.verifyLocalKeysCoverV3EpochsOrAbort(Util.publicKeyToAddress(nodeKey.getPublicKey())); .verifyLocalKeysCoverV3EpochsOrAbort(Util.publicKeyToAddress(nodeKey.getPublicKey()));
// AERE AIP-22 stage 4: H_fin (aere.pq.finalityBlock), from which a message's author is the id of its post-quantum seal's
// index once that seal verifies, and the envelope's ECDSA is not read; refuses unless the keys are read from the v3 epochs
// at or below it (AERE-PQC-FIN-01). Inert when unset, which is every chain today.
AerePqFinality.installFromSystemProperties(aereAnchorConfig.chainId());
// ...and the four seal layers must be armed at or below it (AERE-PQC-FIN-02): the commit layer is the one that judges a
// COMMIT's seal (its author is bound to the commit seal's signer), and the layers stay the second judge of the rest.
PqFinalityLayersGuard.verifyOrAbort(AerePqFinality.finalityBlock());
// ...and a node that seals must seal all four messages from H_fin, or it would be mute there (AERE-PQC-FIN-03)
PqFinalityLayersGuard.verifyEmissionOrAbort(
AerePqFinality.finalityBlock(),
FalconSealSupport.instance().signingEnabled(),
FalconSealSupport.instance().attachBlock(),
FalconSealSupport.proposalAttachBlock(),
FalconSealSupport.prepareAttachBlock(),
FalconSealSupport.roundChangeAttachBlock());
if (AerePqFinality.finalityBlock() != AerePqIdentity.NEVER) {
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"
+ " envelope's ECDSA is not read (four seal layers armed at or below it, this node seals: {})",
AerePqFinality.finalityBlock(),
FalconSealSupport.instance().signingEnabled());
}
PqAnchorSyncModeGuard.verifySyncModeOrAbort( PqAnchorSyncModeGuard.verifySyncModeOrAbort(
syncConfig == null || syncConfig.getSyncMode() == null syncConfig == null || syncConfig.getSyncMode() == null

View File

@ -0,0 +1,194 @@
/*
* 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.consensus.common.bft.payload.Payload;
import org.hyperledger.besu.datatypes.Address;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
/**
* AERE AIP-22 stage 4 (plan 9.6.8): from H_fin ({@code aere.pq.finalityBlock}) the ECDSA signature of a
* consensus message's envelope is no longer a criterion. The author of a message is the post-quantum id
* of the row its seal's index names in the v3 epoch in force at the message's height, and ONLY once that
* seal VERIFIES: a PROPOSAL, PREPARE or ROUND-CHANGE seal over its own domain, through the same verifier
* the seal layers use; a COMMIT's index only when it names the id of the commit seal's signer (the commit
* layer, armed at or below H_fin by AERE-PQC-FIN-02, judges the seal itself). The envelope's recovered
* ECDSA address is not read for authorship, so a validator may sign the envelope with any secp256k1 key.
* Anything else at or above H_fin has no author ({@link #NO_AUTHOR}, never in a set).
*
* <p>WHY HERE AND NOT ONLY IN THE LAYERS (read in the code 2026-09-23 ~18:02Z): QbftController relays a message
* as soon as its author is in the set, BEFORE the layers judge it. An author read from an unverified
* index would let anyone make every validator relay garbage in any validator's name; before H_fin that
* needed a validator's ECDSA key. The layers keep judging exactly as before.
*
* <p>Requires the post-quantum identity (stage 3) and the keys read from the v3 epochs (stage 3b):
* H_fin must be at or above H_k, else startup is refused (AERE-PQC-FIN-01). Unset: never, which is
* every chain today.
*/
public final class AerePqFinality {
/** The system property naming H_fin. */
public static final String PROPERTY = "aere.pq.finalityBlock";
/** The author of a message that cannot have one from H_fin (no seal, or an index with no row). */
public static final Address NO_AUTHOR = Address.ZERO;
/** Verifies a Falcon seal of a registry index at a height (the seal layers' verifier in production). */
@FunctionalInterface
public interface SealVerifier {
/**
* Whether the seal verifies.
*
* @param height the message's height
* @param index the seal's registry index
* @param message the message the seal signs
* @param signature the seal's signature
* @return true when it verifies under that index's key
*/
boolean verify(long height, int index, Bytes message, Bytes signature);
}
private static volatile long finalityBlock = AerePqIdentity.NEVER;
private static volatile long chainId;
private static volatile SealVerifier verifier = (h, i, m, s) -> false;
private AerePqFinality() {}
/**
* Arms H_fin (tests and the startup path).
*
* @param hFin H_fin, or {@link AerePqIdentity#NEVER}
* @param chain the chain id every seal message carries
* @param sealVerifier the verifier of the seal layers
*/
public static void install(final long hFin, final long chain, final SealVerifier sealVerifier) {
if (hFin != AerePqIdentity.NEVER) {
final long keysFrom =
AerePqIdentity.installed().map(AerePqIdentity.Config::keysFrom).orElse(AerePqIdentity.NEVER);
if (keysFrom == AerePqIdentity.NEVER || hFin < keysFrom) {
throw new IllegalStateException(
"AERE-PQC-FIN-01: "
+ PROPERTY
+ "="
+ hFin
+ " needs the post-quantum identity and the keys read from the v3 epochs (aere.pq.registryV3.keysFrom)"
+ " at or below it (keysFrom "
+ (keysFrom == AerePqIdentity.NEVER ? "unset" : Long.toString(keysFrom))
+ ")");
}
if (chain <= 0 || sealVerifier == null) {
throw new IllegalStateException(
"AERE-PQC-FIN-01: "
+ PROPERTY
+ "="
+ hFin
+ " needs the chain id (aere.pq.chainId) and a seal verifier: every author it gives is a verified seal");
}
}
finalityBlock = hFin;
chainId = chain;
verifier = sealVerifier == null ? (h, i, m, s) -> false : sealVerifier;
}
/**
* H_fin as armed on this node.
*
* @return H_fin, or {@link AerePqIdentity#NEVER} when unset
*/
public static long finalityBlock() {
return finalityBlock;
}
/** Disarms (tests). */
public static void uninstall() {
finalityBlock = AerePqIdentity.NEVER;
chainId = 0;
verifier = (h, i, m, s) -> false;
}
/**
* Arms from {@link #PROPERTY}; unset means never. Call after the identity is installed.
*
* @param chain the chain id of the anchor configuration (aere.pq.chainId)
*/
public static void installFromSystemProperties(final long chain) {
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-FIN-01: " + PROPERTY + "='" + v + "' is not a height", e);
}
final PqSignerRegistry layers = PqSignerRegistry.falconSealSupport();
install(h, chain, layers::verifyAtOwnHead);
}
/**
* Whether the post-quantum seal is the author of messages at this height.
*
* @param height the message's height
* @return true from H_fin when armed
*/
public static boolean active(final long height) {
return height >= finalityBlock;
}
/**
* The author of a message from H_fin: the id of its seal's index at the message's height, once the
* seal verifies (a COMMIT: once the index names the commit seal signer's id).
*
* @param payload the message payload
* @param height the message's height
* @return the id, or {@link #NO_AUTHOR}
*/
public static Address authorFromSeal(final Payload payload, final long height) {
if (!(payload instanceof PqSealed sealed)) {
return NO_AUTHOR;
}
final Optional<FalconSeal> seal = sealed.getFalconSeal();
if (seal.isEmpty()) {
return NO_AUTHOR;
}
final int index = seal.get().getValidatorIndex();
final Optional<Address> id = AerePqIdentity.v3IdAt(height, index);
if (id.isEmpty()) {
return NO_AUTHOR;
}
try {
final Optional<Address> commitSigner = sealed.commitSealSigner();
if (commitSigner.isPresent()) {
return id.get().equals(AerePqIdentity.author(commitSigner.get(), height)) ? id.get() : NO_AUTHOR;
}
final Optional<Bytes32> message = sealed.pqSealMessage(chainId);
if (message.isEmpty()) {
return NO_AUTHOR;
}
return verifier.verify(height, index, message.get(), seal.get().getSignature())
? id.get()
: NO_AUTHOR;
} catch (final RuntimeException e) {
// a seal we cannot judge authors nothing: "cannot judge" is never a pass
return NO_AUTHOR;
}
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.Address;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
/**
* AERE AIP-22 stage 4: a consensus payload that carries a post-quantum seal {@code (index, signature)}.
* The four QBFT payloads (proposal, prepare, commit, round-change) implement it; from H_fin the author
* of a message is the id of its seal's index, and only once the seal is VERIFIED
* ({@link AerePqFinality#authorFromSeal}): the author decides whether a message is even relayed, which
* happens before the layers judge it.
*/
public interface PqSealed {
/**
* The post-quantum seal this payload carries, if any.
*
* @return the seal
*/
Optional<FalconSeal> getFalconSeal();
/**
* The message this payload's seal signs over a chain, as the seal layer that judges it builds it
* (PREPARE, PROPOSAL and ROUND-CHANGE carry every field of it). Empty when the payload alone does not
* determine it (a COMMIT's seal signs the block's on-chain hash).
*
* @param chainId the chain id that goes into the message
* @return the message, or empty
*/
default Optional<Bytes32> pqSealMessage(final long chainId) {
return Optional.empty();
}
/**
* For a COMMIT, the address that signed its commit seal; empty for every other payload. From H_fin a
* COMMIT's author is its seal index's id only when that is this signer's id (the binding its seal
* layer checks), and the seal itself is judged by the commit layer, armed at or below H_fin.
*
* @return the commit seal's signer, or empty
*/
default Optional<Address> commitSealSigner() {
return Optional.empty();
}
}

View File

@ -0,0 +1,243 @@
/*
* 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.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.consensus.common.bft.payload.Payload;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData;
import org.hyperledger.besu.crypto.Hash;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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 4 (iii): from H_fin the author of a message is the id of its post-quantum seal's index,
* ONCE THE SEAL VERIFIES, and the ECDSA signature of the envelope is not read. Every message here is
* signed by a FOREIGN key (not any validator's), so a validator's id can only come from the seal. The
* verifier is injected with the seal layers' contract (height, index, message, signature): it accepts
* exactly the signature {@code keccak(index || message)}, so a seal of another index, over another
* message, or of any other bytes does not verify. A COMMIT's author is its index's id only when that is
* the commit seal signer's id. Below H_fin, and with H_fin unset, the author is the (translated) ECDSA
* signer, as in stage 3. The runner's negative controls plant "H_fin never active" and "every seal
* verifies" at run time and these probes turn red.
*/
public class AerePqFinalityAuthorTest {
private static final SignatureAlgorithm ALG = SignatureAlgorithmFactory.getInstance();
private static final long CHAIN = 28001L;
private static final AerePqFinality.SealVerifier V =
(h, i, m, s) -> s.equals(sigFor(i, m));
private long hId;
private final List<Address> id = new ArrayList<>();
private final List<Address> ecdsa = new ArrayList<>();
private final KeyPair foreign = ALG.generateKeyPair();
private static Bytes sigFor(final int index, final Bytes message) {
// the index as its 32 bits (a negative index has a signature too, it just names no row)
return Hash.keccak256(Bytes.concatenate(Bytes.ofUnsignedLong(Integer.toUnsignedLong(index)), message));
}
private static Bytes32 messageAt(final long height) {
return Hash.keccak256(Bytes.concatenate(Bytes.wrap("t4-msg".getBytes(java.nio.charset.StandardCharsets.US_ASCII)), Bytes.ofUnsignedLong(height)));
}
/** A PREPARE-like payload: its seal signs {@link #messageAt}(height); a COMMIT-like one names a commit seal signer. */
private static final class Sealed implements Payload, PqSealed {
private final long height;
private final Optional<FalconSeal> seal;
private final Optional<Address> commitSigner;
private final boolean messageThrows;
Sealed(final long height, final Optional<FalconSeal> seal, final Optional<Address> commitSigner, final boolean messageThrows) {
this.height = height;
this.seal = seal;
this.commitSigner = commitSigner;
this.messageThrows = messageThrows;
}
@Override
public void writeTo(final RLPOutput out) {
out.startList();
out.writeLongScalar(height);
out.endList();
}
@Override
public int getMessageType() {
return 0x12;
}
@Override
public org.hyperledger.besu.datatypes.Hash hashForSignature() {
return org.hyperledger.besu.datatypes.Hash.hash(Bytes.ofUnsignedLong(height));
}
@Override
public ConsensusRoundIdentifier getRoundIdentifier() {
return new ConsensusRoundIdentifier(height, 0);
}
@Override
public Optional<FalconSeal> getFalconSeal() {
return seal;
}
@Override
public Optional<Bytes32> pqSealMessage(final long chainId) {
if (messageThrows) {
throw new IllegalArgumentException("cannot build");
}
return commitSigner.isPresent() || chainId != CHAIN ? Optional.empty() : Optional.of(messageAt(height));
}
@Override
public Optional<Address> commitSealSigner() {
return commitSigner;
}
}
private Address signed(final Sealed p) {
return SignedData.create(p, ALG.sign(Bytes32.wrap(p.hashForSignature().getBytes()), foreign)).getAuthor();
}
private static Sealed prepare(final long height, final Optional<FalconSeal> seal) {
return new Sealed(height, seal, Optional.empty(), false);
}
/** A seal of index {@code index} that verifies over the message of {@code signedHeight}. */
private static Optional<FalconSeal> good(final int index, final long signedHeight) {
return Optional.of(new FalconSeal(index, sigFor(index, messageAt(signedHeight))));
}
@BeforeEach
public void arm() throws IOException {
final JsonNode v;
try (InputStream in = getClass().getResourceAsStream("/aere/registry-v3/ok-epoch-a.json")) {
assertThat(in).isNotNull();
v = new ObjectMapper().readTree(in);
}
final PqRegistryV3.Registry epoch =
PqRegistryV3.load(
v.get("manifest"), v.get("chainId").asLong(),
List.of("falcon-512", "slh-dsa-sha2-128s"), null, true);
for (final PqRegistryV3.Row r : epoch.rows()) {
id.add(Address.wrap(Bytes.wrap(r.id())));
ecdsa.add(Address.wrap(Bytes.wrap(r.ecdsaAddress())));
}
hId = epoch.bindHeight();
final TreeMap<Long, PqRegistryV3.Registry> epochs = new TreeMap<>();
epochs.put(hId, epoch);
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
AerePqIdentity.install(new AerePqIdentity.Config(hId, epochs, hId));
}
@AfterEach
public void disarm() {
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
}
@Test
public void fromHfinTheAuthorIsTheIdOfAVerifiedSealWhateverKeySignedTheEnvelope() {
final long hFin = hId + 100;
AerePqFinality.install(hFin, CHAIN, V);
final Address foreignAddr = Util.publicKeyToAddress(foreign.getPublicKey());
assertThat(id).doesNotContain(foreignAddr);
for (int i = 0; i < id.size(); i++) {
assertThat(signed(prepare(hFin, good(i, hFin)))).as("index %s at H_fin", i).isEqualTo(id.get(i));
assertThat(signed(prepare(hFin + 500, good(i, hFin + 500)))).isEqualTo(id.get(i));
}
// one block below H_fin the envelope's signer is still the author: a foreign key authors nothing of the set
assertThat(signed(prepare(hFin - 1, good(0, hFin - 1)))).isEqualTo(foreignAddr);
}
@Test
public void fromHfinASealThatDoesNotVerifyAuthorsNothing() {
final long hFin = hId + 100;
AerePqFinality.install(hFin, CHAIN, V);
// the fixture can express the defect: the same index with its own seal IS an author
assertThat(signed(prepare(hFin, good(0, hFin)))).isEqualTo(id.get(0));
// index 0 carrying index 1's seal
assertThat(signed(prepare(hFin, Optional.of(new FalconSeal(0, sigFor(1, messageAt(hFin)))))))
.isEqualTo(AerePqFinality.NO_AUTHOR);
// index 0's seal over another height's message
assertThat(signed(prepare(hFin, good(0, hFin + 1)))).isEqualTo(AerePqFinality.NO_AUTHOR);
// any other bytes
assertThat(signed(prepare(hFin, Optional.of(new FalconSeal(0, Bytes.of(1, 2, 3))))))
.isEqualTo(AerePqFinality.NO_AUTHOR);
// no seal, an index outside the epoch, a negative index, a message that cannot be built
assertThat(signed(prepare(hFin, Optional.empty()))).isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(signed(prepare(hFin, good(id.size(), hFin)))).isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(signed(prepare(hFin, good(-1, hFin)))).isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(signed(new Sealed(hFin, good(0, hFin), Optional.empty(), true)))
.isEqualTo(AerePqFinality.NO_AUTHOR);
}
@Test
public void fromHfinACommitsAuthorIsItsIndexOnlyWhenThatIsTheCommitSealSigner() {
final long hFin = hId + 100;
AerePqFinality.install(hFin, CHAIN, V);
final Optional<FalconSeal> anySeal = Optional.of(new FalconSeal(0, Bytes.of(9)));
assertThat(signed(new Sealed(hFin, anySeal, Optional.of(ecdsa.get(0)), false))).isEqualTo(id.get(0));
assertThat(signed(new Sealed(hFin, anySeal, Optional.of(ecdsa.get(1)), false)))
.isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(
signed(
new Sealed(
hFin, anySeal, Optional.of(Util.publicKeyToAddress(foreign.getPublicKey())), false)))
.isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(signed(new Sealed(hFin, Optional.empty(), Optional.of(ecdsa.get(0)), false)))
.isEqualTo(AerePqFinality.NO_AUTHOR);
}
@Test
public void unsetHfinLeavesTheEcdsaSignerAsTheAuthorAndHfinNeedsKeysFromV3AndAChain() {
final Address foreignAddr = Util.publicKeyToAddress(foreign.getPublicKey());
assertThat(signed(prepare(hId + 100, good(0, hId + 100)))).isEqualTo(foreignAddr);
// H_fin below H_k, without a chain id, or without keys read from the v3 epochs refuses to arm
assertThatThrownBy(() -> AerePqFinality.install(hId - 1, CHAIN, V))
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("AERE-PQC-FIN-01");
assertThatThrownBy(() -> AerePqFinality.install(hId + 100, 0, V))
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("AERE-PQC-FIN-01");
AerePqIdentity.uninstall();
assertThatThrownBy(() -> AerePqFinality.install(hId + 100, CHAIN, V))
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("AERE-PQC-FIN-01");
}
}

View File

@ -22,6 +22,7 @@ package org.hyperledger.besu.consensus.qbft.core.payload;
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.PqSealed;
import org.hyperledger.besu.consensus.common.bft.PqAnchorV2; import org.hyperledger.besu.consensus.common.bft.PqAnchorV2;
import org.hyperledger.besu.consensus.common.bft.SchemeSeal; import org.hyperledger.besu.consensus.common.bft.SchemeSeal;
import org.hyperledger.besu.consensus.common.bft.SealSchemes; import org.hyperledger.besu.consensus.common.bft.SealSchemes;
@ -29,7 +30,9 @@ import org.hyperledger.besu.consensus.common.bft.payload.Payload;
import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1; import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1;
import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.ethereum.rlp.RLPOutput;
@ -54,7 +57,7 @@ import org.apache.tuweni.bytes.Bytes;
* <p>AERE FIX-MALEABILITATE: this payload has exactly ONE valid encoding for a given value. See * <p>AERE FIX-MALEABILITATE: this payload has exactly ONE valid encoding for a given value. See
* {@link #readFrom(RLPInput)}. * {@link #readFrom(RLPInput)}.
*/ */
public class CommitPayload extends QbftPayload { public class CommitPayload extends QbftPayload implements PqSealed {
private static final int TYPE = QbftV1.COMMIT; private static final int TYPE = QbftV1.COMMIT;
private final ConsensusRoundIdentifier roundIdentifier; private final ConsensusRoundIdentifier roundIdentifier;
private final Hash digest; private final Hash digest;
@ -323,10 +326,21 @@ public class CommitPayload extends QbftPayload {
* *
* @return the Falcon seal if this commit carried one, otherwise empty * @return the Falcon seal if this commit carried one, otherwise empty
*/ */
@Override
public Optional<FalconSeal> getFalconSeal() { public Optional<FalconSeal> getFalconSeal() {
return falconSeal; return falconSeal;
} }
/** AIP-22 stage 4: the commit seal's signer, recovered as CommitValidator recovers it (empty when it does not recover). */
@Override
public Optional<Address> commitSealSigner() {
try {
return Optional.ofNullable(Util.signatureToAddress(commitSeal, digest));
} catch (final RuntimeException e) {
return Optional.empty();
}
}
/** /**
* Gets the non-Falcon scheme seals of a hybrid certificate. * Gets the non-Falcon scheme seals of a hybrid certificate.
* *

View File

@ -22,6 +22,8 @@ package org.hyperledger.besu.consensus.qbft.core.payload;
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.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqSealed;
import org.hyperledger.besu.consensus.common.bft.payload.Payload; import org.hyperledger.besu.consensus.common.bft.payload.Payload;
import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1; import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1;
import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Hash;
@ -34,6 +36,7 @@ import java.util.Optional;
import java.util.StringJoiner; import java.util.StringJoiner;
import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
/** /**
* The Prepare payload. * The Prepare payload.
@ -54,7 +57,7 @@ import org.apache.tuweni.bytes.Bytes;
* bytes as a commit seal, a PREPARE seal given honestly could be pasted onto a forged COMMIT and * bytes as a commit seal, a PREPARE seal given honestly could be pasted onto a forged COMMIT and
* the enforcement there would accept it. * the enforcement there would accept it.
*/ */
public class PreparePayload extends QbftPayload { public class PreparePayload extends QbftPayload implements PqSealed {
private static final int TYPE = QbftV1.PREPARE; private static final int TYPE = QbftV1.PREPARE;
private final ConsensusRoundIdentifier roundIdentifier; private final ConsensusRoundIdentifier roundIdentifier;
private final Hash digest; private final Hash digest;
@ -171,10 +174,22 @@ public class PreparePayload extends QbftPayload {
* *
* @return the seal, or empty * @return the seal, or empty
*/ */
@Override
public Optional<FalconSeal> getFalconSeal() { public Optional<FalconSeal> getFalconSeal() {
return falconSeal; return falconSeal;
} }
/** AIP-22 stage 4: the PREPARE seal's message, as PqPrepareEnforcement builds it. */
@Override
public Optional<Bytes32> pqSealMessage(final long chainId) {
return Optional.of(
PqAnchor.prepareMessage(
chainId,
roundIdentifier.getSequenceNumber(),
roundIdentifier.getRoundNumber(),
digest.getBytes()));
}
@Override @Override
public ConsensusRoundIdentifier getRoundIdentifier() { public ConsensusRoundIdentifier getRoundIdentifier() {
return roundIdentifier; return roundIdentifier;

View File

@ -22,6 +22,8 @@ package org.hyperledger.besu.consensus.qbft.core.payload;
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.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqSealed;
import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1; import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1;
import org.hyperledger.besu.consensus.qbft.core.types.QbftBlock; import org.hyperledger.besu.consensus.qbft.core.types.QbftBlock;
import org.hyperledger.besu.consensus.qbft.core.types.QbftBlockCodec; import org.hyperledger.besu.consensus.qbft.core.types.QbftBlockCodec;
@ -35,6 +37,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import org.apache.tuweni.bytes.Bytes32;
/** /**
* The Proposal payload. * The Proposal payload.
@ -63,7 +66,7 @@ import com.google.common.base.MoreObjects;
* and it must be the last element; otherwise the message is refused. Messages of the upstream * and it must be the last element; otherwise the message is refused. Messages of the upstream
* shapes (three or four elements) are read exactly as upstream reads them. * shapes (three or four elements) are read exactly as upstream reads them.
*/ */
public class ProposalPayload extends QbftPayload { public class ProposalPayload extends QbftPayload implements PqSealed {
private static final int TYPE = QbftV1.PROPOSAL; private static final int TYPE = QbftV1.PROPOSAL;
private final ConsensusRoundIdentifier roundIdentifier; private final ConsensusRoundIdentifier roundIdentifier;
@ -202,10 +205,22 @@ public class ProposalPayload extends QbftPayload {
* *
* @return the seal, or empty * @return the seal, or empty
*/ */
@Override
public Optional<FalconSeal> getFalconSeal() { public Optional<FalconSeal> getFalconSeal() {
return falconSeal; return falconSeal;
} }
/** AIP-22 stage 4: the PROPOSAL seal's message over the proposed block's hash, as PqProposalEnforcement builds it. */
@Override
public Optional<Bytes32> pqSealMessage(final long chainId) {
return Optional.of(
PqAnchor.proposalMessage(
chainId,
roundIdentifier.getSequenceNumber(),
roundIdentifier.getRoundNumber(),
proposedBlock.getHash().getBytes()));
}
@Override @Override
public int getMessageType() { public int getMessageType() {
return TYPE; return TYPE;

View File

@ -22,6 +22,8 @@ package org.hyperledger.besu.consensus.qbft.core.payload;
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.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqSealed;
import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1; import org.hyperledger.besu.consensus.qbft.core.messagedata.QbftV1;
import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput;
@ -31,6 +33,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import org.apache.tuweni.bytes.Bytes32;
/** /**
* The Round change payload. * The Round change payload.
@ -61,7 +64,7 @@ import com.google.common.base.MoreObjects;
* it must be the last element; otherwise the message is refused. Messages of the upstream shape * it must be the last element; otherwise the message is refused. Messages of the upstream shape
* (three elements) are read exactly as upstream reads them. * (three elements) are read exactly as upstream reads them.
*/ */
public class RoundChangePayload extends QbftPayload { public class RoundChangePayload extends QbftPayload implements PqSealed {
private static final int TYPE = QbftV1.ROUND_CHANGE; private static final int TYPE = QbftV1.ROUND_CHANGE;
private final ConsensusRoundIdentifier roundChangeIdentifier; private final ConsensusRoundIdentifier roundChangeIdentifier;
private final Optional<PreparedRoundMetadata> preparedRoundMetadata; private final Optional<PreparedRoundMetadata> preparedRoundMetadata;
@ -115,10 +118,27 @@ public class RoundChangePayload extends QbftPayload {
* *
* @return the seal, or empty * @return the seal, or empty
*/ */
@Override
public Optional<FalconSeal> getFalconSeal() { public Optional<FalconSeal> getFalconSeal() {
return falconSeal; return falconSeal;
} }
/** AIP-22 stage 4: the ROUND-CHANGE seal's message, prepared or not, as PqRoundChangeEnforcement builds it. */
@Override
public Optional<Bytes32> pqSealMessage(final long chainId) {
final long height = roundChangeIdentifier.getSequenceNumber();
final int targetRound = roundChangeIdentifier.getRoundNumber();
return Optional.of(
preparedRoundMetadata.isPresent()
? PqAnchor.roundChangeMessage(
chainId,
height,
targetRound,
preparedRoundMetadata.get().getPreparedRound(),
preparedRoundMetadata.get().getPreparedBlockHash().getBytes())
: PqAnchor.roundChangeMessage(chainId, height, targetRound));
}
@Override @Override
public void writeTo(final RLPOutput rlpOutput) { public void writeTo(final RLPOutput rlpOutput) {
// RLP encode of the message data content (round identifier and prepared certificate) // RLP encode of the message data content (round identifier and prepared certificate)

View File

@ -0,0 +1,181 @@
/*
* 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.core.validation;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.LongPredicate;
/**
* AIP-22 stage 4: H_fin may be armed only where every post-quantum seal is VERIFIED from H_fin.
*
* <p>From H_fin a message's author is the id of its seal's index ({@code AerePqFinality}), and the
* envelope's ECDSA signature is no longer read. For a PROPOSAL, PREPARE or ROUND-CHANGE the author is
* given only once the seal verifies; a COMMIT's author is its index's id only when that is the commit
* seal signer's id, and what judges the COMMIT's post-quantum seal is the commit layer alone. Each
* layer is armed on its own height. With the commit layer unset, or armed above H_fin, a Commit whose
* ECDSA commit seal was forged (the adversary stage 4 exists for) would count with any bytes as its
* post-quantum seal; and the other three layers are the second judge of what the author already
* checked. So this guard refuses to start such a node (AERE-PQC-FIN-02), before the network is up.
* Emergency disarming of a layer after H_fin therefore needs H_fin unset too, on every node: one
* uniform step back to ECDSA authorship, never a node that trusts unverified seals.
*/
public final class PqFinalityLayersGuard {
/**
* One seal layer as the guard sees it.
*
* @param property the property that arms it
* @param armedAt whether it is armed at a height; null when the property is set nowhere
*/
public record Layer(String property, LongPredicate armedAt) {}
private PqFinalityLayersGuard() {}
/**
* The refusal, if any.
*
* @param hFin H_fin, or {@link AerePqIdentity#NEVER}
* @param layers the four seal layers
* @return empty when H_fin is unset or every layer is armed at or below it; otherwise the refusal
*/
public static Optional<String> refusal(final long hFin, final List<Layer> layers) {
if (hFin == AerePqIdentity.NEVER) {
return Optional.empty();
}
final List<String> missing = new ArrayList<>();
for (final Layer l : layers) {
if (l.armedAt() == null || !l.armedAt().test(hFin)) {
missing.add(l.property() + (l.armedAt() == null ? " unset" : " armed above " + hFin));
}
}
if (missing.isEmpty()) {
return Optional.empty();
}
return Optional.of(
"AERE-PQC-FIN-02: aere.pq.finalityBlock="
+ hFin
+ " makes a message's author the id of its post-quantum seal's index, so every seal"
+ " must be verified from H_fin; not armed at or below it: "
+ String.join(", ", missing)
+ ". Arm the four layers at or below H_fin on every node, or unset H_fin on every node.");
}
/**
* The four layers as this node's configuration arms them.
*
* @return proposal, prepare, commit, round-change
*/
public static List<Layer> fromSystemConfig() {
final PqProposalEnforcement p = PqProposalEnforcement.fromSystemConfig();
final PqPrepareEnforcement pr = PqPrepareEnforcement.fromSystemConfig();
final PqCommitEnforcement c = PqCommitEnforcement.fromSystemConfig();
final PqRoundChangeEnforcement rc = PqRoundChangeEnforcement.fromSystemConfig();
return List.of(
new Layer(PqProposalEnforcement.PROPERTY_FORK_BLOCK, p == null ? null : p::armedAt),
new Layer(PqPrepareEnforcement.PROPERTY_FORK_BLOCK, pr == null ? null : pr::armedAt),
new Layer(PqCommitEnforcement.PROPERTY_FORK_BLOCK, c == null ? null : c::armedAt),
new Layer(
PqRoundChangeEnforcement.PROPERTY_FORK_BLOCK, rc == null ? null : rc::armedAt));
}
/**
* Refuses to start when H_fin is armed over an unverified layer.
*
* @param hFin H_fin as armed on this node
* @throws IllegalStateException AERE-PQC-FIN-02
*/
public static void verifyOrAbort(final long hFin) {
final Optional<String> r = refusal(hFin, fromSystemConfig());
if (r.isPresent()) {
throw new IllegalStateException(r.get());
}
}
/**
* AERE-PQC-FIN-03: from H_fin an unsealed message has no author, so a node that seals (holds its
* Falcon key; from H_k every registered validator must, stage 3b-ii) must seal all four messages at
* or below H_fin, or it goes mute at H_fin while looking healthy: a loss of liveness, not safety.
*
* @param hFin H_fin, or {@link AerePqIdentity#NEVER}
* @param signs whether this node seals (a reader does not)
* @param attachCommit aere.falcon.attachBlock ({@link Long#MAX_VALUE} when unset)
* @param attachProposal aere.pq.proposalPq.attachBlock
* @param attachPrepare aere.pq.preparePq.attachBlock
* @param attachRoundChange aere.pq.roundChangePq.attachBlock
* @return the refusal, or empty
*/
public static Optional<String> emissionRefusal(
final long hFin,
final boolean signs,
final long attachCommit,
final long attachProposal,
final long attachPrepare,
final long attachRoundChange) {
if (hFin == AerePqIdentity.NEVER || !signs) {
return Optional.empty();
}
final List<String> missing = new ArrayList<>();
gate(missing, "aere.falcon.attachBlock", attachCommit, hFin);
gate(missing, "aere.pq.proposalPq.attachBlock", attachProposal, hFin);
gate(missing, "aere.pq.preparePq.attachBlock", attachPrepare, hFin);
gate(missing, "aere.pq.roundChangePq.attachBlock", attachRoundChange, hFin);
if (missing.isEmpty()) {
return Optional.empty();
}
return Optional.of(
"AERE-PQC-FIN-03: aere.pq.finalityBlock="
+ hFin
+ " makes an unsealed message authorless, and this node would not seal from H_fin: "
+ String.join(", ", missing)
+ ". It would go mute at H_fin. Arm them at or below H_fin, or unset H_fin, on every validator.");
}
private static void gate(final List<String> missing, final String property, final long h, final long hFin) {
if (h < 0 || h == Long.MAX_VALUE) {
missing.add(property + " unset");
} else if (h > hFin) {
missing.add(property + " armed above H_fin (" + h + ")");
}
}
/**
* Refuses to start when a sealing node would not seal every message from H_fin.
*
* @param hFin H_fin as armed on this node
* @param signs whether this node seals
* @param attachCommit commit attachment height
* @param attachProposal proposal attachment height
* @param attachPrepare prepare attachment height
* @param attachRoundChange round-change attachment height
* @throws IllegalStateException AERE-PQC-FIN-03
*/
public static void verifyEmissionOrAbort(
final long hFin,
final boolean signs,
final long attachCommit,
final long attachProposal,
final long attachPrepare,
final long attachRoundChange) {
final Optional<String> r =
emissionRefusal(hFin, signs, attachCommit, attachProposal, attachPrepare, attachRoundChange);
if (r.isPresent()) {
throw new IllegalStateException(r.get());
}
}
}

View File

@ -0,0 +1,204 @@
/*
* 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.core.validation;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.consensus.common.bft.AerePqFinality;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
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.common.bft.payload.Payload;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData;
import org.hyperledger.besu.consensus.qbft.core.QbftBlockTestFixture;
import org.hyperledger.besu.consensus.qbft.core.payload.CommitPayload;
import org.hyperledger.besu.consensus.qbft.core.payload.PreparePayload;
import org.hyperledger.besu.consensus.qbft.core.payload.PreparedRoundMetadata;
import org.hyperledger.besu.consensus.qbft.core.payload.ProposalPayload;
import org.hyperledger.besu.consensus.qbft.core.payload.RoundChangePayload;
import org.hyperledger.besu.consensus.qbft.core.types.QbftBlock;
import org.hyperledger.besu.consensus.qbft.core.types.QbftBlockCodec;
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.util.ArrayList;
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;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* AIP-22 stage 4 on the REAL QBFT payloads, with real Falcon-512 keys generated for the run: from H_fin
* the author of a PREPARE, a PROPOSAL and a ROUND-CHANGE is the id of its seal's index only when the
* seal verifies over the message its own layer builds, and a COMMIT's only when that index names the
* commit seal signer's id. Every envelope is signed by a FOREIGN node key, so a validator's id can only
* come from the seal. Each positive has its negative on the same fixture: another index's seal, a seal
* over another round, a ROUND-CHANGE with prepared metadata sealed in the unprepared form, a proposal
* sealed over another block, a commit seal of another validator. The runner's negative control plants
* "every seal verifies" at run time and these probes turn red.
*/
@ExtendWith(MockitoExtension.class)
public class AereFinalityMessageTest {
private static final long CHAIN = 28001;
private static final long BIND = 10; // H_k must be the bindHeight of a v3 epoch at or above H_id
private static final long H_ID = 10;
private static final long H_FIN = 20;
private static final int N = 4;
private static final SealScheme FALCON = SealSchemes.FALCON_512;
private final Hash digest = Hash.hash(Bytes.of(7));
private final NodeKey foreign = NodeKeyUtils.generate();
private final List<NodeKey> ecdsa = new ArrayList<>();
private final List<SealScheme.PrivateHandle> falcon = new ArrayList<>();
private final List<Address> ids = new ArrayList<>();
private @Mock QbftBlockCodec blockCodec;
@BeforeEach
public void registry() {
final List<PqRegistryV3.Row> rows = new ArrayList<>();
for (int i = 0; i < N; i++) {
final NodeKey nk = NodeKeyUtils.generate();
ecdsa.add(nk);
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 Bytes32 rh = PqRegistryV3.rowHash(CHAIN, BIND, N, bare);
final SECPSignature claim = nk.sign(PqRegistryV3.claimDigest(CHAIN, rh));
final Address addr = org.hyperledger.besu.ethereum.core.Util.publicKeyToAddress(nk.getPublicKey());
rows.add(
new PqRegistryV3.Row(
i, id, keys, null, Map.of(), addr.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 PqRegistryV3.Registry reg =
new PqRegistryV3.Registry(
CHAIN, BIND, N, rows, hashes, root, PqRegistryV3.registryHash(CHAIN, BIND, N, root));
final TreeMap<Long, PqRegistryV3.Registry> epochs = new TreeMap<>();
epochs.put(BIND, reg);
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
AerePqIdentity.install(new AerePqIdentity.Config(H_ID, epochs, H_ID));
// the production verifier reads the same v3 key from H_k (FalconSealSupport.verifyAtOwnHead)
AerePqFinality.install(
H_FIN,
CHAIN,
(h, i, m, s) ->
AerePqIdentity.v3KeyAt(h, i, "falcon-512")
.map(k -> FALCON.verifyRaw(k, m.toArrayUnsafe(), s.toArrayUnsafe()))
.orElse(false));
}
@AfterEach
public void disarm() {
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
}
private Optional<FalconSeal> seal(final int index, final int signer, final Bytes32 message) {
return Optional.of(
new FalconSeal(index, Bytes.wrap(FALCON.sign(falcon.get(signer), message.toArrayUnsafe()).orElseThrow())));
}
private <P extends Payload> Address authorOf(final P payload) {
return SignedData.create(payload, foreign.sign(Bytes32.wrap(payload.hashForSignature().getBytes())))
.getAuthor();
}
private static ConsensusRoundIdentifier round(final int r) {
return new ConsensusRoundIdentifier(H_FIN, r);
}
@Test
public void aPrepareIsAuthoredOnlyByAVerifiedSealOfItsIndex() {
final Bytes32 m = PqAnchor.prepareMessage(CHAIN, H_FIN, 0, digest.getBytes());
for (int i = 0; i < N; i++) {
assertThat(authorOf(new PreparePayload(round(0), digest, seal(i, i, m)))).as("node %s", i).isEqualTo(ids.get(i));
}
// index 0 with node 1's seal; node 0's seal over round 1; no seal
assertThat(authorOf(new PreparePayload(round(0), digest, seal(0, 1, m)))).isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(
authorOf(
new PreparePayload(
round(0), digest, seal(0, 0, PqAnchor.prepareMessage(CHAIN, H_FIN, 1, digest.getBytes())))))
.isEqualTo(AerePqFinality.NO_AUTHOR);
assertThat(authorOf(new PreparePayload(round(0), digest, Optional.empty()))).isEqualTo(AerePqFinality.NO_AUTHOR);
}
@Test
public void aRoundChangeIsAuthoredOnlyByASealInTheFormItsMetadataNames() {
final Bytes32 bare = PqAnchor.roundChangeMessage(CHAIN, H_FIN, 2);
assertThat(authorOf(new RoundChangePayload(round(2), Optional.empty(), seal(1, 1, bare)))).isEqualTo(ids.get(1));
final Optional<PreparedRoundMetadata> meta = Optional.of(new PreparedRoundMetadata(digest, 1));
final Bytes32 prepared = PqAnchor.roundChangeMessage(CHAIN, H_FIN, 2, 1, digest.getBytes());
assertThat(authorOf(new RoundChangePayload(round(2), meta, seal(1, 1, prepared)))).isEqualTo(ids.get(1));
// the prepared claim sealed in the unprepared form does not author: the seal must cover the claim
assertThat(authorOf(new RoundChangePayload(round(2), meta, seal(1, 1, bare)))).isEqualTo(AerePqFinality.NO_AUTHOR);
}
@Test
public void aProposalIsAuthoredOnlyByASealOverItsOwnBlock() {
final QbftBlock block =
new QbftBlockTestFixture()
.blockHeader(new QbftBlockHeaderTestFixture().number(H_FIN).buildHeader())
.build();
final Bytes32 m = PqAnchor.proposalMessage(CHAIN, H_FIN, 0, block.getHash().getBytes());
assertThat(authorOf(new ProposalPayload(round(0), block, blockCodec, Optional.empty(), seal(2, 2, m))))
.isEqualTo(ids.get(2));
final Bytes32 other = PqAnchor.proposalMessage(CHAIN, H_FIN, 0, digest.getBytes());
assertThat(authorOf(new ProposalPayload(round(0), block, blockCodec, Optional.empty(), seal(2, 2, other))))
.isEqualTo(AerePqFinality.NO_AUTHOR);
}
@Test
public void aCommitIsAuthoredByItsIndexOnlyWhenThatIsTheCommitSealSigner() {
final Optional<FalconSeal> any = Optional.of(new FalconSeal(3, Bytes.of(1)));
final SECPSignature own = ecdsa.get(3).sign(Bytes32.wrap(digest.getBytes()));
final SECPSignature other = ecdsa.get(0).sign(Bytes32.wrap(digest.getBytes()));
assertThat(authorOf(new CommitPayload(round(0), digest, own, any))).isEqualTo(ids.get(3));
assertThat(authorOf(new CommitPayload(round(0), digest, other, any))).isEqualTo(AerePqFinality.NO_AUTHOR);
}
}

View File

@ -0,0 +1,206 @@
/*
* 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.core.validation;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.consensus.common.bft.AerePqFinality;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
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.core.messagewrappers.Commit;
import org.hyperledger.besu.consensus.qbft.core.messagewrappers.Prepare;
import org.hyperledger.besu.consensus.qbft.core.messagewrappers.RoundChange;
import org.hyperledger.besu.consensus.qbft.core.payload.MessageFactory;
import org.hyperledger.besu.consensus.qbft.core.types.QbftBlockCodec;
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 org.hyperledger.besu.ethereum.core.Util;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
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.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.mockito.Mockito;
/**
* AIP-22 stage 4: the WRITER of the shared authorship vectors. Real QBFT messages, encoded on the wire
* exactly as this client sends them, each ENVELOPE signed by a FOREIGN node key, each carrying a real
* Falcon-512 seal (keys generated for the run, nothing here touches a real key): the second client must
* decode every one to the SAME author this client gives it. Every expected author is asserted here, at
* writing time, through this client's own decoding path (SignedData), so a vector cannot say what this
* client does not do. Runs only with AERE_T4_VECTORS_OUT set to the output directory.
*/
@EnabledIfEnvironmentVariable(named = "AERE_T4_VECTORS_OUT", matches = ".+")
public class AereFinalityVectorsWriter {
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 int N = 4;
private static final SealScheme FALCON = SealSchemes.FALCON_512;
@AfterEach
public void disarm() {
AerePqFinality.uninstall();
AerePqIdentity.uninstall();
}
private static String hex(final Bytes b) {
return b.toUnprefixedHexString();
}
@Test
public void writeTheSharedAuthorshipVectors() throws IOException {
final List<NodeKey> ecdsa = new ArrayList<>();
final List<SealScheme.PrivateHandle> falcon = new ArrayList<>();
final List<byte[]> std = new ArrayList<>();
final List<Address> ids = new ArrayList<>();
final List<PqRegistryV3.Row> rows = new ArrayList<>();
for (int i = 0; i < N; i++) {
final NodeKey nk = NodeKeyUtils.generate();
ecdsa.add(nk);
final SealScheme.GeneratedPair p = FALCON.generate(SecureRandomProvider.createSecureRandom());
falcon.add(p.privateKey());
final byte[] s = new byte[897];
s[0] = 0x09;
System.arraycopy(p.publicRegistryForm(), 0, s, 1, 896);
std.add(s);
final byte[] id = PqRegistryV3.idOf(s);
final Bytes32 popMsg = PqRegistryV3.popMessage(CHAIN, BIND, N, i, id, FALCON.wireId(), s);
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, s, 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)));
AerePqIdentity.install(new AerePqIdentity.Config(H_ID, epochs, H_ID));
AerePqFinality.install(
H_FIN,
CHAIN,
(h, i, m, s) ->
AerePqIdentity.v3KeyAt(h, i, "falcon-512")
.map(k -> FALCON.verifyRaw(k, m.toArrayUnsafe(), s.toArrayUnsafe()))
.orElse(false));
final NodeKey foreign = NodeKeyUtils.generate();
final Address foreignAddr = Util.publicKeyToAddress(foreign.getPublicKey());
final MessageFactory mf = new MessageFactory(foreign, Mockito.mock(QbftBlockCodec.class));
final Hash digest = Hash.hash(Bytes.of(7));
final List<String[]> out = new ArrayList<>(); // name, type, height, wire, expected author
// 1. a PREPARE sealed by its own index: the index's id
final Bytes32 pm = PqAnchor.prepareMessage(CHAIN, H_FIN, 0, digest.getBytes());
final Prepare p1 = mf.createPrepare(new ConsensusRoundIdentifier(H_FIN, 0), digest, seal(falcon, 1, 1, pm));
assertThat(p1.getAuthor()).isEqualTo(ids.get(1));
out.add(new String[] {"prepare-own-seal", "prepare", Long.toString(H_FIN), hex(p1.encode()), hex(ids.get(1).getBytes())});
// 2. a PREPARE naming index 0 with index 1's seal: no author
final Prepare p2 = mf.createPrepare(new ConsensusRoundIdentifier(H_FIN, 0), digest, seal(falcon, 0, 1, pm));
assertThat(p2.getAuthor()).isEqualTo(AerePqFinality.NO_AUTHOR);
out.add(new String[] {"prepare-another-index-seal", "prepare", Long.toString(H_FIN), hex(p2.encode()), hex(AerePqFinality.NO_AUTHOR.getBytes())});
// 3. a PREPARE one block below H_fin: the envelope's (foreign) signer
final Bytes32 pmb = PqAnchor.prepareMessage(CHAIN, H_FIN - 1, 0, digest.getBytes());
final Prepare p3 = mf.createPrepare(new ConsensusRoundIdentifier(H_FIN - 1, 0), digest, seal(falcon, 1, 1, pmb));
assertThat(p3.getAuthor()).isEqualTo(foreignAddr);
out.add(new String[] {"prepare-below-hfin", "prepare", Long.toString(H_FIN - 1), hex(p3.encode()), hex(foreignAddr.getBytes())});
// 4. a ROUND-CHANGE without a prepared claim, sealed by its own index
final Bytes32 rm = PqAnchor.roundChangeMessage(CHAIN, H_FIN, 2);
final RoundChange r1 = mf.createRoundChange(new ConsensusRoundIdentifier(H_FIN, 2), Optional.empty(), seal(falcon, 2, 2, rm));
assertThat(r1.getAuthor()).isEqualTo(ids.get(2));
out.add(new String[] {"roundchange-own-seal", "roundchange", Long.toString(H_FIN), hex(r1.encode()), hex(ids.get(2).getBytes())});
// 5. a ROUND-CHANGE sealed over another target round: no author
final RoundChange r2 =
mf.createRoundChange(
new ConsensusRoundIdentifier(H_FIN, 2), Optional.empty(),
seal(falcon, 2, 2, PqAnchor.roundChangeMessage(CHAIN, H_FIN, 3)));
assertThat(r2.getAuthor()).isEqualTo(AerePqFinality.NO_AUTHOR);
out.add(new String[] {"roundchange-other-round", "roundchange", Long.toString(H_FIN), hex(r2.encode()), hex(AerePqFinality.NO_AUTHOR.getBytes())});
// 6. a COMMIT naming index 3 with validator 3's commit seal: index 3's id
final SECPSignature own = ecdsa.get(3).sign(Bytes32.wrap(digest.getBytes()));
final Commit c1 = mf.createCommit(new ConsensusRoundIdentifier(H_FIN, 0), digest, own, Optional.of(new FalconSeal(3, Bytes.of(1))));
assertThat(c1.getAuthor()).isEqualTo(ids.get(3));
out.add(new String[] {"commit-own-commit-seal", "commit", Long.toString(H_FIN), hex(c1.encode()), hex(ids.get(3).getBytes())});
// 7. a COMMIT naming index 3 with validator 0's commit seal: no author
final SECPSignature other = ecdsa.get(0).sign(Bytes32.wrap(digest.getBytes()));
final Commit c2 = mf.createCommit(new ConsensusRoundIdentifier(H_FIN, 0), digest, other, Optional.of(new FalconSeal(3, Bytes.of(1))));
assertThat(c2.getAuthor()).isEqualTo(AerePqFinality.NO_AUTHOR);
out.add(new String[] {"commit-another-commit-seal", "commit", Long.toString(H_FIN), hex(c2.encode()), hex(AerePqFinality.NO_AUTHOR.getBytes())});
final StringBuilder j = new StringBuilder();
j.append("{\n \"note\": \"AIP-22 stage 4 shared authorship vectors, written by AereFinalityVectorsWriter (Besu-derived client); PROBE keys\",\n");
j.append(" \"chainId\": ").append(CHAIN).append(",\n \"bindHeight\": ").append(BIND)
.append(",\n \"identityBlock\": ").append(H_ID).append(",\n \"keysFrom\": ").append(H_ID)
.append(",\n \"finalityBlock\": ").append(H_FIN).append(",\n \"rows\": [\n");
for (int i = 0; i < N; i++) {
j.append(" {\"index\": ").append(i).append(", \"id\": \"").append(hex(ids.get(i).getBytes()))
.append("\", \"falcon\": \"").append(hex(Bytes.wrap(std.get(i))))
.append("\", \"ecdsa\": \"").append(hex(Util.publicKeyToAddress(ecdsa.get(i).getPublicKey()).getBytes()))
.append("\"}").append(i + 1 < N ? ",\n" : "\n");
}
j.append(" ],\n \"messages\": [\n");
for (int k = 0; k < out.size(); k++) {
final String[] m = out.get(k);
j.append(" {\"name\": \"").append(m[0]).append("\", \"type\": \"").append(m[1]).append("\", \"height\": ").append(m[2])
.append(", \"wire\": \"").append(m[3]).append("\", \"author\": \"").append(m[4]).append("\"}")
.append(k + 1 < out.size() ? ",\n" : "\n");
}
j.append(" ]\n}\n");
final Path dir = Path.of(System.getenv("AERE_T4_VECTORS_OUT"));
Files.createDirectories(dir);
Files.writeString(dir.resolve("author-t4.json"), j.toString(), StandardCharsets.UTF_8);
}
private static Optional<FalconSeal> seal(
final List<SealScheme.PrivateHandle> falcon, final int index, final int signer, final Bytes32 message) {
return Optional.of(
new FalconSeal(index, Bytes.wrap(FALCON.sign(falcon.get(signer), message.toArrayUnsafe()).orElseThrow())));
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.core.validation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
/**
* AIP-22 stage 4: H_fin is refused over a seal layer that is not verified from H_fin (AERE-PQC-FIN-02),
* named layer by layer; H_fin unset is inert. The last probe goes through the production path (the
* four properties the layers really read). The runner's negative control plants "never refuse" at run
* time and these probes turn red.
*/
public class PqFinalityLayersGuardTest {
private static final long H_FIN = 5_000L;
private static final String[] PROPS = {
PqProposalEnforcement.PROPERTY_FORK_BLOCK,
PqPrepareEnforcement.PROPERTY_FORK_BLOCK,
PqCommitEnforcement.PROPERTY_FORK_BLOCK,
PqRoundChangeEnforcement.PROPERTY_FORK_BLOCK
};
@AfterEach
public void clear() {
for (final String p : PROPS) {
System.clearProperty(p);
}
}
private static PqFinalityLayersGuard.Layer armedFrom(final String property, final long from) {
return new PqFinalityLayersGuard.Layer(property, h -> h >= from);
}
private static List<PqFinalityLayersGuard.Layer> allArmedFrom(final long from) {
return List.of(
armedFrom(PROPS[0], from), armedFrom(PROPS[1], from), armedFrom(PROPS[2], from),
armedFrom(PROPS[3], from));
}
@Test
public void everyLayerArmedAtOrBelowHfinPassesAndHfinUnsetIsInert() {
assertThat(PqFinalityLayersGuard.refusal(H_FIN, allArmedFrom(H_FIN))).isEmpty();
assertThat(PqFinalityLayersGuard.refusal(H_FIN, allArmedFrom(100))).isEmpty();
final List<PqFinalityLayersGuard.Layer> none =
List.of(
new PqFinalityLayersGuard.Layer(PROPS[0], null),
new PqFinalityLayersGuard.Layer(PROPS[1], null),
new PqFinalityLayersGuard.Layer(PROPS[2], null),
new PqFinalityLayersGuard.Layer(PROPS[3], null));
assertThat(PqFinalityLayersGuard.refusal(AerePqIdentity.NEVER, none)).isEmpty();
}
@Test
public void aLayerUnsetOrArmedAboveHfinIsRefusedByName() {
for (int i = 0; i < PROPS.length; i++) {
final List<PqFinalityLayersGuard.Layer> unset = new java.util.ArrayList<>(allArmedFrom(100));
unset.set(i, new PqFinalityLayersGuard.Layer(PROPS[i], null));
final Optional<String> r = PqFinalityLayersGuard.refusal(H_FIN, unset);
assertThat(r).as("layer %s unset", PROPS[i]).isPresent();
assertThat(r.get()).startsWith("AERE-PQC-FIN-02").contains(PROPS[i] + " unset");
final List<PqFinalityLayersGuard.Layer> late = new java.util.ArrayList<>(allArmedFrom(100));
late.set(i, armedFrom(PROPS[i], H_FIN + 1));
final Optional<String> r2 = PqFinalityLayersGuard.refusal(H_FIN, late);
assertThat(r2).as("layer %s armed one block above H_fin", PROPS[i]).isPresent();
assertThat(r2.get()).contains(PROPS[i] + " armed above " + H_FIN);
}
}
@Test
public void productionPathReadsTheFourPropertiesTheLayersRead() {
for (final String p : PROPS) {
System.setProperty(p, Long.toString(H_FIN - 10));
}
PqFinalityLayersGuard.verifyOrAbort(H_FIN);
System.clearProperty(PqRoundChangeEnforcement.PROPERTY_FORK_BLOCK);
assertThatThrownBy(() -> PqFinalityLayersGuard.verifyOrAbort(H_FIN))
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("AERE-PQC-FIN-02")
.hasMessageContaining(PqRoundChangeEnforcement.PROPERTY_FORK_BLOCK + " unset");
// H_fin unset: inert whatever the layers
PqFinalityLayersGuard.verifyOrAbort(AerePqIdentity.NEVER);
}
@Test
public void aSealingNodeMustSealAllFourMessagesAtOrBelowHfin() {
final long never = Long.MAX_VALUE;
assertThat(PqFinalityLayersGuard.emissionRefusal(AerePqIdentity.NEVER, true, never, never, never, never))
.as("unset H_fin asks nothing")
.isEmpty();
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, false, never, never, never, never))
.as("a reader seals nothing and needs nothing")
.isEmpty();
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, true, 32, 200, 200, 200)).isEmpty();
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, true, H_FIN, H_FIN, H_FIN, H_FIN)).isEmpty();
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, true, 32, never, 200, 200))
.hasValueSatisfying(
r -> assertThat(r).startsWith("AERE-PQC-FIN-03").contains("aere.pq.proposalPq.attachBlock unset"));
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, true, 32, 200, H_FIN + 1, 200))
.hasValueSatisfying(r -> assertThat(r).contains("aere.pq.preparePq.attachBlock armed above H_fin"));
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, true, 32, 200, 200, never))
.hasValueSatisfying(r -> assertThat(r).contains("aere.pq.roundChangePq.attachBlock unset"));
assertThat(PqFinalityLayersGuard.emissionRefusal(H_FIN, true, H_FIN + 5, 200, 200, 200))
.hasValueSatisfying(r -> assertThat(r).contains("aere.falcon.attachBlock armed above H_fin"));
assertThatThrownBy(() -> PqFinalityLayersGuard.verifyEmissionOrAbort(H_FIN, true, 32, never, 200, 200))
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("AERE-PQC-FIN-03");
}
}

View File

@ -33,6 +33,7 @@ import org.hyperledger.besu.consensus.qbft.headervalidationrules.FalconSealValid
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.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;
@ -188,7 +189,11 @@ public class QbftBlockHeaderValidationRulesetFactory {
// that once, against the head that existed at startup; a rotation entry in // that once, against the head that existed at startup; a rotation entry in
// the schedule can pass underneath a running node and never be noticed. // the schedule can pass underneath a running node and never be noticed.
// Inert when no schedule is configured, which is chain 2800 today. // Inert when no schedule is configured, which is chain 2800 today.
.addRule(new PqRegistryBindingRule()); .addRule(new PqRegistryBindingRule())
// AERE AIP-22 stage 4: the checkpoint (aere.pq.checkpoint=<height>:<hash>); a header at a
// checkpoint height with another hash is refused. Attached, so it runs at import. Inert
// when unset, which is every chain today; a malformed value refuses at load.
.addRule(PqCheckpointRule.fromSystemConfiguration());
// Currently the minimum acceptable time between blocks is 1 second. The timestamp of an // Currently the minimum acceptable time between blocks is 1 second. The timestamp of an
// Ethereum header is stored as seconds since Unix epoch so blocks being produced more // Ethereum header is stored as seconds since Unix epoch so blocks being produced more

View File

@ -0,0 +1,133 @@
/*
* 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.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import java.util.Collections;
import java.util.NavigableMap;
import java.util.TreeMap;
import com.google.common.base.Splitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AERE AIP-22 stage 4 (plan 9.6.7 point iv): the CHECKPOINT, which is how the weak subjectivity of a
* finality gadget is handled, stated publicly rather than hidden. A node that synchronises from
* genesis without other knowledge can be handed, by an eclipsing neighbour, a fork that ends before
* the first certified anchor; it can never reach the honest chain's height with valid certificates,
* but the eclipsed node does not know that height. Given a recent anchor from outside (height and
* block hash, from the published registry epoch or from its operator), it refuses any header at that
* height with another hash, so every chain it accepts runs through the checkpoint, and a rewrite at
* any height below it would have to reproduce every certificate above.
*
* <p>{@code aere.pq.checkpoint=<height>:<block hash>[,<height>:<block hash>...]}. Unset or blank:
* inert (every header passes), which is every chain as it stands. A value it cannot parse refuses at
* load (AERE-PQC-CHECKPOINT-01), never silently degrades to "no checkpoint". ATTACHED, so it runs at
* import under SKIP_DETACHED, the mode full sync imports with (the same reason R1A exists).
*/
public class PqCheckpointRule implements AttachedBlockHeaderValidationRule {
/** The system property that names the checkpoints. */
public static final String PROPERTY = "aere.pq.checkpoint";
private static final Logger LOG = LoggerFactory.getLogger(PqCheckpointRule.class);
private final NavigableMap<Long, Hash> checkpoints;
/**
* A rule over the given checkpoints (height to block hash).
*
* @param checkpoints the checkpoints; empty means inert
*/
public PqCheckpointRule(final NavigableMap<Long, Hash> checkpoints) {
this.checkpoints = Collections.unmodifiableNavigableMap(new TreeMap<>(checkpoints));
}
/**
* The rule configured on this node by {@link #PROPERTY}.
*
* @return the rule
*/
public static PqCheckpointRule fromSystemConfiguration() {
return new PqCheckpointRule(parse(System.getProperty(PROPERTY)));
}
/**
* The checkpoints named by this node's configuration.
*
* @return height to block hash, empty when unset
*/
public NavigableMap<Long, Hash> checkpoints() {
return checkpoints;
}
/**
* Parses {@code <height>:<block hash>[,...]}; fail-closed on anything malformed.
*
* @param value the property value, possibly null
* @return height to block hash
*/
static NavigableMap<Long, Hash> parse(final String value) {
final TreeMap<Long, Hash> m = new TreeMap<>();
if (value == null || value.isBlank()) {
return m;
}
for (final String part : Splitter.on(',').trimResults().omitEmptyStrings().split(value)) {
final int c = part.indexOf(':');
if (c <= 0) {
throw new IllegalArgumentException(
"AERE-PQC-CHECKPOINT-01: " + PROPERTY + " entry '" + part + "' is not <height>:<block hash>");
}
final long height;
final Hash hash;
try {
height = Long.parseLong(part.substring(0, c).trim());
hash = Hash.fromHexString(part.substring(c + 1).trim());
} catch (final RuntimeException e) {
throw new IllegalArgumentException(
"AERE-PQC-CHECKPOINT-01: " + PROPERTY + " entry '" + part + "' is not <height>:<32-byte block hash>", e);
}
if (height < 1) {
throw new IllegalArgumentException(
"AERE-PQC-CHECKPOINT-01: " + PROPERTY + " height " + height + " must be at least 1");
}
if (m.put(height, hash) != null) {
throw new IllegalArgumentException(
"AERE-PQC-CHECKPOINT-01: " + PROPERTY + " names height " + height + " twice");
}
}
return m;
}
@Override
public boolean validate(
final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) {
final Hash want = checkpoints.get(header.getNumber());
if (want == null || want.equals(header.getHash())) {
return true;
}
LOG.warn(
"AERE-PQC-CHECKPOINT-02: block {} has hash {} but this node's checkpoint at that height is {}; REFUSED (a chain that does not run through the checkpoint is never adopted)",
header.getNumber(),
header.getHash(),
want);
return false;
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;
/**
* AIP-22 stage 4: the checkpoint rule. At the checkpoint height only the checkpoint's hash passes;
* every other height passes; unset is inert; a malformed value refuses at load with its reason. The
* negative control of the runner plants "never refuse" at run time and turns the refusal probe red.
*/
class PqCheckpointRuleTest {
private static BlockHeader header(final long number, final long timestamp) {
return new BlockHeaderTestFixture().number(number).timestamp(timestamp).buildHeader();
}
@Test
void atTheCheckpointHeightOnlyTheCheckpointHashPassesAndAnotherHashIsRefused() {
final BlockHeader honest = header(2048, 1000);
final BlockHeader fork = header(2048, 1001);
assertThat(fork.getHash()).isNotEqualTo(honest.getHash());
final TreeMap<Long, Hash> cp = new TreeMap<>();
cp.put(2048L, honest.getHash());
final PqCheckpointRule rule = new PqCheckpointRule(cp);
assertThat(rule.validate(honest, null, null)).isTrue();
assertThat(rule.validate(fork, null, null)).isFalse();
// other heights are not judged by the checkpoint
assertThat(rule.validate(header(2047, 999), null, null)).isTrue();
assertThat(rule.validate(header(2049, 1002), null, null)).isTrue();
}
@Test
void unsetIsInertAndTheParserReadsSeveralCheckpoints() {
final PqCheckpointRule inert = new PqCheckpointRule(PqCheckpointRule.parse(null));
assertThat(inert.checkpoints()).isEmpty();
assertThat(inert.validate(header(2048, 1001), null, null)).isTrue();
assertThat(PqCheckpointRule.parse(" ")).isEmpty();
final String h1 = header(10, 1).getHash().toHexString();
final String h2 = header(20, 2).getHash().toHexString();
assertThat(PqCheckpointRule.parse("10:" + h1 + ", 20:" + h2)).hasSize(2).containsKeys(10L, 20L);
}
@Test
void aMalformedValueRefusesAtLoadWithItsReason() {
final String h = header(10, 1).getHash().toHexString();
for (final String bad : new String[] {"10", "x:" + h, "10:0x1234", "0:" + h, "10:" + h + ",10:" + h}) {
assertThatThrownBy(() -> PqCheckpointRule.parse(bad))
.as(bad)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageStartingWith("AERE-PQC-CHECKPOINT-01");
}
}
}