Skip to content

The approval app's crypto in .dag: bitwise, bignat, SHA-256, P-256 (blocked on MachineWidth reflection) - #11645

Merged
gunbai-bot[bot] merged 39 commits into
mainfrom
session/swift-ibex-621-dag-crypto
Sep 20, 2026
Merged

gunbai-bot[bot] merged 39 commits into
mainfrom
session/swift-ibex-621-dag-crypto

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

The approval app's crypto, authored in .dag and intended to run compiled. Draft: blocked on compile-time reflection of MachineWidth<N>, dispatched as its own lane.

What this carries

Eight files, nothing else:

  • std.bitwise — bitwise operations over bounded naturals, emulated arithmetically. Conjunction from a derived nibble table; xor and or follow from it.
  • std.bignat — multi-limb unsigned naturals, 24-bit limbs (a limb product is below 2^48, and 24 bits is exactly three octets), Montgomery CIOS, Fermat inverse.
  • extdeps.crypto.sha2 — SHA-256, FIPS 180-4.
  • extdeps.crypto.nist_p256 — P-256 (SP 800-186) and ECDSA verify (FIPS 186-5 §6.4.2), Jacobian arithmetic with Shamir's trick.
  • std.bounded_nat — the declared operation family for bounded naturals, over a supplied width: a three-arm width admission, a sole-constructed carrier whose range admission is its only mint, checked arithmetic with typed outcomes, and add_mod / mul_mod as the named wrapping arm. It is what std.operator_realization's refusal points at. The UInt32 / UInt8 bindings are not here: they need the width reified from the declaration.
  • Three witness modules: the FIPS 180-2 SHA-256 vectors and the bitwise identities; RFC 6979 §A.2.5 and Wycheproof P-256 vectors.

