The published line and the local line of this repository had no common ancestor: the public one carried the hygiene pass (no host names, no internal paths), the local one carried a month of corrections that never shipped. This commit ports the local work onto the public line, keeping the public hygiene wording wherever the two touched the same sentence, and keeping the public version of AERE-CROSS-CLIENT-DETERMINISM.md entirely. Carried: LICENSE/LICENSING corrections, VERIFY-POLICY.md, CITATIONS-UNRESOLVED.md remeasured 2026-08-11, the 'audited' adjective removed from next to Bouncy Castle, citation paths rewritten to published form, AIP-8, the QA consolidation report, the second EIP validation pass, fork-height corrections, the AereSink / threshold-factory correction, the forge test floor, and the architecture-map updates.
521 lines
30 KiB
Markdown
521 lines
30 KiB
Markdown
# Aere Quantum Migration: Accounts and Network Keys
|
|
|
|
Status: user account migration is REAL and TESTED (contract + Hardhat suite,
|
|
below). Network key rotation is DESIGN ONLY and founder + audit gated. This
|
|
document is the last quantum-resistance layer at the key-management level (quantum
|
|
layer 11), and it is honest about which half is a user-callable contract today and
|
|
which half is a gated network operation.
|
|
|
|
Scope boundary, stated once and never crossed in this document: moving an account
|
|
(or a network operator) from a classical ECDSA key to a post-quantum key changes
|
|
who can authorize. It does NOT make Aere consensus post-quantum. Mainnet chain 2800
|
|
still seals blocks with classical secp256k1 QBFT, and the on-chain ZK verifiers
|
|
(BN254 Groth16) are classical. Validator-key rotation to a post-quantum scheme is
|
|
therefore coupled to the separate, gated consensus-PQC activation, not implied by
|
|
anything here.
|
|
|
|
Two flag conventions are used throughout:
|
|
|
|
- `[VERIFY]` a claim that should be confirmed against live chain state or a
|
|
deployment record before it is relied on.
|
|
- `[MEASURE]` a number or outcome that depends on a measurement or an operational
|
|
run that has not been performed here.
|
|
|
|
---
|
|
|
|
## Part A. User account migration (real, tested)
|
|
|
|
### A.1 Why migrate at all: harvest-now-decrypt-later
|
|
|
|
An Aere externally-owned account (EOA) is authorized by a secp256k1 ECDSA key. Two
|
|
facts make that a long-term liability once a cryptographically relevant quantum
|
|
computer exists:
|
|
|
|
1. ECDSA over secp256k1 is broken by Shor's algorithm. A sufficiently large
|
|
quantum computer recovers the private key from the public key.
|
|
2. An EOA reveals its public key the first time it sends a transaction (the public
|
|
key is recovered from the signature). So the exposure is not hypothetical for
|
|
any account that has ever transacted; the key material an attacker needs is
|
|
already on chain.
|
|
|
|
The threat is not "funds move today." It is "an adversary records the chain now and
|
|
breaks the keys later." That is the harvest-now-decrypt-later threat applied to
|
|
account authorization: long-dormant, high-value holdings sitting under a classical
|
|
key are exactly the accounts most worth attacking on a horizon of years. The
|
|
mitigation is to move such holdings, while the classical key is still safe, onto an
|
|
account whose spending authority is a post-quantum key.
|
|
|
|
Aere already ships the destinations for that move as live primitives. What was
|
|
missing, and what this document delivers, is a clean, no-custody path to perform
|
|
the move. That gap is quantum layer 11 in `aerenew/build-tracker/control.json`.
|
|
|
|
### A.2 The destinations: Aere's live post-quantum account primitives
|
|
|
|
The migration does not invent a new account type. It targets the ones Aere already
|
|
runs:
|
|
|
|
| Primitive | What it is | Auth | Status |
|
|
|---|---|---|---|
|
|
| `AerePQCAccount` | ERC-4337 v0.7 smart account owned solely by a NIST Falcon-512 key (897-byte pubkey) | Falcon-512 only, no ECDSA fallback | live sample `0xa42a5e7F...4326f` `[VERIFY]` |
|
|
| `AerePQCAccountFactory` | CREATE2 factory; account address is a pure function of (falconPubKey, salt) | n/a (permissionless deploy) | live `0xd5315Ea7...CE58` `[VERIFY]` |
|
|
| `AereHybridAuth` | Defense-in-depth: authorizes only if BOTH an ECDSA leg and a Falcon-512 leg verify over the same 32-byte hash | ECDSA AND Falcon-512 | repo source, no mainnet address `[VERIFY]` |
|
|
| `AereFalcon512Verifier` | Live Solidity Falcon-512 verifier the accounts delegate to | n/a | live `0x4E8e9682...D8fFC` `[VERIFY]` |
|
|
| `AerePQCKeyRegistry` | Proof-of-possession key registry | n/a | live `0x1eCa3c5A...3691` `[VERIFY]` |
|
|
|
|
Two destination shapes matter for a holder:
|
|
|
|
- PQC account (Falcon-512 only). Spending authority becomes a single lattice key.
|
|
Strongest post-quantum posture, but there is no ECDSA fallback: lose the Falcon
|
|
key and the account is gone. Register the key in `AerePQCKeyRegistry` and set PQC
|
|
social-recovery guardians BEFORE moving material value.
|
|
- Hybrid account (ECDSA + Falcon-512, via `AereHybridAuth`). Transition-era: an
|
|
attacker must break BOTH secp256k1 and Falcon-512. Good for a staged move while a
|
|
holder still wants a classical leg. `AereHybridAuth` is repo source with no
|
|
mainnet address as of 2026-07-19, so standing it up is a founder/deployer step
|
|
`[VERIFY]`.
|
|
|
|
The counterfactual address of a Falcon key's PQC account is pure CREATE2 math and
|
|
can be derived off-chain (see the migration toolkit, `aerenew/pqc-migration-toolkit`,
|
|
`predictPqcAccountAddress`, verified there against the live sample account). The
|
|
account can receive assets at that address before it is deployed, and be deployed
|
|
later via `createAccount`, which is permissionless and idempotent.
|
|
|
|
### A.3 The contract: `AereAccountMigrator`
|
|
|
|
File: `aere-contracts/contracts/pqc/AereAccountMigrator.sol` (solc 0.8.23,
|
|
OpenZeppelin SafeERC20 + ReentrancyGuard).
|
|
|
|
It is a pure conduit that atomically moves a caller-specified set of ERC-20
|
|
balances, and optionally native AERE, from the caller (the old EOA) to a
|
|
destination post-quantum account, in one transaction authorized by the caller's
|
|
ECDSA key. Two entrypoints:
|
|
|
|
- `migrate(destination, tokens[], amounts[], moveNative)`: move to an explicit
|
|
destination address.
|
|
- `migrateToPqcAccount(factory, falconPubKey, salt, expectedDestination, tokens[], amounts[], moveNative)`:
|
|
DERIVE the destination from a Falcon-512 key via the live
|
|
`AerePQCAccountFactory.predictAddress`, so the holder gets cryptographic
|
|
assurance they are sweeping into the post-quantum account their Falcon key
|
|
controls, and nowhere else. `expectedDestination` is an optional guard: if
|
|
non-zero it must equal the derived address, else the call reverts.
|
|
|
|
A read-only `predictPqcAccount(factory, falconPubKey, salt)` lets a frontend
|
|
confirm the destination before signing.
|
|
|
|
#### Authorization: pre-approval, or EIP-7702
|
|
|
|
The move is authorized by the caller's classical ECDSA key. The migrator does not
|
|
invent an authorization scheme; it uses ordinary ERC-20 allowances. Two supply
|
|
patterns, both documented:
|
|
|
|
1. Pre-approval (default, works today, no protocol feature). The holder sends one
|
|
`approve(migrator, amount)` per ERC-20, then calls `migrate`. Each
|
|
`safeTransferFrom` consumes the allowance and moves the tokens straight to the
|
|
destination.
|
|
2. EIP-7702 delegation (single-transaction UX). Aere supports EIP-7702 (see
|
|
`aerenew/docs/AERE-EIP-COMPATIBILITY-MATRIX.md`, row 7702 "Supported", the
|
|
EOA-delegation primitive Aere carries in place of EIP-3074). The EOA can, in one
|
|
transaction, temporarily set its account code to a batching delegate that both
|
|
approves and calls `migrate`, removing the separate approve transactions. The
|
|
migrator is agnostic to which pattern is used; under EIP-7702 the caller is
|
|
still the EOA and every invariant below is unchanged.
|
|
|
|
#### No custody, no admin (the core safety property)
|
|
|
|
- In `migrate` the caller is the `from` of every `SafeERC20.safeTransferFrom`, and
|
|
the caller-specified `destination` is the `to`. Tokens move caller to destination
|
|
in one hop. The migrator is never the recipient, so it never holds an ERC-20
|
|
balance.
|
|
- Native AERE (`msg.value`) is forwarded, in full, to that same `destination`,
|
|
inside the same call, guarded by `nonReentrant`.
|
|
- There is no owner, no admin, no upgrade, no initializer. There is no withdraw,
|
|
rescue, or sweep function, so anything accidentally sent to the migrator can
|
|
never be pulled out by anyone (stuck-but-safe, not drainable).
|
|
- There is no code path that sends value to any address other than the
|
|
caller-specified destination.
|
|
|
|
#### Fail-closed and atomic
|
|
|
|
- `destination` must be non-zero (`ZeroDestination`).
|
|
- Token and amount arrays must be equal length (`LengthMismatch`); each token
|
|
non-zero (`ZeroToken`); each amount non-zero (`ZeroAmount`).
|
|
- Native is strict: `moveNative` true requires non-zero `msg.value` (`ZeroNative`);
|
|
any `msg.value` sent while `moveNative` is false reverts (`StrayNative`), so value
|
|
can never be stranded. An empty request (no tokens, no native) reverts
|
|
(`NothingToMigrate`).
|
|
- A single failing transfer reverts the WHOLE transaction (SafeERC20 reverts on a
|
|
false/failing ERC-20), so the migration is all-or-nothing. It can never leave a
|
|
holder with a partially drained old account.
|
|
|
|
#### The `Migrated` event
|
|
|
|
`Migrated(address indexed oldAccount, address indexed newAccount, address[] tokens, uint256[] amounts, uint256 nativeAmount)`
|
|
binds the old classical account, the new post-quantum account, and the exact asset
|
|
set that moved. This is the on-chain, auditable record of a migration.
|
|
|
|
### A.4 The user flow
|
|
|
|
1. Choose a destination. Generate (or reuse) a Falcon-512 key; derive its
|
|
counterfactual `AerePQCAccount` address off-chain (toolkit
|
|
`predictPqcAccountAddress`, or the on-chain view `predictPqcAccount`). For a
|
|
hybrid transition, register an `AereHybridAuth` identity instead.
|
|
2. Prepare recovery FIRST. If the destination is Falcon-only, register the key in
|
|
`AerePQCKeyRegistry` (proof-of-possession) and set PQC social-recovery
|
|
guardians before moving material value. A Falcon-only account has no ECDSA
|
|
fallback.
|
|
3. Approve. From the old EOA, `approve(migrator, amount)` per ERC-20 (or bundle the
|
|
approvals with the migrate via EIP-7702).
|
|
4. Migrate. Call `migrateToPqcAccount(...)` (recommended, destination is bound to
|
|
your Falcon key) or `migrate(destination, ...)`. One transaction moves the
|
|
whole asset set plus optional native AERE to the destination. The `Migrated`
|
|
event is your receipt.
|
|
5. Deploy the account when convenient. `AerePQCAccountFactory.createAccount` is
|
|
permissionless and idempotent; the account can be deployed before or after the
|
|
assets arrive at its counterfactual address.
|
|
|
|
The migration toolkit (`aerenew/pqc-migration-toolkit`) is the companion that
|
|
scans an account's quantum exposure (RED/YELLOW/GREEN), derives the destination
|
|
address, and prints a step-by-step plan and a gas estimate. This contract is the
|
|
on-chain executor for step 4.
|
|
|
|
### A.5 Test result (real)
|
|
|
|
Suite: `aere-contracts/test/account-migrator.test.js`. Run from
|
|
`aerenew/contracts` with `npx hardhat test test/account-migrator.test.js`.
|
|
|
|
Result on 2026-07-19: 9 passing.
|
|
|
|
The destination in the strong-path tests is a REAL `AerePQCAccount` deployed
|
|
through the REAL `AerePQCAccountFactory` (the Falcon verifier is mocked because a
|
|
genuine Falcon-512 signature cannot be produced inside the hardhat process; the
|
|
CREATE2 derivation and every asset movement are fully real). Covered:
|
|
|
|
- a user migrates ERC-20 balances from an EOA to a destination in one tx (both
|
|
`migrate` and the Falcon-derived `migrateToPqcAccount`);
|
|
- native AERE forwards to the destination, with nothing stuck in the migrator;
|
|
- a zero destination reverts (`ZeroDestination`);
|
|
- a failed token transfer reverts the whole migration, and the first (would-be
|
|
successful) token is NOT moved (atomicity);
|
|
- the migrator sends only to the specified destination; an unrelated third party
|
|
receives nothing;
|
|
- no admin function exists (no owner/withdraw/rescue/sweep/transferOwnership/
|
|
initialize in the ABI), and tokens force-sent to the migrator cannot be pulled
|
|
out by anyone;
|
|
- fail-closed native handling (`StrayNative`, `ZeroNative`, `NothingToMigrate`) and
|
|
malformed asset sets (`LengthMismatch`, `ZeroToken`, `ZeroAmount`).
|
|
|
|
---
|
|
|
|
## Part B. Network key rotation (design only, founder + audit gated)
|
|
|
|
This half is DESIGN. Nothing here is a today-action, and nothing here is executed
|
|
by any contract in this repository. It is documented so the end-to-end
|
|
quantum-resistance picture is complete and honest about what remains.
|
|
|
|
There are two network-level classical keys, and they are NOT the same kind of
|
|
problem.
|
|
|
|
### B.1 Foundation operator key rotation (operational key management) `[MEASURE]`
|
|
|
|
What it is. The Foundation operator key is the ECDSA key that holds privileged
|
|
roles on operational contracts (for example NavOracle attestation, compliance
|
|
registries, the deployer/owner roles that have not yet been handed to the
|
|
Timelock). It is a normal account key, not a consensus key.
|
|
|
|
Why rotate it. Same harvest-now-decrypt-later logic as a user account: a long-lived
|
|
operator key under ECDSA is a standing liability on a multi-year horizon.
|
|
|
|
How, honestly. This is an operational key-management step, not a protocol change:
|
|
|
|
1. Stand up the post-quantum destination for the operator authority. The natural
|
|
choice is an `AerePQCAccount` (Falcon-512) or, better for an operator, a
|
|
post-quantum threshold account (the `AereThresholdAccount` family, PQC t-of-n
|
|
4337) so operator authority is not a single key. `[VERIFY]` which threshold
|
|
account is the intended operator destination.
|
|
2. Transfer each privileged role from the old ECDSA operator to the new
|
|
post-quantum account. On contracts that expose role transfer (owner /
|
|
role-grant), this is a sequence of ordinary role-transfer transactions. On
|
|
immutable core-economic contracts (Sink, sAERE, burn) there is no admin to
|
|
rotate, by design, so they need nothing.
|
|
3. Where governance is the intended long-term authority, the path is role to
|
|
Timelock, and the Post-Quantum Governance design
|
|
(`aerenew/docs/AERE-POST-QUANTUM-GOVERNANCE.md`) covers granting a PQC threshold
|
|
account the proposer/executor role on the Timelock.
|
|
|
|
Gating. This is founder-gated because it moves real privileged authority over live
|
|
contracts, and it is signed by the Foundation key, not by any contract here. It is
|
|
NOT coupled to consensus. The cost/outcome of a full rotation across the live role
|
|
set is `[MEASURE]` (it depends on the exact contracts and roles enumerated at
|
|
rotation time). No role is rotated by publishing this document.
|
|
|
|
### B.2 Validator signing key rotation (coupled to consensus-PQC) `[MEASURE]`
|
|
|
|
What it is. Each validator seals QBFT blocks with a classical secp256k1 key. This
|
|
is the consensus signing key, not an application account key.
|
|
|
|
The honest coupling. Today validator seals are classical ECDSA QBFT. Rotating a
|
|
validator's signing key to a post-quantum scheme is NOT an isolated key swap: the
|
|
consensus protocol itself has to accept and verify post-quantum seals. In other
|
|
words, a validator cannot "rotate to a Falcon seal" while the rest of the network
|
|
still expects an ECDSA seal. Validator-key rotation to PQ is therefore coupled to
|
|
the consensus-PQC activation, which is tracked as its own gated item:
|
|
|
|
- The in-place PQC activation work (hybrid ECDSA + Falcon BLOCKING seal, activated
|
|
via an anchor contract on the live chain, proven at small N in isolation) is the
|
|
mechanism that would let the seal set become post-quantum without a re-genesis.
|
|
- That flip is HIGH risk (it can halt the chain), and its gates are: N greater than
|
|
or equal to 9 validators, a soak period, an external consensus audit, and the
|
|
founder's explicit GO. See the mainnet queue in
|
|
`aerenew/build-tracker/control.json` ("Consens post-quantum LIVE", risk MARE).
|
|
|
|
So the sequence is: consensus-PQC activation FIRST (gated), and validator signing
|
|
keys rotate to their post-quantum form AS PART OF that activation, not before it
|
|
and not independently. Doing validator-key rotation "early" has no meaning while
|
|
consensus verifies classical seals.
|
|
|
|
Gating. Founder-gated AND external-audit-gated AND dependent on the consensus-PQC
|
|
item. Not a today-action. Nothing in this repository activates it. The outcome
|
|
(fork-freedom, halt/recover behavior at the target N) is `[MEASURE]` against a real
|
|
soak at N greater than or equal to 9.
|
|
|
|
### B.3 Summary of the network half
|
|
|
|
| Network key | Kind of change | Coupled to consensus? | Gate | Today-action? |
|
|
|---|---|---|---|---|
|
|
| Foundation operator key | Operational role transfer to a PQC account | No | Founder | No (founder-signed op) |
|
|
| Validator signing keys | Consensus seal scheme change | Yes | Founder + external audit + N>=9 + soak | No |
|
|
|
|
The single most important honesty point: none of this makes consensus post-quantum
|
|
by itself. The user account migration in Part A is a real, deployable-today,
|
|
no-custody contract that hardens ACCOUNT authorization. The network half in Part B
|
|
is the operator-key rotation (an operational step) plus the validator-key rotation
|
|
(inseparable from the gated consensus-PQC flip). This is the last quantum-resistance
|
|
layer, and it is largely gated, not a switch to flip tonight.
|
|
|
|
---
|
|
|
|
## Part C. Authority residue: why moving the assets is not finishing the job
|
|
|
|
Added 2026-07-19. Part A moves an account's ASSETS. This part addresses what Part A
|
|
leaves behind, which is the more dangerous half.
|
|
|
|
### C.1 The gap
|
|
|
|
After a perfect Part A migration, the old classical EOA is empty. It is not
|
|
harmless. It still carries:
|
|
|
|
1. Every ERC-20 allowance it ever granted. `approve` state lives on the token
|
|
contract keyed by owner, and sweeping a balance does not touch it. Any token
|
|
that later arrives at the old EOA is immediately drainable by every spender
|
|
still holding a non-zero allowance.
|
|
2. Any role, ownership, or membership granted to it on other contracts. Sweeping
|
|
an ERC-20 balance does not transfer an `onlyOwner` role.
|
|
3. Its status as a valid payment destination. Airdrops, refunds, vesting unlocks,
|
|
exchange withdrawals, and counterparties working from an address book keep
|
|
sending to it. The account keeps re-accruing value under a Shor-breakable key.
|
|
4. Its status as a valid signer for any EIP-712 or EIP-1271 flow that checks only
|
|
ECDSA.
|
|
|
|
So an account that has "migrated" can be empty today and hold a meaningful balance
|
|
again in six months, under exactly the key the migration was meant to retire. The
|
|
adversary in the model does not need to act now. Harvest-now-decrypt-later means
|
|
they break the key years later and then collect whatever has pooled there since.
|
|
|
|
### C.2 Which accounts are actually exposed, precisely
|
|
|
|
The exposure that matters is exposure of the PUBLIC KEY, not of the address. An
|
|
Aere address is `keccak(pubkey)[12:]`, a hash, and a hash is not Shor-breakable.
|
|
The public key itself is what Shor's algorithm needs.
|
|
|
|
| Account state | Public key on chain? | Exposed to a future quantum adversary? |
|
|
|---|---|---|
|
|
| Never sent a transaction (receive-only) | No. Only the address hash is public | NOT exposed today. The preimage has never been published |
|
|
| Has sent at least one transaction | Yes. The pubkey is recoverable from the signature | EXPOSED. The material an adversary needs is already public and already archived |
|
|
| Has signed any off-chain message that was published (EIP-191, EIP-712, a permit) | Yes, same recovery applies | EXPOSED, even with zero on-chain transactions |
|
|
| Deployed contract / smart account | No secp256k1 key of its own | Not exposed as a key. Exposure is whatever its OWNER key is |
|
|
|
|
The practical reading: any address that has ever spent anything is already exposed,
|
|
and that is the overwhelming majority of active accounts. Nothing can un-expose
|
|
them. The only remaining lever is to stop keeping value there, and to stop the old
|
|
key from carrying authority. That is what Part A and this part do respectively.
|
|
|
|
A receive-only account is genuinely in a better position, and it has a strictly
|
|
better migration: move the funds in a single transaction to a post-quantum account.
|
|
That first spend does expose the key, but it exposes it at the moment the value is
|
|
already leaving, which is the one moment exposure costs nothing.
|
|
|
|
This document does NOT claim a count or percentage of exposed accounts on chain
|
|
2800. That would require indexing the full chain history and it was not measured.
|
|
|
|
### C.3 What is a contract, and what deliberately is not
|
|
|
|
Two things are needed. Only one of them should be a contract, and saying so plainly
|
|
is the point.
|
|
|
|
Allowance revocation is NOT a new contract. `approve` can only be called by the
|
|
token owner, so no third-party contract can revoke on an EOA's behalf. The
|
|
revocation has to be executed BY the account. Aere Network already ships the tool:
|
|
`AereDelegate7702V2.executeBatch` in `aerenew/contracts/contracts/delegation`
|
|
batches the `approve(spender, 0)` calls into one EIP-7702 transaction from the EOA
|
|
itself. Writing a second contract for this would add a security surface and buy
|
|
nothing, so none was written. Enumerating the live spender set per token is an
|
|
indexer job and belongs in the migration toolkit, not in Solidity.
|
|
|
|
Retirement declaration IS a contract, because it needs to be permanent, public, and
|
|
machine-readable, and no existing primitive provides that.
|
|
|
|
#### C.3.1 Evidence that no account-level retirement registry already existed
|
|
|
|
"Nothing existing provides it" is the kind of claim that has to be auditable rather
|
|
than asserted, so here is the full search and the full result. Run from
|
|
`aerenew/contracts/contracts`:
|
|
|
|
```
|
|
grep -rnE "[Rr]etire|RETIRE" --include=*.sol .
|
|
```
|
|
|
|
Result on 2026-07-20: **68 matching lines across 9 files**. 46 of those lines are in
|
|
the new `pqc/AereAccountRetirement.sol` itself, leaving **22 pre-existing lines
|
|
across 8 files**. An earlier build note cited only four hits, which was a filtered
|
|
subset presented as the whole result. That was wrong and this section replaces it.
|
|
The conclusion is unchanged, but it is now checkable. Every pre-existing hit:
|
|
|
|
| File | Lines | What it actually is | Account-level retirement registry? |
|
|
|---|---|---|---|
|
|
| `AereCoinbaseSplitterV2.sol` | 1 | Prose comment saying the V1 contract was retired | No. English usage in a comment, no code |
|
|
| `aere-contracts/contracts/faucet/AereFaucetV2.sol` | 1 | Comment: the faucet can be "retired" by setting `dripAmount` to 0 | No. Describes a parameter, not a registry |
|
|
| `aere-contracts/contracts/modular/AerePQCEncryptedIntent.sol` | 5 | `retireEncapKey()`, `EncapKeyRetired`, an `active` flag | No. Retires one advertised ML-KEM encapsulation key so no NEW intent targets it. Scoped to a key, not to the account, and the account stays fully active |
|
|
| `aere-contracts/contracts/modular/AerePQCKeyExchange.sol` | 7 | `retireKemKey()`, `KemKeyRetired`, `active` flag, "register / rotate / retire" | No. Same shape: retires one advertised ML-KEM key for handshake targeting. The account keeps transacting |
|
|
| `aere-contracts/contracts/mpc/AereThresholdRegistry.sol` | 3 | `Status.RETIRED` enum member, `CommitteeRetired` event, one comment | No, and weaker than it looks. The subject is a threshold COMMITTEE, not an EOA. Also, `Status.RETIRED` is never assigned and `CommitteeRetired` is never emitted anywhere in the file: the only status writes are `ACTIVE` (line 174, 358) and `PAUSED` (line 350). The state is declared and unreachable |
|
|
| `aere-contracts/contracts/pqc/AereHybridAuthorizer.sol` | 1 | Comment: the Foundation "retires a scheme" on the registry | No. Subject is a signature SCHEME, protocol-wide |
|
|
| `aere-contracts/contracts/pqc/AerePQCOrderAuthorizer.sol` | 1 | Same scheme-retirement comment | No. Same, subject is a scheme |
|
|
| `aere-contracts/contracts/pqc/AerePQCKeyRegistry.sol` | 3 | Rotation "retires the old one", plus append-only key lists that include retired keys | No. Subject is a PQC KEY belonging to an identity. Retiring a key is the opposite direction of travel: it presumes the identity continues, under a new key. There is no way to mark the classical account itself as retired, and no successor pointer |
|
|
|
|
The pattern across all 22 is the same and it is the reason the gap was real. Every
|
|
pre-existing use retires a KEY, a SCHEME or a COMMITTEE, always as a rotation step
|
|
that presumes the subject carries on under new material. None of them takes an EOA
|
|
as the subject, none of them is a one-way latch, and none of them publishes a
|
|
successor account. `AerePQCKeyRegistry` is the closest and still does not fit: it
|
|
would let an identity mark a Falcon key stale, never the secp256k1 account.
|
|
|
|
Search limits, stated: this is a text search over `*.sol` under
|
|
`aerenew/contracts/contracts` only. It would not catch a registry that implements
|
|
the same semantics without ever using the word "retire" (for example one built
|
|
around "deprecate", "sunset" or "revoke" on an account subject), and it does not
|
|
cover non-Solidity sources or deployed-but-unsourced contracts. Those are the
|
|
conditions under which the conclusion could still be wrong.
|
|
|
|
### C.4 `AereAccountRetirement`, the one-way latch
|
|
|
|
File: `aerenew/contracts/contracts/pqc/AereAccountRetirement.sol` (solc 0.8.23, no
|
|
dependencies, no admin, no payable function, holds nothing).
|
|
|
|
An account calls `retire(successor)` and irreversibly declares its classical key
|
|
retired, optionally naming the post-quantum account that replaces it.
|
|
`retireToPqcAccount(factory, falconPubKey, salt, expectedSuccessor)` derives the
|
|
successor from a Falcon-512 key through an `AerePQCAccountFactory` rather than
|
|
taking a literal address. Passing the zero successor is legitimate and supported,
|
|
for an owner who does not want to publicly link the old address to the new one.
|
|
|
|
**What deriving the successor does and does not guarantee.** Because the pointer can
|
|
never be corrected, it is worth being exact about which mistakes this removes.
|
|
|
|
It removes the transcription class: a mistyped, truncated, wrong-checksum or
|
|
clipboard-swapped successor address. The caller never supplies the successor at all,
|
|
so there is nothing to mistype; the address is CREATE2 math over the Falcon public
|
|
key the caller holds. That is a real and worthwhile guarantee, and it is why the
|
|
entrypoint exists.
|
|
|
|
It does NOT remove the wrong-successor class in general. `factory` is a
|
|
caller-supplied address and the contract does not validate it. There is no
|
|
allowlist, no code check and no interface assertion, so a wrong or hostile factory
|
|
returns a wrong successor and that successor is latched permanently. A hostile
|
|
factory can return an address it controls. What the design does is move the trust
|
|
from "did I type twenty bytes correctly" to "do I trust this factory", which is a
|
|
better question to have to answer, not the absence of one.
|
|
|
|
The caller must therefore supply a factory they trust. Verify the factory address
|
|
out of band against the published deployment record before calling, and prefer
|
|
passing a non-zero `expectedSuccessor` so the derived address must additionally
|
|
match one reviewed off chain. Those two checks together, not the function alone, are
|
|
what make the permanent statement safe.
|
|
|
|
An earlier revision of this section and of the contract NatSpec said this made the
|
|
wrong-successor mistake "impossible". That was an overstatement of a narrower true
|
|
guarantee and has been corrected here and in
|
|
`aerenew/contracts/contracts/pqc/AereAccountRetirement.sol`.
|
|
|
|
Why the latch is one-way. The adversary is someone who breaks the secp256k1 key
|
|
LATER and can then sign anything the original owner could. If retirement were
|
|
revocable that adversary would un-retire the account and continue. If the successor
|
|
were mutable they would repoint it at themselves and harvest anyone honoring the
|
|
pointer. Immutability is what lets the declaration survive the compromise it exists
|
|
to outlive. The honest owner acts once, while the key is still safe, and the
|
|
statement outlives the key. This is the property the test suite targets directly.
|
|
|
|
Integrators read `isRetired(account)`, `successorOf(account)`, or call the
|
|
reverting guard `requireActive(account)` at the top of a sensitive function.
|
|
|
|
### C.5 What this honestly cannot do
|
|
|
|
Stated plainly, because a registry is easy to oversell:
|
|
|
|
- It cannot stop native AERE or a non-integrating ERC-20 from being sent to a
|
|
retired address. Base-protocol transfers consult no registry, and making them do
|
|
so would be a consensus change. Aere is not making one. Consensus on chain 2800
|
|
is and remains classical secp256k1 ECDSA QBFT.
|
|
- It cannot revoke an allowance. See C.3.
|
|
- It cannot help an account whose key is ALREADY broken. An adversary holding the
|
|
key can retire it too, or simply move the assets. This works only in the window
|
|
where the honest owner acts first, which is the entire argument for migrating
|
|
early rather than waiting for a quantum event.
|
|
- It binds only the contracts, wallets, indexers and counterparties that CHOOSE to
|
|
read it. It is an opt-in enforcement surface and an advisory signal, not a
|
|
protocol rule.
|
|
|
|
Its value is being a permanent, checkable, machine-readable statement of intent
|
|
that an attacker who later owns the key cannot retract.
|
|
|
|
### C.6 Test result (real)
|
|
|
|
Suite: `aerenew/contracts/test/account-retirement.test.js`. Run from
|
|
`aerenew/contracts`:
|
|
|
|
`npx hardhat test test/account-migrator.test.js test/account-retirement.test.js`
|
|
|
|
Result on 2026-07-19: 20 passing (9 pre-existing migrator tests, 11 new retirement
|
|
tests). Covered by the new tests: retirement with and without a published
|
|
successor; Falcon-derived successor through the real factory; mismatch guard; the
|
|
latch holds against an attacker who has the key (cannot un-retire, cannot repoint,
|
|
via either entrypoint); retirement is strictly self-scoped so no third party or
|
|
admin can retire another account; no admin surface and no payable function; the
|
|
self-referential successor is rejected; `requireActive` guards correctly; a
|
|
never-retired account reads as a clean zero record.
|
|
|
|
The "no payable function" assertion was checked against a positive control: the
|
|
same ABI inspection reports 2 payable functions on `AereAccountMigrator` and 0 on
|
|
`AereAccountRetirement`, so the assertion is not passing vacuously.
|
|
|
|
Deployment status: NOT DEPLOYED. This contract exists in the repository and passes
|
|
its suite. It is not on mainnet 2800 and no transaction was sent. Deploying it is
|
|
founder-gated.
|
|
|
|
---
|
|
|
|
## Files
|
|
|
|
- `aere-contracts/contracts/pqc/AereAccountMigrator.sol` (contract)
|
|
- `aere-contracts/test/account-migrator.test.js` (Hardhat suite, 9 passing)
|
|
- `aerenew/contracts/contracts/pqc/AereAccountRetirement.sol` (contract, Part C,
|
|
NOT deployed)
|
|
- `aerenew/contracts/test/account-retirement.test.js` (Hardhat suite, 11 passing)
|
|
- `aere-contracts/contracts/delegation/AereDelegate7702V2.sol` (`executeBatch`,
|
|
the existing tool for batch allowance revocation; no new contract was written
|
|
for revocation)
|
|
- `aerenew/pqc-migration-toolkit/` (scanner + derivation + cost simulator)
|
|
- `aere-contracts/contracts/pqc/AerePQCAccount.sol`,
|
|
`AerePQCAccountFactory.sol`, `AereHybridAuth.sol` (destinations)
|
|
- `aerenew/docs/AERE-POST-QUANTUM-GOVERNANCE.md` (Timelock role grant to a PQC
|
|
threshold account, the governance path for operator authority)
|
|
- `aerenew/docs/AERE-EIP-COMPATIBILITY-MATRIX.md` (EIP-7702 support)
|