347 lines
18 KiB
Markdown
347 lines
18 KiB
Markdown
# 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+):
|
|
|
|
1. `scan.js` / `aere-pqc scan`, a real PQC-readiness scanner (EOA vs contract,
|
|
and which signature primitives a contract uses).
|
|
2. `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.
|
|
3. `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 at `0x100`) and Aere's live PQC precompile band.
|
|
- **GREEN**, PQC-capable. A contract that uses Aere's live native PQC precompiles
|
|
(`0x0AE1..0x0AE5`).
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
1. **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.
|
|
2. **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 `0x0AE1` from the bare number `0x0AE5` used 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 misread `AereHybridAuth` as 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 deployed `AerePQCAccount` and `AereHybridAuth` actually take) delegates to
|
|
a normal contract address, so it reads as RED here even though it is
|
|
PQC-capable. The bundled `AereHybridAuth` fixture 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 address `1`. 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.
|
|
|
|
```bash
|
|
# 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 counterfactual
|
|
`AerePQCAccount` address, pure CREATE2 math, no RPC. Mirrors
|
|
`AerePQCAccountFactory.predictAddress` and is verified in `selftest.js` against
|
|
the live on-chain sample account.
|
|
- `enumerateAssets({ rpcUrl, owner, tokens })`: READ-ONLY. `eth_getBalance` for
|
|
native AERE and `eth_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 stamped `signed:false`,
|
|
shaped `{ to, from, value, data, chainId }` (nonce/gas left for the signer).
|
|
Two paths:
|
|
- `direct` (works today, no extra contract): per-asset `ERC-20 transfer` +
|
|
native send straight to the target account. Not atomic.
|
|
- `migrator` (atomic, all-or-nothing): `approve(migrator, amount)` per token
|
|
then one `AereAccountMigrator.migrateToPqcAccount(...)` that binds the
|
|
destination to the Falcon key on-chain. REQUIRES an explicit `--migrator`
|
|
address, because `AereAccountMigrator` is repo source in the audit-gated
|
|
`notDeployedHeld` set and has NO live mainnet address (see below).
|
|
- `estimateMigrationGas(plan, rpcUrl)`: attaches a read-only `eth_estimateGas`
|
|
result 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 atomic `migrate` / `migrateToPqcAccount` conduit)
|
|
is repo source (`contracts/pqc/AereAccountMigrator.sol`) in the audit-gated
|
|
`notDeployedHeld.externalAuditGated_fundFlow` set. It has NO mainnet address.
|
|
The `migrator` path therefore refuses to build unless you pass an explicit
|
|
`--migrator` address, and it prints a `[VERIFY]` warning telling you to confirm
|
|
that address holds the exact audited bytecode before granting it any approval.
|
|
- `AereHybridAuth` is 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 via `eth_estimateGas`); PQC auth gas is `[CITED]`.
|
|
- `simulate.js` / `aere-pqc simulate` is the fleet projection across many
|
|
accounts.
|
|
|
|
```bash
|
|
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).
|
|
- `predictPqcAccountAddress` re-derives the LIVE sample `AerePQCAccount` address
|
|
`0xa42a5e7F72E46BadC11367650Ec34D676194326f` from 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:
|
|
`AerePQCAttestation` GREEN, `AerePQCTxAccount` GREEN, `AereHybridAuth` RED (the
|
|
Solidity-verifier scope limit), `AereFalcon512Verifier` RED (its `0x0100`
|
|
constants 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 in `lib/abi.js` is a correct Solidity ABI encoder; the
|
|
ground truth was generated with `aerenew/contracts/node_modules/ethers` and
|
|
hardcoded into `selftest.js` so the check stays zero-dependency).
|
|
- `buildMigrationTransactions` returns UNSIGNED objects only: every tx is stamped
|
|
`signed:false`, none carries an `r`/`s`/`v`/`signature` field, and the target
|
|
matches the CREATE2 derivation. The `migrator` path 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 scan` fetched real bytecode and classified the live
|
|
`AerePQCAttestation` (`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 --estimate`
|
|
produced two unsigned txs and attached REAL `eth_estimateGas` results:
|
|
`createAccount` 59,064 gas (low because the salt-0 sample account is ALREADY
|
|
deployed, so the factory's idempotent `createAccount` short-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.
|