aere-quantum/verify-layer
2026-09-29 22:58:20 +03:00
..
dovezi-notarizare-testnet Add verify-layer (an audit-log sidecar whose head can be notarized on Aere Network for post-quantum finality) and proof-kinds (the AIP-23 envelope builder it uses) 2026-09-29 18:09:34 +03:00
control-negativ-sidecar.mjs verify-layer: signed heads. keygen makes the operator's ML-DSA-65 head key; attest-head --sign-key signs the head statement in the form the AIP-23 reference verifier checks at its signature level (outside the statement, so the statementHash and its notarization are unchanged); verify-log --attested --signer requires the expected key, and without it says who signed and that no expected signer was compared. A signature says who vouches for the head, not when and not that the history is true. Tests 44/44 with the reference verifier (39 run and 5 skipped without it); negative control 9/9 here. 2026-09-29 22:58:20 +03:00
package.json Add verify-layer (an audit-log sidecar whose head can be notarized on Aere Network for post-quantum finality) and proof-kinds (the AIP-23 envelope builder it uses) 2026-09-29 18:09:34 +03:00
proba-notarizare-testnet.mjs Add verify-layer (an audit-log sidecar whose head can be notarized on Aere Network for post-quantum finality) and proof-kinds (the AIP-23 envelope builder it uses) 2026-09-29 18:09:34 +03:00
proba-sidecar.mjs verify-layer: signed heads. keygen makes the operator's ML-DSA-65 head key; attest-head --sign-key signs the head statement in the form the AIP-23 reference verifier checks at its signature level (outside the statement, so the statementHash and its notarization are unchanged); verify-log --attested --signer requires the expected key, and without it says who signed and that no expected signer was compared. A signature says who vouches for the head, not when and not that the history is true. Tests 44/44 with the reference verifier (39 run and 5 skipped without it); negative control 9/9 here. 2026-09-29 22:58:20 +03:00
README.md verify-layer: signed heads. keygen makes the operator's ML-DSA-65 head key; attest-head --sign-key signs the head statement in the form the AIP-23 reference verifier checks at its signature level (outside the statement, so the statementHash and its notarization are unchanged); verify-log --attested --signer requires the expected key, and without it says who signed and that no expected signer was compared. A signature says who vouches for the head, not when and not that the history is true. Tests 44/44 with the reference verifier (39 run and 5 skipped without it); negative control 9/9 here. 2026-09-29 22:58:20 +03:00
sidecar.mjs verify-layer: signed heads. keygen makes the operator's ML-DSA-65 head key; attest-head --sign-key signs the head statement in the form the AIP-23 reference verifier checks at its signature level (outside the statement, so the statementHash and its notarization are unchanged); verify-log --attested --signer requires the expected key, and without it says who signed and that no expected signer was compared. A signature says who vouches for the head, not when and not that the history is true. Tests 44/44 with the reference verifier (39 run and 5 skipped without it); negative control 9/9 here. 2026-09-29 22:58:20 +03:00

AERE Verification Layer (sidecar)

A sidecar that runs next to a deployment, on any cloud or on premises, and keeps an audit log of it. Every entry is an AERE Proof Protocol envelope (AIP-23), built by the same proof-kinds builder and checked by the same AIP-23 reference verifier as every other AERE proof, and every entry covers the hash of the one before it (an append-only hash chain, hash = sha256(seq | prev | envelope), genesis 0x00..00). The head of the chain can be notarized on Aere Network, which gives the history up to that head post-quantum finality that anyone can check.

Node.js 24, no dependencies; notarize-head alone needs ethers (npm install in this directory).

What it proves, and what it does not

  • The content of the entries is what the host says. The sidecar runs on the host and reads the export the operator makes. An entry proves that the host declared a container was running, with this image and this configuration, at the time it declared (--at is also a declaration). It does not prove that the declaration is true.

  • The chain has no key. Whoever can write the log file can rebuild the whole chain from genesis with any content, and verify-log on that file alone reports it intact. A partial edit is caught (the chain breaks at the edited entry and verify-log names it), a full rewrite is not.

  • What is checkable without trusting the host is the history before a published head. attest-head turns the current head into an envelope; notarize-head records its hash on the AereNotary contract; the AIP-23 reference verifier then gives it post-quantum finality (the most recent certified anchor, the parent header bound by hash, its state root, and a Merkle proof of firstSeen[statementHash]). verify-log --attested <head.json> requires that today's log continues that head: the same first count entries, the entry count - 1 hashing to the attested head. A rewritten or shortened history fails.

  • A head can be signed (2026-09-29). keygen makes an ML-DSA-65 key for the operator; attest-head --sign-key signs the head statement in the form the AIP-23 reference verifier checks at its signature level; verify-log --attested head.json --signer head.pub.pem then requires that the head was signed by that key. A signature says who vouches for the head, not when (the notarization says when) and not that the history is true: whoever holds the key can sign the head of a rewritten history, and without --signer a head signed by anyone passes, which the output says.

So: publish heads often (every deployment, or on a timer), sign them with the operator's key, and judge a log against the latest head you hold from outside the host, with the signer you expect, never on its own.

Commands

