Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
313 lines
16 KiB
YAML
313 lines
16 KiB
YAML
# Aere Network contracts: continuous verification
|
|
#
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# WHY THIS FILE IS AT .github/workflows/ AND NOT .gitea/workflows/
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# The canonical host is our own Gitea instance at git.aere.network. Gitea Actions
|
|
# scans BOTH `.gitea/workflows/` and `.github/workflows/`, and prefers `.gitea/`
|
|
# only when both exist. GitHub Actions scans `.github/workflows/` only.
|
|
# Putting the single source of truth at `.github/workflows/ci.yml` therefore
|
|
# means the same unmodified file runs on both hosts. That is the point: a
|
|
# stranger who mirrors this repo to GitHub gets the same pipeline with zero
|
|
# edits, and can compare their run against ours.
|
|
#
|
|
# Portability notes for anyone porting this elsewhere:
|
|
# * `actions/checkout@v4`, `actions/setup-node@v4` and
|
|
# `actions/upload-artifact@v3` all resolve on Gitea Actions, which proxies
|
|
# to github.com for action resolution by default.
|
|
# * upload-artifact is pinned at v3, not v4. Gitea's artifact backend
|
|
# implements the v3 upload protocol; v4 changed it and fails on Gitea at the
|
|
# time of writing. On GitHub, v3 still runs. If you are GitHub-only, v4 is
|
|
# fine.
|
|
# * Gitea runners need a label matching `runs-on`. Ours registers
|
|
# `ubuntu-22.04`. If your runner registers only `ubuntu-latest`, change the
|
|
# three `runs-on:` lines.
|
|
#
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# HONESTY STATEMENT: DOES THIS PIPELINE CURRENTLY PASS?
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Recorded here rather than in a README so it cannot drift away from the file it
|
|
# describes. Status as of 2026-07-20, measured on the maintainer's machine
|
|
# (Windows 11, Node v24.14.1, npm 11.11.0), not assumed:
|
|
#
|
|
# job: build-and-test .......... SEE STATUS BLOCK BELOW (filled from a real run)
|
|
# job: pqc-kat ................. SEE STATUS BLOCK BELOW
|
|
# job: coverage ................ RUNS, but produces a SCOPED number, not a
|
|
# whole-repo number. See the long comment on
|
|
# that job. This is a measured limitation of a
|
|
# 16 GB runner, not a choice to look good.
|
|
#
|
|
# What this pipeline deliberately does NOT do, because each would be a way of
|
|
# being green while being wrong:
|
|
# * no `continue-on-error` on any compile, test or KAT step;
|
|
# * no `|| true` swallowing a non-zero exit anywhere in a gating step;
|
|
# * no coverage threshold invented below the real number to manufacture a
|
|
# pass. The one threshold that exists is set AT a measured value and exists
|
|
# to catch regression;
|
|
# * no test file excluded from the suite for being inconvenient. `skipFiles`
|
|
# in .solcover.js is empty;
|
|
# * `npm ci` (not `npm install`), so the lockfile is authoritative and a
|
|
# drifting transitive dependency fails the build instead of silently
|
|
# changing what was tested.
|
|
#
|
|
# If the suite goes red, the correct response is to fix the suite. Editing this
|
|
# file to make it green again is the failure mode this file exists to prevent.
|
|
|
|
name: contracts-ci
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
# Least privilege. This pipeline reads code and writes artifacts. Nothing else.
|
|
permissions:
|
|
contents: read
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# THE SECRETS BOUNDARY
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# This is a public CI on a public repository. It has NO access to, and must
|
|
# never be given access to:
|
|
#
|
|
# * validator signing keys for the 7 chain-2800 validators;
|
|
# * any deploy key, funded account private key, or mnemonic with a balance;
|
|
# * infrastructure credentials (SSH, Hetzner API, DNS, TLS private keys);
|
|
# * RPC admin or any authenticated write endpoint;
|
|
# * the explorer, relayer or oracle service credentials.
|
|
#
|
|
# The `env:` block below is exhaustive and contains no `secrets.*` reference.
|
|
# That is verifiable by reading this file: grep it for `secrets.` and you get
|
|
# nothing. A fork's pull request runs the identical pipeline with the identical
|
|
# (empty) credential surface.
|
|
#
|
|
# WHY A PUBLIC CI STRUCTURALLY CANNOT TOUCH THE LIVE CHAIN, beyond just not
|
|
# holding keys:
|
|
# 1. Every job runs against an in-process Hardhat EVM at chainId 31337. It
|
|
# never dials chain 2800.
|
|
# 2. The only account material anywhere in the test path is the well-known
|
|
# public Hardhat mnemonic ("test test test ... junk"), which holds nothing
|
|
# on any real network.
|
|
# 3. Writing to chain 2800 requires a signature from a funded key. No such key
|
|
# exists in this repository, in this pipeline, or in the runner
|
|
# environment, so a compromised workflow file, a malicious dependency, or a
|
|
# hostile pull request still cannot produce a valid mainnet transaction.
|
|
# 4. Deployment is a separate, human-gated, offline procedure. It is not
|
|
# automated from CI by design, and moving it into CI would be a downgrade.
|
|
#
|
|
# Naming this boundary is part of the argument, not a caveat to it. "Radical
|
|
# open source" means every number here is reproducible by a stranger; it does
|
|
# not mean the keys are open. Linux publishes all of its code and none of its
|
|
# signing keys.
|
|
|
|
env:
|
|
# Coverage instrumentation is memory-hungry. Raise V8's old-space so the
|
|
# sharded run has room. NOTE: this bounds only the V8 old-space heap; the bulk
|
|
# of coverage's growth is Hardhat's in-process EVM state plus the
|
|
# per-statement hit maps, which live OUTSIDE this budget. Do not expect this
|
|
# setting alone to make a whole-repo run fit.
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
# Never let a stray config or test reach a real endpoint from CI.
|
|
AERE_RPC_URL: http://127.0.0.1:0
|
|
|
|
jobs:
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
# 1. THE GATE. Clean-checkout install, compile, full suite. No exceptions.
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
build-and-test:
|
|
name: Install (lockfile) → compile → full test suite
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 90
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
# Pinned to the version the numbers in REPRODUCE.md were measured on.
|
|
node-version: '24.14.1'
|
|
cache: 'npm'
|
|
|
|
- name: Record toolchain versions
|
|
# Printed so any run can be compared against any other run. A number is
|
|
# only reproducible if you know what produced it.
|
|
run: |
|
|
echo "node: $(node --version)"
|
|
echo "npm: $(npm --version)"
|
|
echo "os: $(uname -a)"
|
|
echo "mem: $(free -g | awk '/^Mem:/{print $2" GB"}')"
|
|
echo "cores: $(nproc)"
|
|
|
|
- name: Install from the lockfile
|
|
# `npm ci` NOT `npm install`. ci deletes node_modules and installs the
|
|
# exact lockfile tree, failing if package.json and package-lock.json
|
|
# disagree. That is precisely what an outsider cloning this repo does,
|
|
# and it is the step that catches "works because of what is already on
|
|
# my machine".
|
|
run: npm ci
|
|
|
|
- name: Compile contracts
|
|
run: npx hardhat compile
|
|
|
|
- name: Full test suite
|
|
# THE GATE. No continue-on-error. A single failing test fails the run.
|
|
run: npx hardhat test
|
|
|
|
- name: Upload compiled artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: build-artifacts
|
|
path: |
|
|
artifacts/
|
|
retention-days: 30
|
|
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
# 2. PQC KNOWN-ANSWER TESTS, isolated so a crypto regression is legible.
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
#
|
|
# These run inside the full suite too. They are ALSO run alone, because
|
|
# "did the post-quantum verifiers still match the official NIST vectors" is
|
|
# the single question this project is most often asked, and burying its answer
|
|
# in a 1,400-line log is a bad answer.
|
|
#
|
|
# SCOPE HONESTY, because this is the most misread part of the project:
|
|
# * These KATs exercise the SOLIDITY verifier implementations against the
|
|
# official NIST reference vectors (falcon512-KAT.rsp / falcon1024-KAT.rsp,
|
|
# FIPS 202 SHAKE256).
|
|
# * They run on the local Hardhat EVM, which has NO Aere precompiles. The
|
|
# precompile-backed verification paths (0x0AE1 to 0x0AE5 live on chain
|
|
# 2800; 0x0AE6 ML-KEM-768 and 0x0AE7 HashToPoint are TESTNET-ONLY) cannot
|
|
# be exercised here and are therefore NOT covered by this job. Verifying
|
|
# those requires a precompile-bearing devnet. Do not read a green tick
|
|
# here as evidence about the precompiles.
|
|
# * Aere consensus is CLASSICAL secp256k1 ECDSA QBFT. Post-quantum
|
|
# cryptography exists at the signature, precompile, account and transport
|
|
# layers only. Nothing in this job says anything about consensus.
|
|
pqc-kat:
|
|
name: PQC known-answer tests (NIST vectors)
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '24.14.1'
|
|
cache: 'npm'
|
|
|
|
- name: Install from the lockfile
|
|
run: npm ci
|
|
|
|
- name: Compile contracts
|
|
run: npx hardhat compile
|
|
|
|
- name: Falcon / SPHINCS+ / ML-DSA known-answer tests
|
|
# No continue-on-error. A KAT mismatch is a hard failure: it means an
|
|
# implementation stopped agreeing with the NIST reference.
|
|
run: |
|
|
npx hardhat test \
|
|
test/AereFalcon512Verifier.test.js \
|
|
test/AereFalcon1024Verifier.test.js \
|
|
test/AereSphincsVerifier.test.js \
|
|
test/AerePQCMessageVerifier.test.js \
|
|
test/AereCryptoRegistry.test.js \
|
|
test/AereCryptoRegistry.agility.test.js \
|
|
test/AereCryptoRegistry.newprecompiles.test.js
|
|
|
|
- name: Upload KAT vectors used
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: pqc-kat-vectors
|
|
path: |
|
|
test/falcon512_kat0.json
|
|
test/falcon1024_kat0.json
|
|
retention-days: 90
|
|
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
# 3. COVERAGE. Read the scope statement before quoting any number from here.
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
#
|
|
# WHAT THIS JOB PRODUCES: a SCOPED coverage number over a named cluster of
|
|
# contracts, plus the raw istanbul JSON as an artifact so anyone can recompute
|
|
# it. It does NOT produce a whole-repository coverage percentage.
|
|
#
|
|
# WHY NOT, stated plainly rather than hidden behind a green tick:
|
|
# A single-process whole-repo coverage run does not fit in a standard CI
|
|
# runner's memory. Measured on a 15.78 GB / 33.78 GB-commit box, the process
|
|
# reached the entire commit limit and was killed by the OS during the TEST
|
|
# phase. The COMPILE phase is solved: all contracts instrument and compile,
|
|
# and zero contracts are excluded from instrumentation. The remaining blocker
|
|
# is memory during test execution, caused by per-statement hit maps for the
|
|
# on-chain crypto suites, which execute tens of millions of statements per
|
|
# verify.
|
|
#
|
|
# The path to a real whole-repo number is sharding: run coverage over subsets
|
|
# with --testfiles, then merge the per-shard istanbul JSONs. That merge is
|
|
# arithmetically valid because instrumentation is deterministic over identical
|
|
# sources, so every shard emits identical statement/branch/function maps and
|
|
# the per-counter arrays sum. Until that full sharded run completes end to
|
|
# end, the honest whole-repo figure is NOT MEASURED, and this pipeline says so
|
|
# rather than substituting a scoped number for it.
|
|
#
|
|
# ANY figure of the "99.8% coverage" kind that has ever been attached to this
|
|
# project was never produced by a coverage tool. It was a target written as
|
|
# though achieved. It is not a claim this repository makes. The numbers below
|
|
# are the real ones, and they are lower.
|
|
coverage:
|
|
name: Coverage (scoped, with artifacts)
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 120
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '24.14.1'
|
|
cache: 'npm'
|
|
|
|
- name: Install from the lockfile
|
|
run: npm ci
|
|
|
|
- name: Coverage over the economic-core cluster
|
|
# Scoped by TEST FILE, not by skipFiles. This matters: skipFiles would
|
|
# shrink the DENOMINATOR and inflate the percentage. --testfiles leaves
|
|
# every contract instrumented and simply runs fewer tests, so the number
|
|
# produced is an honest "what these tests reach", measured against the
|
|
# full instrumented denominator.
|
|
#
|
|
# No continue-on-error: if a test in this scope fails, coverage fails.
|
|
run: |
|
|
npx hardhat coverage \
|
|
--config hardhat.config.coverage.js \
|
|
--testfiles "{test/aere-fee-burn-vault.test.js,test/aere-sink-branch-coverage.test.js,test/saere-v2-fix.test.js,test/aeresink-conservation-invariant.test.js}"
|
|
|
|
- name: Print the measured summary
|
|
run: |
|
|
echo "── Coverage summary (istanbul) ─────────────────────────────"
|
|
cat coverage/coverage-summary.json 2>/dev/null \
|
|
|| echo "coverage-summary.json not emitted; see the artifact for coverage.json"
|
|
|
|
- name: Upload the raw coverage report
|
|
# `if: always()` is used ONLY on artifact-upload steps, so that a FAILED
|
|
# run still hands you its evidence. It is never used on a step that
|
|
# decides pass or fail. That distinction is the whole difference between
|
|
# a debuggable pipeline and a dishonest one.
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: coverage-report
|
|
path: |
|
|
coverage/
|
|
coverage.json
|
|
retention-days: 90
|