| fixtures | ||
| lib | ||
| notarizare | ||
| aere-pqc.js | ||
| LICENSE | ||
| package.json | ||
| plan.js | ||
| proba-vocabular.mjs | ||
| raport.js | ||
| README.md | ||
| scan-gratuit.mjs | ||
| scan.js | ||
| selftest.js | ||
| simulate.js | ||
| VERIFY-US.md | ||
Aere Quantum Migration Toolkit
A starting toolkit to assess and migrate EVM accounts toward post-quantum readiness on Aere Network (chain 2800), or on any EVM chain reachable by RPC.
Aere Network is the post-quantum EVM L1: it ships five live NIST post-quantum
verification precompiles on mainnet (Falcon-512, Falcon-1024, ML-DSA-44,
SLH-DSA-128s, SHAKE256, at 0x0AE1..0x0AE5, activated at block 9,189,161), plus
a live Falcon-512-owned smart account and factory. That makes Aere the natural
home for tooling that helps others measure their quantum exposure and move to a
post-quantum account. This toolkit extends Aere's internal account-migration
work outward into something anyone can run.
It is a real product with one unified CLI and zero runtime dependencies (pure Node built-ins, Node 18+):
scan.js/aere-pqc scan, a real PQC-readiness scanner (EOA vs contract, and which signature primitives a contract uses).lib/migrator.js/aere-pqc build-migration, the transaction-CONSTRUCTION SDK: given a classical ECDSA EOA and a target Falcon-512 public key it derives the counterfactual post-quantum account, enumerates the assets to move (read-only), and emits the exact sequence of UNSIGNED transactions a holder would sign. It never signs, deploys, or moves funds.lib/readiness.js/aere-pqc readiness+simulate.js/aere-pqc simulate, the quantum-readiness cost simulator: one-time migration gas plus ongoing per-authorization cost, from the measured PQC verify gas.
aere-pqc is the single entry point that wraps scan, simulate,
build-migration, and readiness. The lower-level scan.js, plan.js,
simulate.js CLIs remain for direct use.
The scope boundary (this never moves)
Migrating an account's authentication to a post-quantum scheme changes how that account authorizes. It does NOT make Aere consensus post-quantum. Mainnet 2800 still seals blocks with classical secp256k1 QBFT, and the on-chain ZK verifiers (BN254 Groth16) are classical. Nothing in this toolkit claims otherwise, and the scanner and simulator both restate the boundary in their output.
Honest status
This is a starting toolkit, not a finished product. What is real and verified is
listed under "What actually runs" below. What is heuristic, out of scope, or not
yet deployed is flagged inline with [VERIFY] and [MEASURE], in the code and
in the reports the tools print.
1. The readiness scanner: scan.js
Given an EVM address, the scanner reports quantum exposure as a colour:
- RED, classical-only. An EOA (secp256k1 ECDSA authenticated, so Shor-breakable and unable to hold verification code), or a contract with no detectable post-quantum precompile usage.
- YELLOW, hybrid. A contract that uses both a classical signature primitive
(ecrecover at
0x01, or P-256 at0x100) and Aere's live PQC precompile band. - GREEN, PQC-capable. A contract that uses Aere's live native PQC precompiles
(
0x0AE1..0x0AE5).
# live scan over JSON-RPC (this is the only part that needs network access)
node scan.js 0x465d9E3b476BF98Aa1393079e240Db5D2a9bEA6A --rpc https://rpc.aere.network
# offline: scan a bundled real-bytecode fixture
node scan.js --fixture AerePQCAttestation
# offline: scan bytecode you already have
node scan.js 0xYourContract --bytecode 0x60806040...
# machine-readable
node scan.js 0x465d... --rpc https://rpc.aere.network --json
How it detects, and why it is honest
The scanner does not grep the hex. A naive search for 610ae1 (PUSH2 0x0AE1)
false-positives, because those bytes can sit inside another instruction's push
data. Two techniques make the detection real:
- Opcode-aware decoding. The bytecode is walked as an instruction stream, skipping each PUSH's immediate bytes, so a matched constant is a genuine push.
- Local stack simulation to resolve CALL targets. For every
CALL/STATICCALL/DELEGATECALL/CALLCODE the scanner recovers the address
argument (the second stack item) by simulating the stack per basic block. That
distinguishes an actual call to
0x0AE1from the bare number0x0AE5used as a memory length near an unrelated staticcall, which is precisely the false positive a proximity heuristic hits. (An earlier proximity-only version of this tool misreadAereHybridAuthas hybrid for exactly that reason; the stack-simulation version reads it correctly.)
The signature-verify addresses 0x0AE1..0x0AE4 (2785..2788) are not round
numbers, so a genuine opcode-aligned push of one is a medium-confidence signal
even when the call target is staged through memory and cannot be resolved
locally. The common literals 0x01, 0x100, and 0x0AE5 are never used as a
fallback signal; they count only when stack simulation resolves them as an actual
call target.
Scanner limitations (read these)
- Bytecode scanning only sees NATIVE precompile calls. A contract that verifies
Falcon through the SOLIDITY
AereFalcon512Verifier(0x4E8e...D8fFC, the path the deployedAerePQCAccountandAereHybridAuthactually take) delegates to a normal contract address, so it reads as RED here even though it is PQC-capable. The bundledAereHybridAuthfixture demonstrates this on purpose. Confirm an auth path against source when the scanner says RED. ecrecover(0x01) detection is high confidence only because stack simulation resolves the call target to the exact address1. A contract that never calls a signature precompile (authorization is by owner or role) also reads RED, which is correct: it has no quantum-vulnerable signature surface to migrate.- The scanner classifies signature-verification exposure. It does not audit key management, upgradeability, or business logic.
2. The migration SDK: lib/migrator.js (transaction construction)
Given a classical ECDSA EOA and a target NIST Falcon-512 public key, this module
builds the exact sequence of UNSIGNED transactions a holder would sign to move
their assets onto a post-quantum AerePQCAccount. It is the concrete,
signable-artifact layer on top of the older lib/migrate.js plan builder.
# demo (uses the live sample account's real Falcon key; demo EOA 0x..dEaD)
node aere-pqc.js build-migration
# real build: derive the target, enumerate a real EOA's assets read-only, and
# attach a live gas estimate to each unsigned tx
node aere-pqc.js build-migration --eoa 0xYourEoa --falcon 0x09... \
--rpc https://rpc.aere.network --native 1000000000000000 --estimate
# atomic sweep via the (audit-gated, not-yet-deployed) AereAccountMigrator
node aere-pqc.js build-migration --eoa 0xYourEoa --path migrator \
--migrator 0xVerifiedMigrator --tokens 0xTokenA,0xTokenB --amounts 1000,2000
# machine-readable
node aere-pqc.js build-migration --eoa 0xYourEoa --rpc https://rpc.aere.network --json
Key functions (import ... from '@aere/pqc-migration-toolkit/migrator'):
predictTargetAddress({ falconPubKey, salt }): the counterfactualAerePQCAccountaddress, pure CREATE2 math, no RPC. MirrorsAerePQCAccountFactory.predictAddressand is verified inselftest.jsagainst the live on-chain sample account.enumerateAssets({ rpcUrl, owner, tokens }): READ-ONLY.eth_getBalancefor native AERE andeth_call balanceOf(owner)per ERC-20 candidate; returns which assets have a non-zero balance and therefore need moving. Signs nothing.buildMigrationTransactions({ path, eoa, falconPubKey, tokens, nativeWei, ... }): the core. Returns an array of UNSIGNED tx objects, each stampedsigned:false, shaped{ to, from, value, data, chainId }(nonce/gas left for the signer). Two paths:direct(works today, no extra contract): per-assetERC-20 transfer+ native send straight to the target account. Not atomic.migrator(atomic, all-or-nothing):approve(migrator, amount)per token then oneAereAccountMigrator.migrateToPqcAccount(...)that binds the destination to the Falcon key on-chain. REQUIRES an explicit--migratoraddress, becauseAereAccountMigratoris repo source in the audit-gatednotDeployedHeldset and has NO live mainnet address (see below).
estimateMigrationGas(plan, rpcUrl): attaches a read-onlyeth_estimateGasresult to each unsigned tx (or an error string when a step cannot be simulated pre-deploy). Broadcasts nothing.encodeMigrateCalldata,encodeMigrateToPqcAccountCalldata,encodeApproveCalldata,encodeTransferCalldata,encodeCreateAccountCalldata: the individual calldata builders, each verified byte-for-byte against ethers v6 (see "What actually runs").
The calldata encoder (lib/abi.js) is a zero-dependency Solidity ABI encoder for
exactly the types the migrator needs (uint256, address, bool, bytes, address[], uint256[]), validated against ethers 6.16.0.
The migration paths
PQC account (Falcon-512 owned). Derive the counterfactual account
(step 1, off-chain), deploy it through the live factory with
createAccount(falconPubKey, salt) (step 2, permissionless and idempotent), then
move assets and roles off the old EOA into it (step 3). After the move, spending
authority is the Falcon-512 key only. There is no ECDSA fallback, so register the
key in AerePQCKeyRegistry (proof-of-possession) and set PQC social-recovery
guardians before moving material value.
Hybrid account (ECDSA + Falcon-512). Register a hybrid identity in
AereHybridAuth binding your existing classical address to a Falcon-512 key,
then authorize with both legs during the transition. An attacker must break both
secp256k1 AND Falcon-512.
EIP-7702 delegation. Aere supports EIP-7702, so an EOA can temporarily delegate execution to account code to batch a migration move in a single EOA transaction.
Founder-gated caveat, and what works today
The AerePQCAccountFactory (0xd5315Ea7...CE58) is live, and createAccount is
permissionless, so deploying a PQC account and moving assets into it with the
direct path is possible TODAY with no special authority: the whole direct
sequence is plain factory + ERC-20 + native transactions the holder signs.
What is NOT deployed:
AereAccountMigrator(the atomicmigrate/migrateToPqcAccountconduit) is repo source (contracts/pqc/AereAccountMigrator.sol) in the audit-gatednotDeployedHeld.externalAuditGated_fundFlowset. It has NO mainnet address. Themigratorpath therefore refuses to build unless you pass an explicit--migratoraddress, and it prints a[VERIFY]warning telling you to confirm that address holds the exact audited bytecode before granting it any approval.AereHybridAuthis repo source with no mainnet address, so the hybrid path's deploy step is a founder/deployer step, flagged[VERIFY].
Deploying NEW contracts or a corrected V2 redeploy is signed by the Foundation/deployer key, never by this toolkit.
3. The cost simulator: aere-pqc readiness and simulate.js
Two views on cost, both over the MEASURED PQC verify gas from
aerenew/docs/AERE-BENCHMARK-REPORT.md:
aere-pqc readiness <address> --rpc <url>is the per-account report: it scans the account, enumerates its movable assets read-only, and prints the one-time migration gas (account deploy + asset moves) plus the ongoing per-authorization cost. Asset-move gas is a clearly-labeled[VERIFY]planning estimate anchored to the one asset-move figure measured live this session (a native AERE send at 21,246 gas viaeth_estimateGas); PQC auth gas is[CITED].simulate.js/aere-pqc simulateis the fleet projection across many accounts.
aere-pqc readiness 0xYourEoa --rpc https://rpc.aere.network --tokens 0xTokenA
node simulate.js --count 100 --scheme hybrid --auths 5
node simulate.js --accounts accounts.json --gwei 1
node simulate.js --schemes # list modeled schemes
Modeled per-authorization gas (native precompile verify-and-record receipt,
[CITED: AERE-BENCHMARK-REPORT.md Part C.1]):
| Scheme | Marginal verify gas | Verify-and-record tx gas |
|---|---|---|
| Falcon-512 | 40,000 | 86,336 |
| Hybrid (ECDSA + Falcon-512) | 43,000 | 89,336 |
| Falcon-1024 | 75,000 | 145,496 |
| ML-DSA-44 | 55,000 | 351,050 |
| SLH-DSA-128s | 350,000 | 558,276 |
The falcon512_solidity scheme instead models the CURRENTLY-DEPLOYED
AerePQCAccount, which verifies Falcon in the EVM via the Solidity verifier: one
authorization measured 10,278,313 gas on-chain [CITED: pqc-account.json], under
the EIP-7825 2^24 per-tx cap. These two Falcon paths (native precompile ~86k vs
deployed-account Solidity ~10.5M) must not be conflated; the simulator keeps them
distinct. Account CREATE2 deploy gas is 1,493,084 [CITED: pqc-account.json].
What actually runs (verified, not asserted)
node selftest.js runs with no network and checks all of this (21 assertions,
all PASS):
keccak256("")equals the known Ethereum/Keccak vector (the zero-dependency keccak is correct).predictPqcAccountAddressre-derives the LIVE sampleAerePQCAccountaddress0xa42a5e7F72E46BadC11367650Ec34D676194326ffrom its real 897-byte Falcon-512 key (proves the CREATE2 derivation is byte-correct against a real deployment).- The scanner classifies the four bundled REAL bytecode fixtures correctly:
AerePQCAttestationGREEN,AerePQCTxAccountGREEN,AereHybridAuthRED (the Solidity-verifier scope limit),AereFalcon512VerifierRED (its0x0100constants are literals, not P-256). A synthetic hybrid reads YELLOW; empty code reads RED (EOA). - The migration-SDK calldata builders (
migrate,approve,transfer,balanceOf) are byte-identical to values independently generated by ethers 6.16.0 (the encoder inlib/abi.jsis a correct Solidity ABI encoder; the ground truth was generated withaerenew/contracts/node_modules/ethersand hardcoded intoselftest.jsso the check stays zero-dependency). buildMigrationTransactionsreturns UNSIGNED objects only: every tx is stampedsigned:false, none carries anr/s/v/signaturefield, and the target matches the CREATE2 derivation. Themigratorpath refuses to build without an explicit migrator address (because it is not deployed).- The readiness report composes the CITED deployed-account auth figure (10,278,313 gas) and counts movable assets correctly.
Additionally verified live against https://rpc.aere.network (chain 2800,
~block 10,416,783, 2026-07-19):
aere-pqc scanfetched real bytecode and classified the liveAerePQCAttestation(0x465d...) as GREEN (4402 bytes) and the live deployer EOA (0xbeB3...) as RED (0 bytes of code).aere-pqc build-migration --eoa 0xbeB3... --native 1000000000000000 --estimateproduced two unsigned txs and attached REALeth_estimateGasresults:createAccount59,064 gas (low because the salt-0 sample account is ALREADY deployed, so the factory's idempotentcreateAccountshort-circuits; a FIRST deploy is the cited 1,493,084), and the native send 21,246 gas.- Honest hazard surfaced by the live estimate: auto-draining the FULL native
balance of an ACTIVE account fails, because the sender must reserve gas AND the
balance can move between the read and the estimate. The tool now reserves a gas
buffer by default and supports
--native <wei>for a fixed, race-free amount. aere-pqc readiness 0xbeB3... --rpc ...reported a one-time migration cost of 1,514,330 gas (1,493,084 deploy + 21,246 native) = 0.001514330 AERE at 1 Gwei, and an ongoing 10,278,313 gas/auth (deployed reality) vs an 86,336 gas/auth native-precompile projection.
Nothing above broadcasts a transaction. Every network call is a read
(eth_getCode, eth_getBalance, eth_call, eth_estimateGas, eth_gasPrice).
Addresses used (chain 2800)
| Name | Address | Status |
|---|---|---|
| PQC precompile band | 0x0AE1..0x0AE5 |
live (block 9,189,161) |
| P-256 (RIP-7212/7951) | 0x100 |
live (Osaka) |
| ecrecover | 0x01 |
protocol constant |
| AerePQCAccountFactory | 0xd5315Ea7caa60d320c4f34b1bEd70dd9cc02CE58 |
live |
| AerePQCAccount (sample) | 0xa42a5e7F72E46BadC11367650Ec34D676194326f |
live |
| AereFalcon512Verifier (Solidity) | 0x4E8e9682329e646784fB3bd01430aA4bA54D8fFC |
live |
| AerePQCKeyRegistry | 0x1eCa3c5ADcBD0b22636D8672b00faC6D89363691 |
live |
| AereEntryPointV2 | 0x8D6f40598d552fF0Cb358b6012cF4227B86aF770 |
live |
| AereHybridAuth | (repo source) | not deployed [VERIFY] |
| AereAccountMigrator | (repo source) | not deployed, audit-gated [VERIFY] |
Testnet-only PQC precompiles 0x0AE6 (ML-KEM-768) and 0x0AE7 (Falcon HashToPoint) are NOT on mainnet 2800; a mainnet
call to them returns empty. The scanner names them and flags [VERIFY] if seen.
Files
aere-pqc.js unified CLI: scan | simulate | build-migration | readiness
scan.js readiness scanner CLI (also aere-pqc scan)
plan.js migration-plan CLI (older step-by-step plan builder)
simulate.js migration cost simulator CLI (also aere-pqc simulate)
selftest.js no-network proof: keccak + CREATE2 + fixtures + calldata + txs
lib/keccak.js zero-dependency keccak256 + hex/checksum helpers
lib/abi.js zero-dependency Solidity ABI encoder (validated vs ethers v6)
lib/bytecode.js opcode-aware decoder + stack-simulation CALL-target resolver
lib/scanner.js account classifier (EOA vs contract, RED/YELLOW/GREEN)
lib/migrate.js plan builder + CREATE2 derivation (the original SDK)
lib/migrator.js transaction-construction SDK (unsigned txs, asset enumeration)
lib/readiness.js per-account quantum-readiness + migration-cost report
lib/simulate.js fleet cost model over the measured PQC verify gas
lib/precompiles.js address bands + measured gas, single source of truth
lib/rpc.js minimal read-only JSON-RPC client (global fetch)
fixtures/ real deployed bytecode extracted from this repo's artifacts
The one hard invariant
This toolkit CONSTRUCTS and SIMULATES. It never signs, deploys, or moves funds.
There is no private key anywhere in it. build-migration hands you inert,
unsigned transaction objects; you review and sign them in your own wallet. Every
network call is read-only. Executing a migration is user-gated (and, for the
migrator/hybrid contracts, audit-and-founder-gated).
License
MIT.