pqc-migration-toolkit/lib/simulate.js
Aere Network de9a79298c Aere PQC migration toolkit: measure an address quantum exposure from its bytecode
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.
2026-08-17 10:31:31 +03:00

128 lines
5.9 KiB
JavaScript

// simulate.js, migration cost estimator. Given a set of accounts and a target
// post-quantum scheme, estimate the gas to migrate and to authorize, using the
// MEASURED PQC verify costs from the Aere benchmark report. Zero dependencies.
//
// All per-scheme gas is [CITED: AERE-BENCHMARK-REPORT.md Part C.1] (native
// precompile marginal gas + the verify-and-record tx gasUsed receipt). The
// deployed-account Falcon path is [CITED: pqc-account.json] (Solidity verifier,
// ~10.5M gas). This estimator does not invent numbers; it composes the cited
// ones and is explicit about which path each figure represents.
import {
PQC, HYBRID_MARGINAL_GAS, CLASSICAL,
AERE_PQC_ACCOUNT_MEASURED_USEROP_GAS, FALCON512_SOLIDITY_VERIFIER_GAS,
} from './precompiles.js';
// One-time CREATE2 deploy of an AerePQCAccount. [CITED: pqc-account.json createGasUsed]
export const ACCOUNT_DEPLOY_GAS = 1493084;
// The live 1 Gwei base-fee floor on chain 2800. [CITED: EIP-COMPATIBILITY-MATRIX EIP-1559 row]
export const GAS_PRICE_GWEI = 1;
// Per-scheme model. `verifyTxGas` is the realistic on-chain verify-and-record
// receipt via the NATIVE precompile. `note` records path caveats.
const SCHEME_MODEL = {
falcon512: { label: 'Falcon-512 (native precompile)', verifyTxGas: PQC.falcon512.verifyAndRecordTxGas, marginal: PQC.falcon512.marginalGas },
falcon1024: { label: 'Falcon-1024 (native precompile)', verifyTxGas: PQC.falcon1024.verifyAndRecordTxGas, marginal: PQC.falcon1024.marginalGas },
mldsa44: { label: 'ML-DSA-44 (native precompile)', verifyTxGas: PQC.mldsa44.verifyAndRecordTxGas, marginal: PQC.mldsa44.marginalGas },
slhdsa128s: { label: 'SLH-DSA-128s (native precompile)', verifyTxGas: PQC.slhdsa128s.verifyAndRecordTxGas, marginal: PQC.slhdsa128s.marginalGas },
hybrid: { label: 'Hybrid ECDSA + Falcon-512 (native)', verifyTxGas: PQC.falcon512.verifyAndRecordTxGas + CLASSICAL.ecrecover.marginalGas, marginal: HYBRID_MARGINAL_GAS },
// The path an ACTUALLY-DEPLOYED AerePQCAccount takes today (Solidity verifier).
falcon512_solidity: { label: 'Falcon-512 (deployed AerePQCAccount, Solidity verifier)', verifyTxGas: AERE_PQC_ACCOUNT_MEASURED_USEROP_GAS, marginal: FALCON512_SOLIDITY_VERIFIER_GAS },
};
export function schemeList() {
return Object.keys(SCHEME_MODEL);
}
function gasToAere(gas, gweiPrice = GAS_PRICE_GWEI) {
// gas * price(Gwei) * 1e-9 AERE/Gwei-gas
return gas * gweiPrice * 1e-9;
}
/**
* Estimate migration cost for a set of accounts.
* @param {Array<{address?:string, scheme?:string, deployAccount?:boolean, authsPerAccount?:number}>} accounts
* @param {object} opts
* @param {string} opts.defaultScheme default scheme when an account omits one
* @param {number} opts.defaultAuths default authorizations/account to project
* @param {number} opts.gweiPrice gas price in Gwei (default 1, the live floor)
*/
export function simulateMigration(accounts, opts = {}) {
const defaultScheme = opts.defaultScheme || 'falcon512';
const defaultAuths = opts.defaultAuths ?? 1;
const gweiPrice = opts.gweiPrice ?? GAS_PRICE_GWEI;
const rows = [];
let totalDeployGas = 0;
let totalAuthGas = 0;
for (const acc of accounts) {
const scheme = acc.scheme || defaultScheme;
const model = SCHEME_MODEL[scheme];
if (!model) throw new Error(`unknown scheme "${scheme}". Known: ${schemeList().join(', ')}`);
const deployAccount = acc.deployAccount !== false; // default: deploy a smart account
const auths = acc.authsPerAccount ?? defaultAuths;
const deployGas = deployAccount ? ACCOUNT_DEPLOY_GAS : 0;
const authGas = model.verifyTxGas * auths;
totalDeployGas += deployGas;
totalAuthGas += authGas;
rows.push({
address: acc.address || '(counterfactual)',
scheme,
schemeLabel: model.label,
deployGas,
authsProjected: auths,
perAuthGas: model.verifyTxGas,
authGas,
totalGas: deployGas + authGas,
});
}
const totalGas = totalDeployGas + totalAuthGas;
return {
accounts: rows,
totals: {
count: accounts.length,
totalDeployGas,
totalAuthGas,
totalGas,
gweiPrice,
estimatedAere: gasToAere(totalGas, gweiPrice),
},
assumptions: [
`Account deploy gas = ${ACCOUNT_DEPLOY_GAS.toLocaleString()} (CREATE2 AerePQCAccount, [CITED: pqc-account.json]).`,
`Per-auth gas is the native-precompile verify-and-record receipt [CITED: AERE-BENCHMARK-REPORT.md Part C.1]; the scheme "falcon512_solidity" instead models the CURRENTLY-DEPLOYED AerePQCAccount path (~10.5M gas, [CITED: pqc-account.json]).`,
`Gas price = ${gweiPrice} Gwei (the live 1 Gwei base-fee floor on chain 2800).`,
`SCOPE: these costs are for ACCOUNT/APPLICATION authentication. They do not make Aere consensus post-quantum.`,
],
};
}
/** Render a simulation result as a plain-text table for the CLI. */
export function formatSimulation(sim) {
const lines = [];
const pad = (s, n) => String(s).padEnd(n);
const padL = (s, n) => String(s).padStart(n);
lines.push(pad('ADDRESS', 26) + pad('SCHEME', 20) + padL('DEPLOY', 12) + padL('AUTHS', 7) + padL('PER-AUTH', 12) + padL('TOTAL', 14));
lines.push('-'.repeat(91));
for (const r of sim.accounts) {
const a = r.address.length > 24 ? r.address.slice(0, 10) + '..' + r.address.slice(-8) : r.address;
lines.push(
pad(a, 26) + pad(r.scheme, 20) + padL(r.deployGas.toLocaleString(), 12) +
padL(r.authsProjected, 7) + padL(r.perAuthGas.toLocaleString(), 12) + padL(r.totalGas.toLocaleString(), 14),
);
}
lines.push('-'.repeat(91));
const t = sim.totals;
lines.push(pad(`TOTAL (${t.count} accounts)`, 46) + padL(t.totalDeployGas.toLocaleString(), 12) + padL('', 7) + padL(t.totalAuthGas.toLocaleString(), 12) + padL(t.totalGas.toLocaleString(), 14));
lines.push('');
lines.push(`Estimated cost at ${t.gweiPrice} Gwei: ${t.estimatedAere.toFixed(6)} AERE total.`);
lines.push('');
lines.push('Assumptions:');
for (const a of sim.assumptions) lines.push(' - ' + a);
return lines.join('\n');
}