| .. | ||
| fixturi | ||
| adaptoare.mjs | ||
| bitcoin.mjs | ||
| clasifica.mjs | ||
| ethereum.mjs | ||
| limbaj.mjs | ||
| machete.mjs | ||
| noble.mjs | ||
| package.json | ||
| paza-retea.mjs | ||
| proba-expunere.mjs | ||
| raport.mjs | ||
| README.md | ||
Quantum exposure report
A command-line tool that classifies a list of Bitcoin and Ethereum addresses by whether their public key is already readable
from chain data, in one of three declared modes, and writes a dated report: a JSON, a Markdown for people, and an AERE Proof
Protocol (AIP-23) compliance envelope that binds the JSON, so that anyone can check later which report was issued.
It records what was readable on the date of the report. It does not protect, move or recover any coins, it does not change what a spend reveals once a quantum computer exists, and it says nothing about the future.
node raport.mjs --mod 1 --adrese addresses.txt --iesire out/
node raport.mjs --mod 2 --adrese addresses.txt --electrum 127.0.0.1:50001 --iesire out/
node raport.mjs --mod 2 --adrese addresses.txt --esplora http://127.0.0.1:3000 --iesire out/
node raport.mjs --mod 2 --adrese addresses.txt --bitcoind http://127.0.0.1:8332 --txids ids.txt --iesire out/
node raport.mjs --mod 2 --adrese addresses.txt --eth-rpc http://127.0.0.1:8545 --eth-blocks 20000000..20000500 --iesire out/
node raport.mjs --mod 3 --adrese addresses.txt --esplora https://example-provider.invalid --consimtamant-tert "written consent" --iesire out/
addresses.txt holds one entry per line: a Bitcoin address (base58check, bech32 or bech32m, checked), script:<scriptPubKey hex>, p2pk:<public key hex>, or an Ethereum address (mixed case is checked as EIP-55).
Requirements: Node.js 24 and two packages declared in package.json, @noble/hashes (keccak-256, RIPEMD-160) and
@noble/curves (secp256k1 public-key recovery): run npm install --ignore-scripts --no-audit --no-fund in this directory. The
loader (noble.mjs) also accepts the packages from a parent node_modules, from a directory named by AERE_EXPUNERE_NODE_MODULES
(the test sets it to this directory's node_modules for the copies it runs), or from the AERE anchor verifier's node_modules
when this directory lives in the AERE development tree, and uses a candidate only when it has the exports the tool calls: a
@noble/curves 2.x found first (it has no ProjectivePoint and no CURVE) is passed over for the next one. Without a usable
version the tool and the test stop with exit code 2 and name the missing package and what was found; they do not crash inside
an import. For Bitcoin Core, credentials come from AERE_BITCOIND_COOKIE (path of
the node's cookie file) or AERE_BITCOIND_AUTH (user:password); no value of either is ever printed.
The three modes
| mode | reads | opens connections to | sees spends |
|---|---|---|---|
| 1, offline | nothing but the address | nobody | no |
| 2, your own node or index | your Electrum server, Esplora index, Bitcoin Core or Ethereum JSON-RPC, through one adapter | that one declared target | Electrum and Esplora: yes, as served by them, on the chain they serve (its genesis block, checked against the address's network, and on Bitcoin mainnet its block at a checkpoint height, checked against the checkpoint) and up to the tip they serve (read before the history; both named in every verdict), for the address and for its sibling scripts (below); Bitcoin Core: only the transactions named in --txids (it keeps no address index), read once for the whole report, and judged per address, on the chain the node serves (its genesis block and its block at a checkpoint height, read once and judged as for Electrum and Esplora). Since no answer of the node shows that list to be the whole history of an address (an output funded and spent by transactions not given is invisible to it), an address read through Bitcoin Core is exposed or unmeasured, never no on-chain evidence of exposure; the reason names the case: no list, a list none of whose confirmed transactions touches the address (a touch only in the node's mempool is not one, and is named), an output of the address the node no longer holds as unspent and whose spending transaction is not in the list, or a list that the tool cannot check is complete; Ethereum: the blocks of the --eth-blocks window |
| 3, a third-party provider | the same adapters | that provider, only with --consimtamant-tert "<written consent>"; the addresses are sent to it; a target that resolves to a private or loopback address is refused |
as in mode 2 |
Mode 1 classifies by script or address type only. Mode 2 is what a custodian runs against its own infrastructure. Mode 3 exists for a requester without a node and is refused without the explicit consent flag; the consent text is recorded in the report.
The two labels, and what each verdict carries
Every address receives exactly one of two labels, or the state unmeasured with a named reason:
exposed (on-chain evidence): a public key, or the script holding the keys, is readable from chain data the report names.no on-chain evidence of exposure: nothing read in this mode shows the key. It says nothing about disclosure off chain, about data this mode cannot read, or about the future.
Each verdict lists what was seen and what was not seen, in a fixed vocabulary, for example:
P2PK output: the public key is the scriptP2TR output: the output key (the tweaked public key) is the scriptpublic key revealed in the scriptSig (HASH160 equals the address hash), in the spend <txid> (input 0, height H)P2SH redeem script (HASH160 equals the address hash): 2-of-3 multisig, 3 public keys revealed, in the spend <txid> ...the script commits to HASH160 of a key, and 1 public key(s) with that hash are pushed in the same input(P2PKH-shaped redeem or witness scripts, hash-locked contracts)public key recovered from the signature of transaction <hash> in block H; keccak256 of that key gives this addressEIP-7702 delegation designator in the account code ...: the chain sets this code only after an authorization signed with this account's own key was included, and, when found in the window,authorization signed by this account in transaction <hash> ...: the authority recovered from the authorization's signature is this address- not seen in mode 1:
spends from this address (mode 1 reads no chain data; a single spend would reveal the key or the script) - in every Electrum or Esplora verdict:
history served up to height H (tip header time T)andchain served: Bitcoin mainnet (genesis block 000000000019d6...; at height 969696 the block 000...9f0b, the checkpoint this tool holds; the tip header states a target within its proof-of-work limit; whether it is the chain with the most work is not verified); in every Bitcoin Core verdict the same chain line, without the tip; in every Ethereum verdict:head block N served by the endpoint (block time T), and the account's nonce and codeat head block N, proven under its state root (eth_getProof) the same HASH160 of the key under another script type, P2WPKH bc1q...: public key revealed in the witness ..., or, beside "no on-chain evidence",sibling scripts of the same HASH160 read through the same target: ...; no spend there reveals the key
Every "revealed" statement is computed by the tool from the bytes the target served, never copied from an index's label:
- a public key is a 33-byte (02/03), 65-byte (04) or hybrid 65-byte (06/07, the parity of y in the prefix, accepted by consensus)
push that decodes as a point on secp256k1, and it counts when its HASH160 equals the address hash (any push of the scriptSig,
any element of the witness: a legacy scriptSig need not end with the key), or equals a 20-byte hash the revealed script commits
to; a push whose HASH160 equals the hash but that does not decode as a key is named, and gives
unmeasured; - a redeem or witness script counts when its HASH160 (P2SH) or SHA-256 (P2WSH) equals the hash in the output;
- inside a revealed script of no standard form, a key counts only when it is a valid point AND the operand of a signature check
(
OP_CHECKSIG,OP_CHECKSIGVERIFY,OP_CHECKSIGADD, or the key run of anOP_CHECKMULTISIG); a push that only has the shape of a key is reported as such and givesunmeasured, notexposed; - every Bitcoin transaction must hash to its id: Electrum and Bitcoin Core from its raw bytes (or, for a Bitcoin Core node that
serves no
hex, re-serialized from its decoded form), Esplora re-serialized from the fields the index serves. The id does not cover the witness: a key or a script found there counts by its own hash equality with the address, which no served byte can fake; - an Ethereum key is recovered from a transaction or authorization signature and must hash to the address, and the transaction that carries it (the one sent, or the type-4 transaction that carries the authorization) must hash to the hash the endpoint gives, re-derived from its served fields.
For Ethereum, the nonce and the code of an account are read only at the head block the verdict names, from an account proof
(eth_getProof) under the state root of that block, and the code must hash to the code hash proven (below, "The head of an
Ethereum endpoint"); nothing is read at latest. A nonce above zero with no transaction or authorization found in the searched
blocks gives unmeasured, with the advice to widen --eth-blocks; a window that would read no block (it starts after the head)
is refused. Every Ethereum verdict names the head block the endpoint served and its time.
What gets NO label at all, exposed or not, and is unmeasured with its reason (README sections below):
- a Bitcoin target (Electrum, Esplora, Bitcoin Core) that serves ANOTHER CHAIN than the address's network: its genesis block is
not one of that network (a
p2pk:orscript:line names no network and is read on Bitcoin mainnet only), or the tip header it serves (Electrum, Esplora) states a proof-of-work target above the limit of the chain its genesis block names (a signet or regtest header served under the mainnet genesis block), or, under the mainnet genesis block, its block at the checkpoint height (below) is not the checkpoint, which is what a chain that split from Bitcoin (Bitcoin Cash, Bitcoin SV, eCash) serves:the target serves another chain; and a target under the mainnet genesis block whose chain ends below the lowest checkpoint, where such a chain cannot be told apart; - a target that serves blocks AFTER the report date: the header time of its tip, the time of an Ethereum head, or the block time
of a transaction given to Bitcoin Core is more than 2 hours after the reference instant (below), and, for Bitcoin Core, whose
scan reads the node's unspent set at the time of the run, a report dated a day that ended more than 2 hours before the run:
the target serves blocks after the report date. A transaction given to Bitcoin Core, on the node's active chain, served without its block time, cannot be placed against the date and leaves every address unmeasured too.
What never becomes no on-chain evidence of exposure, and is unmeasured with its reason instead:
- an empty, erroneous, truncated or inconsistent answer (a transaction that does not hash to its id, fewer transactions than the target counts, an index that counts more spent outputs, or more outputs to the script, than the pages it served carry, a count of an Esplora index that is not a count: null, a string, a negative or a missing value, which is never read as zero, a transaction in the history of a script that neither pays nor spends it, an identifier that is not one: a transaction id or a block hash that is not 64 hex digits, which is quoted as the target's text);
- anything read through Bitcoin Core that is not
exposed. In order: without--txids, or with a--txidslist none of whose CONFIRMED transactions touches the address (the scan sees only unspent outputs, so a spent output is invisible there; a list given for other addresses says nothing about this one, and a transaction the node holds only in its mempool says nothing about the address's history on chain); with an incomplete list (an output of the address read from the given transactions that the node's scan no longer finds unspent, and that none of them spends, was spent by a transaction not given, where the key may be revealed); and otherwise because the tool cannot check that the list is the address's whole history: the label "no on-chain evidence" would rest on the requester's list, not on a measurement. Read the address through an index that serves its history (Electrum, Esplora) to measure it; - a revealing spend that the target holds only in its mempool (public, but not on chain yet), or a spend its index counts in the
mempool and its pages did not carry; for a P2TR, P2PK or bare-multisig script that already has an output on chain, a pending
spend does not take that evidence away, and the label stays
exposed. For Bitcoin Core, "on chain" means a block of the node's ACTIVE chain: a transaction it serves with a block hash andconfirmations0 (a block off its active chain, which its transaction index still names) is treated like one in its mempool, and the reason names it; - a P2TR, P2PK or bare-multisig output that the target holds only in its mempool (its key is public there, not yet on chain), or
that an Esplora index COUNTS in its mempool (
mempool_stats.funded_txo_count) above the unconfirmed outputs its page carried; and an Esplora index that counts more transactions of the script in its mempool (mempool_stats.tx_count) than its page carried, or that serves nomempool_statscounts that are counts (none of these comparisons can then be made), unless the key is already on chain (the answer is incomplete, and what it did not serve may show the key); - a target BEHIND the chain: the header time of the tip it serves is more than 2 hours before the reference instant (below), or
its tip could not be read; an Ethereum endpoint whose head time is more than 30 minutes before it, that reports through
eth_syncingthat it is syncing, or whose head was not read; an Ethereum account whose state at that head was not proven (noeth_getProofat the head number, a proof that does not lead to the head block's state root, a code that does not hash to the code hash proven); and a sibling script (below) that could not be measured; - a spend whose revealed script form this tool does not classify, and a spend SEEN of a hash-type output in which the tool reads no key or script: a valid spend reveals the preimage of the hash, so such a spend holds it in a form this tool does not read. "Seen" means that the input spends an output paying the address (its funding was read), or that a push or a script in it hashes to the address, even when the funding was not served: a segwit spend served without its witness (the transaction id does not cover the witness) still carries the redeem script that hashes to a P2SH-P2WPKH address.
The chain and the tip of the target
An Electrum or Esplora adapter first reads which CHAIN the target serves: Electrum server.features (its genesis_hash), Esplora
/block-height/0. The genesis block must be one of the networks of the address: base58check 1... / 3... and bech32 bc1 are
Bitcoin mainnet; base58check m... / n... / 2... are testnet3, testnet4, signet or regtest, bech32 tb1 the first three, and
bcrt1 regtest; a p2pk: or script: line names no network and is read on mainnet only. The five genesis hashes are in
bitcoin.mjs (RETELE_BTC, from Bitcoin Core's chain parameters; the test recomputes each from the fields of its genesis header),
with each chain's proof-of-work limit. Every Electrum or Esplora verdict names the chain served, as its second evidence line
(and every Bitcoin Core verdict as its first): chain served: Bitcoin mainnet (genesis block 000000000019d6...; at height 969696 the block ..., the checkpoint this tool holds; ...).
A chain that split from Bitcoin after its genesis block shares that block and the proof-of-work limit: Bitcoin Cash split at
height 478559, and Bitcoin SV and eCash split later from Bitcoin Cash; their Electrum and Esplora servers are public and answer
with the mainnet genesis hash, and their tips state mainnet-range targets. So on Bitcoin mainnet the adapter also reads the block
the target serves at a CHECKPOINT height (Electrum blockchain.block.header, whose 80 bytes must be a header; Esplora
/block-height/:h; Bitcoin Core getblockhash), and its hash must be the checkpoint's: otherwise the address is unmeasured,
the target serves another chain: one that shares the genesis block of Bitcoin mainnet and split from it before height H, and the
chain line no longer says "Bitcoin mainnet". The checkpoints are in bitcoin.mjs (RETELE_BTC.mainnet.puncte): blocks 967680 and
969696, the two pins of the shield's Bitcoin header follower (btc/antete.mjs, PUNCTE_PINATE), whose hash and 80 header bytes
were read from two public block explorers of different operators on 2026-10-11 and agreed; that agreement is their whole
provenance, and the test requires the tool's table to equal those pins and each header to hash to its hash and meet its proof of
work. The adapter uses the highest checkpoint at or below the end of the target's chain (its tip height; for Bitcoin Core
getblockcount). What this does NOT tell apart: a chain that split from Bitcoin AFTER the checkpoint used (none is known on
2026-10-12); and a target whose chain ends below the lowest checkpoint (967680) cannot be checked at all, so it is unmeasured.
The test networks hold no checkpoint: a chain that shares one of their genesis blocks (every custom signet shares the default
signet's) is not told apart from it. None of this shows the chain to be the one with the most work.
It then reads the target's tip BEFORE the history: Electrum blockchain.headers.subscribe (its height and its 80-byte header);
Esplora /blocks/tip/height, the hash of the block at that height (/block-height/:h) and that block's header
(/block/:hash/header), which must hash to that hash. A header counts only when its hash meets the proof of work its own bits
field states (every block header does), and its bits must state a target within the proof-of-work limit of the chain the genesis
block names: a signet or regtest header (easy targets) served under the mainnet genesis block is not a mainnet header, and the
address is unmeasured, the target serves another chain. This does not show the header to be on the chain with the most work:
a target that serves the mainnet genesis block and headers mined at mainnet's minimum difficulty would pass, which this report
does not verify (Limits). Every transaction confirmed at or below that height is in the history served after it, and every
verdict says history served up to height H (tip header time T). Each Esplora path is a separate HTTP request: behind a load
balancer the genesis block, the checkpoint block, the tip and the history may come from different machines, and no answer shows
it; this is NOT measured, and the test does not exercise it.
no on-chain evidence of exposure is given only when T is at most 2 hours before the reference instant: the end of the
report's date, or the moment of the run when that comes first (a report dated today is judged against the clock of the machine
that runs it; a wrong clock moves the reference). Otherwise the address is unmeasured, target behind the chain: tip H at T.
Why 2 hours: blocks arrive every 10 minutes on average, and under the exponential model of block arrivals a gap of more than two
hours has a probability of about e^-12 per block (about once in three years; a model, not a measured frequency); a header's time
is set by its miner and only has to exceed the median of the 11 previous blocks, so the tip of a target that has caught up can
carry a time up to about an hour old. Two hours keep such a target measurable, and leave a target about 12 or more blocks behind
unmeasured. A target behind by less than that is not detected; the verdict names H and T so that the reader can see it. A target
that serves a recent tip and an old history is serving a chain that is not the canonical one, which this report does not verify
(Limits). An exposed verdict does not depend on how far behind the tip is: the evidence it names is on chain whatever came after.
Bitcoin Core verdicts name no tip: through Bitcoin Core an address is never no on-chain evidence of exposure (its verdicts rest on
the transaction ids the requester gives, each hashed to its id). Bitcoin Core is asked which chain it serves, once per report
(getblockhash 0, getblockcount, getblockhash at the checkpoint height), and judged by its genesis block and its checkpoint
block as above; it serves no tip header here, so the proof-of-work limit is not judged for it.
The head of an Ethereum endpoint
The Ethereum adapter reads eth_syncing and the HEAD block (the block at the number eth_blockNumber gave, without its
transactions) before the account. Every Ethereum verdict names it: head block N served by the endpoint (block time T).
no on-chain evidence of exposure is given only when the endpoint does not report that it is syncing (eth_syncing false) and
T is at most 30 minutes before the reference instant; otherwise unmeasured, endpoint behind the chain, with the head or
the sync status named. Why 30 minutes: a block every 12-second slot, so an endpoint that has caught up serves a head seconds old;
the margin leaves room for a skewed clock and a run of missed slots, and leaves an endpoint about 150 or more blocks behind
unmeasured. An endpoint behind by less than that is not detected; the verdict names the head so that the reader can see it. A
nonce of 0 read at an old head says nothing about the transactions sent after it, which is the reason for the rule.
The account is read AT that head and nowhere else: eth_getProof(address, [], N) at the head number N, an account proof that is
walked from the stateRoot of the head block just read, every node looked up by its own keccak hash (ethereum.mjs
contDinDovada; the walker follows the published one beside the AERE anchor verifier, and the test runs both on the same proofs),
which gives the nonce and the code hash (or shows the account absent: nonce 0, no code); then eth_getCode at N, which must hash
to that code hash. Behind a load balancer, a machine that does not hold block N answers with an error (header not found), and a
proof of any other state does not lead to that state root: the account is then unmeasured, named, never labelled from a nonce
or a code read at latest from another machine; and a state reached after the head (an EIP-7702 designator set later, say)
cannot be credited to block N. An endpoint that does not serve eth_getProof (or no longer keeps the state of block N) leaves the
account unmeasured, named: that is the price of this binding. What the proof binds: the nonce and the code to the state root of
the block whose number and time the verdict names. What it does NOT bind, and is NOT measured: that block itself to the chain
Ethereum agreed on. Its number, time and state root are the endpoint's answer, and this tool checks neither the block's hash, nor
its parent, nor the attestations of the validators (no light client is run here); an endpoint that serves a block of its own
making, with a state of its own making, is not detected. The blocks of the --eth-blocks window are separate requests, each
checked only through the transactions it carries (each hashed to its served hash, each key recovered).
The date of the report
--data YYYY-MM-DD is the day the report records (default: the UTC day of the run), and the reference instant is the end of that
day, or the moment of the run when that comes first. A day after the UTC day of the run is refused (exit 1, nothing written: no
report and no envelope dated in the future). A day in the past is accepted, and no verdict, exposed or not, may rest on what the
target served after it: the header time of an Electrum or Esplora tip, the time of an Ethereum head and the block time of every
transaction given to Bitcoin Core must be at most 2 hours after the reference instant (mainnet nodes refuse a header more than 2
hours ahead of their own clock, so a target that serves the chain passes this for a report dated the day it is run). Bitcoin Core's
scan reads the node's unspent set at the time of the run, so a Bitcoin Core report dated a day that ended more than 2 hours before
the run is unmeasured on every address. Otherwise the address is unmeasured, the target serves blocks after the report date.
The same key under another script type
One key stands behind several scripts that the same 20-byte HASH160 determines: P2PKH, P2WPKH and P2SH-P2WPKH (whose redeem script
is 0x0014 || hash). A spend from any of them reveals the same key. In modes 2 and 3 the tool reads these SIBLING scripts too:
for a P2PKH or P2WPKH input the other two forms of its hash (the key itself is not known from a hash, so nothing else is derived).
A p2pk: input gives the key itself, so more is derived from it (12 siblings): the three forms of HASH160 of the key as given; the
same point in each of its other encodings (compressed 02/03, uncompressed 04, hybrid 06/07, each accepted by consensus in a
signature check) under P2PK and under the three forms of its own HASH160; and the P2TR output of the key used as an internal key
with no script tree (BIP-86: Q = P + t*G, t a hash of P; the test checks the derivation on the BIP-86 test vector). Electrum and
Esplora read each sibling by its script, through the same target (mode 3 therefore sends the siblings to the provider too);
Bitcoin Core judges each sibling on the given transactions, with no scan of its own (an unspent P2PK or P2TR output of a sibling
that no given transaction carries is not seen: that can leave the input unmeasured where a scan would show it exposed, never
no on-chain evidence, which Bitcoin Core never gives). A sibling that shows the key on chain makes the input exposed, with the
sibling and its relation named (the same HASH160 of the key under another script type, ..., the same key in its uncompressed encoding, under P2PKH ..., a P2TR output with no script tree (BIP-86) whose internal key is this key, ...). A BIP-86 output
shows its output key, this key tweaked, not this key itself: the verdict says so in its "not seen" column (whoever recovers the
private key of the output key controls that output, and the private key of this key follows from it for anyone who also knows this
key). A sibling that cannot be measured leaves the input unmeasured; beside no on-chain evidence of exposure the siblings read
are listed. What cannot be derived from an input is named in its "not seen" column (project document, N8): for a P2PKH or P2WPKH
input, a P2PK or bare-multisig output holding the key itself, a P2TR output or script, a P2SH or P2WSH script holding the key and
the key in its other encoding (another HASH160); for a p2pk: input, only what depends on something the input does not give: a
P2TR output whose key commits to a script tree, a P2SH or P2WSH script holding the key, a bare-multisig output holding it (the few
forms that commit to this key alone, such as a 1-of-1 multisig or a P2WSH of <key> OP_CHECKSIG, are not searched either); and, for
a P2SH, P2WSH or P2TR input, the keys inside it under any other type. Each such verdict also names the other chains that may share
the key (an EVM chain where the same address is the same account, a Bitcoin fork, a test network) and says which one chain was read.
What a chain cannot show: the declarations
The report asks three questions and records the answers as declarations by the requester, never as measurements, and no answer changes a label:
- whether an extended public key (xpub / ypub / zpub) of a wallet holding any of the addresses was ever given to a third party (a service, a companion application, a co-signer, a backup);
- whether any of the keys signed anything off chain: Bitcoin signed messages (BIP-137, BIP-322), PSBTs sent to others, Ethereum EIP-191 or EIP-712 signatures (permits, exchange orders, sign-in messages), or a transaction that was broadcast and never confirmed (replaced by fee, dropped from mempools): it disclosed the key exactly as a confirmed spend would, and once it is gone no chain shows it;
- whether the same key, or the same address, signed on another chain: another EVM chain where the address is the same account (a layer 2, a sidechain, a test network), a Bitcoin fork that shares the history before its split, a test network, or a chain of another kind using the same secp256k1 key. The report read one chain, named in each verdict.
--xpub-partajat da|nu|necunoscut, --semnaturi-offchain da|nu|necunoscut and --alte-lanturi da|nu|necunoscut give the
answers; without them the report says the question was asked and not answered.
Deadlines cited
The report cites, for the classical algorithms behind every address (ECDSA and Schnorr on secp256k1), the deadlines of NIST IR
8547 (initial public draft, document date 2024-11-12: deprecated after 2030, disallowed after 2035) and of the EU coordinated
implementation roadmap (2025-06-23: inventory by 2026-12-31, high-risk use cases by 2030-12-31, medium-risk by 2035-12-31),
and, with --cnsa, CNSA 2.0 as a secondary-source citation. Each regime is listed with its document, version or status, source
and the date it was read. The values come from one place, the compliance-report module of the control plane; this tool does not
keep its own copy.
Output
raport.json (the report), raport.md (the same for people) and plic-aip23.json: an AIP-23 compliance envelope whose
evidenceHash is the SHA-256 of the canonical JSON of raport.json (keys sorted recursively, no spaces), with subject (a label
derived from the address list, or --subiect), policy (the report format version) and result (the summary line). The
envelope is checked by the published AIP-23 reference verifier (form and integrity levels; the finality level applies once the
envelope is notarized). The report contains the addresses and nothing about who holds them.
Exit codes: 0 report written and every address classified; 2 report written with some addresses unmeasured, or the tool could
not run (a missing dependency, named); 1 refused (bad input, no consent in mode 3, a private target in mode 3, a data-source flag
in mode 1, a transaction id given with --txids that the node does not serve, whose bytes do not hash to it, or whose block hash
is not one, an --eth-blocks window that starts after the head, a --data day after the UTC day of the run, or a report whose own
text would contain a word the language gate forbids), and then nothing is written. The language gate judges the tool's text; what the requester gave verbatim (the
--subiect label, the consent text, the address lines, the target's host) is recorded as given, and so is what the target served
(an error body, an error message, a server version string, an identifier or a quantity that is not one, and the WHOLE message of
a TLS error, which carries the names of the target's certificate), which the report quotes as [served: ...]; a message cut to its length limit inside such a quote keeps the quote closed. A value that the tool converts
(an Ethereum quantity, a signature field) is checked for its form first, so that a conversion error never carries the served text
into the report, and the delegate of an EIP-7702 authorization is written from the bytes the authority signed. The gate's words
(listed in limbaj.mjs, the one file that names them) are bounded by letters and digits only, so a hyphen or a space before or
after a word does not shield it, and its compounds are caught written with a hyphen, a space or nothing; the tool's own negations
("it does not protect") stay clean. Error
messages mask every long hexadecimal string, except the transaction id a refusal names, which is the requester's own input.
Proof that nothing leaves the machine
paza-retea.mjs is a connection counter loaded into the report process before the tool (node --import paza-retea.mjs raport.mjs ...). It journals, and in blocking mode refuses before any packet leaves:
- every TCP connection opened through
net.Socket(net, tls, http, https, http2, fetch, WebSocket), its arguments read the way Node.js reads them; when the caller supplies its ownlookup, the address that lookup returned is journaled too; - every connection of
net.Socketto a path (a named pipe, a Unix socket, an HTTPsocketPath, or a string given tosocket.connectthat is not a port, which Node.js opens as a pipe), judged on the path AS THE SYSTEM OPENS IT, not on the string given: on Windows the path is normalized first (\\.\pipe\..\UNC\host\pipe\xis opened as\\.\UNC\host\pipe\x, over SMB), and only the local pipe namespace passes,\\.\pipe\<name>or\\?\pipe\<name>with a name that holds no backslash. Every other form is judged like a host: a UNC name, an NT path such as\??\UNC\host\..., a GLOBALROOT device path, a drive or a relative path (which may be a network mount). This is a list of what passes, not of what is forbidden; - every UDP send, its arguments read the way Node.js reads them (a connected socket sends to its remote address, and an offset or
a length is not a port; a port may be a numeric string; no address means the loopback), and every multicast membership change
(
addMembershipand the rest, which make the system send an IGMP or MLD report); - every DNS function of
dns,dns.promises,dns.Resolveranddns.promises.Resolverthat may send a query (all butgetServers, the result-order pair,cancelandsetLocalAddress), so that a query function added by a later Node.js is judged too (Node 24 hasresolveTlsa, which a fixed list had missed). Onlylookuppasses, for a declared target name or an IP literal (getaddrinfosends nothing for a literal);resolve*,reverse,lookupServiceand every other query go to a DNS server whatever host they name, and are refused.setServers, which decides where every later query goes, is journaled and refused, on the module, ondns.promisesand on the base prototype theResolverinstances share; - every call of
process.binding(the door to the internal TCP, UDP and DNS handles, which do not go throughnet.Socket), every native addon load (process.dlopen), every worker thread (worker_threads.Workeris replaced by a constructor function, so the original is not reachable as its prototype), and every child process: thechild_processfunctions, theChildProcessclass itself (where every asynchronous form ends) andprocess.execve.
What it does not see is said here, not measured as zero: in journal mode, the connections made through a handle taken from
process.binding or by a native addon (only the call or the load is journaled), those of a child process started with its own
environment (the guard reaches child Node.js processes through NODE_OPTIONS, so only when they inherit it; the start is
journaled), those of programs other than Node.js started as children, and those of the program process.execve puts in place
of the process on POSIX (the call is journaled); in both modes any file opened on a path the system resolves over a network (a
UNC path, a network mount), whatever opens it (fs, import, node:sqlite: none is wrapped), WHERE a declared target name
resolves (the system's answer, or the caller's own lookup, whose answer is journaled for TCP and not judged), internalBinding
(not reachable without --expose-internals), node:quic where a build offers it (the Node.js 24.14 measured here has no such
module, with or without --experimental-quic), code written on purpose to reach the original functions by some other way than
the ones named above (the guard measures the ordinary calls of the tool and of its libraries; it is not a sandbox), and anything
outside the Node.js process. In blocking mode each of those starts (and every multicast membership change and setServers) is
refused, so none of them runs. Workers are started with the guard preloaded
(--require in their execArgv), so in journal mode their connections are in the journal. The tool itself uses none of these
paths: its sources import no child_process, worker_threads or native module and call no process.binding or lookupService, which
the test checks.
The test (proba-expunere.mjs) runs every mode under it: mode 1 shows zero connections and zero DNS queries; mode 2 shows exactly
as many connections as the mock target accepted, all to the declared host and port. Its negative control plants, in a copy of the
tool, one exemplar of each path above and requires, in blocking mode, the journal to name each and the guard to refuse each: the
network paths toward a documentation address (198.51.100.9) or names under .invalid, lookupService on 127.0.0.1 (a literal is
a real query there), the named pipe toward 127.0.0.1 as its server, and the paths that carry no address (process.binding,
process.dlopen, a worker, the worker taken by reflection, which must not start, a child process through a function and through
the class, process.execve). Three more controls count what ARRIVES at a listener of the test, and each has inverse plantings
in a copy of the guard itself: the pipe forms that name a server through .., through the NT prefix \??\, through
GLOBALROOT\Device\Mup or through a string given to socket.connect reach a pipe the test listens on (over SMB on loopback)
when let through, and in blocking mode only the local form arrives (the guard judging the raw string, or reading the string as a
port with localhost declared, lets them arrive); the DNS queries of mode 2's configuration (a reverse, a TXT, a TLSA and an A
query naming the declared literal or name, and every setServers) reach nothing, with the process's DNS servers moved, before
the guard loads, to a UDP listener of the test on 127.0.0.2, so that nothing leaves the machine even if the guard let them
through (the old rule for a declared literal, and a fixed list of function names, each let datagrams arrive); and the UDP sends
to the declared target, in the three argument forms of send and through sendto, arrive while a send to another port and a multicast membership on the
loopback interface are refused (the old reading of the arguments, and an unwrapped membership, turn it red). In journal mode,
where a planted call really runs, it plants only local ones (a worker's and a child's connection to 127.0.0.1, a connected UDP
socket, a connection with its own lookup) and requires each in the journal. The tool run without the guard produces no journal,
and the test treats that as unmeasured, not as zero; the planted copy is never run without the guard, since its paths would send
real packets. The pipe control needs the SMB client of Windows (loopback only) and is not measured on other systems.
Test
node proba-expunere.mjs
Codec checks with real public vectors (BIP-173/350 test vectors, the EIP-155 example, every mainnet transaction in the fixtures
re-serialized byte for byte and hashed to its id, every transaction of a recorded Ethereum block with its sender key recovered
and its hash re-derived, the EIP-7702 authorization of that block with its authority recovered, transactions of types 0x1 and
0x3 signed with a test key and recovered); mode 1 and mode 2 through four mock targets (Electrum, Esplora, Bitcoin Core, Ethereum
JSON-RPC) that serve responses recorded from a public index on the date written in fixturi/*.json; and negative controls: a
planted foreign provider on each network path is caught, an empty or error answer and a mute target give unmeasured, Bitcoin
Core without --txids, with only the funding transactions of a spent address, with a list that touches only one of the
addresses, with a list that touches an address only through a transaction in the node's mempool, or with the funding of an
output still unspent, gives unmeasured (never "no on-chain evidence") with the reason of each case, a revealing spend that
Bitcoin Core names in a block off its active chain gives unmeasured (exposed once on the active chain), a transaction id the node does not
serve and a decoded transaction with one value changed are refused (the decoded form is re-serialized and must hash to its id),
an Esplora transaction whose id or one value was changed gives unmeasured, a planted spend on a synthetic address moves the
label to exposed (also through a P2PKH-shaped redeem script and a witness script, through a second Esplora page, through a
hybrid key and a scriptSig that does not end with the key), a spend seen with no preimage the tool can read gives unmeasured
(also a P2SH-P2WPKH spend served without its witness and without its funding, seen through its redeem script), a
spend seen only in the mempool gives unmeasured while a P2TR output already on chain stays exposed beside a pending
script-path spend, a P2TR output only in the mempool gives unmeasured, a replaced spend is named among the declarations, a
33-byte push in a non-standard witness script that is not the operand of a signature check gives unmeasured (the same key under
OP_CHECKSIG gives exposed), an EIP-7702 designator gives exposed, an authorization in a type-4 transaction whose hash was
changed is not used as evidence, an --eth-blocks window after the head is refused, a forbidden label planted in a copy is
refused by the language gate while a --subiect or an error body served by the target holding such a word is recorded as given,
and so is, quoted, such a word served in place of an identifier (an Electrum tx_hash, an Esplora txid, a scantxoutset
output, an Ethereum transaction hash), of a quantity (eth_chainId, a signature field) or after the 20 bytes of an EIP-7702
delegate, and inside a served message cut at its length limit (a Bitcoin Core block hash that is not one refuses the answer
instead), mode 3 without consent and mode 3 toward a private target are refused with nothing sent, a report or envelope changed after
signing is refused, a mock started detached by a parent that exits stops by itself, and a @noble/curves 2.x found first is
passed over (exit 0 with 1.x beside it, exit 2 named without); a target whose tip is the real header of block 969686
(2026-10-03) gives unmeasured ("target behind the chain") where the recorded tip gives no on-chain evidence, and a tip header
changed by one byte, or one that does not hash to the block hash the index gives, is not a tip; a key revealed only at a sibling
script (a P2WPKH spend, for its P2PKH address and for the p2pk: line of the key) gives exposed with the sibling named, through
Electrum and Esplora, and every no on-chain evidence names the siblings read, the N8 line and the other chains; the third
question is asked; a TLS listener of the test whose certificate names a forbidden word gives a written report (unmeasured, the
message quoted), not a refusal; an Esplora index that counts a mempool spend its page does not carry, beside another mempool
transaction of the address that the page does carry, gives unmeasured. Since round 7: the five genesis hashes are recomputed
from their headers and must equal the tool's table, and the BIP-86 derivation must give the BIP-86 test vector; a target serving
the regtest genesis block and a fresh regtest tip gives unmeasured ("serves another chain") for a mainnet address and measures a
bcrt1 address; a signet tip (mined once, see the test) under the mainnet genesis block gives unmeasured ("above the limit of
Bitcoin mainnet") for a P2PKH address and a p2pk: line, while under the signet genesis block a tb1 address is measured and the
p2pk: line is unmeasured ("mainnet only"); every verdict on the fixtures names the chain; an Ethereum endpoint whose head is
block 18,000,000 (2023-09-07) or that reports eth_syncing gives unmeasured ("endpoint behind the chain") where the recorded head
gives no on-chain evidence naming the head; --data 2030-01-01 is refused in modes 1 and 2, and --data 2012-01-01 gives
unmeasured ("serves blocks after the report date") on every address through Electrum, Esplora, Ethereum and Bitcoin Core with
and without --txids; through Bitcoin Core, a revealing spend in a block timed 400 days ahead, or served without its block time,
gives unmeasured (exposed with a past block time); a p2pk: line whose key is shown only in its other encoding (a spent P2PKH
of the uncompressed form, a P2PK output of the compressed form) or by its BIP-86 output gives exposed with the sibling named,
through Electrum and Esplora, and one with no history gives no on-chain evidence with its 12 siblings listed and an N8 line that
names only what cannot be derived; an Esplora index that counts a mempool output of a P2TR with no output on chain, or a mempool
transaction of a P2PKH, that its page does not carry gives unmeasured. Since round 8: the tool's checkpoints equal the pins of
btc/antete.mjs and their headers hash to their hashes, and the checkpoint chosen is the highest at or below the tip; the trie
builder of the test gives the known roots of the Ethereum trie test vectors (the empty trie, "dogs", "puppy"), and the tool's
proof walker agrees with the published one on every proof of the fixture and on an absence; an Ethereum mock that answers as a load
balancer with a machine behind (header not found at the head, nonce 0 at latest) gives unmeasured for an account that the
old reading labelled "no on-chain evidence", a state after the head at latest (an EIP-7702 designator) does not label the account
at the head, and a code that does not hash to the proven code hash, a proof of another state and an endpoint without eth_getProof
give unmeasured, while the real designator still gives exposed; a target of a chain that split from Bitcoin (the mainnet genesis
block, a real mainnet tip, another block at the checkpoint height) gives unmeasured ("serves another chain: ... split from it
before height 969696") through Electrum, Esplora and Bitcoin Core, and a chain ending below the lowest checkpoint gives unmeasured;
Bitcoin Core on regtest gives unmeasured for a p2pk: line it would otherwise label exposed from its scan; an Esplora count that
is null, a missing or negative mempool_stats, an index that counts more outputs than its pages carry, and a transaction in a
script's history that neither pays nor spends it (Esplora and Electrum) each give unmeasured. For each defect the test exists to catch (an empty answer
labelled "no on-chain evidence", Bitcoin Core without --txids, with an incomplete list, with a list that does not touch the
address, that touches it only in the mempool, or that cannot be shown complete, labelled "no on-chain evidence", a pending spend
that removes the evidence of a P2TR output, a spend seen labelled "no on-chain evidence" (also one seen only through its hash), a
P2TR output only in the mempool labelled "no output exists yet", an Esplora or type-4 transaction cited by an id it does not hash
to, a served error body or identifier refused by the language gate, a guard that judges a pipe on its raw string, that lets a
declared literal be queried, that wraps a fixed list of DNS names, that reads a datagram's offset as its port, a mock that
outlives its parent, a loader that crashes on the wrong @noble/curves; since round 6, a tip left unjudged, sibling scripts not read, a TLS message
left unquoted, the mempool rule that looked only for an empty page; since round 7, a genesis block not compared with the address's
network, a tip header's proof-of-work limit not judged, an Ethereum head time or sync status not judged, a report date not judged
or a future one not refused, a Bitcoin Core block time ignored or missing, the other encodings and the BIP-86 output of a p2pk:
key not derived, the mempool output and transaction counts not compared; since round 8, an Ethereum account read at latest, its
proven values replaced by the fields served beside the proof, its code not hashed to the proven code hash, a checkpoint not
compared, a chain below the lowest checkpoint let through, Bitcoin Core's chain not judged, an Esplora count read as Number(x), an
unread mempool_stats ignored, the outputs counted on chain not compared, a transaction that does not touch the script accepted),
the test plants the defect in a copy of the tool (or of
the guard) and requires its own check to turn red on it. Where one rule backs up another (Bitcoin Core never gives "no on-chain
evidence"), the planting of an earlier rule removes the backstop too, so that each rule is shown to hold the label by itself.
Every check has three states (green, red, broken); a control that cannot plant is a failure; an exception inside the test is a
failure (exit 1), never "unmeasured". The copies are siblings of this directory (.expunere-planta-*, ignored by git), and the
test's working directory is expunere-proba-<pid>-* in the system's temporary directory; a run that is killed leaves both, and
the next run deletes those whose process (the pid in the name) is gone, never those of a run still alive. Working directories of
the form used before 2026-10-12 (expunere-proba-* without a pid) cannot be judged: the run names them and leaves them. A pid
reused by another process keeps its orphan until that process ends. Not measured by the test: mode 3
against a real provider, and the adapters against real servers (the Bitcoin Core responses in the fixtures are shaped after the
RPC documentation, not recorded from a node). The sibling scripts of the recorded vectors were not recorded: the mocks answer a
script they do not hold as an index answers a script never used (an empty history), so the test's no on-chain evidence on the
never-spent P2PKH vector rests on the recorded history of the address itself and on empty, unrecorded answers for its siblings.
The tip was not recorded with the fixtures either: the mocks serve the real header of block 971000 (2026-10-11T22:53:11Z), taken
from the header chain the C6 Bitcoin P2P follower validated that evening (provenance in each fixture's _sursa.nota), and the
test dates its reports 2026-10-11, so that the reference instant (the end of that day) is fixed and the tip is within its margin.
Nor were the genesis answers (server.features, /block-height/0: the mainnet genesis hash, a constant of the chain that the test
recomputes) or the Ethereum head and eth_syncing (false, and for the head block only its number and the EARLIEST time it can
carry, derived from the recorded block 517 blocks before it; provenance in _sursa.nota). Reports through Bitcoin Core are dated
the day of the run, since its scan reads the node at the time of the run. The Esplora load-balancer case above is not measured.
Since round 8 the fixtures also answer the checkpoint blocks (Electrum blockchain.block.header, Esplora /block-height/:h,
Bitcoin Core getblockhash) and the chain of the Bitcoin Core node (getblockhash 0, getblockcount 971000): none was recorded;
the checkpoint answers are the pins of btc/antete.mjs. The fork of the test serves, at the checkpoint height, the real header of
another mainnet block (969686), not a header of Bitcoin Cash: no such header can be produced offline, and what the guard compares
is the hash at that height. The Ethereum STATE of the fixtures is SYNTHETIC: a Merkle-Patricia trie of the four recorded accounts
(balance 0, empty storage), built from the code and the transaction count recorded at latest, whose root is given as the state
root of the head block (which was not recorded either); the test checks the builder against the Ethereum trie test vectors and
the walker against the published one, so a real eth_getProof answer of a public endpoint is NOT measured here.
The fixtures keep every path and field the adapters read on the recorded answers; the paths no adapter requests (Esplora
/tx/:id, /tx/:id/hex, /utxo) and, in the Esplora transaction pages, the fields the adapter never reads (*_asm,
scriptpubkey_type, scriptpubkey_address) were removed to keep them small, as written in each file's _sursa.nota. The Bitcoin
Core fixture keeps the node's verbose form with hex for every transaction (what the adapter parses and hashes to the id), and
the decoded vin / vout, which the adapter reads only when a node serves no hex, for the transactions of the three roles on
which the test exercises that path; its confirmations are derived from the recorded heights (the adapter does not read them).
The real vectors (fixturi/vectori.txt, provenance in fixturi/alese.json) are historical or ownerless addresses where a
never-spent address was needed (the block-1 coinbase key, the P2PKH form of the block-11 coinbase key, a provably unspendable
and never-funded P2TR address, the BIP-350 example address whose output key is the generator point, the addresses of the
private keys 3 and 7, the burn address 0x...dEaD), and otherwise addresses with no remaining balance on the recording date.
Reviewed on 2026-10-14 (round 9, the hostile review of the round-8 form as left on disk; the round-8 agent was stopped by the
account's weekly limit before its report): node proba-expunere.mjs exit 0, 164 green, 0 red, 0 broken, 1 unmeasured (mode 3
against a real provider, by design), 123 s; the round-7 reviewer's attack harness replayed against this code: R1 (an Ethereum
balancer with one machine behind) not reproduced (0 of 40 "no on-chain evidence" with a random balancer, unmeasured on each
route), R3, R4, R5 (Esplora counts) not reproduced, R8 (a key spent before the split) exposed on the fork as it must be; R8b
still prints "no on-chain evidence" in THAT harness only because its mock answers /block-height/969696 and
blockchain.block.header(969696) with the tool's own checkpoint block (recorded in the fixtures): it models an index that serves
Bitcoin's headers and checkpoint with a foreign history, which is the lying index named under Limits (the history is not checked
against the headers with Merkle proofs), not an honest fork, whose block at that height is its own (case m.2, unmeasured) or whose
chain ends below 967680 (m.3, unmeasured).
Outside the AERE development tree
The tool and its test look for what they need in two layouts: the AERE development tree (tools/aere-quantum-shield/expunere/
beside cloud-gateway/ and verificator-falcon/) and the public copy (exposure-report/ beside readiness/, control-plane/
and proof-kinds/ in the aere-quantum repository). Each file is found by its place, or named by an environment variable:
| file | what it is | development tree | public copy | variable |
|---|---|---|---|---|
adrese-private.mjs |
the private-address guard of mode 3 (required) | cloud-gateway/ |
readiness/ |
AERE_EXPUNERE_ADRESE_PRIVATE |
raport-conformitate.mjs |
the compliance-report module: deadlines, canonical hashing (required) | cloud-gateway/control-plane/ |
control-plane/ |
AERE_EXPUNERE_TERMENE |
proof-kinds.mjs |
the AIP-23 envelope builder (required) | tools/proof-kinds/ |
proof-kinds/ |
AERE_EXPUNERE_PROOF_KINDS |
verify.mjs |
the AIP-23 reference verifier (the test compares with it) | tools/aere-proof-protocol/ |
not in the copy | AERE_PROOF_PROTOCOL_VERIFY |
verify-proof.mjs, mpt.mjs |
the anchor verifier's RLP encoder and account-proof walker (the test compares with them) | verificator-falcon/ |
not in the copy | AERE_VERIFY_PROOF (mpt.mjs beside it) |
antete.mjs |
the shield's Bitcoin header pins (the test compares the checkpoints with them) | tools/aere-quantum-shield/btc/ |
not in the copy | AERE_EXPUNERE_PINI |
node_modules/ |
the two npm packages, for a copy of the tool that has none (the test's copies) | the anchor verifier's | this directory's, after npm install |
AERE_EXPUNERE_NODE_MODULES |
The two npm packages are resolved as described at the top (npm install --ignore-scripts --no-audit --no-fund in this directory).
In a copy without the three "compared with" neighbours the test still runs; the rows that need them (the compliance envelopes
judged by the reference verifier, the RLP encoder, the MPT walker, the checkpoint pins) and the scan of this directory for names
from the AERE host inventory are reported as NEMASURAT with the reason, not as passed; the exit code stays 0, as it does for every
NEMASURAT row (it is 1 only when a row is red or broken). The git-ignore row is NEMASURAT outside a git tree. Measured on
2026-10-14 in the public copy after npm install: 153 rows passed, 0 red, 0 broken, 12 NEMASURAT (the ten rows above, the
scan and the git-ignore row); in the AERE development tree: 164 passed, 1 NEMASURAT (mode 3 against a real provider). Nothing in the public copy reads anything from the AERE development tree.
Files
raport.mjs (the command line), clasifica.mjs (the classification and its vocabulary), limbaj.mjs (the two labels and the
language gate), bitcoin.mjs (addresses, scripts, transactions, evidence from inputs, block headers, the networks and the
mainnet checkpoints), ethereum.mjs (EIP-55, RLP, signing hashes, key recovery, EIP-7702 authorizations, the account-proof walker
and, for the test, a trie builder), adaptoare.mjs (the four adapters), noble.mjs (the dependency loader),
paza-retea.mjs (the connection counter), machete.mjs (mock targets for the test), fixturi/ (recorded responses and the
real vectors), proba-expunere.mjs (the test), package.json.