# Spec 9: Aere entropy honesty fix — real drand BLS verification ## The gap (what was dishonest) `aerenew/contracts/contracts/oracle/AereRandomnessBeacon.sol` (live-deployed at `0x25b6317efD8C7d425210F56Ee1E204852CD8213C`) shipped with a placeholder verifier: ```solidity function _verify(uint64 /*round*/, bytes calldata /*signature*/) internal pure returns (bool) { // Phase 2 implementation goes here. return true; } ``` It verified nothing. Worse, `strictVerifyEnabled` defaults to `false`, so even that stub was never called: any bytes could be submitted as a "drand round". It also derived randomness as `keccak256(signature)`, which is NOT drand's canonical randomness (`SHA-256(signature)`). ## What I built New files (the live beacon was NOT modified): - `aerenew/contracts/contracts/oracle/AereRandomnessBeaconV2.sol` - `library AereDrandQuicknetBLS` — real BLS12-381 verification on EIP-2537: - `messageForRound` = `SHA-256(round as 8-byte big-endian)` (quicknet is UNCHAINED) - `hashToField` = RFC 9380 `expand_message_xmd` (SHA-256, len 128) + reduce mod p via MODEXP. Uses only SHA-256 (0x02) + MODEXP (0x05), so it runs on any EVM. - `hashToG1` = RFC 9380 SSWU_RO to G1 via `BLS12_MAP_FP_TO_G1` (0x10) + `BLS12_G1ADD` (0x0b). (map already clears the cofactor, and cofactor clearing is a homomorphism, so `map(u0)+map(u1)` equals the RFC result.) - `decompressG1` = zcash/ETH2 compressed-G1 decompression via MODEXP sqrt (p ≡ 3 mod 4, sqrt = a^((p+1)/4)); consumes drand's exact 48-byte sig. - `verifyRoundCompressed` = pairing check `e(H(m),pk) * e(sig,-G2) == 1` via `BLS12_PAIRING_CHECK` (0x0f). - `contract AereRandomnessBeaconV2` — `submitRound` ALWAYS verifies (no phase flag, no admin off-switch); stores canonical `SHA-256(signature)` randomness. - `aerenew/contracts/test/AereRandomnessBeaconV2.test.js` — real drand vectors. - `aerenew/contracts/hardhat.config.prague.js` — pins the local `hardhat` network to the `prague` hardfork so EIP-2537 is available for the full test. **Fail-closed everywhere:** any wrong-length input, absent precompile (empty returndata), wrong-length precompile output, non-square x, point at infinity, or failed pairing resolves to `false`. There is no path that returns `true` without a genuine pairing equality. When EIP-2537 is absent, verification returns `false` and `submitRound` reverts (proven by the fail-closed-absence test). ## drand chain targeted (pinned, not guessed) League of Entropy **quicknet**, scheme `bls-unchained-g1-rfc9380` (confirmed live at `api.drand.sh//info`): - chain hash: `52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971` - public key (G2, 96-byte compressed): `83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a` - period 3 s, genesis 1692803367, unchained, sigs on G1 / pubkey on G2 - hash-to-curve DST: `BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_` All scheme parameters were proven correct BEFORE writing Solidity, by independently verifying genuine round 1000000 with `@noble/curves` (pairing of `H(m)` against the pubkey equals pairing of the signature against the G2 generator → true; wrong round → false). ## Real test vectors used (genuine, from api.drand.sh) - round 1000000: sig `83ad29e4...abe72`, randomness `b22aad47...440af3` - round 1000001: sig `a5bd91e5...c4218`, randomness `9f45f439...84ff` (used as a valid-but-wrong-round negative case) Confirmed on-chain: `SHA-256(sig)` reproduces the published randomness for both. ## Test result (REAL, not [MEASURE]) Run from `aerenew/contracts`: 1. Default config (`cancun`, no EIP-2537): `npx hardhat test test/AereRandomnessBeaconV2.test.js` -> **11 passing, 5 pending**. Message reconstruction, RFC 9380 hash-to-field (matches @noble reference), G1 decompression (matches @noble reference), canonical randomness, input validation, and the fail-closed-absence checks all pass. The pairing cases correctly report `[MEASURE]` and skip because the precompiles are absent. 2. Prague config (EIP-2537 live in the local EDR EVM): `npx hardhat test test/AereRandomnessBeaconV2.test.js --config hardhat.config.prague.js` -> **15 passing, 1 pending**. Full on-chain BLS verification: - hash-to-curve to G1 matches the independent @noble reference - genuine round 1000000 verifies **true**; genuine round 1000001 verifies **true** - valid signature submitted for the wrong round verifies **false** - tampered signature verifies **false** - `submitRound(1000000, genuineSig)` stores randomness == published `b22aad47...440af3`; double-submit reverts; a non-verifying signature reverts fail-closed with no storage. The local EDR EVM at the `prague` hardfork implements the same EIP-2537 spec that is "Supported" on Aere mainnet (per `docs/AERE-EIP-COMPATIBILITY-MATRIX.md`), so this is a genuine end-to-end pass of the on-chain verification logic. ## Honest status - This is a BUILT-AND-TESTED replacement, NOT a live swap. The deployed beacon was not touched. Activating V2 (or wiring real verification into the live contract) on Aere mainnet is founder-gated. - [MEASURE, optional] A final confirmation run against the actual Aere mainnet RPC (chain 2800) would close the loop that mainnet's EIP-2537 matches the local EDR semantics. The compatibility matrix lists 2537 as Supported; the logic itself is proven locally on an EIP-2537 EVM. - QUANTUM SCOPE: drand's BLS12-381 is CLASSICAL cryptography and is Shor-breakable. This fix makes the beacon actually verify what it stores (a correctness + honesty fix). It does NOT make the randomness post-quantum and is unrelated to Aere's PQC precompile track (Falcon/ML-DSA/SLH-DSA). ## [VERIFY] flags - None on the drand scheme parameters: the DST, message construction, and G1/G2 placement are all confirmed by the genuine round verifying true both in @noble/curves and in the on-chain pairing. - The embedded `PUBKEY_G2` and `NEG_G2_GENERATOR` constants are off-chain decompressions of, respectively, the canonical 96-byte compressed quicknet key and the standard BLS12-381 G2 generator. They are reproducible by anyone and are transitively validated by the passing pairing (a wrong pubkey/generator would make round 1000000 fail). Not a blocking [VERIFY], but flagged as a precomputed constant for an auditor to re-derive.