signature: public RSA verification (PKCS #1 v1.5, PSS) in std/signature (step 1 of gap 6) #110
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Gap 6 of FIRST-PARTY.md asks for RSA and ECDSA in
std/signature. ECDSA P-256 verification (ES256) is already there (#225). What is missing for JWTRS256/PS256and OpenID Connect is a public RSA verifier:tools/registry/src/rsa.trbholds a private one (RSASSA-PKCS1-v1_5 with SHA-256, Montgomery over 16-bit limbs). This issue is step 1 of gap 6: make that verifier a public part ofstd/signature, add RSASSA-PSS, and move the registry onto it.Out of scope here, filed just in time when they start (see the decision comment): RSA-OAEP encryption (
caching_sha2_passwordoftorb/mysql, which is not built yet and requires TLS until then) and ECDSA signing (needs RFC 6979 and constant-time scalar arithmetic).Design record points
std/signature) and gap 10 (torb/jwt,torb/oauth: "HMAC, RSA and ECDSA verification instd/signature").RS256verified in TorbScript, "no new native, both back ends compute the same bits"): stays true.std/signature(P256PublicKey,SignatureError, factories that validate,with TryFrom<..., SignatureError>).Scope
Two Conventional Commits in one PR:
feat(signature): RsaPublicKey with RSASSA-PKCS1-v1_5 verification- move the arithmetic oftools/registry/src/rsa.trbtostd/signature/src/rsa.trb(limb helpers may share./montgomerywhere the shapes fit, otherwise stay private), exposeRsaPublicKey(from modulus and exponent bytes, i.e. a JWK'snande, via a factory that returnsResult<RsaPublicKey, SignatureError>) withverifiesPkcs1Sha256(message, signature): Bool;tools/registryimports it fromstd/signature, its privatersa.trbis deleted.feat(signature): RSASSA-PSS verification (PS256)-verifiesPssSha256(message, signature): Bool(RFC 8017 section 8.1.2 / 9.1.2, MGF1 with SHA-256, salt length 32 as JWA fixes it for PS256).Policy carried over from the private version: keys under 2048 bits, even moduli, an exponent below 3 or even, a signature of the wrong length or not below the modulus are refused (
false; the factory rejects what it can); the encoded message is compared whole, no lenient parser. Variable time is fine (public operation only) and is stated in the doc comment as inp256.trb.Acceptance criteria
std/signatureexportsRsaPublicKeywithverifiesPkcs1Sha256andverifiesPssSha256(doc comments with example and "Pitfalls",torb doc --checkclean).std/signature/tests/rsa.test.trb: valid RS256 and PS256 vectors (2048 and 3072-bit keys, generated outside the repo with a standard tool and checked in as hex), plus Wycheproof-style negatives: wrong message, flipped bit in signature, signature >= modulus, wrong length, wrong DigestInfo/padding (BER-lenient and trailing garbage forms), PSS with wrong salt length and bad trailer byte, 1024-bit and even modulus refused.tools/registryverifies RS256 throughstd/signature; its JWT tests still pass unchanged andtools/registry/src/rsa.trbis gone.skills/regenerated if docs changed.sh tools/gates.sh agreen in forge CI.Seed impact
None expected:
.trbonly instd/signatureandtools/registry; no syntax change, no new native, no driver flag, no std name looked up by string by the compiler. If the move shows otherwise, stop and comment.Size
One PR, two commits (above). Pure TorbScript; the performance of the 2048-bit modular exponentiation in the VM is already measured by the registry in production and unchanged by a move.
Dependencies
None open.
[loop:torbscript] intake: in scope (author orion, label loop), source FIRST-PARTY.md gap 6, not a duplicate. No dependencies; crypto work, refine must settle the algorithm set and test vectors. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z
std/cipherwith ChaCha20-Poly1305 (RFC 8439) #264[loop:torbscript] intake: unchanged. Refine is queued behind the in-progress refines (#227, #230), ready buffer is being filled by those. next: waiting (refine queued, ready buffer) until 2026-10-10T04:00:00Z
[loop:torbscript] claim refine by session:loop-torbscript-20261026 until 2026-10-26T02:30:00Z
signature: RSA and ECDSAto signature: public RSA verification (PKCS #1 v1.5, PSS) in std/signature (step 1 of gap 6)[loop:torbscript] decided by the loop: gap 6 is split. ECDSA P-256 verification already exists (#225), so the open work is RSA. Step 1 (this issue, narrowed) = move the registry's private RS256 verifier into std/signature as
RsaPublicKeyand add PSS (PS256): 2 commits, std and registry.trbonly, no seed impact. Left out and filed just in time when they start: RSA-OAEP encryption (only consumer istorb/mysql, not built, TLS required until then) and ECDSA signing (RFC 6979 plus constant-time scalar maths, no consumer yet). SHA-256 only (what JWA RS256/PS256 use); RS384/RS512 wait for a consumer and SHA-384 in std/digest. API as methods on the key (verifiesPkcs1Sha256,verifiesPssSha256), following #146 andP256PublicKey; PSS salt length 32 as JWA fixes it. No owner question: nothing public, costly or product-directional.[loop:torbscript] refine: issue rewritten (goal, acceptance criteria, design record points, seed impact none, size one PR / two commits), duplicates checked (no other open issue on RSA/ECDSA/PSS/OAEP), no open dependency; label
readyset. ~60k tokens. next: ready[loop:torbscript] claim implement by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z
[loop:torbscript] implement: checkpoint, context budget reached. PR #489 (branch loop/110-rsa-public-key, commit e0d0ab6d, no CI run yet), ~110k tokens, next: waiting (continuation worker).
Done: std/signature/src/rsa.trb (RsaPublicKey.fromComponents, verifiesPkcs1Sha256, verifiesPssSha256 incl. MGF1; registry arithmetic moved), SignatureError.NotAnRsaKey, lib.trb export, tools/registry oidc.trb/webauthn.trb/trusted-publishing test moved onto std/signature, registry rsa.trb deleted, FIRST-PARTY gap 6 + RELEASE.md + registry README updated.
Not done: (1) nothing has been compiled or run yet: first run
torb format --checkand one quick test locally if a torb binary exists, else a CI run; likely syntax risks in rsa.trb:const block = match ... { None => return false },encoded = block[1..length]slices,database[0] = ...on Bytes,.ok().map({...}) ?? falsein oidc.trb. (2) std/signature/tests/rsa.test.trb with RS256/PS256 vectors (2048 and 3072 bit, generate with openssl, hex) and the Wycheproof-style negatives from the issue. (3) split into the two commits of the plan (PSS into its own commit, e.g. by interactive rebase of e0d0ab6d), run tools/check-commits.sh. (4)torb doc --check, regenerate skills/ if docs changed, add Closes #110 to the PR body, ci-wait.sh untilsh tools/gates.sh ais green, request Argus.[loop:torbscript] collect: implement checkpoint collected (e0d0ab6d on PR #489). Continuation worker 2 of 3 started on the checkpoint steps (compile, rsa.test.trb vectors, PSS split, CI); implement claim kept. next: waiting (continuation worker)
[loop:torbscript] implement: checkpoint (continuation 2), context budget reached. PR #489, branch loop/110-rsa-public-key now at
093b4fe5(still one commit, force-pushed), no CI run yet, ~100k tokens. next: waiting (continuation worker 3).Done this round:
torbfrom the forge releases, unpacked to a temp dir, withTORB_STD/TORB_RUNTIMEpointing at the worktree. That is enough for check/format/VM tests and needs no C compiler.None => return falseinside amatchused as an expression does not parse.verifiesPssSha256now matches onpublicOperationand hands off to a file-privatepssMatches(message, block, bitsOfModulus).torb formatapplied to the touched .trb files.torb check std/signature: 15 files, no problems.torb check tools/registry: 52 files, no problems. The.ok().map({...}) ?? falsein oidc.trb is fine.torb test tools/registry/tests/trusted-publishing.test.trb(VM): 30 passed, 0 failed. RS256 now runs through std'sRsaPublicKeyand still verifies against the Node vectors.Remaining:
verifiesPssSha256,pssMatches,mgf1Sha256, the PSS docs) into a second Conventional Commit. Run tools/check-commits.sh andtorb doc --check, and regenerate skills/ if needed.Closes #110to the PR body, ci-wait.sh untilgates.sh ais green, request argus.[loop:torbscript] claim implement (continuation 3) by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z
[loop:torbscript] implement: checkpoint (continuation 3), context budget reached. PR #489, branch loop/110-rsa-public-key pushed as
093b4fe5plus one new commit (test(signature): vectors and negatives; also fixes the factory: a 2041-bit modulus (256 bytes, top byte < 0x80) and an empty exponent were not refused). ~140k tokens. next: waiting (continuation worker 4).Done: std/signature/tests/rsa.test.trb + rsa-vectors.trb (RS256/PS256 for 2048, 3072 and 2049-bit keys from OpenSSL / python cryptography, hand-built malformed PKCS#1 and PSS blocks, factory refusals). Local run with nightly-20261025 torb (TORB_STD/TORB_RUNTIME on the worktree, VM): 19 passed, 0 failed. torb format applied.
Remaining: (1) split PSS into its own commit: squash
093b4fe5+ the test commit, then commit 1 = RsaPublicKey + PKCS1 (drop verifiesPssSha256, pssMatches, mgf1Sha256, the PS256 group in rsa.test.trb, the ps* arms in rsa-vectors.trb, the PSS words in lib.trb docs and FIRST-PARTY row), commit 2 = feat(signature): RSASSA-PSS verification (PS256). Run tools/check-commits.sh. (2) torb doc --check, regenerate skills/ if needed. (3) force-push with lease to loop/110-* only, add Closes #110 to the PR body. (4) ci-wait.sh until gates.sh a is green, fix red. (5) request Argus. Local torb binary unpacked at /tmp/torb110 (delete at the end; torb check/test takes 2 to 5 minutes per run, run in background). Worktree torbscript-language-110 left in place.[loop:torbscript] implement: Correction: the push of
b5bdc0ebto loop/110-rsa-public-key is now done (the comment above was posted before it succeeded).[loop:torbscript] release implement: the third continuation (the per-item maximum) ended with tests pushed on PR #489 (head
b5bdc0eb). The rest is finishing work on the open PR, not new implementation: split PSS into its own second commit,torb doc --checkandskills/, force-push with lease,Closes #110in the PR body, CI untilgates.sh ais green, request review. next: fix (one fix worker on the same branch, remainder as listed in the checkpoint above)[loop:torbscript] claim fix by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z (finish PR 489)
[loop:torbscript] implement: PR #489 now two commits (
0ac8d497RsaPublicKey + PKCS1,9ed4598cPSS), tools/check-commits.sh clean, force-pushed with lease, PR body has Closes #110, Argus already requested. Local checks (nightly torb, VM): rsa tests pass,torb doc --check --no-run std/signatureno problems (fixed one example style finding), docs index/skill checks up to date (no skills/ change needed; docs/design has no pdisc frontmatter). CI for9ed4598crunning, ci-wait in flight. next: waiting (CI gates.sh a on9ed4598c; fix red findings if any)[loop:torbscript] fix: checkpoint (context budget). PR #489, branch loop/110-rsa-public-key at
48d0f3c2(two commits:976ab9a2RsaPublicKey + PKCS1,48d0f3c2PSS; check-commits clean; PR body has Closes #110; Argus already requested). First CI run found only a stale pdisc stamp on tools/registry/README.md: fixed by stamp (folded into commit 1, force-pushed). Local (nightly torb, VM): rsa tests pass, doc --check --no-run std/signature clean, docs index/skill checks current. CI for48d0f3c2is queued (all four checks pending ~15 min, runner queue), no verdict yet. Local worktree code/wt/torbscript-language-110, backup branch backup-110-final (local only), torb binary at /tmp/torb110 (delete at the end). next: waiting (forge CI on48d0f3c2; if red, fix on the same branch, then Argus re-review)[loop:torbscript] release fix: the CI follow-up worker timed out; PR 489 head
48d0f3c2now has red CI checks, next: fix (read the failing jobs on48d0f3c2, fix on the same branch)[loop:torbscript] claim fix by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z (PR 489 tier A and gates red)
[loop:torbscript] fix: both red jobs on
48d0f3c2had one cause each:torb docs declarations --check(docs/standard-library/signature.md lackedcase NotAnRsaKey(reason: String)in the SignatureError block, folded into commit 1) andformat --check(std/signature/tests/rsa-vectors.trb match arms indented two spaces too little, folded into both commits). Two-commit structure kept (e6a47f3aRsaPublicKey + PKCS1, PSS on top), tools/check-commits.sh clean, force-pushed with lease, head now2120578a, Argus review re-requested. Gates not run locally. next: waiting (forge CI run 1824 on2120578astill queued after 10 min: gates, commit messages, what-the-change-touches pending; when it ends, check gates/tier A: green -> waiting on Argus review, red -> fix again)