secure(erc7730): close EIP-712 nested-struct fund-routing address hide #107
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |