Skip to content

refactor(bytes): one codec module for base64/hex/byte normalization - #371

Merged
PastaPastaPasta merged 1 commit into
stagingfrom
refactor/bytes-codec
Sep 7, 2026
Merged

refactor(bytes): one codec module for base64/hex/byte normalization#371
PastaPastaPasta merged 1 commit into
stagingfrom
refactor/bytes-codec

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Owner

Second PR of the anti-slop series (after #370). One codec module for the byte conversions that were re-implemented across the tree.

What was there

Helper Hand-rolled copies
base64 → bytes 13
hex → bytes 4 (plus 9 inline parseInt(substr, 16) loops)
"normalize whatever the SDK returned" 6 (two byte-identical private methods in the private-feed services)
bytes equality 2

They did not agree. Most tried base64 before hex, which mis-decodes hex identity-key data since a 66-character hex string is also syntactically valid base64. One returned an empty Uint8Array on failure with a warning, which the private-feed decryptor downstream reported as "your access has been revoked". lib/crypto reached into lib/services through dynamic imports just to call getPublicKey.

What replaces it

lib/bytes.ts, 140 lines, with lib/bytes.test.ts:

  • bytesToBase64 / base64ToBytes, bytesToHex / hexToBytes, bytesToBase64Url, bytesEqual, isHexString
  • normalizeBytes(value): accepts Uint8Array, number[], a JSON-serialised Node Buffer, hex strings, base64 strings; returns null on anything else. Hex is tried first for the reason above (real base64 of random bytes is never all hex digits).
  • requireBytes(value, label): same, but throws with the field name.

Decoders return Uint8Array<ArrayBuffer> so results go straight to Web Crypto without a cast.

Behaviour changes worth knowing

  • The private-feed document transforms use requireBytes. A field that cannot be decoded now fails the query (caught by the existing per-method catch, logged, returns null/[]) rather than producing an empty array that surfaces later as a REVOKED message. Every replaced site was checked to sit inside such a catch.
  • The 54-line "Credit Transfer Debug" block in tip-service.sendTip is removed. It re-fetched the identity, derived a keypair, and logged the WIF prefix and derived public key on every tip.
  • lib/crypto/key-validation.ts and key-derivation.ts import getPublicKey from crypto/keys instead of dynamically importing the private-feed crypto service. No import cycle: keys.ts depends only on hash.ts, wif.ts and bytes.ts.
  • The DM service's 60-line public-key extractor (three field names, each with a bs58/base64 branch) is now a loop over the shared codec. Its Buffer usage is gone.

Wire formats are unchanged. The base64 loops replaced are byte-for-byte the same operation, so existing DMs, key backups and the state-transition retry cache all decode as before.

Review

Independently reviewed against the base: no confirmed bugs. One plausible note (a bad document in a paginated private-feed list now fails the whole page instead of one entry, but documentToPlainObject keeps byte fields as Uint8Array, so strings never reach those sites in practice). The reviewer's cosmetic nits (an unused exported Bytes alias, two now-redundant as ArrayBuffer casts) are folded into the next PR, which touches those files anyway.

Validation

npm run lint, npx tsc --noEmit, npm run test (60 specs), npx knip --include files,dependencies,unlisted, npm run build all clean locally.

🤖 Generated with Claude Code

Thirteen files carried their own base64 decoder, four their own hex decoder, and six a private 'normalize whatever the SDK returned' helper, with subtly different rules (some tried base64 before hex and silently mis-decoded hex key data, one returned an empty array on failure). lib/bytes.ts is now the single source: bytesToBase64/base64ToBytes, bytesToHex/hexToBytes, bytesToBase64Url, bytesEqual, normalizeBytes (lenient, null on failure) and requireBytes (throws with a field label). Strings are tried as hex first, since hex key data is also syntactically valid base64 but never the other way round.

Private-feed document transforms now use requireBytes, so a field that cannot be decoded surfaces as a query error instead of an empty array that the decryptor later reports as REVOKED. lib/crypto no longer reaches into lib/services for getPublicKey; it uses crypto/keys directly. DM service drops its Buffer usage and 60-line public-key extractor in favour of the shared codec. The 54-line credit-transfer debug block in tip-service, which re-fetched the identity and logged a WIF prefix on every tip, is removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 1f420e8a-3dbe-4457-a2b9-352507807c7f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — 32 ahead in queue (commit 2e8765b)
Queue position: 33/33

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.

2 participants