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.
128 lines
5.9 KiB
JavaScript
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');
|
|
}
|