Evidence so far

  • sha256_fips_witness_test: 7/7 interpreted. The round constants and initial hash values were checked against a derivation from the primes, not transcribed on trust.
  • P-256: the RFC 6979 'sample' verify answers correctly, 226,455 ms interpreted. The 12-witness file HAS NEVER COMPLETED A RUN -- three attempts now, the third on 2026-09-20 through a verified-provenance binary, all killed by container restarts (the third died after 53 bytes of output, at host slice pressure of 80 of 88 GiB across 41 containers). I am not retrying it further, and the reason is structural rather than bad luck: at the measured interpreted verify cost the suite needs roughly 30-45 minutes uninterrupted, which is longer than this container's restart interval under load. That cost is not a separate mystery -- it IS the Milestone-1 feasibility finding already reported, since the gate was 'a few seconds' per verify. What would run them: the same driver on a host that stays up for the length of the run, or a CI lane, neither of which exists for witnesses today.
  • SHA-256 of 1 KiB: 2,724 ms interpreted, digest correct.
  • bounded_nat_witness_test: 8 of 8 pass, executed 2026-09-20. Run as gunbc run over a driver that tags each failing witness by name, against a gunbc I built from this exact tree (8b7792a) into a per-session target dir. Positive arm: exit 0, empty output, 109s. Negative control, because exit 0 with empty output is also what a broken driver returns: inverting one witness with ! gives exit 1 and prints a_value_inside_the_width_mints_and_one_outside_it_does_not. So RED is reachable and the green is readable.
  • Instrument provenance matters here and is not incidental. An earlier run of the same driver through the SHARED /cargo-target/release/gunbc failed in std.keyed_row / std.keyed_roster — files this branch does not touch. That binary is not the seed: it carries another lane's unlanded kinding check (verified by a string the seed does not contain; my build has 0 occurrences, it has 1). The failure was a fact about the instrument, not the corpus, and it is reported to that lane. Per-session target dirs are why the result above is trustworthy.
  • Still not run by any required lane — not because CI runs no claims (it does again, since Add the fleet lane the hosted gate cannot carry: fold, drift gate, repair #11791), but because this witness is not in the floor's prefix roster. See below.
  • Not re-verified against current main. Local .dag execution is currently unusable in my container: a module importing only std.process takes 19 minutes to run on this main, against about 110 seconds for the same shape earlier the same day. That is a discriminating control — it contains none of this PR's code — and it holds with a gunbc rebuilt from this exact tree, so it is neither these modules nor a stale binary. The figures above are therefore from the pre-merge tree; and CI does not check them either, for the reason stated above.

Why it is blocked

Emitting this closure to native Rust is refused at 18 sites: arithmetic on std.integer's bounded naturals has no host realization (UInt32, UInt8). The approved repair is one bounded-natural operation family in std.integer, derived once over N, with checked arithmetic and explicitly-named wrapping. That needs the N of MachineWidth<N> readable in a body, which the operator ruled on 2026-09-20 as compile-time static reflection. That compiler work is a separate lane; this PR waits for it, then converts every infix site here.

Two failure modes found on the way landed separately in #11702.

🤖 Generated with Claude Code

What this PR is, and what it is not

In scope: the six .dag crypto modules and std.bounded_nat. None of them names a width, and each stands on its own.

Explicitly OUT of scope: the 18 infix conversion sites, and the interpreted-vs-native equal-answer witnesses. Their trigger is the compile-time reification of the N in MachineWidth<N> (operator ruling, 2026-09-20), which is a separate lane. So if you are wondering why the operation family has no callers yet: its binding layer is a declared frontier with a named trigger, not an oversight. std.bounded_nat takes its width as a supplied value until that lands.

Re-running the witnesses, since nothing else will

CORRECTED 2026-09-20 — I was wrong about this, in the pessimistic direction. I checked v2.workflow.required_floor required_gate_prefixes, found no match for these modules, and concluded CI never runs them. That reads ONE selector and concludes about the whole lane. The floor job ALSO selects by changed enclosing declaration, and it reaches them: floor run 35505867616 on d3de6681685 planned 35 of this PR's witnesses as planned_as_changed_witness and executed 27 of them.

module planned passed note
bounded_nat_witness_test 10 10 standing=admitted, 0–2 ms each
bignat_montgomery_witness_test 6 6
sha256_fips_witness_test 7 7
p256_dag_ecdsa_witness_test 12 4 8 did not reach a verdict — see below

So a green check on this PR does mean 27 of these witnesses ran, in a required lane. That is stronger evidence than this body previously claimed. To reproduce locally:

CARGO_TARGET_DIR=/cargo-target/<your-session-id> cargo build --release -p v1-compiler --bin gunbc
/cargo-target/<your-session-id>/release/gunbc run \
  --source-root dag --source-root src/v2 --source-root .scratch \
  --entry .scratch/scratch_bn_driver.dag --function main

The driver ANDs the eight test fns and prints the name of each one that fails, so exit 0 with empty output is a pass and exit 1 names the failure. Build with a PER-SESSION target dir: the shared /cargo-target/release/gunbc is whatever another session built last, and may not be the seed.

Review response: the host-width rule was over-prohibiting, and is fixed

The reviewer asked whether host-width safeguards reject valid operands. For the wrapping product they did, and that is now fixed in 95004fb. Answering the concrete case from the code:

  1. Before: bounded_nat_product_admitted was 2N <= 62, shared by both multiplications, so UInt32 refused with ProductExceedsHostInt in bounded_nat_mul and bounded_nat_mul_mod. Only the checked arm needed that: detecting overflow requires the whole product to exist; reduction mod 2^N needs only the low N bits.
  2. Was any consumer affected? No, and that is a boundary rather than a gap. extdeps.crypto.sha2 is additive — its only * are raw Int byte-packing (word * 256, 8 * (i - 1)) — and extdeps.crypto.nist_p256 routes all field multiplication through std.bignat Montgomery arithmetic (52 references; zero uses of UInt32 or std.bounded_nat). So nothing in this PR needed the refused operation.
  3. Fixed anyway, because an unreached over-prohibition still costs the next consumer. Splitting one operand at h = N / 2 computes the low N bits without forming the product. UInt32 wrapping multiplication now succeeds; UInt32 checked multiplication still refuses, correctly.

The new ceiling is stated, not rounded. The split admits N <= 41 — not every width the carrier admits — and 42..62 still refuse as a property of the naive host realization, with a next-rung trigger naming the capability (bignat routing or a recursive split). The pre-existing bounded_nat_width_ceiling_trigger is a different ceiling (magnitudes exceeding the host Int, i.e. UInt64/UInt128), where bignat genuinely is required; it is unchanged and still correct.

Evidence: 10/10 witnesses pass (exit 0), and each new witness was re-run inverted to show its red is reachable (exit 1, naming both). Both new vectors have 64-bit full products, so neither answer is reachable by forming the product on this host — the split is doing the work. The ceiling is asserted at its 41/42 edge so a later widening is reported rather than absorbed.

Review round 2: what is closed, and what is explicitly NOT

The boundary was wrong and is fixed (42/43, not 41/42). The split's admission compared its bit demand against bounded_nat_host_bit_ceiling() = 62 — the widest carrier this family admits — when the relevant limit is the host's positive capacity, 63. Two unrelated limits behind one constant: the same defect this branch had just removed one level up. Width 42's worst intermediate is 9223367638806167553 against an i64 max of 9223372036854775807; 43 is the first that genuinely does not fit.

The witness had canonized that off-by-one — it asserted a refusal at width 42 for 3 × 5, product 15. It now asserts the admitted edge with near-maximal operands, (2^42 − 1)² mod 2^42 = 1, an answer unobtainable by forming the true 84-bit product.

Checked multiplication consumes std.checked_arithmetic rather than re-deriving it: that module already owns "PRE-CHECK, NEVER POST-CHECK" and the division-based bound test. Only the bound is local here (the carrier's 2^N − 1, not the host range).

The refusal arm was renamed from ProductExceedsHostInt, which stated a falsehood — at width 43, 3 × 5 is 15 and nothing exceeds anything — to WrappingProductUnavailableAtWidth, with a trigger naming both sufficient routes (recursive splitting or std.bignat).

std.bignat: fabrication closed, invariant admission NOT closed

BigNat and MontgomeryModulus are now sole_constructor. Every overflow argument in that module rests on each limb lying in [0, 2^24), yet as plain records any caller could write BigNat { limbs: [-5, 99999999] } and the arithmetic would compute on it.

This is a partial close and must not be read as a full one. sole_constructor restricts who may construct, not what may be constructed. montgomery_modulus still derives its fields from unchecked arguments, so an even modulus, zero modulus, or negative width remains constructible inside the module. The next-rung trigger names the capability (a refusing admission establishing each invariant) and is deferred because making it refusing turns p256_field/p256_order into refusable data rows, cascading through every consumer of the field arithmetic — and the P-256 suite has never executed, so that restructuring cannot yet be read for breakage.

Paired witness: 5 small-modulus claims exercise the real producers (Montgomery round trip, a multiplication that must reduce — 20 × 30 = 600 = 6 × 97 + 18 — additive wrap, zero). 5/5 pass: the first executed evidence std.bignat has ever had. Control: setting bignat_base to 2^24 − 1 turns three of the five red by name; the other two do not depend on the base under that perturbation and are not credited with discrimination they lack.

Review round 3 (69054): the failure mode closed at its own site — one half of it

std.bitwise is new in this PR, and it was landing fresh population of a class already filed on main: word32_xor was a + b - 2 * word32_and(...), whose sum reaches 2^33 − 2 while typed UInt32. The failure-mode row cites that line verbatim, DESIGN §4b puts the class below the ladder, and no drop was declared. The finding was correct.

Fixed by removing the oversized intermediate, not by declaring it. Exclusive or is nibble-wise through a derived table (as word32_and already was); disjunction is xor + and, which are disjoint bit sets, so their sum is the disjunction and is bounded by it; addition decides the carry before forming the sum. Verified at the edges and over 60,000 random pairs against the reference operations — zero mismatches, largest xor intermediate 4294960992, inside 2^32. SHA-256 still 7/7, bounded_nat 10/10.

This closes ONE of two classes and must not be read as both. The invalid state bounded_natural_arithmetic_evaluated_as_unbounded_int names — "a value typed UInt32 that is not a 32-bit unsigned natural" — is now unreachable in std.bitwise. But std.operator_realization refuses infix arithmetic on a structural operand regardless of magnitude, so these sites still emit as operator refusals: the separate class accepted_source_emits_uncompilable_target. In-range values do not make infix emittable.

The consumer frontier's trigger was stated at the wrong grain and is corrected. It said this family waits on reification. It does not — the operations take a width as a value, and the witnesses pass bit_width(count: 32) today. The real blocker is an import cycle: std.bounded_nat imports std.bitwise for bitwise_pow2, so bitwise cannot import back (DESIGN §3, acyclicity). Breakable by relocating bitwise_pow2. The second cost is named beside it: threading a six-arm BoundedNatArith through SHA-256's 64-round fold. Deferred to its own change with the P-256 suite as its gate, in that order.

Also removed: bounded_nat_width_ceiling_trigger, a String prose row with no reader (DESIGN §4c dead data).

Review round 4 (69076): the repair had skipped its own 64-bit half

word64_add was let lo = a.lo + b.lo over two UInt32 halves — the same population the note added in the previous commit says word32_xor/word32_or/word32_add were rewritten to leave. The repair was applied to the 32-bit family and skipped for the 64-bit one in the same diff, while writing the explanation of why it was needed. Live, not latent: sha256_fips_witness_test drives lo to exactly 2^32 on every run.

Fixed by reusing the already-corrected word32_add rather than repeating its reasoning — the carry is decided before either sum is formed, and both halves go through word32_add instead of a raw %.

The rest of the file was scanned, not just the site named, since "applied here, skipped there" was the defect. Six remaining arithmetic sites over UInt32 halves are all sums of disjoint bit ranges; measured over 80,000 random inputs the largest intermediate is 4294964487, inside 2^32. word64_add was the only defect.

Control is discriminating, not a formality: replacing the carry with a constant 0 reds word64_add_carries_across_the_halves_and_rotr_swaps_them by name. SHA-256 7/7 with the fix; the implementation also checked against 64-bit modular addition over 80,000 pairs plus edges — zero mismatches.

P-256: what was obtained, and a §4b(3) drop for what is UNKNOWN

What this PR is: substrate acceptance with an explicit evidence gap. It is not security sign-off.
It does NOT state, and must not be read to imply, that the full P-256 verifier is verified, that the
ES256 security argument holds, or that the production device-signature path has been verified. The
binding from the app into the verifier is not exercised here at all and is a separately named
frontier (gunbc.auth.approval_device_redemption ecdsa_verification_realization_frontier).

EVIDENCE OBTAINED — and its exact extent

Two things, and nothing wider:

  1. the four cheap claims that refuse before any curve math (a_point_off_the_curve_is_absent,
    wycheproof_2_r_plus_n_is_absent, wycheproof_11_r_and_s_zero_are_absent,
    wycheproof_26_r_equal_to_n_is_absent);
  2. the standalone accept/reject pair below.
run vector wall exit
accept RFC 6979 §A.2.5 sample (P-256/SHA-256) 192 s 0 — returns true
reject the same vector, first octet of r flipped 239 → 238 205 s 1 — returns false
accept RFC 6979 §A.2.5 test 734 s 0 — returns true

What that establishes is bounded: the verifier accepts two published vectors and rejects a one-bit
perturbation of one of them. Before it, every executed P-256 witness was an is_absent case, all of
which a verifier returning Absent unconditionally would pass. It does not establish the verifier.

THE EIGHT VERIFICATION-PERFORMING CLAIMS ARE UNKNOWN, NOT PASSING

Stated in those words because the distinction is the whole point: five are exit=124 timeouts on a
1200 s cap under host memory pressure and one was never attempted, so no verdict exists for them in
either direction
. A timeout is not a failure and is emphatically not a pass.

THE DROP — §4b(3), five parts, exact and bounded

  • PREVIOUS RUNG: none held. These eight never reached a terminal verdict on any required path, so
    the honest previous state is unexecuted, not a lost guarantee.
  • TEMPORARY RUNG: outside the modeled guarantee — deliberately not a rung.
  • REASON: one interpreted verify costs 13.5 GiB peak RSS (VmHWM 14,132,132 kB) and
    192 s–734 s–unbounded wall depending on host residency; the required floor applies a ~8 s per-claim
    wall deadline, and the available remote runner is 7.63 GiB. A per-claim step budget would not bind
    this, because the binding constraint is resident memory.
  • BOUNDED POPULATION — the exact eight, in test.claim.p256_dag_ecdsa_witness_test:
    the_rfc6979_sample_signature_verifies, the_rfc6979_test_signature_verifies,
    a_signature_for_a_different_message_is_false, the_wrong_key_is_false,
    wycheproof_1_the_malleable_signature_verifies, wycheproof_4_n_minus_r_is_false,
    wycheproof_60_the_shamir_edge_case_verifies, the_base_point_is_on_the_curve_and_has_order_n.
  • RESTORATION TRIGGER, at capability grain: the dedicated P-256 instrument executes the exact
    eight-member verification population against the exact emitted native build, reaches the expected
    terminal verdict for every member, and carries a discriminating semantic control.

WHAT THE GREEN CHECK IS NOT

floor = SUCCESS on this PR is a budget refusal, not a pass. The floor emitted verdict=FloorRefused, 8 × INTERRUPTED-BEFORE-VERDICT raised_by=wall_deadline and 12 × ENROLMENT-MARGIN-REFUSED, and still concluded success — because the witness floor does not gate at all: docs/design-rung-drops.md, "The witness floor as a required merge gate", declared 2026-09-20, rung drop, deleted without replacement. Do not read coverage from it.

Not measured, stated so nothing is inferred

eval_steps for a verify is unmeasured (the floor preempts these before measuring — cost=UNMEASURED). Attribution of the cost between std.bignat mul/mod and the interpreter is unmeasured. What is measured: the three non-verifying claims cost ~288.5 K steps at ~415 ms, so ~99.8 % of a verify's wall is curve math rather than load — which separates curve math from overhead, not bignat from interpreter.

Brian Searls and others added 28 commits September 18, 2026 06:15
…pp Attest, ECDSA P-256

The protocol between the roadmap server and the operator's phone for redeeming an
approval with a biometric-gated device signature, modeled before any server route or
Swift. iOS is the V1 realization; Android is modeled at every platform point (FCM,
Keystore key attestation) and realized by nothing yet.

- extdeps.crypto.signature: ECDSA P-256/SHA-256 interface (FIPS 186-5), one wire
  encoding per key and signature, sole_constructor evidence naming its message.
- extdeps.apple.apns / secure_enclave / app_attest, extdeps.google.fcm,
  extdeps.android.key_attestation: cited upstream shapes.
- gunbc.auth.approval_device_redemption: push is an opaque wake-up only; the app fetches
  the stored request, signs over a server challenge, the stored request text, the verb
  and the capability; server owns decided_at and derives the login; signature REQUIRED
  on the device route; RedemptionIdentityEvidence names the legacy arms by mechanism.
- Server routes, verification primitives, ApprovalTarget, cutover and Android are
  declared frontiers; the existing /approve route and store are untouched while the
  Mt. Collins first boot runs on them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…icts, route paths

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Ns provider token

Two v1 host primitives over RustCrypto p256 0.13 (digest 0.10, the family sha2 0.10
and hmac 0.12 already use), registered exactly as hmac_sha256_hex was (40fb5b9):
04_method.dag signature + infer_method mirror (--required-regen
first_generation_equal=true), dispatch roster, interpreter arm, std.primitives
contract + surface name + roster, v1_interpreter_primitive_surface row.

- p256_ecdsa_verify_b64url(key_point_b64url, signature_b64url, message) -> Bool?:
  65-octet SEC 1 uncompressed point, 64-octet r||s, unpadded base64url; the message
  is SHA-256 hashed by the verifier. Absent on any non-admitted encoding.
- extdeps.crypto.signature p256_ecdsa_verify: decodes both inputs, hands the
  DECODED octet lengths to signature_verification_from_implementation. Three new
  arms keep absence from reading as a mismatch: VerifyingKeyUndecodable,
  SignatureUndecodable, SignatureEncodingRefused (right lengths, not a point on the
  curve / scalar in range).
- es256_jwt_sign(p8_pem_secret, key_id, team_id, issued_at_epoch) -> String?:
  header {alg ES256, kid}, claims {iss, iat}, JOSE r||s, RFC 6979 deterministic.
- extdeps.apple.apns apns_provider_token -> ApnsProviderTokenMint
  (Signed | AuthKeyUnreadable | IssuedAtBeforeEpoch).
- Witnesses (test.claim.p256_ecdsa_witness_test): RFC 7515 A.3's PUBLISHED ES256
  signature verifies; tampered message, tampered signature and wrong key are
  SignatureInvalid; the compressed spelling of the right key is
  VerifyingKeyMalformed{33}; an off-curve point is SignatureEncodingRefused; the
  provider token equals an exact expected JWT (independently verified with openssl)
  and verifies under the RFC public key; unreadable key and negative iat refuse.
  Rust unit tests beside the arms.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ective framing, App Attest current checks

- AdmittedDeviceRedemption and VerifiedDeviceEnrollment are sole_constructor; the decision
  commit consumes only the admitted value, and the login comes from the code's issuance.
- Enrolment code, enrolment and revocation are generations of ONE CAS slot, so consuming the
  code is creating the enrolment; expired, reused, other-bytes and Android refuse.
- Every signed/MAC'd message is length-framed (code-point counts): injective for any field
  content, where the unit-separator join was not (capability_text itself contains it).
- capability_tag_hex names the tag's real encoding.
- App Attest: AppIdPrefix (not team id), validation category and bundle version refusals,
  seam-minted VerifiedAttestation/VerifiedAssertion carrying key, receipt and client data;
  redemption joins the assertion to the enrolled attest key and the exact signed bytes.
- APNs: top-level custom-data carrier; a 200 without apns-id is undecodable.
- Reads that return capabilities are assertion-authenticated; residual stated.
- std.measure Second/ByteSize for APNs ceilings and signature sizes (review 67564).
- Unconsumed declarations removed or given named consumers; Android attestation corrected;
  iOS realization frontier added; stale approval_push references fixed.
- gunbc.auth.approval_device_redemption_fixtures renders the cross-language vectors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…named in their frontiers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… split wire contract into approval_device_wire

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mption_fixtures regen)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…_realization_frontier (review 67617)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…itable, not validated (review 67625)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Admission takes the signing input plus seam-minted verdicts; SignedRedemption, EnrolmentRequest,
  PresentedEnrollmentEvidence and PushRegistration are wire bodies in approval_device_wire, with
  their deferred consumer stated once; RedemptionIdentityEvidence deleted.
- Capability tag carried in its canonical base64url spelling and converted through
  capability_tag_hex (refusing a non-canonical spelling); witnesses start from issue_capability and
  refuse a hex tag placed in the base64url field.
- App Attest assertion result renamed AssertionAuthentic and scoped to the checks it makes; counter
  and challenge left to the consumer, whose replay authority is stated; stored public key joined.
- APNs: only Apple's documented priorities (10, 5); push type scoped to alert.
- Read transcript vectors and a discriminating witness; enrolment store (code/enrolment/revocation
  as one CAS slot) with record round-trip witnesses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	dag/gunbc/auth/approval_device_redemption.dag
…ane A)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ks already carry them

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…expectation typed

- vectors.json is gunbc.generated_artifact ApprovalDeviceVectorsArtifact (new RepoConsumer
  CrossLanguageClientTest), so the required generated-artifact phase and the refusing merge driver
  gate it; the module's own regen/agree are deleted as a second route.
- device_store_write takes std.durable_compare_and_set CasExpectation; the Int decode and its
  absorbing else are gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…7674)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… owner (side-chat condition)

The issuer takes no login: issue_enrolment_code writes approval_operator_login. No HTTP issuer,
since the loopback dashboard is reachable by every on-host POSIX user; the enrolment POST only
consumes an already-issued code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	dag/gunbc/auth/approval_device_redemption.dag
…fact (from CI heal bundle)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…6_jwt_sign; the JWS is assembled in .dag (review 67702)

The host kernel is only the P-256 signature. The APNs provider token's header, claims,
base64url segments and compact form are now assembled from
extdeps.languages.json.emit, a new extdeps.auth.jws (RFC 7515) and
extdeps.crypto.signature p256_ecdsa_sign, and the token is typed as
extdeps.auth.jwt JwtCompactSerialization. The unreachable issued_at < 0 check goes
with the old primitive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.gitattributes
#	.github/workflows/witnesses.yml
#	dag/gunbc/generated_artifact.dag
#	dag/gunbc/generated_artifact_emit.dag
…and P-256 ECDSA verify in .dag

