mirror of
https://github.com/saymrwulf/anza-ed25519-verified.git
synced 2026-09-03 20:13:46 +00:00
Closes four round-7/8 findings. Certified by the round-12 sweep: five repositories, both buttons and every self-test, 48/48 GREEN. ── `scalar-statements-unbound` (gpt, round 7, CRITICAL) ──────────────────── The main button bound its 31 certificates' elaborated statements and reachable specification bodies. This button bound NONE of its thirteen, while TRUSTED-BASE item 8 said the audit covers "every certificate" — false across the 44-certificate surface. The finding was raised in round 7, lost from the round-8 work list by an F-number collision between two reviewers, and re-raised in round 8. Proofs/ScalarAudit.lean is generated from each fork's OWN Audit.lean, so the canonicalisation is provably the same code: pp.all rendering, whitespace normalisation, transitive specification closure. check-scalar.sh Phase 3c pins the block's digest, requires the committed copy to match byte-for-byte so a mismatch can be DIFFED, and cross-checks the auditor's certificate set against the button's CERTS array. dalek ecf3a3f8 · anza 0d942e47 · risc0 4b550a61 · betrusted 4b550a61 risc0 and betrusted share a digest and that is correct, not a collision: their ScalarSubSpec.lean differs only in doc prose and in `black_box` entries inside `simp only [...]` lists AFTER `:= by`. Proof scripts. They bind the same statements over the same specifications, which is the documented scope. selftest-scalar-statements.sh ships the two attacks the reviewer asked for: ok gutted statement caught (cone unchanged) ok rewritten specification body caught (name and cone unchanged) The second rewrites a reachable reference body to `id (…)` — DEFINITIONALLY EQUAL, so the corpus compiles and every proof typechecks and the cone is byte-identical. Every earlier phase is blind to it. ── `drv-surface-no-cones` + `accounting-certifies-enumeration` (claude) ──── The round-7 accounting identity proved every kernel constant was ENUMERATED. The reviewer showed enumeration is not audit: their planted claim WAS enumerated, as DRV|LTLAccAudit.bait.smuggled|theorem with a real cone, and nothing examined it — rows had no cone, no allowlist covered them, the statement digest does not reach instruments, and Phase 2b gates DECLARED AXIOMS, a different question. "Progress of one step, not two." DRV rows now carry their axiom cone and are pinned in driver-allowlist.txt by inventory_gate.sh with a DRV tag — the same implementation that pins the corpus, in both directions, because a second copy of a coverage gate is a second thing to drift. The axiom policy is per-surface and enforced per surface: the corpus admits exactly the sanctioned boundary, the instruments admit none, and an instrument axiom fails EVEN WHEN ALLOWLISTED. Verified with the reviewer's own payload, both placements: before the walk -> UNCLASSIFIED: DRV|…|bait.smuggled|theorem|Classical.choice,Quot.sound,propext after the walk -> ACCOUNTING FAILED names it (kernel-side) ── `drv-naming-heuristic` (claude, round 7) ──────────────────────────────── Retired as load-bearing rather than patched. The rule admits a theorem whose name extends a constant declared alongside it, and "breaks in one line" — declare `def bait`, then `theorem bait.smuggled` walks through. It stays as a fast readable first check; membership in a committed allowlist is what now carries the weight, and a new row fails closed whatever it is called. ── what round 11 caught, which was mine ─────────────────────────────────── DRV rows first shipped WITHOUT their originating driver. dalek and anza run two drivers, each declaring its own `corpus`; keyed on name alone those two distinct declarations produced one byte-identical row, `sort -u` collapsed them, and the trailers summed to 37 against 36. The estate had already learned this on the corpus walk — INV rows carry their module because two modules both declare CurveFieldProofs.zero_spec — and I rebuilt the record without it. Rows now carry their driver, and the gate FAILS CLOSED ON DUPLICATE RECORDS naming the collision: two declarations sharing one entry means one is covered by the other's, which is exactly how a real declaration hides. The trailer now checks what the drivers EMITTED, not what survives de-duplication — conflating "the run was truncated" with "two rows were identical" is what let a record-format defect present itself as an arithmetic complaint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
323 lines
17 KiB
Bash
Executable file
323 lines
17 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# check-scalar.sh — THE SECOND BUTTON (Scalar52 arithmetic mod ℓ).
|
||
#
|
||
# Runs against the merged gen/CurveField universe (the scalar module lives
|
||
# there since the merge; see extract.sh). Guarded compiles throughout.
|
||
#
|
||
# Brought to the same standard as check.sh on 2026-07-30 (P0-b). Before that
|
||
# it was the weakest link in the estate, and increasingly so as the main button
|
||
# was hardened: no source-integrity check, no harness pin, no dead-file gate,
|
||
# no declaration inventory, an EVADABLE source-text grep for axioms where the
|
||
# main button asks the kernel, and a count of matching output lines where the
|
||
# main button asserts each certificate's cone individually.
|
||
#
|
||
# Phases:
|
||
# 0 source integrity + harness pin (so running this button alone is also
|
||
# protected, not only running it after check.sh)
|
||
# 1 the two-button seam: every shipped proof source belongs to exactly one
|
||
# of the two manifests, asserted against check.sh's, both directions
|
||
# 2 compile the scalar layer, with a dead-file gate over Scalar*
|
||
# 2b kernel-side axiom-declaration gate over the compiled artifacts
|
||
# 2c environment-derived declaration inventory, diffed both directions
|
||
# 3 per-certificate exact-cone audit
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
set -uo pipefail
|
||
source ~/aeneas-toolchain/env.sh
|
||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||
AENEAS_LEAN="$AENEAS_HOME/backends/lean"
|
||
TIMEOUT="${LEAN_TIMEOUT:-300}"
|
||
CORES="${LEAN_MAX_CORES:-0-3}"
|
||
GEN=(CurveField/TypesExternal CurveField/Types CurveField/FunsExternal CurveField/Funs)
|
||
PROOFS=(ScalarDenote ScalarLoop ScalarSubSpec ScalarAddSpec ScalarMulSpec ScalarMontSpec ScalarReduceSpec ScalarFullMulSpec ScalarMain ScalarWideSpec ScalarBytesSpec ScalarUnpackSpec ScalarFromBytesSpec ScalarAudit )
|
||
# Fully-qualified scalar certificates. Each must report EXACTLY the standard
|
||
# three axioms — asserted per certificate, not by counting how many lines of
|
||
# output happened to match. A count cannot tell you WHICH certificate is clean.
|
||
CERTS=(
|
||
ScalarProofs.L_val
|
||
ScalarProofs.sub_loop_spec
|
||
ScalarProofs.cond_add_l_one_spec
|
||
ScalarProofs.sub_val_spec
|
||
ScalarProofs.add_val_spec
|
||
ScalarProofs.mul_internal_spec
|
||
ScalarProofs.part1_spec
|
||
ScalarProofs.montgomery_reduce_spec
|
||
ScalarProofs.mul_spec
|
||
ScalarProofs.scalarImplementation
|
||
ScalarProofs.montgomery_mul_spec
|
||
ScalarProofs.bytes_unpack_spec
|
||
ScalarProofs.from_bytes_wide_spec
|
||
)
|
||
EXPECTED="[propext, Classical.choice, Quot.sound]"
|
||
|
||
# ── Phase 0: source integrity + harness pin ─────────────────────────────────
|
||
echo "=== Phase 0: source integrity + harness pin ==="
|
||
free -m | awk '/Mem:/{if($7<2048){print "FATAL: <2GB RAM available — refusing to compile"; exit 1}}' || exit 1
|
||
for f in "$HERE"/Proofs/Scalar*.lean; do
|
||
[ -f "$f" ] || continue
|
||
if ! grep -qE '^(/-|import |namespace |theorem |def |open |set_option |--)' "$f"; then
|
||
echo "CORRUPTED: $f is not Lean source (olean clobber?). Restore: git checkout HEAD -- $f"
|
||
exit 1
|
||
fi
|
||
done
|
||
# The pin file and its policy live with check.sh; this button verifies the same
|
||
# pins so that running it ALONE is protected too. If check.sh is absent the
|
||
# harness is not pinned and that is a hard stop, not a warning.
|
||
if [ ! -s "$HERE/HARNESS.sha256" ]; then
|
||
echo "FATAL: HARNESS.sha256 is missing or empty — the harness is unpinned."; exit 1
|
||
fi
|
||
if ! ( cd "$HERE" && sha256sum -c --quiet HARNESS.sha256 ) ; then
|
||
echo "FATAL: a harness file does not match its pin. The button you are running"
|
||
echo "is not the button that was reviewed."; exit 1
|
||
fi
|
||
echo " sources valid; $(wc -l < "$HERE/HARNESS.sha256") harness files match their pins"
|
||
|
||
# ── Phase 1: the two-button seam ────────────────────────────────────────────
|
||
# The mirror of check.sh's Phase 1b. Each button reads the other's manifest, so
|
||
# a new Proofs/ScalarX.lean cannot be absent from one by exemption and from the
|
||
# other by omission — which is exactly what it was until today.
|
||
echo "=== Phase 1: two-button seam ==="
|
||
SEAMFAIL=0
|
||
MAIN_SH="$HERE/check.sh"
|
||
if [ ! -f "$MAIN_SH" ]; then
|
||
echo " FATAL: check.sh is absent — most of the corpus would go unchecked."; exit 1
|
||
fi
|
||
MAIN_MANIFEST=$(sed -n '/^PROOFS=(/,/^)/p' "$MAIN_SH" | grep -oE '^ [A-Za-z][A-Za-z0-9]*' | tr -d ' ' | sort -u)
|
||
if [ -z "$MAIN_MANIFEST" ]; then
|
||
echo " FATAL: could not read check.sh's manifest; refusing to guess its scope."; exit 1
|
||
fi
|
||
SCALAR_MANIFEST=$(printf '%s\n' "${PROOFS[@]}" | sort -u)
|
||
for f in "$HERE"/Proofs/*.lean; do
|
||
b=$(basename "$f" .lean)
|
||
case "$b" in AxiomCheck|Inventory|InventoryBasic|InventoryCore|InventoryScalar) continue;; esac
|
||
inm=0; ins=0
|
||
grep -qx "$b" <<<"$MAIN_MANIFEST" && inm=1
|
||
grep -qx "$b" <<<"$SCALAR_MANIFEST" && ins=1
|
||
if [ $((inm + ins)) -eq 0 ]; then
|
||
echo " ORPHAN: Proofs/$b.lean is in NEITHER manifest — compiled and audited by no button"; SEAMFAIL=1
|
||
elif [ $((inm + ins)) -eq 2 ]; then
|
||
echo " DOUBLE-CLAIMED: Proofs/$b.lean is in BOTH manifests"; SEAMFAIL=1
|
||
fi
|
||
done
|
||
[ "$SEAMFAIL" = 0 ] && echo " every proof source belongs to exactly one button"
|
||
[ "$SEAMFAIL" = 0 ] || { echo "SEAM CHECK FAILED"; exit 1; }
|
||
|
||
# ── Phase 2: compile ────────────────────────────────────────────────────────
|
||
echo "=== Phase 2: compile (guarded) ==="
|
||
cd "$AENEAS_LEAN"
|
||
lake env bash -c "
|
||
set -uo pipefail
|
||
cd '$HERE/gen' && export LEAN_PATH=\"\$LEAN_PATH:\$PWD:$HERE\"
|
||
for m in ${GEN[*]}; do echo \" · gen \$m\"; LEAN_TIMEOUT=$TIMEOUT LEAN_MEM_MB=6144 '$HERE/lean-guard' \"\$m.lean\" || exit 1; done
|
||
cd '$HERE'
|
||
for m in ${PROOFS[*]}; do echo \" · proof \$m\"; LEAN_TIMEOUT=$TIMEOUT LEAN_MEM_MB=4096 '$HERE/lean-guard' \"Proofs/\$m.lean\" || exit 1; done
|
||
# NO NAME-PREFIX DEAD-FILE GATE HERE. A Scalar* NAME does not imply this
|
||
# button owns the file: Proofs/ScalarPackSpec.lean is in check.sh's manifest,
|
||
# and a prefix gate demanded it be in this one. Phase 1 above is the correct
|
||
# test and strictly stronger — it requires every proof source to be in
|
||
# EXACTLY ONE of the two manifests, by membership rather than by spelling.
|
||
" || { echo FAIL; exit 1; }
|
||
|
||
# ── Phase 2b: kernel-side axiom-declaration gate ────────────────────────────
|
||
# The source-text grep this button used until today is evadable four ways on
|
||
# Lean v4.30.0-rc2 (indented, @[simp], unsafe, and name-on-the-next-line — all
|
||
# compile, none match an anchored pattern). Ask the kernel instead, reading the
|
||
# compiled artifacts, exactly as check.sh Phase 2b does.
|
||
echo "=== Phase 2b: kernel-side axiom-declaration gate ==="
|
||
# The scanned set is this button's MANIFEST, not everything spelled Scalar*.
|
||
# Proofs/ScalarPackSpec.lean is compiled by check.sh, so a glob swept in an
|
||
# artifact this button does not own — and on a tree where check.sh had not run,
|
||
# that olean is absent and the coverage count would fail for a false reason.
|
||
SCALAR_OLEANS=$(printf '"%s.olean", ' "${PROOFS[@]}" | sed 's/, $//')
|
||
GATE=$(mktemp "$HERE/.axgate-scalar-XXXX.lean")
|
||
{
|
||
echo "import Lean"
|
||
echo "open Lean"
|
||
echo "def expected : List String := [$SCALAR_OLEANS]"
|
||
cat <<'LEANGATE'
|
||
|
||
run_cmd do
|
||
let dir : System.FilePath := "Proofs"
|
||
let mut errs : Array String := #[]
|
||
let mut nMod := 0
|
||
for name in expected do
|
||
let p := dir / name
|
||
-- FAIL CLOSED ON ABSENCE: a manifest module whose artifact is missing makes
|
||
-- this gate vacuous for that module, which must be an error, not a skip.
|
||
unless (← p.pathExists) do
|
||
throwError "COVERAGE: {name} is in the manifest but its compiled artifact is absent"
|
||
nMod := nMod + 1
|
||
let (mod, _) ← readModuleData p
|
||
for ci in mod.constants do
|
||
if ci matches .axiomInfo _ then
|
||
errs := errs.push s!" {name}: {ci.name}"
|
||
unless errs.isEmpty do
|
||
throwError "AXIOM DECLARED in the scalar layer:\n{String.intercalate "\n" errs.toList}"
|
||
logInfo s!" kernel confirms: {nMod} compiled scalar modules (the manifest, by membership), none declares an axiom"
|
||
LEANGATE
|
||
} > "$GATE"
|
||
cd "$AENEAS_LEAN"
|
||
GATE_RC=0
|
||
lake env bash -c "
|
||
set -uo pipefail
|
||
cd '$HERE/gen' && export LEAN_PATH=\"\$LEAN_PATH:\$PWD:$HERE\"
|
||
cd '$HERE'
|
||
LEAN_TIMEOUT=$TIMEOUT LEAN_MAX_CORES=$CORES '$HERE/lean-guard' '$GATE'
|
||
" || GATE_RC=$?
|
||
rm -f "$GATE" "${GATE%.lean}.olean"
|
||
[ "$GATE_RC" -eq 0 ] || { echo "SCALAR AXIOM GATE FAILED"; exit 1; }
|
||
|
||
# ── Phase 2c: declaration inventory ─────────────────────────────────────────
|
||
# Until today these 13 modules were the only part of the proof corpus with no
|
||
# declaration inventory: check.sh Phase 2c covers the main chain and named them
|
||
# as uncovered on every run. This closes that.
|
||
echo "=== Phase 2c: scalar declaration inventory ==="
|
||
INVFAIL=0
|
||
INVLOG=$(mktemp /tmp/check-scalar-inv-XXXX.log)
|
||
cd "$AENEAS_LEAN"
|
||
lake env bash -c "
|
||
set -uo pipefail
|
||
cd '$HERE/gen' && export LEAN_PATH=\"\$LEAN_PATH:\$PWD:$HERE\"
|
||
cd '$HERE'
|
||
LEAN_TIMEOUT=$TIMEOUT LEAN_MEM_MB=8192 '$HERE/lean-guard' Proofs/InventoryScalar.lean
|
||
" > "$INVLOG" 2>&1 || { cat "$INVLOG"; echo "SCALAR INVENTORY COMPILE FAILED"; rm -f "$INVLOG"; exit 1; }
|
||
OBS=$(mktemp /tmp/check-scalar-obs-XXXX.log)
|
||
grep -E '^INV\|' "$INVLOG" > "$OBS"
|
||
grep '^INV-COUNT|' "$INVLOG" | tail -1 >> "$OBS"
|
||
"$HERE/inventory_gate.sh" "$OBS" "$HERE/inventory-allowlist-scalar.txt" || INVFAIL=1
|
||
rm -f "$INVLOG" "$OBS"
|
||
# The driver's corpus list must BE this script's manifest, both directions.
|
||
COVERED=$(grep -oE '`Proofs\.[A-Za-z0-9]+' "$HERE/Proofs/InventoryScalar.lean" | sed 's/`Proofs\.//' | sort -u)
|
||
for m in "${PROOFS[@]}"; do
|
||
grep -qx "$m" <<<"$COVERED" || { echo " UNINVENTORIED: $m is compiled here but the driver does not cover it"; INVFAIL=1; }
|
||
done
|
||
while read -r m; do
|
||
[ -z "$m" ] && continue
|
||
case " ${PROOFS[*]} " in (*" $m "*) ;; (*) echo " PHANTOM: driver claims $m, not in this manifest"; INVFAIL=1;; esac
|
||
done <<<"$COVERED"
|
||
[ "$INVFAIL" = 0 ] || { echo "SCALAR INVENTORY FAILED"; exit 1; }
|
||
|
||
# ── Phase 3: per-certificate exact-cone audit ───────────────────────────────
|
||
# Was: count the lines of #print axioms output that matched the clean cone and
|
||
# compare against 13. A count cannot say WHICH certificate is clean, and it
|
||
# passes just as happily if one certificate's cone is reported twice. Each
|
||
# certificate is now asserted by name.
|
||
echo "=== Phase 3: per-certificate exact-cone audit ==="
|
||
cd "$AENEAS_LEAN"
|
||
AUD_OUT=$(lake env bash -c "
|
||
set -uo pipefail
|
||
export LEAN_PATH=\"\$LEAN_PATH:$HERE/gen:$HERE\"
|
||
cd '$HERE'
|
||
AUD=\$(mktemp '$HERE/.audit-scalar-XXXX.lean')
|
||
{ echo 'import Proofs.ScalarFromBytesSpec'; echo 'import Proofs.ScalarMain'; echo 'import Proofs.ScalarUnpackSpec'; echo 'import Proofs.ScalarWideSpec'
|
||
for c in ${CERTS[*]}; do echo \"#print axioms \$c\"; done; } > \"\$AUD\"
|
||
OUT=\$(LEAN_TIMEOUT=$TIMEOUT LEAN_MEM_MB=4096 '$HERE/lean-guard' \"\$AUD\" 2>&1)
|
||
rm -f \"\$AUD\" \"\${AUD%.lean}.olean\"
|
||
echo \"\$OUT\"
|
||
") || { echo "$AUD_OUT"; echo "SCALAR AUDIT COMPILE FAILED"; exit 1; }
|
||
FLAT=$(tr '\n' ' ' <<<"$AUD_OUT" | tr -s ' ')
|
||
AUDFAIL=0
|
||
for c in "${CERTS[@]}"; do
|
||
grep -qF "'$c' depends on axioms: $EXPECTED" <<<"$FLAT" \
|
||
|| { echo " NOT CLEAN or NOT FOUND: $c"; AUDFAIL=1; }
|
||
done
|
||
if [ "$AUDFAIL" != 0 ]; then
|
||
echo "SCALAR AXIOM AUDIT FAILED"; echo "$AUD_OUT" | tail -20 | sed 's/^/ /'; exit 1
|
||
fi
|
||
echo " ${#CERTS[@]}/${#CERTS[@]} scalar certificates report exactly $EXPECTED"
|
||
|
||
# ── Phase 3c: statement + specification binding ─────────────────────────────
|
||
# WHAT PHASE 3 DOES NOT ESTABLISH — and why this repository claimed something
|
||
# false for four rounds. Round-7 review (GPT-5.6, register key
|
||
# `scalar-statements-unbound`, CRITICAL): the main button bound its 31
|
||
# certificates' elaborated statements and reachable specification bodies; this
|
||
# button bound NONE of its thirteen. Meanwhile TRUSTED-BASE item 8 said the
|
||
# audit covers "every certificate" and each README said check.sh audits every
|
||
# certificate. Both were false across the 44-certificate repository surface.
|
||
#
|
||
# The finding was raised in round 7, was lost from the round-8 work list by an
|
||
# F-number collision between two reviewers, and was re-raised in round 8. It is
|
||
# closed here.
|
||
#
|
||
# Phase 3 proves each scalar certificate rests on exactly the standard three
|
||
# axioms. It does not say WHAT THE THEOREM SAYS. A certificate gutted to a
|
||
# tautology of the same cone passes it, and so does one whose reference
|
||
# definition has been redefined to BE the extracted code — at which point the
|
||
# theorem reads `loop = loop` and the cone is byte-identical.
|
||
#
|
||
# Proofs/ScalarAudit.lean emits a canonical block holding the policy constants,
|
||
# every scalar certificate's fully-elaborated statement (`pp.all`, so implicit
|
||
# arguments, instances and universes are all visible), and the body of every
|
||
# specification constant transitively reachable from those statements. This
|
||
# phase binds its SHA-256, and the block's INPUT is committed too, so a
|
||
# mismatch can be DIFFED rather than merely reported.
|
||
#
|
||
# To rotate deliberately: run this button, take the printed OBSERVED digest,
|
||
# and update the constant below AND SCALAR-AUDIT-MANIFEST.txt in the same
|
||
# reviewable commit. An author who edits a statement and refreshes the digest
|
||
# together is caught by reading the diff, not by this script.
|
||
EXPECTED_SCALAR_AUDIT_SHA256="0d942e472f5e5cbe428a824f4cd12dcb2d6b26e71db91a0665a4a90e341e9ce8"
|
||
echo "=== Phase 3c: scalar statement + specification binding ==="
|
||
cd "$AENEAS_LEAN"
|
||
# The compiler's own exit code is the primary signal; the transcript is only
|
||
# corroboration. A timeout or a memory clamp exits non-zero WITHOUT printing
|
||
# "error:", so grepping the text alone would let it through.
|
||
SAUD_RC=0
|
||
SAUD_OUT=$(lake env bash -c "
|
||
set -uo pipefail
|
||
cd '$HERE/gen' && export LEAN_PATH=\"\$LEAN_PATH:\$PWD:$HERE\"
|
||
cd '$HERE'
|
||
LEAN_TIMEOUT=$TIMEOUT LEAN_MEM_MB=8192 '$HERE/lean-guard' Proofs/ScalarAudit.lean 2>&1
|
||
" ) || SAUD_RC=$?
|
||
if [ "$SAUD_RC" -ne 0 ]; then
|
||
echo "SCALAR AUDIT FAILED — Proofs/ScalarAudit.lean exited $SAUD_RC:"
|
||
tail -20 <<<"$SAUD_OUT" | sed 's/^/ /'
|
||
exit 1
|
||
fi
|
||
if grep -q 'error:' <<<"$SAUD_OUT"; then
|
||
echo "SCALAR AUDIT FAILED — Proofs/ScalarAudit.lean did not elaborate cleanly:"
|
||
grep 'error:' <<<"$SAUD_OUT" | head -20 | sed 's/^/ /'
|
||
exit 1
|
||
fi
|
||
SBLOCK=$(awk '/SCALAR-AUDIT-MANIFEST-BEGIN/{f=1;next} /SCALAR-AUDIT-MANIFEST-END/{f=0} f' <<<"$SAUD_OUT")
|
||
# FAIL CLOSED ON ABSENCE: no block and a matching block must not share a path.
|
||
if [ -z "$SBLOCK" ]; then
|
||
echo "SCALAR AUDIT FAILED — no SCALAR-AUDIT-MANIFEST block was emitted (fail-closed)."; exit 1
|
||
fi
|
||
SGOT_SHA=$(printf '%s\n' "$SBLOCK" | sha256sum | cut -d' ' -f1)
|
||
if [ "$SGOT_SHA" != "$EXPECTED_SCALAR_AUDIT_SHA256" ]; then
|
||
printf '%s\n' "$SBLOCK" > "$HERE/.scalar-audit-manifest.observed"
|
||
echo "SCALAR AUDIT FAILED — audit-manifest digest mismatch."
|
||
echo " expected: $EXPECTED_SCALAR_AUDIT_SHA256"
|
||
echo " observed: $SGOT_SHA"
|
||
echo " A statement, a specification body, or a policy constant changed."
|
||
echo " First differences against the committed block:"
|
||
diff -u "$HERE/SCALAR-AUDIT-MANIFEST.txt" "$HERE/.scalar-audit-manifest.observed" 2>/dev/null \
|
||
| head -30 | sed 's/^/ /' || echo " (SCALAR-AUDIT-MANIFEST.txt absent — cannot diff)"
|
||
rm -f "$HERE/.scalar-audit-manifest.observed"
|
||
exit 1
|
||
fi
|
||
# The digest's INPUT must be committed and current, or the diff above would
|
||
# compare against a stale reference and quietly mislead the next reader.
|
||
if ! printf '%s\n' "$SBLOCK" | cmp -s - "$HERE/SCALAR-AUDIT-MANIFEST.txt"; then
|
||
echo "SCALAR AUDIT FAILED — the committed SCALAR-AUDIT-MANIFEST.txt does not match the emitted block."
|
||
echo " (the digest matched, so the committed copy is stale — refresh it)"; exit 1
|
||
fi
|
||
# CROSS-CHECK the certificate list against the CERTS array Phase 3 audits, so a
|
||
# certificate cannot be dropped from the auditor's manifest unnoticed.
|
||
SAUD_CERTS=$(grep -o 'AUDITED-SCALAR-CERTIFICATES:.*' <<<"$SAUD_OUT" \
|
||
| sed 's/AUDITED-SCALAR-CERTIFICATES: //' | tr ' ' '\n' | sort -u | sed '/^$/d')
|
||
SBASH_CERTS=$(printf '%s\n' "${CERTS[@]}" | sort -u)
|
||
if [ "$SAUD_CERTS" != "$SBASH_CERTS" ]; then
|
||
echo "SCALAR AUDIT FAILED — the auditor's certificate set differs from this button's CERTS array:"
|
||
diff <(echo "$SBASH_CERTS") <(echo "$SAUD_CERTS") | sed 's/^/ /'
|
||
exit 1
|
||
fi
|
||
echo " ${#CERTS[@]} scalar statements + reachable specification bodies bound, sha256 = $SGOT_SHA"
|
||
cd "$HERE"
|
||
|
||
|
||
echo ""
|
||
echo "SCALAR LAYER COMPLETE: add, sub, mul (Montgomery reduction, double round"
|
||
echo "through RR) proven mod ℓ; aggregate certificate scalarImplementation"
|
||
echo "kernel-audited; declaration surface inventoried; harness pinned."
|