node sidecar.mjs record --kind runtime --artifact <file> --attested 0x<sha256> [--host h] [--log p] [--at T]
node sidecar.mjs record --kind deployment --name <name> --version <v> --content-file <file> [--host h] [--log p] [--at T]
node sidecar.mjs record --kind proof --proof-file <envelope.json>     # any AIP-23 envelope, after checking its form and hash
node sidecar.mjs scan --source docker|kubernetes --input <export.json> [--host h] [--log p]
node sidecar.mjs verify-log --log p [--attested head.json [--signer head.pub.pem]]   # 0 intact (and continues the head), 1 broken or not continued
node sidecar.mjs attest-head --log p [--host h] [--out head.json] [--sign-key head.key.pem]   # the head as an aere-audit-head envelope
node sidecar.mjs keygen --out <dir>                                    # the operator's ML-DSA-65 head key (head.key.pem 0600, head.pub.pem)
node sidecar.mjs notarize-head --head head.json --rpc <url> --key-file <file> [--notary 0x..] [--out notarized.json]
node sidecar.mjs bundle --log p [--out bundle.json]                    # {host, count, head, chainOk, entries[]} for a console

The default log is aere-audit.log in the current directory. Writers take a lock file beside the log (<log>.lock, holding the writer's process id), so two writers on the same host do not break the chain; a lock left by a process that no longer exists is removed after 10 seconds. A log that is already broken is never appended to.

record --kind runtime records that a file on the host (the binary that runs) has the digest you expected: matches says whether it does. record --kind deployment records the digest of a deployed artifact, never its content.

The runtime adapter

scan reads an export the operator makes on the host, so the sidecar needs no access to the Docker daemon, the cluster, or any cloud credential:

docker inspect $(docker ps -q) > export.json          # then: scan --source docker --input export.json
kubectl get pods -A -o json > export.json             # then: scan --source kubernetes --input export.json

For every container that is running, it writes a canonical descriptor (image and image id, name, namespace and pod on Kubernetes, start time, restart count, the hash of the command line, the names of the environment variables, the destinations of the mounts) and records the descriptor's digest as a deployment entry. No environment value, no argument in clear and no host path of a mount is stored. The descriptors themselves are written beside the log (<log>.descriptori.jsonl), so an auditor can re-hash each one and compare it with the chain. An export with no running container, or one that is not in the expected shape, is refused rather than recorded as "zero deployments".

A hash does not hide a guessable secret: if a password is passed on a command line, anyone who knows the rest of the command can test guesses against commandSha256. Pass secrets through files or the environment, not arguments.

Notarization

notarize-head reads the chain id from the RPC endpoint (it does not trust an argument), refuses an attestation whose statement does not match its hash before sending anything, and sends nothing if the head is already notarized. On chain 2800 (Aere Network mainnet) the notarization is a real transaction paid by the key's account, so it is sent only with AERE_CONFIRM_MAINNET=yes. The key is read from a file (d=<hex> or PRIVATE_KEY=0x<hex>) and never printed; error messages are cut before any long hex string.

dovezi-notarizare-testnet/ holds a real run on the public testnet (chain 28001): a log of three test deployments and its head, notarized in block 3,699,939 on 2026-09-27. Anyone can check both halves:

node sidecar.mjs verify-log --log dovezi-notarizare-testnet/audit.log --attested dovezi-notarizare-testnet/cap-notarizat-28001.json
node verify-proof.mjs dovezi-notarizare-testnet/cap-notarizat-28001.json      # the AIP-23 reference verifier (aere-node/tools)

The first says the log is the one that was notarized; the second, measured on 2026-09-29, reports finality: PASSED post-quantum under a certified anchor of the testnet, and VALID.

Tests

node proba-sidecar.mjs              # 44 checks
node control-negativ-sidecar.mjs    # puts each guard back to its absent form and requires the named check to fail

proba-sidecar.mjs records runtime, deployment and envelope entries; checks that a modified entry breaks the chain at its seq, a changed hash is caught, and nothing is appended to a broken chain; that a chain rebuilt from genesis passes verify-log alone (the stated limit) but fails against the attested head, as do a shortened log and a modified attestation, while a log that only grew passes; that a signed head passes the verifier's signature level and --signer, while a head signed by another key, an unsigned head with --signer, and a damaged signature are refused; that two writers appending 150 entries each at the same time leave an intact chain of 300; that a line that is not JSON is reported as broken at its seq; that the Docker and Kubernetes adapters record only running containers and no secret value; and that every envelope is VALID for the AIP-23 reference verifier. The verifier is looked for at AERE_VERIFY_PROOF (for example verify-proof.mjs from the aere-node repository, after npm install there); without it, the five checks that need it are reported as skipped and the exit code is 2, not 0.

control-negativ-sidecar.mjs measured 10 of 10 on 2026-09-29: the version before the review (taken from the development history, skipped where that history is absent) and nine plantings, each disabling one guard (the writer lock, the head comparison, the length check, the attestation hash check, the handling of an unreadable line, the digest check of --attested, the head signature check, the comparison with --signer, the refusal of an unsigned head when --signer is given). proba-notarizare-testnet.mjs --key-file <testnet key> repeats the on-chain run above (9 checks, it needs a funded testnet key).

What it is not (yet)

It does not report to a live console over the network, and it has no per-cloud adapters that read deployments from the AWS, Azure or GCP APIs with credentials: the runtime adapter above, without credentials, is the first one. It signs heads, not each entry, and the host name in an entry is still a declaration. No third party has reviewed it.