std.bitwise: UInt32 / Word64 ops by arithmetic, conjunction from a derived nibble table.
std.bignat: 24-bit limbs (product < 2^48; conversion is octet slicing), Montgomery CIOS.
extdeps.crypto.sha2: FIPS 180-4 SHA-256 (constants checked against a derivation from the primes).
extdeps.crypto.nist_p256: SP 800-186 P-256, Jacobian arithmetic, Shamir, FIPS 186-5 verify.

Measured on the v1 interpreter (clock in-process): SHA-256 of 1 KiB = 2724 ms, correct;
one P-256 verify (RFC 6979 A.2.5 'sample') = 226455 ms, answer true. Above the gate's
few-second bound: stopped and reported, not optimized. Not wired into any seam yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e emitter refuses body annotations, DESIGN 4c)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, at rustc (18-site receipt from the A1 gate)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…arithmetic as unbounded Int (std.bitwise xor reaching 2^33 typed UInt32)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The operator's no-Rust-escape-hatch ruling retires the host builtin route, and #11585's
modules plus one of the two failure-mode rows have since landed on main. What remains is
the substrate crypto and its witnesses: std.bitwise, std.bignat, extdeps.crypto.sha2,
extdeps.crypto.nist_p256, and the SHA-256 and P-256 witness modules.

Dropped here: the p256/es256 interpreter arms, their registrations, Cargo edges and
Cargo.lock; extdeps.auth.jws (its consumer did not land, so it would be unconsumed);
and the files main already carries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title approval app A: ECDSA/ES256/App Attest primitives The approval app's crypto in .dag: bitwise, bignat, SHA-256, P-256 (blocked on MachineWidth reflection) Sep 20, 2026
…over a supplied width

Items 1-3 of the approved A2 shape, authored now because the corrected ruling leaves nothing
in them waiting on the compiler lane: the type-level index keeps taking a Nat literal or
PointerWidth, and BitWidth is std.measure's existing declaration reached at the value layer.

- Three-arm width admission: WidthAdmitted, WidthNotStatic (MachineWidth<PointerWidth> has no
  constant to reify; IntPlatform and UIntPlatform are its live inhabitants) and
  WidthExceedsHostInt. The two refusals stay separate because they are different deficits.
  WidthExceedsHostInt carries its next-rung trigger: arbitrary precision through std.bignat,
  SUFFICIENT FOR UInt64 and UInt128 to become admitted widths.
- A sole-constructed carrier whose range admission is its only mint, so an out-of-width value
  has no constructor.
- Checked add/sub/mul/div/rem with typed outcomes, plus add_mod/mul_mod as the declared
  wrapping arm. WidthMismatch refuses two widths as operands rather than widening one.
  Multiplication carries its own admission, since a product needs twice the width.

