Skip to content

secure(erc7730): close EIP-712 nested-struct fund-routing address hide #107

secure(erc7730): close EIP-712 nested-struct fund-routing address hide

secure(erc7730): close EIP-712 nested-struct fund-routing address hide #107

Workflow file for this run

name: CI
# Born-green CI for PQSigner. Each job runs a command verified to pass on the
# current tree (2026-06-16). The two pre-existing reds tracked in earlier
# revisions of this file are now FIXED and GATED here (docs/work-todo.md §34):
# * usb_hw raw-MMIO source-pin (commit 4d1d6476 — raw read/write_volatile
# migrated to hw::mmio::Reg32). Gated by the `secure-tests` job (the full
# sphincs-tz-secure host suite: 2076 passed / 0 failed on this tree).
# * The `contracts` job was dying at COMPILE time in CI: the Foundry libs
# (forge-std, solady, account-abstraction, …) are git-pinned by
# contracts/smart-wallet/foundry.lock, but `lib/` is gitignored and they
# are NOT git submodules — so a clean CI checkout had nothing to compile
# against ("Unable to resolve imports"). The job now restores each lib at
# its EXACT foundry.lock rev, then runs the full default `forge test`
# (109 passed / 0 failed / 1 skipped — the skip is DeployedBytecodeReproCheck,
# which only runs under the deploy profile). The committed codehash pins
# (0xf1ef… verifier, 0x43c654… wallet) are canonical for exactly this
# foundry.lock lib set; an extra/missing lib perturbs solc metadata and
# therefore the codehash, so the restore must be lib-exact.
#
# Intentionally NOT in CI (kept as local/manual gates — slow + need the
# patched halmos toolchain): the `deploy` foundry profile (runs=999999) and
# `make -C contracts/verification verify-bytecode` (the symbolic discharge).
# Under the deploy profile DeployedBytecodeReproCheck reproduces the on-chain
# Base Mainnet bytecode exactly (verified locally in a clean lib-exact tree).
on:
push:
branches: [master]
# Skip CI when a push touches ONLY docs / proofs / formal-models — none of
# these feed any CI job (the verify-bytecode + kontrol gates are local-only,
# and contracts/verification/** is never compiled by the `contracts` job,
# which builds contracts/smart-wallet). A push that ALSO changes any other
# file still runs the full suite. Conserves private-repo Actions minutes.
paths-ignore:
- '**/*.md'
- 'docs/**'
- '**/*.lean'
- 'contracts/verification/**'
pull_request:
paths-ignore:
- '**/*.md'
- 'docs/**'
- '**/*.lean'
- 'contracts/verification/**'
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
invariant-gates:
name: Invariant gates (#5 / #6 / #7)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
# Deterministic supply-chain gate (fail-hard):
# * bans — invariant #5: no classical-signer crate (deny.toml [bans])
# * advisories — real CVEs in any dep (unmaintained is workspace-scoped per
# deny.toml so transitive-unmaintained deps don't tripwire CI)
# * sources — deps only from expected registries/remotes (typosquat /
# dependency-confusion guard)
# Licenses are deliberately NOT a hard gate (a compliance tripwire, not a
# security property) — captured by the SBOM (`make sbom`) instead.
- name: cargo deny check (advisories + bans + sources)
uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2
with:
command: check advisories bans sources
# Invariants #5/#6/#7 (source level): the hard ERROR rules must be clean.
- name: semgrep invariant rules (ERROR-level)
run: |
python3 -m pip install --quiet semgrep==1.167.0
semgrep --config .semgrep/pqsigner-invariants.yml --severity ERROR --error --metrics off
# A3.1 transcription-fidelity gate: the deployed verifier SOURCE
# (SPHINCsC10Asm.sol) and its Lean hand transcription (c10Program) must not
# drift. Positional constant cross-check (per `-- L<n>` anchors) + global set
# equality + statement-kind histogram. Pure Python, no Lean toolchain needed —
# this is the ONE A3.1 residual the kernel proof `execC10Asm_eq` cannot see (it
# relates c10Program<->spec, never c10Program<->.sol). See
# contracts/verification/docs/A3_1_ADVERSARIAL_REVIEW_2026-06-18.md.
- name: A3.1 c10Program <-> SPHINCsC10Asm.sol transcription lint
run: make -C contracts/verification verify-transcription
supply-chain-vet:
name: cargo-vet (dependency audit attestation)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# Every dep must be trusted-audited (imported audit sets, pinned in
# supply-chain/imports.lock) or exempted in supply-chain/config.toml; a
# new unaudited transitive dep fails here. rust-cache caches the built
# cargo-vet binary so this is fast after the first run.
- name: cargo install cargo-vet
run: cargo install --locked cargo-vet
- name: cargo vet --locked
run: cargo vet --locked
prod-config-gate:
name: Production feature-set gate (MED-2)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
targets: thumbv8m.main-none-eabi
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# Resolve the CANONICAL shipping feature set (PROD_SHIP_FEATURES) and
# hard-fail if any never-ship feature (e2e-test/dev-testkey/mock-se/
# debug-log/… ) is active OR any required hardening feature
# (mode-production/optiga-lock-operational/optiga-hw-counter/consumption-mask/
# tamp*/tzic-wipe/Tier-1 SE keys) is MISSING — including transitively.
# `prod-check-ship` pins the production feature string so the gate
# exercises the real hardened image, not the dev default. Belt-and-braces
# with the compile fences in nsc/mod.rs. Fast (cargo tree only).
# HIGH-1 (audit pin-unlock 20260625): the required-feature allowlist is
# what closes the "silent unhardened ship" gap.
- name: make prod-check-ship
run: make prod-check-ship
host-tests:
name: Host unit tests (pure-logic crates)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: cargo test (pure-logic crates)
run: |
cargo test --locked --lib \
-p pqsigner-proto -p pqsigner-tx-core -p pqsigner-aa \
-p pqsigner-domain -p pqsigner-tx -p pqsigner-erc7730 \
-p sphincs-c10 -p pqsigner-fi
# ERC-7730 descriptor catalog gate: (1) the firmware-pinned
# `ERC7730_DESCRIPTORS_ROOT` in secure/src/db_roots.rs must match a
# fresh dbgen build — catches a forgotten `cargo run -p dbgen` after a
# descriptor edit (a stale root would silently fail closed on-device);
# (2) every compiled IR re-parses through the on-device parser and its
# Merkle proof re-verifies. Targets exist locally as
# `make check-erc7730-descriptors`; wired here so the catalog can never
# drift on merge.
- name: ERC-7730 descriptor catalog in-sync (drift gate)
run: cargo run --locked -q -p pqsigner-xtask -- gen-erc7730-descriptors --check
- name: ERC-7730 dbgen round-trip (IR re-parse + proof re-verify)
run: cargo test --locked -p dbgen --test erc7730_roundtrip
# A3.1 independent-source KAT leg (work-todo #7): the EXISTING Rust verifier
# must accept FRESH output from the clean-room Python C10 signer
# (contracts/verification/scripts/independent_c10_signer.py) — implementation
# diversity for the differential oracle. python3 is present on ubuntu-latest.
- name: independent C10 signer cross-verifier (Python signer -> Rust verify)
run: cargo test --locked -p sphincs-c10 --test independent_signer_xcheck --release
secure-tests:
name: Secure-world unit tests (host)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# The secure crate host-builds with --no-default-features (the
# semihosting backend is arch-gated to a host stub). This is the
# `make test-unit` secure path; gates the usb_hw raw-MMIO fix.
- name: cargo test (sphincs-tz-secure, host)
run: |
cargo test --locked -p sphincs-tz-secure \
--no-default-features \
--features mock-se,debug-log,ui-semihosting
miri:
name: Miri (UB in host-reachable unsafe)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly
components: miri
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# UB detection over the genuine host-reachable `unsafe`: the FI volatile
# helpers (pqsigner-fi), the tx-core decoders, and the secure-world
# NS-pointer deref + validation (the int->ptr boundary). Same crate set
# as `make miri`. Light enough to run per-push (no checker download,
# unlike Kani which is nightly-scheduled).
- name: cargo miri test (FI / tx-core / NS-ptr)
run: make miri
contracts:
name: Contracts (forge test)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1
# lib/ is gitignored and the deps are git-pinned by foundry.lock (NOT
# submodules), so restore each at its EXACT locked rev. The codehash
# freeze tests are lib-exact: an extra/missing lib changes the
# auto-generated remappings, which solc folds into the metadata hash
# appended to the runtime bytecode → a different codehash.
- name: Restore pinned Foundry libs (foundry.lock revs)
working-directory: contracts/smart-wallet
run: |
set -euo pipefail
declare -A URL=(
[forge-std]=https://github.com/foundry-rs/forge-std
[solady]=https://github.com/vectorized/solady
[account-abstraction]=https://github.com/eth-infinitism/account-abstraction
[openzeppelin-contracts]=https://github.com/openzeppelin/openzeppelin-contracts
[p256-verifier]=https://github.com/daimo-eth/p256-verifier
[safe-singleton-deployer-sol]=https://github.com/wilsoncusack/safe-singleton-deployer-sol
[webauthn-sol]=https://github.com/base-org/webauthn-sol
)
mkdir -p lib
for key in $(jq -r 'keys[]' foundry.lock); do
name="${key#lib/}"
url="${URL[$name]:-}"
if [ -z "$url" ]; then
echo "::error::foundry.lock dep '$name' has no URL mapping in ci.yml — add it"; exit 1
fi
rev=$(jq -r --arg k "$key" '.[$k].rev' foundry.lock)
git clone -q "$url" "lib/$name"
git -C "lib/$name" -c advice.detachedHead=false checkout -q "$rev"
echo " restored $name @ $rev"
done
- name: forge test (default profile)
working-directory: contracts/smart-wallet
run: forge test