Zero dependencies, no account, no API. Every command takes --rpc, so it runs against a node you operate and never needs ours. That is the point: an exposure measurement you cannot re-run is an opinion with a logo on it. Ships with two self-checks meant to be run by you, not just by us. The second, proba-vocabular.mjs, exists because of a real defect found on 2026-08-16: the scanner emits RED / YELLOW / GREEN, two downstream files each wrote their own copy of that list, and both wrote AMBER, so a hybrid contract, exactly a client who has already started migrating, was reported as unmeasurable. Both sides were self-consistent, so nothing we had could see it. The vocabulary now has one exported source, and the test walks the whole path with a negative control that makes it able to fail. The same scanner returns RED about our own contracts. See VERIFY-US.md.
199 lines
11 KiB
JavaScript
199 lines
11 KiB
JavaScript
// readiness.js, the quantum-readiness cost simulator. Given an account (its scan
|
|
// result and, optionally, its enumerated assets) and a target post-quantum
|
|
// scheme, it produces a readiness REPORT: the color, which assets need moving,
|
|
// the one-time migration gas, and the ongoing per-authorization cost.
|
|
//
|
|
// SOURCING DISCIPLINE (load-bearing, not decoration):
|
|
// - PQC verify / auth / account-deploy gas is CITED from committed artifacts
|
|
// (AERE-BENCHMARK-REPORT.md Part C.1, pqc-account.json). Never invented.
|
|
// - Asset-MOVE gas (ERC-20 transfer/approve) is NOT in any Aere benchmark and
|
|
// is NOT measured here, so it is a clearly-labeled [VERIFY] planning
|
|
// estimate, anchored to the one asset-move number this session DID measure
|
|
// live: a native AERE send estimated at 21,246 gas via eth_estimateGas on
|
|
// https://rpc.aere.network (chain 2800, ~block 10,416,783, 2026-07-19).
|
|
// - Gas price is the live 1 Gwei base-fee floor on 2800.
|
|
|
|
import { PQC, CLASSICAL, FALCON512_SOLIDITY_VERIFIER_GAS, AERE_PQC_ACCOUNT_MEASURED_USEROP_GAS } from './precompiles.js';
|
|
import { ACCOUNT_DEPLOY_GAS, GAS_PRICE_GWEI } from './simulate.js';
|
|
|
|
// Native AERE transfer. 21,000 is the fixed EVM base cost for a value-only tx;
|
|
// the live estimate came back 21,246 (a touch above 21,000 for warm-account
|
|
// bookkeeping). We use the measured live figure as the planning number.
|
|
export const NATIVE_SEND_GAS = 21246; // [MEASURED-FRESH] eth_estimateGas, rpc.aere.network, 2026-07-19
|
|
export const NATIVE_SEND_GAS_SOURCE =
|
|
'[MEASURED-FRESH] eth_estimateGas for a native AERE send on https://rpc.aere.network (chain 2800), 2026-07-19 => 21,246 gas (21,000 protocol base + warm-account overhead).';
|
|
|
|
// ERC-20 move gas: NOT measured on Aere, NOT in the benchmark report. Planning
|
|
// estimates only, flagged [VERIFY]. Refine per token with eth_estimateGas.
|
|
export const ERC20_TRANSFER_GAS = 65000; // [VERIFY] cold-recipient transfer, planning estimate
|
|
export const ERC20_APPROVE_GAS = 46000; // [VERIFY] approve, planning estimate
|
|
export const MIGRATOR_PER_TOKEN_GAS = 40000; // [VERIFY] one safeTransferFrom hop inside migrate()
|
|
export const MIGRATOR_BASE_GAS = 30000; // [VERIFY] migrate() call overhead (checks, event)
|
|
export const ERC20_MOVE_GAS_SOURCE =
|
|
'[VERIFY] ERC-20 move gas is a planning estimate, NOT measured on Aere and NOT in AERE-BENCHMARK-REPORT.md. Refine with eth_estimateGas against the real token+allowance.';
|
|
|
|
// Per-scheme ongoing authorization gas. verifyTxGas is the native-precompile
|
|
// verify-and-record receipt [CITED: AERE-BENCHMARK-REPORT.md Part C.1].
|
|
const SCHEME_AUTH = {
|
|
falcon512: { label: 'Falcon-512 (native precompile)', authGas: PQC.falcon512.verifyAndRecordTxGas, cited: 'AERE-BENCHMARK-REPORT.md Part C.1 (86,336)' },
|
|
falcon1024: { label: 'Falcon-1024 (native precompile)', authGas: PQC.falcon1024.verifyAndRecordTxGas, cited: 'AERE-BENCHMARK-REPORT.md Part C.1 (145,496)' },
|
|
mldsa44: { label: 'ML-DSA-44 (native precompile)', authGas: PQC.mldsa44.verifyAndRecordTxGas, cited: 'AERE-BENCHMARK-REPORT.md Part C.1 (351,050)' },
|
|
slhdsa128s: { label: 'SLH-DSA-128s (native precompile)', authGas: PQC.slhdsa128s.verifyAndRecordTxGas, cited: 'AERE-BENCHMARK-REPORT.md Part C.1 (558,276)' },
|
|
hybrid: { label: 'Hybrid ECDSA + Falcon-512 (native)', authGas: PQC.falcon512.verifyAndRecordTxGas + CLASSICAL.ecrecover.marginalGas, cited: 'Part C.1 Falcon-512 86,336 + ecrecover 3,000' },
|
|
// The path a currently-DEPLOYED AerePQCAccount actually takes (Solidity Falcon
|
|
// verifier), measured on-chain. This is the honest default: it is what a
|
|
// migration TODAY costs per auth, ~122x the native projection.
|
|
falcon512_solidity: { label: 'Falcon-512 (deployed AerePQCAccount, Solidity verifier)', authGas: AERE_PQC_ACCOUNT_MEASURED_USEROP_GAS, cited: 'pqc-account.json handleOpsGasUsed 10,278,313 (real on-chain receipt)' },
|
|
};
|
|
|
|
export function readinessSchemeList() {
|
|
return Object.keys(SCHEME_AUTH);
|
|
}
|
|
|
|
function gasToAere(gas, gwei = GAS_PRICE_GWEI) {
|
|
return gas * gwei * 1e-9;
|
|
}
|
|
|
|
/**
|
|
* Compute the one-time asset-move gas for a set of assets on a given path.
|
|
* @param {{nativeNeedsMove:boolean, erc20Count:number}} assets
|
|
* @param {'direct'|'migrator'} path
|
|
*/
|
|
export function assetMoveGas(assets, path = 'direct') {
|
|
const { nativeNeedsMove = false, erc20Count = 0 } = assets;
|
|
const nativeGas = nativeNeedsMove ? NATIVE_SEND_GAS : 0;
|
|
let erc20Gas = 0;
|
|
const breakdown = [];
|
|
if (nativeNeedsMove) breakdown.push({ item: 'native AERE send', gas: NATIVE_SEND_GAS, source: NATIVE_SEND_GAS_SOURCE });
|
|
if (path === 'direct') {
|
|
erc20Gas = erc20Count * ERC20_TRANSFER_GAS;
|
|
if (erc20Count) breakdown.push({ item: `${erc20Count} x ERC-20 transfer`, gas: erc20Gas, source: ERC20_MOVE_GAS_SOURCE });
|
|
} else if (path === 'migrator') {
|
|
const approveGas = erc20Count * ERC20_APPROVE_GAS;
|
|
const migrateGas = erc20Count > 0 || nativeNeedsMove ? MIGRATOR_BASE_GAS + erc20Count * MIGRATOR_PER_TOKEN_GAS : 0;
|
|
erc20Gas = approveGas + migrateGas;
|
|
if (erc20Count) breakdown.push({ item: `${erc20Count} x ERC-20 approve`, gas: approveGas, source: ERC20_MOVE_GAS_SOURCE });
|
|
if (erc20Count > 0 || nativeNeedsMove) breakdown.push({ item: 'atomic migrate() sweep', gas: migrateGas, source: ERC20_MOVE_GAS_SOURCE });
|
|
} else {
|
|
throw new Error(`unknown path "${path}"`);
|
|
}
|
|
return { total: nativeGas + erc20Gas, nativeGas, erc20Gas, breakdown };
|
|
}
|
|
|
|
/**
|
|
* Build a quantum-readiness report for ONE account.
|
|
*
|
|
* @param {object} p
|
|
* @param {object} p.scan a scan report ({ address, kind, readiness, ... }).
|
|
* @param {object} [p.assets] enumerateAssets() output (native + tokens + movable).
|
|
* @param {string} [p.scheme] target scheme (default falcon512_solidity, the
|
|
* honest deployed-reality path). Native projection also shown.
|
|
* @param {'direct'|'migrator'} [p.path] asset-move path (default direct).
|
|
* @param {boolean} [p.deployAccount] count the CREATE2 deploy (default: only if
|
|
* the account is an EOA, since a contract may already be PQC-capable).
|
|
* @param {number} [p.projectedAuths] ongoing auths to project the running cost.
|
|
* @param {number} [p.gwei] gas price (default 1).
|
|
*/
|
|
export function buildReadinessReport({
|
|
scan, assets, scheme = 'falcon512_solidity', path = 'direct', deployAccount, projectedAuths = 1, gwei = GAS_PRICE_GWEI,
|
|
} = {}) {
|
|
if (!scan) throw new Error('buildReadinessReport requires a scan report');
|
|
const model = SCHEME_AUTH[scheme];
|
|
if (!model) throw new Error(`unknown scheme "${scheme}". Known: ${readinessSchemeList().join(', ')}`);
|
|
|
|
const isEoa = scan.kind === 'eoa';
|
|
const needDeploy = deployAccount ?? isEoa;
|
|
|
|
// asset picture
|
|
let nativeNeedsMove = false;
|
|
let erc20ToMove = [];
|
|
if (assets) {
|
|
nativeNeedsMove = !!(assets.native && assets.native.needsMove);
|
|
erc20ToMove = (assets.tokens || []).filter((t) => t.needsMove);
|
|
}
|
|
const erc20Count = erc20ToMove.length;
|
|
|
|
const deployGas = needDeploy ? ACCOUNT_DEPLOY_GAS : 0;
|
|
const move = assetMoveGas({ nativeNeedsMove, erc20Count }, path);
|
|
const oneTimeGas = deployGas + move.total;
|
|
const ongoingAuthGas = model.authGas * projectedAuths;
|
|
|
|
// native-precompile projection for the same scheme (context, not the deployed cost)
|
|
const nativeProjection = scheme === 'falcon512_solidity'
|
|
? { label: SCHEME_AUTH.falcon512.label, authGas: SCHEME_AUTH.falcon512.authGas, cited: SCHEME_AUTH.falcon512.cited }
|
|
: null;
|
|
|
|
return {
|
|
address: scan.address,
|
|
kind: scan.kind,
|
|
readiness: scan.readiness,
|
|
readinessReason: scan.reason,
|
|
assetsToMove: {
|
|
native: nativeNeedsMove ? { symbol: assets.native.symbol, balanceWei: assets.native.balanceWei } : null,
|
|
erc20: erc20ToMove.map((t) => ({ token: t.token, symbol: t.symbol, balance: t.balance })),
|
|
count: (nativeNeedsMove ? 1 : 0) + erc20Count,
|
|
note: assets ? null : 'No asset enumeration supplied. Pass --rpc (and optional --tokens) to enumerate movable assets.',
|
|
},
|
|
scheme: { key: scheme, ...model },
|
|
nativeProjection,
|
|
gas: {
|
|
deploy: deployGas,
|
|
deploySource: needDeploy ? 'pqc-account.json createGasUsed 1,493,084 [CITED]' : 'no deploy (contract already exists / not an EOA)',
|
|
assetMove: move.total,
|
|
assetMoveBreakdown: move.breakdown,
|
|
oneTimeTotal: oneTimeGas,
|
|
perAuth: model.authGas,
|
|
perAuthSource: '[CITED: ' + model.cited + ']',
|
|
projectedAuths,
|
|
ongoingAuthTotal: ongoingAuthGas,
|
|
},
|
|
cost: {
|
|
gwei,
|
|
oneTimeAere: gasToAere(oneTimeGas, gwei),
|
|
ongoingAuthAere: gasToAere(ongoingAuthGas, gwei),
|
|
},
|
|
scope: 'ACCOUNT/APPLICATION quantum-readiness. This does not, and cannot, make Aere consensus post-quantum (chain 2800 seals with classical secp256k1 QBFT). BN254 ZK verifiers remain classical.',
|
|
};
|
|
}
|
|
|
|
/** Render a readiness report as plain text for the CLI. */
|
|
export function formatReadinessReport(r) {
|
|
const L = [];
|
|
const aere = (n) => n.toFixed(9) + ' AERE';
|
|
L.push('');
|
|
L.push(`Quantum-readiness report ${r.address}`);
|
|
L.push(` status: ${r.readiness} (${r.kind})`);
|
|
if (r.readinessReason) L.push(` reason: ${r.readinessReason}`);
|
|
L.push('');
|
|
L.push(' Assets to move:');
|
|
if (r.assetsToMove.note) {
|
|
L.push(` - ${r.assetsToMove.note}`);
|
|
} else if (r.assetsToMove.count === 0) {
|
|
L.push(' - none detected (nothing to migrate)');
|
|
} else {
|
|
if (r.assetsToMove.native) L.push(` - native AERE: ${r.assetsToMove.native.balanceWei} wei`);
|
|
for (const t of r.assetsToMove.erc20) L.push(` - ERC-20 ${t.symbol || ''} ${t.token}: ${t.balance} (base units)`);
|
|
}
|
|
L.push('');
|
|
L.push(' One-time migration gas:');
|
|
L.push(` deploy target account: ${r.gas.deploy.toLocaleString().padStart(12)} (${r.gas.deploySource})`);
|
|
for (const b of r.gas.assetMoveBreakdown) L.push(` ${(b.item + ':').padEnd(24)}${b.gas.toLocaleString().padStart(12)}`);
|
|
L.push(` one-time TOTAL: ${r.gas.oneTimeTotal.toLocaleString().padStart(12)} = ${aere(r.cost.oneTimeAere)} at ${r.cost.gwei} Gwei`);
|
|
L.push('');
|
|
L.push(' Ongoing per-authorization cost (post-migration):');
|
|
L.push(` scheme: ${r.scheme.label}`);
|
|
L.push(` per auth: ${r.gas.perAuth.toLocaleString()} gas ${r.gas.perAuthSource}`);
|
|
L.push(` ${r.gas.projectedAuths} projected auth(s): ${r.gas.ongoingAuthTotal.toLocaleString()} gas = ${aere(r.cost.ongoingAuthAere)}`);
|
|
if (r.nativeProjection) {
|
|
L.push(` (native-precompile projection for the same scheme: ${r.nativeProjection.authGas.toLocaleString()} gas/auth [CITED: ${r.nativeProjection.cited}], available once the account verifies Falcon via the native 0x0AE1 precompile instead of the Solidity verifier.)`);
|
|
}
|
|
L.push('');
|
|
const hasErc20Estimate = r.gas.assetMoveBreakdown.some((b) => b.source && b.source.startsWith('[VERIFY]'));
|
|
L.push(hasErc20Estimate
|
|
? ' Note: ERC-20 move gas figures are [VERIFY] planning estimates, not measured on Aere; native-send gas is a fresh live measurement (21,246). PQC auth gas is [CITED].'
|
|
: ' Note: PQC auth gas is [CITED]; native-send gas is a fresh live measurement (21,246).');
|
|
L.push(` Scope: ${r.scope}`);
|
|
L.push('');
|
|
return L.join('\n');
|
|
}
|