WidthNotStatic has no producer here and says so: only the binding layer, which needs
reification, can mint it. Not authored: the UInt32/UInt8 bindings, the 18 conversions, any
default width.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 20, 2026 07:54
gunbc-ci-auto-heal and others added 9 commits September 20, 2026 08:09
…oduct's ceiling

A reviewer asked whether the host-width rule refuses operands that are valid and
computable. For the WRAPPING product it did, and that was an over-prohibition
(DESIGN 4d): reduction modulo 2^N depends only on the low N bits, while the shared
`bounded_nat_product_admitted` demanded 2N <= 62 because the CHECKED product needs
the whole product in order to detect overflow. UInt32 multiplication therefore
refused on both arms, though only one of them needed to.

Splitting one operand at h = N / 2 gives

  (a * b) mod 2^N = ((a1 * b) mod 2^(N - h)) * 2^h + a0 * b  (mod 2^N)

whose intermediates are h + N and (N - h) + N bits. So `bounded_nat_mul_mod` now
computes UInt32 wrapping products, while `bounded_nat_mul` on the same operands
still refuses -- correctly, since its 64-bit product does not exist on this host.
That distinction is what the shared ceiling was hiding.

The new ceiling is stated rather than rounded up: the split admits N <= 41, not
every width the carrier admits, and 42..62 still refuse as a property of the naive
host realization. Its next-rung trigger names the capability -- bignat routing or a
recursive split -- sufficient for every admitted width to have a computable
wrapping product.

Witnesses, run and read (10/10, exit 0), each with the inverted control run to show
its red is reachable: both new vectors have 64-bit full products, so neither answer
is obtainable by forming the product on this host, and the ceiling is asserted at
its 41/42 edge so a later widening is reported rather than absorbed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pe's width

REQUEST_CHANGES blocker 2, and a correction to a claim I made and built on. The
checked product was admitted by `2N <= 62`, keyed on the TYPE's width rather than
the OPERANDS' values, so 40-bit 3 * 3 was refused although its product is 9. The
refused population was every small product of a wide type.

The justification I gave -- that the checked arm "needs the full product in order
to detect overflow" -- was false. Overflow is decidable before multiplying:

  b != 0 && a > (2^N - 1) / b   <=>   a * b > 2^N - 1

which forms no product, and when it admits, the product is bounded by 2^N - 1 and
so sits inside the carrier's already-admitted width. Verified exhaustively over
N = 3, 5, 8, 12: zero mismatches against the direct comparison.

So `ProductExceedsHostInt` leaves the checked arm entirely. Re-derived rather than
assumed (blocker item 4): the variant still has exactly ONE producer -- the
wrapping arm at widths 42..62, where the split's intermediates genuinely do not
fit -- so it stays vocabulary something produces. `bounded_nat_product_admitted`
had no remaining reference and is deleted.

The witness that asserted the old refusal ENCODED THE DEFECT rather than catching
it, and is replaced by three points of the corrected behaviour: a tiny product of
a wide type, a genuinely overflowing one, and the exact top of the width. A second
witness of mine asserted that checked UInt32 still refused; the suite caught it
red on this change, and it now asserts Overflows, which is the honest answer --
the two arms differ in the QUESTION they answer, not in their host headroom.

10/10 witnesses pass, with both rewritten witnesses re-run inverted to show their
red is reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…aims a falsehood

Review of #11645, four findings, all correct. The arithmetic was re-derived here
rather than accepted.

THE OFF-BY-ONE, AND ITS ROOT. The split's admission compared its intermediate bit
demand against `bounded_nat_host_bit_ceiling()` == 62 -- but that constant is the
widest CARRIER this family admits (62 because a SUM needs N + 1 bits), not the
host integer's positive capacity, which is 63. Two unrelated limits behind one
constant: the same defect this branch removed one level up when it split the
checked and wrapping admissions apart. Width 42's largest intermediate is
9223367638806167553 against a host maximum of 9223372036854775807 -- representable
-- so 42 was being refused for no reason. 43 is the first width that genuinely
cannot fit. `bounded_nat_host_positive_bit_capacity()` now states that limit
separately.

THE WITNESS HAD CANONIZED THE BUG. It asserted a REFUSAL at width 42 for 3 * 5,
whose product is 15, so it certified the off-by-one and exercised only the
admission predicate rather than the intermediate bound that predicate is about. It
now asserts the admitted edge with near-maximal operands -- (2^42 - 1)^2 mod 2^42
= 1, an answer unobtainable by forming the 84-bit product -- and the refusal at 43.

CHECKED MULTIPLICATION CONSUMES THE EXISTING AUTHORITY rather than re-deriving it.
std.checked_arithmetic already owns "PRE-CHECK, NEVER POST-CHECK" and the
division-based bound test in checked_int_multiply. The note now cites that as the
pattern being consumed and states what is genuinely local: only the BOUND, since
this family tests the carrier's 2^N - 1 rather than the host's range.

THE ARM NAME STATED A FALSEHOOD. `ProductExceedsHostInt` is false for width-43
3 * 5 -- nothing exceeds anything; a realization refuses the width before looking
at the operands. Renamed `WrappingProductUnavailableAtWidth`, with a trigger naming
BOTH sufficient routes, recursive splitting or std.bignat.

EVIDENCE. 10/10 pass. The boundary control is the discriminating one: restoring the
constant to 62 turns the width-42 witness RED and names it, so the witness catches
the exact off-by-one rather than asserting the predicate against itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hat assumed their invariant

REQUEST_CHANGES blocker 1, closed PARTIALLY and labelled as partial.

THE HOLE. Every overflow argument in this module rests on each limb lying in
[0, 2^24) -- that is what keeps a limb product below 2^48 and the CIOS accumulator
inside the host integer. But BigNat was an ordinary record, so any caller could
write BigNat { limbs: [-5, 99999999] } and the arithmetic would compute
confidently on it. MontgomeryModulus was likewise assemblable with an even or zero
modulus, a negative width, or an inconsistent inverse. A proof resting on an
invariant the constructor does not enforce is validation standing where
construction was available (DESIGN 5), and montgomery_add's own note asserted
"both operands below m, which every montgomery_* producer guarantees" -- a
guarantee nothing enforced.

WHAT IS CLOSED: fabrication. sole_constructor means no expression outside
std.bignat mints either carrier. This was cheap and safe because the real
population is ELEVEN constructions, all inside the module, zero outside -- my
earlier count of 32 across two files was a grep over "-> BigNat {", which counted
function signatures rather than constructions.

WHAT IS NOT CLOSED, stated on the carrier rather than implied: sole_constructor
restricts WHO may construct, not WHAT may be constructed. montgomery_modulus still
computes its fields from unchecked arguments. Its next-rung trigger names the
capability -- a refusing admission establishing positive width, odd nonzero
modulus, modulus fitting the width, and consistent derived fields -- and says why
it is deferred: making it refusing turns p256_field and p256_order into refusable
data rows, cascading through every consumer of the field arithmetic, and the P-256
suite has never executed, so that restructuring cannot yet be read for breakage.

