diff --git a/.iyarc b/.iyarc index 0bf2fb5919..15f4c66e4d 100644 --- a/.iyarc +++ b/.iyarc @@ -56,3 +56,13 @@ GHSA-qffp-2rhf-9h96 # - We only use tar for packing; low risk in terms of exploitability # - Security exception approved GHSA-9ppj-qmqm-q256 + +# Excluded because: +# - CVE-2026-4258: missing point-on-curve validation in sjcl.ecc.basicKey.publicKey() +# - Transitive dependency via @bitgo/abstract-lightning > macaroon > sjcl +# - The vulnerability is in sjcl.ecc (ECDH invalid-curve attack); macaroon only uses +# sjcl.codec, sjcl.bitArray, sjcl.misc.hmac, and sjcl.hash.sha256 — no ECC operations +# - Additionally, @bitgo/sjcl (our fork) does not include sjcl.ecc at all +# - Resolved sjcl -> npm:@bitgo/sjcl@1.0.1 in root resolutions; sjcl.ecc is absent at runtime +# - No patched version of sjcl exists upstream (first_patched_version: null) +GHSA-2w8x-224x-785m diff --git a/Dockerfile b/Dockerfile index beaa840fb3..49e234d06d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,8 +45,8 @@ COPY --from=builder /tmp/bitgo/modules/abstract-lightning /var/modules/abstract- COPY --from=builder /tmp/bitgo/modules/sdk-core /var/modules/sdk-core/ COPY --from=builder /tmp/bitgo/modules/sdk-lib-mpc /var/modules/sdk-lib-mpc/ COPY --from=builder /tmp/bitgo/modules/sdk-opensslbytes /var/modules/sdk-opensslbytes/ -COPY --from=builder /tmp/bitgo/modules/secp256k1 /var/modules/secp256k1/ COPY --from=builder /tmp/bitgo/modules/sjcl /var/modules/sjcl/ +COPY --from=builder /tmp/bitgo/modules/secp256k1 /var/modules/secp256k1/ COPY --from=builder /tmp/bitgo/modules/statics /var/modules/statics/ COPY --from=builder /tmp/bitgo/modules/utxo-lib /var/modules/utxo-lib/ COPY --from=builder /tmp/bitgo/modules/blake2b /var/modules/blake2b/ @@ -145,8 +145,8 @@ RUN cd /var/modules/abstract-lightning && yarn link && \ cd /var/modules/sdk-core && yarn link && \ cd /var/modules/sdk-lib-mpc && yarn link && \ cd /var/modules/sdk-opensslbytes && yarn link && \ -cd /var/modules/secp256k1 && yarn link && \ cd /var/modules/sjcl && yarn link && \ +cd /var/modules/secp256k1 && yarn link && \ cd /var/modules/statics && yarn link && \ cd /var/modules/utxo-lib && yarn link && \ cd /var/modules/blake2b && yarn link && \ @@ -248,8 +248,8 @@ RUN cd /var/bitgo-express && \ yarn link @bitgo/sdk-core && \ yarn link @bitgo/sdk-lib-mpc && \ yarn link @bitgo/sdk-opensslbytes && \ - yarn link @bitgo/secp256k1 && \ yarn link @bitgo/sjcl && \ + yarn link @bitgo/secp256k1 && \ yarn link @bitgo/statics && \ yarn link @bitgo/utxo-lib && \ yarn link @bitgo/blake2b && \ diff --git a/modules/sdk-lib-mpc/package.json b/modules/sdk-lib-mpc/package.json index 6e87cacde1..9fd063f5ec 100644 --- a/modules/sdk-lib-mpc/package.json +++ b/modules/sdk-lib-mpc/package.json @@ -53,12 +53,11 @@ }, "devDependencies": { "@bitgo/sdk-opensslbytes": "^2.1.0", + "@bitgo/sjcl": "^1.0.1", "@silencelaboratories/dkls-wasm-ll-bundler": "1.2.0-pre.4", "@types/lodash": "^4.14.151", "@types/node": "^24.10.9", - "@types/sjcl": "1.0.34", - "nyc": "^15.0.0", - "sjcl": "1.0.8" + "nyc": "^15.0.0" }, "peerDependencies": { "@silencelaboratories/dkls-wasm-ll-bundler": "1.2.0-pre.4" diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts index 4f55d4b586..a0c6e6e35c 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts @@ -7,7 +7,7 @@ import assert from 'assert'; import { Keyshare } from '@silencelaboratories/dkls-wasm-ll-node'; import { decode } from 'cbor-x'; import * as mpcv2KeyCardData from './fixtures/mpcv2keycarddata'; -import * as sjcl from 'sjcl'; +import * as sjcl from '@bitgo/sjcl'; import { DeserializedBroadcastMessage, DeserializedDklsSignature, diff --git a/modules/sjcl/index.d.ts b/modules/sjcl/index.d.ts new file mode 100644 index 0000000000..3d6bae5786 --- /dev/null +++ b/modules/sjcl/index.d.ts @@ -0,0 +1,268 @@ +export = sjcl; +export as namespace sjcl; + +declare namespace sjcl { + export var bitArray: BitArrayStatic; + export var codec: SjclCodecs; + export var hash: SjclHashes; + export var exception: SjclExceptions; + export var cipher: SjclCiphers; + export var mode: SjclModes; + export var misc: SjclMisc; + export var random: SjclRandom; + export var prng: SjclRandomStatic; + export var keyexchange: Record; + export var json: SjclJson; + export var encrypt: SjclConvenienceEncryptor; + export var decrypt: SjclConvenienceDecryptor; + + // ________________________________________________________________________ + + interface BitArray extends Array {} + + interface BitArrayStatic { + /** Array slices in units of bits. */ + bitSlice(a: BitArray, bstart: number, bend: number): BitArray; + + /** Extract a number packed into a bit array. */ + extract(a: BitArray, bstart: number, blength: number): number; + + /** Concatenate two bit arrays. */ + concat(a1: BitArray, a2: BitArray): BitArray; + + /** Find the length of an array of bits. */ + bitLength(a: BitArray): number; + + /** Truncate an array. */ + clamp(a: BitArray, len: number): BitArray; + + /** Make a partial word for a bit array. */ + partial(len: number, x: number, _end?: number): number; + + /** Get the number of bits used by a partial word. */ + getPartial(x: number): number; + + /** Compare two arrays for equality in a predictable amount of time. */ + equal(a: BitArray, b: BitArray): boolean; + } + + // ________________________________________________________________________ + + interface SjclCodec { + fromBits(bits: BitArray): T; + toBits(value: T): BitArray; + } + + interface SjclCodecs { + utf8String: SjclCodec; + hex: SjclCodec; + bytes: SjclCodec; + base64: SjclCodec; + base64url: SjclCodec; + } + + // ________________________________________________________________________ + + interface SjclHash { + blockSize: number; + reset(): SjclHash; + update(data: BitArray | string): SjclHash; + finalize(): BitArray; + } + + interface SjclHashStatic { + new (hash?: SjclHash): SjclHash; + hash(data: BitArray | string): BitArray; + } + + interface SjclHashes { + sha1: SjclHashStatic; + sha256: SjclHashStatic; + sha512: SjclHashStatic; + } + + // ________________________________________________________________________ + + interface SjclExceptions { + corrupt: SjclExceptionFactory; + invalid: SjclExceptionFactory; + bug: SjclExceptionFactory; + notReady: SjclExceptionFactory; + } + + interface SjclExceptionFactory { + new (message: string): Error; + } + + // ________________________________________________________________________ + + interface SjclCiphers { + aes: SjclCipherStatic; + } + + interface SjclCipher { + encrypt(data: number[]): number[]; + decrypt(data: number[]): number[]; + } + + interface SjclCipherStatic { + new (key: number[]): SjclCipher; + } + + // ________________________________________________________________________ + + interface SjclModes { + gcm: SjclGCMMode; + ccm: SjclCCMMode; + ocb2: SjclOCB2Mode; + } + + interface SjclGCMMode { + encrypt(prf: SjclCipher, plaintext: BitArray, iv: BitArray, adata?: BitArray, tlen?: number): BitArray; + decrypt(prf: SjclCipher, ciphertext: BitArray, iv: BitArray, adata?: BitArray, tlen?: number): BitArray; + } + + interface SjclCCMMode { + encrypt(prf: SjclCipher, plaintext: BitArray, iv: BitArray, adata?: BitArray, tlen?: number): BitArray; + decrypt(prf: SjclCipher, ciphertext: BitArray, iv: BitArray, adata?: BitArray, tlen?: number): BitArray; + } + + interface SjclOCB2Mode { + encrypt( + prf: SjclCipher, + plaintext: BitArray, + iv: BitArray, + adata?: BitArray, + tlen?: number, + premac?: boolean + ): BitArray; + decrypt( + prf: SjclCipher, + ciphertext: BitArray, + iv: BitArray, + adata?: BitArray, + tlen?: number, + premac?: boolean + ): BitArray; + pmac(prf: SjclCipher, adata: BitArray): number[]; + } + + // ________________________________________________________________________ + + interface PBKDF2Params { + iter?: number | undefined; + salt?: BitArray | undefined; + } + + interface SjclMisc { + pbkdf2( + password: BitArray | string, + salt: BitArray | string, + count?: number, + length?: number, + Prff?: SjclPRFFamilyStatic + ): BitArray; + hmac: SjclHMACStatic; + cachedPbkdf2( + password: string, + obj?: PBKDF2Params + ): { + key: BitArray; + salt: BitArray; + }; + } + + class SjclPRFFamily { + encrypt(data: BitArray | string): BitArray; + } + + interface SjclHMAC extends SjclPRFFamily { + mac(data: BitArray | string): BitArray; + reset(): void; + update(data: BitArray | string): void; + digest(): BitArray; + } + + interface SjclPRFFamilyStatic { + new (key: BitArray): SjclPRFFamily; + } + + interface SjclHMACStatic { + new (key: BitArray, Hash?: SjclHashStatic): SjclHMAC; + } + + // ________________________________________________________________________ + + interface SjclRandom { + randomWords(nwords: number, paranoia?: number): BitArray; + setDefaultParanoia(paranoia: number, allowZeroParanoia: string): void; + addEntropy(data: number | number[] | string, estimatedEntropy: number, source: string): void; + isReady(paranoia?: number): number; + getProgress(paranoia?: number): number; + startCollectors(): void; + stopCollectors(): void; + addEventListener(name: string, cb: () => void): void; + removeEventListener(name: string, cb: () => void): void; + } + + interface SjclRandomStatic { + new (defaultParanoia: number): SjclRandom; + } + + // ________________________________________________________________________ + + interface SjclCipherParams { + v?: number | undefined; + iter?: number | undefined; + ks?: number | undefined; + ts?: number | undefined; + mode?: string | undefined; + adata?: string | undefined; + cipher?: string | undefined; + } + + interface SjclCipherEncryptParams extends SjclCipherParams { + salt: BitArray; + iv: BitArray; + } + + interface SjclCipherDecryptParams extends SjclCipherParams { + salt?: BitArray | undefined; + iv?: BitArray | undefined; + } + + interface SjclCipherEncrypted extends SjclCipherEncryptParams { + kemtag?: BitArray | undefined; + ct: BitArray; + } + + interface SjclCipherDecrypted extends SjclCipherEncrypted { + key: BitArray; + } + + interface SjclConvenienceEncryptor { + ( + password: BitArray | string | undefined, + plaintext: string | undefined, + params?: SjclCipherEncryptParams, + rp?: SjclCipherEncrypted + ): string; + } + + interface SjclConvenienceDecryptor { + ( + password: BitArray | string | undefined, + ciphertext: SjclCipherEncrypted | string | undefined, + params?: SjclCipherDecryptParams, + rp?: SjclCipherDecrypted + ): string; + } + + interface SjclJson { + defaults: Required; + encrypt: SjclConvenienceEncryptor; + decrypt: SjclConvenienceDecryptor; + encode(obj: object): string; + decode(obj: string): object; + } +} diff --git a/modules/sjcl/package.json b/modules/sjcl/package.json index 7b63019ca7..802118bd60 100644 --- a/modules/sjcl/package.json +++ b/modules/sjcl/package.json @@ -3,6 +3,7 @@ "version": "1.0.1", "description": "fork of Stanford Javascript Crypto Library", "main": "sjcl.min.js", + "types": "index.d.ts", "author": "BitGo SDK Team ", "license": "(BSD-2-Clause OR GPL-2.0-only)", "repository": { diff --git a/modules/web-demo/package.json b/modules/web-demo/package.json index e6bb0640b2..2fd13a9008 100644 --- a/modules/web-demo/package.json +++ b/modules/web-demo/package.json @@ -63,6 +63,7 @@ "@bitgo/sdk-core": "^36.35.0", "@bitgo/sdk-lib-mpc": "^10.9.0", "@bitgo/sdk-opensslbytes": "^2.1.0", + "@bitgo/sjcl": "^1.0.1", "@bitgo/statics": "^58.31.0", "bitgo": "^50.28.0", "lodash": "^4.17.15", @@ -70,7 +71,6 @@ "react-dom": "^18.0.0", "react-json-view": "^1.21.3", "react-router-dom": "6.3.0", - "sjcl": "1.0.8", "styled-components": "^5.3.5" }, "devDependencies": { diff --git a/modules/web-demo/src/components/KeyCard/fixtures.ts b/modules/web-demo/src/components/KeyCard/fixtures.ts index 1c3697ee9e..401447beee 100644 --- a/modules/web-demo/src/components/KeyCard/fixtures.ts +++ b/modules/web-demo/src/components/KeyCard/fixtures.ts @@ -2,7 +2,7 @@ import { generateKeycard } from '@bitgo/key-card'; import { KeyCurve, coins } from '@bitgo/statics'; import { Keychain } from '@bitgo/sdk-core'; import { DklsDkg, DklsTypes } from '@bitgo/sdk-lib-mpc'; -import * as sjcl from 'sjcl'; +import * as sjcl from '@bitgo/sjcl'; function downloadKeycardImage(coinFamily: string): Promise { return new Promise((resolve, reject) => { diff --git a/package.json b/package.json index 6bba7592d1..2fc4557512 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,8 @@ "**/tronweb/**/validator": "13.15.23", "@isaacs/brace-expansion": "5.0.1", "basic-ftp": ">=5.2.0", - "flatted": "3.4.0" + "flatted": "3.4.2", + "sjcl": "npm:@bitgo/sjcl@1.0.1" }, "workspaces": [ "modules/*" diff --git a/yarn.lock b/yarn.lock index f2e0dcba1e..55730add54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6421,11 +6421,6 @@ resolved "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz" integrity sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww== -"@types/sjcl@1.0.34": - version "1.0.34" - resolved "https://registry.npmjs.org/@types/sjcl/-/sjcl-1.0.34.tgz" - integrity sha512-bQHEeK5DTQRunIfQeUMgtpPsNNCcZyQ9MJuAfW1I7iN0LDunTc78Fu17STbLMd7KiEY/g2zHVApippa70h6HoQ== - "@types/sockjs@^0.3.36": version "0.3.36" resolved "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz" @@ -11781,10 +11776,10 @@ flat@^5.0.2: resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@3.4.0, flatted@^3.2.7, flatted@^3.2.9: - version "3.4.0" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz#92ab2efec9b272eb85a3a25a106c3afbbc990d8b" - integrity sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw== +flatted@3.4.2, flatted@^3.2.7, flatted@^3.2.9: + version "3.4.2" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== flux@^4.0.1: version "4.0.4" @@ -18885,10 +18880,10 @@ sisteransi@^1.0.5: resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -sjcl@1.0.8, sjcl@^1.0.6: - version "1.0.8" - resolved "https://registry.npmjs.org/sjcl/-/sjcl-1.0.8.tgz" - integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ== +sjcl@^1.0.6, "sjcl@npm:@bitgo/sjcl@1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@bitgo/sjcl/-/sjcl-1.0.1.tgz#633fa84608c1cb7461b17ceb6131d96722921fd3" + integrity sha512-dBICMzShC8gXdpSj9cvl4wl9Jkt4h14wt4XQ+/6V6qcC2IObyKRJfaG5TYUU6RvVknhPBPyBx9v84vNKODM5fQ== slash@3.0.0, slash@^3.0.0: version "3.0.0"