Skip to content

The other five base64url copies (GRYT-898) - #379

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-898-client-base64-rest
Sep 4, 2026
Merged

The other five base64url copies (GRYT-898)#379
sivert-io merged 1 commit into
mainfrom
claude/GRYT-898-client-base64-rest

Conversation

@sivert-io

Copy link
Copy Markdown
Member

Review-required path, so this is yours to merge.

#378 said "three copies" and was wrong. It found them by grepping the exported name, which missed every private one. Grepping for btoa instead turned up five more:

File What it had
auth/identity-keys.ts base64UrlEncode + base64UrlDecode
auth/identity-seed.ts base64Url
auth/server-pins.ts base64UrlToBytes + base64UrlEncode
lib/reports/assertion.ts inline btoa on a SHA-256 digest

All four files now use @gryt/crypto. Where a caller hands over an ArrayBuffer, the coercion stays local and only the encoding is shared.

What to look at

server-pins.ts. Its decoder added padding by hand before atob. Crypto's takes either alphabet and padding or none of it, so the padding is gone rather than moved — that is the one place the shape of the call changed. Old decoder against new: 2425 cases, every length 0 to 96 bytes, zero mismatches.

identity-seed.ts and identity-keys.ts are the identity core, but the substitution there is the same one #378 already proved, and both are covered by the vault suites below.

Deliberately left

atob used to turn a JWT payload straight into a string for JSON.parse — in account-api.ts, device-delegation.ts, identity-certificate.ts and keycloak.ts. That is base64url to text, not to bytes, and routing it through the shared decoder means adding a TextDecoder at each site. Worth doing, not the same change, and I did not want to bury it in this one.

Verification

yarn lint clean. identity-vault, message-vault, message-key, message-key-offer and server-notice all pass, against the published @gryt/crypto@0.3.0.

🤖 Generated with Claude Code

#378 said "three copies" and was wrong. It found them by grepping the exported
name, which missed every private one, so `identity-keys.ts`, `identity-seed.ts`,
`server-pins.ts` and `lib/reports/assertion.ts` kept hand-rolling the same
`btoa` pair. Grepping for `btoa` rather than for the name is what found the
rest, and is how this should have been scoped the first time.

All four now use @gryt/crypto. Where a caller hands over an `ArrayBuffer` the
coercion stays local and only the encoding is shared.

`server-pins.ts` is the one worth a second look. Its decoder added padding by
hand before `atob`; crypto's takes either alphabet and padding or none of it, so
the padding is gone rather than moved. Checked rather than assumed — the old
decoder and the new one agree across 2425 cases, every length from 0 to 96
bytes.

What is deliberately left is the other shape: `atob` used to turn a JWT payload
straight into a string for `JSON.parse`, in account-api, device-delegation,
identity-certificate and keycloak. Those are base64url to text, not to bytes,
and routing them through the shared decoder means adding a TextDecoder at each
site. Worth doing and not the same change.

`yarn lint` clean, and identity-vault, message-vault, message-key,
message-key-offer and server-notice all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sivert-io
sivert-io merged commit 9454fed into main Sep 4, 2026
3 checks passed
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