THE PAIRED WITNESS, because a wall whose interior is never exercised is
specification-without-execution. Five small-modulus claims run the real producers
and read their answers: Montgomery round trip, multiplication that must reduce
past the modulus (20 * 30 = 600 = 6 * 97 + 18), additive wrap, and zero. 5/5 pass
-- the first executed evidence std.bignat has ever had.

CONTROL, perturbing the code rather than the assertion: setting bignat_base to
2^24 - 1 turns THREE of the five red by name. The other two do not depend on the
base under that perturbation, so they are weaker claims and are not credited with
discrimination they do not have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… 69034)

BOTH FINDINGS CONFIRMED FROM THE CODE. One correction to the second is below.

FABRICATED DEFAULT ON THE CURVE PARAMETERS. `p256_hex_nat` answered
`Absent => bignat_small(n: 0)`, and it produced p256_p, p256_n, p256_b, p256_gx
and p256_gy -- so a mistyped literal would have substituted ZERO FOR THE FIELD
PRIME and every verification below would have computed confidently on a
degenerate curve. DESIGN 5 makes that a hard reject.

GUARDING THE ARM WAS NOT THE FIX; REMOVING THE DECODE WAS. A refusing
p256_hex_nat would make every consumer answer an Optional for a condition that
cannot occur at runtime, since the input is a source literal. The parameters are
now their big-endian octet encodings read by the total bignat_from_octets, so
there is no arm to fabricate on. The same shape in the P-256 witness
(`Absent => []`, where a mistyped vector became the empty message and the
`is_absent` claims would still have GREENED, defeating their own red) is fixed
the same way: all 14 vectors are octet literals and the decode helper is gone.

THE UNCHECKED PUBLIC MINT. bignat_small took a raw caller Int, and my own note
in the previous commit claimed the internal constructions derive limbs "never
from unchecked caller input" -- false when written, of the very function that
made it false. An annotation is never evidence (DESIGN 4c); that one asserted
the invariant it stood next to a breach of. It now says so.

CORRECTION TO THE FINDING, established by trying to write its test: the review
names n >= 2^72 as overflowing the top limb. True in arithmetic, UNREACHABLE
here -- Int is 64-bit, so the largest expressible n is 2^63 - 1, whose top limb
is 32767, inside 2^24. My first fix guarded it anyway, and that guard was worse
than dead: evaluating `bignat_base * bignat_base * bignat_base` is 2^72, which
RAISES IntegerOverflow, so the check against an impossible input would itself
have been the failure. The unreachable arm is described on the carrier instead
of authored (DESIGN 4b: ask whether the RED is authorable before writing the
check). The NEGATIVE case is real, reachable, and is what bignat_small now
refuses -- it previously minted a negative limb.

The two literal values this module needs are total constructors with identical
limbs (bignat_zero_limbs, bignat_one), so substituting them at the p256 call
sites changes no value.

EVIDENCE: 6/6 bignat witnesses pass, including a new one asserting the negative
refusal AND admission of the largest Int -- the positive control for the note
that no Int escapes three limbs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…trigger row goes

REVIEW 69054, FINDING 1, CONFIRMED FROM THE CODE AND FIXED AT THE SITE. word32_xor
was `a + b - 2 * word32_and(...)` and word32_or was `a + b - word32_and(...)`, whose
answers are right but whose SUM reaches 2^33 - 2 while typed UInt32; word32_add
formed `a + b` before reducing. That is the invalid state
gunbc.recurring_failure_mode bounded_natural_arithmetic_evaluated_as_unbounded_int
names -- "a value typed UInt32 that is not a 32-bit unsigned natural" -- and the row
on main cites word32_xor at this line. bitwise.dag is NEW in this PR, so it was
landing fresh population of a class DESIGN 4b places BELOW the ladder, with no
declared drop. The reviewer is right that a reader of std.bitwise alone could not
have learned that.

FIXED BY REMOVING THE OVERSIZED INTERMEDIATE, NOT BY DECLARING IT. Exclusive or is
now nibble-wise through a derived table, exactly as word32_and already worked: each
lookup is over values below 16 and the accumulator is bounded by the digits
consumed. Disjunction is xor plus and, which are disjoint bit sets, so their sum IS
the disjunction and is bounded by it. Addition decides the carry BEFORE forming the
sum. Verified exhaustively at the edges and over 60,000 random pairs against the
reference operations: zero mismatches, largest xor intermediate 4294960992, inside
2^32. SHA-256 still passes 7/7 and the bounded_nat suite 10/10.

WHAT THIS DOES NOT DO, STATED SO IT IS NOT READ AS MORE: it closes the SILENT
WRONGNESS half only. std.operator_realization refuses infix arithmetic on a
structural operand regardless of MAGNITUDE, so these sites still emit as operator
refusals -- the separate class accepted_source_emits_uncompilable_target. Closing
that one needs the operations to be DECLARED rather than infix, i.e. routing through
std.bounded_nat, which is blocked today by an import cycle rather than by
reification: std.bounded_nat imports std.bitwise for bitwise_pow2, so bitwise cannot
import it back (DESIGN 3, acyclicity). Relocating bitwise_pow2 breaks that cycle and
is the real next step.

FINDING 3: bounded_nat_width_ceiling_trigger was a String prose row with no reader --
DESIGN 4c dead data, the same shape as the rows removed in 7123b08. Deleted; the
// block above already carries the trigger and now attaches to bounded_nat_admit,
which is the function that produces the arm it describes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…RED receipt

TRIGGER STATED AT THE WRONG GRAIN, CORRECTED. std.bounded_nat's frontier said its
first consumer waits on reification of the N in MachineWidth<N>. It does not: the
operations take a width as a VALUE and the witnesses pass bit_width(count: 32)
today, so nothing stopped std.bitwise passing one. Reification lets a CALLER stop
naming a width; it is not what stands between this family and its first consumer.
That is DESIGN 4b(3)'s own test -- a trigger naming less than the capability it
gates is satisfied while the capability stays dead -- applied to a row I wrote.

THE ACTUAL BLOCKER IS AN IMPORT CYCLE: std.bounded_nat imports std.bitwise for
bitwise_pow2, so std.bitwise cannot import back, and DESIGN 3 makes acyclicity the
import graph's only structural law. It is breakable by relocating bitwise_pow2
below both. The SECOND cost is stated beside it rather than left as a one-line
blocker: after the cycle breaks, every word32_* operation answers a six-arm
BoundedNatArith, and threading that through SHA-256's 64-round compression fold is
what the first consumer actually costs. Deferred to its own change with the P-256
suite as its gate, in that order, because the only suite covering that fold has
never returned a result.

