Skip to content

feat(sdk-api): SJCL-free v1 decrypt with temp fallback - #9508

Merged
pranavjain97 merged 1 commit into
masterfrom
pranavjain/wcn-43-remove-sjcl-decrypt-pr1
Aug 19, 2026
Merged

feat(sdk-api): SJCL-free v1 decrypt with temp fallback#9508
pranavjain97 merged 1 commit into
masterfrom
pranavjain/wcn-43-remove-sjcl-decrypt-pr1

Conversation

@pranavjain97

@pranavjain97 pranavjain97 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces a native (SJCL-free) v1 decrypt path in @bitgo/sdk-api using node:crypto on the server and crypto-browserify via webpack's shim in the browser. Public decrypt() routes v1 envelopes through the native path with a
temporary sjcl.decrypt fallback so callers are never blocked if native fails on an unexpected envelope shape.

Part of the ongoing SJCL migration. Also introduces an io-ts codec that validates v1 envelope shape and bounds params before any KDF work.

Behavior

  • v1 envelopes: native node:crypto → SJCL fallback + console.warn on native failure
  • v2 envelopes: unchanged (already native Argon2id)
  • Envelope validation throws before the fallback — no SJCL bypass on malformed input
  • Success on native is silent; a warn is a real signal to investigate

Test evidence

  • 32 Node parity tests against SJCL-produced envelopes (aes-128/256, tag sizes, adata, UTF-8, >64 KiB plaintext, 50 randomised inputs, malformed rejection)
  • 12 browser-shim parity tests through crypto-browserify
  • 8 real BitGo keycard fixture assertions: byte-for-byte identical output between SJCL, node:crypto, and crypto-browserify on user + backup keys from a throwaway testnet Solana wallet
  • 3 fallback behavior tests
  • 250 sdk-api tests pass, lint + tsc clean

Test plan

  • v1-encrypted encryptedPrv/keycards decrypts correctly in Express/sdk
  • Tx signing flow end-to-end on express

@linear-code

linear-code Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

WCN-43

@pranavjain97 pranavjain97 changed the title feat(sdk-api): SJCL-free v1 decrypt; Node default, browser opt-in feat(sdk-api): SJCL-free v1 decrypt with temporary fallback Aug 14, 2026
@pranavjain97 pranavjain97 changed the title feat(sdk-api): SJCL-free v1 decrypt with temporary fallback feat(sdk-api): SJCL-free v1 decrypt with temp fallback Aug 14, 2026
@pranavjain97
pranavjain97 force-pushed the pranavjain/wcn-43-remove-sjcl-decrypt-pr1 branch from 72f280a to 9c43b24 Compare August 14, 2026 18:45
@pranavjain97
pranavjain97 marked this pull request as ready for review August 14, 2026 18:49
@pranavjain97
pranavjain97 requested review from a team as code owners August 14, 2026 18:49
@pranavjain97
pranavjain97 force-pushed the pranavjain/wcn-43-remove-sjcl-decrypt-pr1 branch from 9c43b24 to 7cd08ed Compare August 14, 2026 19:14
Comment thread modules/sdk-api/test/unit/decryptV1.ts Outdated
Comment thread modules/sdk-api/test/unit/decryptV1.ts
Comment thread modules/sdk-api/test/unit/decryptV1.browser.ts Outdated
Comment thread modules/sdk-api/src/encrypt.ts
@pranavjain97
pranavjain97 force-pushed the pranavjain/wcn-43-remove-sjcl-decrypt-pr1 branch from 7cd08ed to 2fab2b3 Compare August 14, 2026 19:43

@danielpeng1 danielpeng1 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.

bump the CI tests are failing

Introduces decryptV1, a native (SJCL-free) replacement for v1 envelope
decrypt using node:crypto on the server and crypto-browserify via the
existing webpack shim in browser bundles. Byte-for-byte compatible with
SJCL's envelope format.

Public decrypt() wraps the native path in a temporary SJCL fallback so
callers are never blocked if native fails on an unexpected envelope
shape. The fallback uses a two-engine vote to decide when to warn:
- Native succeeds -> return, silent
- Iter cap violation -> rethrown (preserves DoS protection)
- Both engines fail -> rethrow SJCL error (mapped upstream to
  "incorrect password"), no warn
- Native fails but SJCL succeeds -> return SJCL result + console.warn
  (engines disagree, real signal for the operator)

This design has zero false-negative risk: any envelope shape our
stricter io-ts codec rejects that SJCL can still decrypt (Scenario D),
and any hypothetical native bug that produces an auth-tag-like error
(Scenario E), both get rescued by the fallback.

decryptV1WithCrypto accepts an injected crypto module so the browser
shim test can exercise the real decrypt code with crypto-browserify
instead of duplicating the CCM logic.

decryptV1WithFallback is exported and accepts an injected native fn so
tests can trigger the fallback path deterministically without stub
frameworks.

An io-ts codec enforces an iter cap of 100k on v1 envelopes up front,
before any KDF work runs.

BitGoAPI.decrypt() adds "Unsupported state or unable to authenticate
data" to the mapping that surfaces as "incorrect password", so the
error contract downstream coin packages depend on stays stable.

Test coverage (222 total, all passing):
- Node parity tests (aes-128/256, adata, UTF-8, >64KiB L=3, 128-bit
  tags, 50 randomised inputs, one representative codec rejection,
  tampered ct, iter cap enforcement)
- Browser-shim parity tests via crypto-browserify (same shape)
- Real BitGo keycard fixture assertions on both engines
- Fallback behavior tests including an injected-native-fn test that
  triggers the SJCL-rescue path deterministically

TICKET: WCN-2079
@pranavjain97
pranavjain97 force-pushed the pranavjain/wcn-43-remove-sjcl-decrypt-pr1 branch from 38b82a1 to 49c9cbb Compare August 18, 2026 20:28

@danielpeng1 danielpeng1 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.

lgtm

@pranavjain97
pranavjain97 merged commit 654a92d into master Aug 19, 2026
25 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.

3 participants