aere-docs/AERE-QUANTUM-MIGRATION.md
Aere Network dd1410c266 The unpublished line of work joins the sanitized public line
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.
2026-08-15 13:54:42 +03:00

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)