RECEIPT FILED ON AN EXISTING CLASS rather than a new row:
check_subject_shape_cannot_represent_the_state_the_check_detects. Review 69034
named two invalid inputs to bignat_small; the negative is real and reachable, and
n >= 2^72 is UNREPRESENTABLE in the subject, since Int is 64-bit and the largest
expressible n has top limb 32767, inside 2^24. A check for it is permanently green
over its own subject.

WHAT THAT ROW DID NOT YET CARRY, and why this receipt is worth its space: the guard
first authored was `n >= bignat_base * bignat_base * bignat_base`, and that
expression IS 2^72, which RAISES IntegerOverflow. So the check against an input that
cannot occur would itself have been the only way to make the function fail --
converting a total function into a raising one, on every call, in service of a state
no caller could supply. A decoration costs trust; this would have cost correctness.

And it was caught by trying to WRITE THE RED: the witness could not be authored,
because 4722366482869645213696 is not a writable Int literal. The row's own
recognition rule was satisfied by the compiler refusing the construction.

bounded_nat 10/10 still passes; the edited ledger row resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 69076, confirmed and correct. word64_add was `let lo = a.lo + b.lo` over two
UInt32 halves -- reaching 2^33 - 2 while typed UInt32 -- and `a.hi + b.hi + carry`
doing it again. That is the same population of
bounded_natural_arithmetic_evaluated_as_unbounded_int that the note I added three
commits ago says word32_xor, word32_or and word32_add were rewritten to leave.

I APPLIED THE REPAIR TO THE 32-BIT FAMILY AND SKIPPED THE 64-BIT ONE IN THE SAME
DIFF, while writing the explanation of why it was needed. The defect survived
inside its own fix. It was live rather than latent: sha256_fips_witness_test drives
lo to exactly 2^32 on every run.

FIXED BY REUSING THE ALREADY-CORRECTED OPERATION rather than repeating its
reasoning. The carry is decided before either sum is formed, by the same comparison
word32_add uses, and both halves are produced by word32_add itself instead of a raw
`%` -- so the wrap is computed once, in one place.

I SCANNED THE REST OF THE FILE RATHER THAN FIXING ONLY THE SITE NAMED, since
"applied here, skipped there" is the defect. Six arithmetic sites remain over
UInt32 halves -- word32_shl, word32_rotr, word32_add's guarded branch, word64_shr
and both word64_rotr halves -- and every one is a sum of DISJOINT bit ranges.
Measured over 80,000 random inputs: largest intermediate 4294964487, inside 2^32.
word64_add was the only defect.

EVIDENCE: SHA-256 7/7 passes, and the control is discriminating rather than a
formality -- replacing the carry with a constant 0 reds
word64_add_carries_across_the_halves_and_rotr_swaps_them by name, so the witness
catches the carry and not merely the shape. The proposed implementation was also
checked against 64-bit modular addition over 80,000 random pairs plus the edges,
including the witness's own all-ones + 1: zero mismatches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…esent tense

Review 69091, confirmed. The header said this module is "the producer behind
extdeps.crypto.signature p256_ecdsa_verify". That module is real and owns the
suite, the encodings and the refusal vocabulary -- but it declares no
p256_ecdsa_verify, and neither does anything else: the name exists nowhere outside
this module and its witnesses. The only importers of all five new modules are the
four witness files.

THIS IS THE CLASS THIS PR CITES TWICE IN ITS OWN RECEIPTS.
unlanded_citation_indistinguishable_at_the_citing_end is exactly a name that reads
as landed at the point it is cited. Those receipt rows flag their unlanded names
scrupulously -- "UNLANDED NAMES, read as such" -- and this header, the one sentence
a reader consults to learn who consumes 1,772 lines of new crypto, did not. Being
the plan does not make a citation true.

Restated as the DESIGN 3c frontier it is: nothing in production consumes this
today, the consumers are the witnesses, and the trigger is named --
extdeps.crypto.signature gains p256_ecdsa_verify binding p256_ecdsa_verify_octets
through signature_verification_from_implementation.

I CHECKED THE OTHER FOUR NEW MODULES RATHER THAN ONLY THE SITE NAMED, since
applying a repair at one site and skipping its siblings is the defect I shipped one
round ago. Two make similar claims and BOTH ARE ACCURATE: std.bitwise cites
std.bit's Word32, which exists (dag/std/bit.dag:16), and extdeps.crypto.sha2 cites
extdeps.crypto.hash's Digest carrier, which exists (dag/extdeps/crypto/hash.dag:34).
nist_p256 was the only fictional citation.

Module resolves after the edit.
@gunbai-bot gunbai-bot Bot mentioned this pull request Sep 20, 2026
6 tasks

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EVIDENCE HOLD — exact head 2caafa5.

The restored floor established the important fact, but not P-256 verification: 4 cheap encoding/range witnesses reached verdicts; the 8 witnesses that exercise scalar/group verification were all budget-refused-before-verdict at the 8000 ms wall deadline. The floor itself classifies each as UNDECIDED with cost above 8000 ms and no upper bound. Green aggregation is therefore not verification evidence.

RULING ON THE ROUTE: a dedicated P-256 instrument with its own measured ceiling is rung-honest. It is not a waiver. It must name the exact bounded witness population, execute it, and treat ceiling expiry as refusal/unknown. A row that merely turns budget-refused-before-verdict into an accepted outcome would be rung inflation. The existing eval-step-cost-drop precedent is deliberately stricter: members still execute to verdict and wall-clock crossing still blocks; membership only changes adjudication of an eval-step overrun and does not raise the budget.

Native emission is NOT the only honest route. It is an honest long-term route for this module only if the production verifier is structurally native-only (or the interpreted production route refuses), the emitted function is this exact .dag verifier rather than a handwritten substitute, and the same positive/negative vectors execute against an exact-head emitted binary. Source→interpretation and source→native are distinct paths; native success cannot be cited as interpreter success.

Recommended recut/evidence order:

  1. Instrument the cost by phase: modulus/context construction, modular inverse, point double/add, double-scalar multiplication, and SHA-256. The implementation already uses Montgomery CIOS; do not describe “move to Montgomery” as the missing repair. Likely candidates are repeated context/R² derivation, Fermat inversions, bit-by-bit Shamir multiplication, or interpreter overhead.
  2. Add a dedicated instrument/runner route with an exact bounded population and a ceiling derived from one complete measurement, plus an overall job bound. Run the 8 substantive witnesses to terminal verdict on the exact head, with a deliberately mutated control that goes red by name.
  3. After MachineWidth emission lands, execute the full vector set against the emitted native verifier and make that the recurring production-path evidence. Retain at least a discriminating interpreted anchor unless the interpreter route is structurally excluded from the verifier's production capability.
  4. If the ordinary required floor ceases to own terminal verdicts for these identities, declare that path loss under §4b(3): previous/temporary rung, exact 8-member population, reason, owner/due date, and a capability-level restoration trigger. The drop may describe the missing standard-floor path; it may not count timeout as correctness.

The run cited in the rollup checked out bb50768, not this current head. It is valid evidence of the cost/coverage class, but final acceptance evidence must execute the selected route again at the exact merge head.

@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Answering review 69110 (dashboard artifact /api/reviews/69110/artifacts/stdout.log) without a push, because its central finding was answered by execution that postdates its read. Head is unchanged at bb50768c93e2f06be9936def4f556ffe8c4796fa.

The finding — "1,789 lines of new crypto land with their central claim never executed" — no longer holds as written. The suite has now executed, standalone, on an uncontended host:

arm vector wall exit
accept RFC 6979 §A.2.5 sample (P-256/SHA-256) 192 s 0 — returns true
reject same vector, first octet of r flipped 239 → 238 205 s 1 — returns false
accept RFC 6979 §A.2.5 test 734 s 0 — returns true

That is the pair the review asked for: an accepted positive control plus a discriminating red on the real acceptance path. It specifically retires the strongest form of the objection — that the four passing witnesses all assert is_absent and were therefore satisfied by a verifier returning Absent unconditionally. A function that never accepts anything cannot produce the first or third row.

The circularity the review identified was real and is fixed. The MontgomeryModulus next-rung trigger was justified by the unexecuted suite, so nothing could ever discharge it. The trigger is now a dedicated P-256 gunbc test row over the emitted native build, stated as what it must be sufficient for.

What still has not executed, declared rather than argued away — a §4b(3) drop in the body with all five parts, population named claim-by-claim: five of the eight accepting-path claims are exit=124 timeouts on a 1200 s cap under host memory pressure, and one (wycheproof_4_n_minus_r_is_false) never ran. Timeouts are reported as neither pass nor fail.

On the review's "compounding" point — it is sharper than stated, and the body now says so. Those eleven claims are discovered by the witnesses lane, and the lane reported SUCCESS anyway. floor = SUCCESS here is a budget refusal, not a pass: the floor emitted verdict=FloorRefused with 8 × INTERRUPTED-BEFORE-VERDICT raised_by=wall_deadline against a ~8 s per-claim budget, claim_executor exited nonzero, and the workflow step swallowed it under set +e. That is declared: docs/design-rung-drops.md, "The witness floor as a required merge gate — declared 2026-09-20", deleted without replacement over the whole enrolled population. So no green check on this PR should be read as coverage, and the executed receipt above is the only evidence.

Why the suite could not run in CI, measured: one interpreted verify peaks at 13.5 GiB RSS (VmHWM 14,132,132 kB) against a 7.63 GiB runner, so every remote attempt failed on memory rather than time. A per-claim step budget would not bind this; a native verify carries no interpreter heap, which is why the trigger is the native instrument row.

eval_steps for a verify remains unmeasured (the floor preempts these before measuring), and attribution between std.bignat mul/mod and the interpreter is unmeasured. Neither is estimated anywhere in the body.

— sent from wise-owl-628

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIND-DOWN APPROVE, exact head bb50768. This supersedes my evidence hold 5260787321 under the operator's wind-down disposition.

The PR body now states the evidence at its actual extent: the four named pre-curve refusal claims plus the standalone P-256 accept/reject executions, and explicitly says that this does not establish the verifier. It labels the exact eight verification-performing claims UNKNOWN rather than passing, treats timeout as neither pass nor failure, and names the bounded eight-member population. The §4b(3) trigger is at capability grain: the dedicated P-256 instrument must execute that exact population against the exact emitted native build, reach every expected terminal verdict, and carry a discriminating semantic control. The body also opens the disposition as substrate acceptance with an explicit evidence gap—not security sign-off—and separately records the app-to-verifier binding as ecdsa_verification_realization_frontier.

This approval accepts the substrate under that explicit evidence gap. It is not P-256/ES256 security sign-off and does not establish the production device-signature path. No new execution or dispatch is required in this wind-down PR.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 20, 2026
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Review 69110 (12:02Z) is right about the state it read, and it is the state the PR now declares rather than disputes. Since then: the floor lane executed the suite (run 35509606112) — four claims passed, the eight verification-performing claims were budget-refused at 8 s each; the lane produced one standalone accept/reject pair (RFC 6979 A.2.5 verifies at 192 s; the same vector with one bit flipped in r rejects at 205 s); one interpreted verification peaks at 13.5 GiB, so no floor budget can admit it. Under the operator's wind-down the PR body was rewritten to the side chat's five constraints: the eight are declared UNKNOWN (not passing) as an exact, bounded §4b(3) drop whose trigger is the capability a dedicated P-256 gunbc test row over the emitted native build is sufficient for; the PR states it is substrate acceptance with an explicit evidence gap, not security sign-off. Side-chat WIND-DOWN APPROVE at exact head bb50768 supersedes the evidence hold; enqueued. The MontgomeryModulus next-rung trigger's circularity is real and is why the trigger is now the native execution, not this suite. — sent from eager-owl-205

Merged via the queue into main with commit f5a7495 Sep 20, 2026
8 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/swift-ibex-621-dag-crypto branch September 20, 2026 16:03
briansrls pushed a commit that referenced this pull request Sep 20, 2026
…f4c577): sealed carrier landed, HMAC lands here over #11645's sha2, the bitwise/machine_word fork is kernel-arms' first slice

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
…P-256 and P-384 as parameter rows, SHA-384, and the frontier retargeted to the decided shape

extdeps.crypto.nist_prime_curve carries the a = -3 Jacobian group law, Shamir double-scalar
ladder, partial public-key validation and ECDSA verification over a SUPPLIED digest, written
once (the formulas #11645 landed for P-256, moved not rewritten). extdeps.crypto.nist_p256 is
now its parameter row plus the SHA-256-bound verifier the device signature uses; the new
extdeps.crypto.nist_p384 is the second row, consumed by Apple's chain step, whose two links are
a P-384 key signing with SHA-256 and a P-384 key signing with SHA-384. extdeps.crypto.sha2 gains
SHA-384 (the SHA-512 computation with its own initial state, truncated) over std.bitwise Word64,
its constants derived from the definition rather than transcribed.

P-256 semantics preserved by execution: the RFC 6979 sample signature verifies and the
different-message control is false through the family fold (88 s / 86 s, 56M / 55M eval steps,
peak 15.5 GiB), and n.G = infinity holds. P-384 established on real bytes: Apple's CA 1 and root
keys are on the curve, a neighbouring point is not; the two chain-link verifications are enrolled
and NOT executed here (at 17 limbs they exceed this container's memory), the same disposition as
#11645's eight P-256 rows: a dedicated native gunbc test row is their executing evidence.

gunbc.auth.approval_device_redemption ecdsa_verification_realization_frontier no longer names
the host-Rust primitive the operator refused; it names the .dag readers landed by #11970/#11975/
this change, the verifier folds still owed, and the execution route decision (natively emitted
handler once MachineWidth<N> reification lands; interpreter cost is the v1-performance row).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant