diff --git a/.github/MIGRATION.md b/.github/MIGRATION.md index 83f53ac85..58104ec8e 100644 --- a/.github/MIGRATION.md +++ b/.github/MIGRATION.md @@ -9,6 +9,7 @@ - [`serialize` methods](#serialize-methods) - [Asset Helper Methods](#asset-helper-methods) - [CLI](#cli) + - [Triplesec](#triplesec) - [Stacks.js (\<=4.x.x) → (5.x.x)](#stacksjs-4xx--5xx) - [Breaking Changes](#breaking-changes-1) - [Buffer to Uint8Array](#buffer-to-uint8array) @@ -32,6 +33,7 @@ - The `serializeXyz` methods were changed to return `string` (hex-encoded) instead of `Uint8Array`. Compatible `serializeXzyBytes` methods were added to ease the migration. [Read more...](#serialize-methods) - The `AssetInfo` type was renamed to `Asset` for accuracy. The `Asset` helper methods were also renamed to to remove the `Info` suffix. [Read more...](#asset-helper-methods) - Remove legacy CLI methods. [Read more...](#cli) +- Disable legacy `triplesec` mnemonic encryption support. [Read more...](#triplesec) ### Stacks Network @@ -190,6 +192,12 @@ The following interfaces and methods were renamed: - Removed the `authenticator` method for legacy Blockstack authentication. +### Triplesec + +Support for encrypting/decrypting mnemonics with `triplesec` was removed. +This impacts the methods: `decrypt`, `decryptMnemonic`, and `decryptLegacy`. +Make sure to update your code to if mnemonics are stored somewhere encrypted using the legacy method. + ## Stacks.js (<=4.x.x) → (5.x.x) ### Breaking Changes diff --git a/package-lock.json b/package-lock.json index eee27a73a..614cc0dd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,9 +39,9 @@ "rimraf": "^5.0.1", "stream-http": "^3.2.0", "ts-jest": "^29.1.1", - "typedoc": "0.24.8", - "typedoc-plugin-replace-text": "3.1.0", - "typescript": "5.1.6", + "typedoc": "^0.24.8", + "typedoc-plugin-replace-text": "^3.1.0", + "typescript": "^5.1.6", "webpack": "^5.88.2", "webpack-bundle-analyzer": "^4.9.0", "webpack-cli": "^5.1.4" @@ -5074,15 +5074,6 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, - "node_modules/@types/triplesec": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/triplesec/-/triplesec-3.0.3.tgz", - "integrity": "sha512-48I+feYkx5aYe8lxOJgm3k8z/DZL3SF1MMLs8YRc8snxXvDdk3pnw98QxiMXxC8vkbsdwRrWt/VFemx5z4+u0w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/wif": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.5.tgz", @@ -12784,6 +12775,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -25516,7 +25508,6 @@ "@types/bs58check": "^2.1.0", "@types/elliptic": "^6.4.12", "@types/sha.js": "^2.4.0", - "@types/triplesec": "^3.0.0", "bitcoinjs-lib": "^5.2.0", "bs58check": "^2.1.2", "crypto-browserify": "^3.12.0", @@ -25524,8 +25515,7 @@ "jsontokens": "^4.0.1", "process": "^0.11.10", "rimraf": "^3.0.2", - "stream-browserify": "^3.0.0", - "triplesec": "^4.0.3" + "stream-browserify": "^3.0.0" } }, "packages/encryption/node_modules/@types/node": { @@ -25546,20 +25536,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/encryption/node_modules/triplesec": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-4.0.3.tgz", - "integrity": "sha512-fug70e1nJoCMxsXQJlETisAALohm84vl++IiTTHEqM7Lgqwz62jrlwqOC/gJEAJjO/ByN127sEcioB56HW3wIw==", - "dev": true, - "dependencies": { - "iced-error": ">=0.0.9", - "iced-lock": "^1.0.1", - "iced-runtime": "^1.0.2", - "more-entropy": ">=0.0.7", - "progress": "~1.1.2", - "uglify-js": "^3.1.9" - } - }, "packages/network": { "name": "@stacks/network", "version": "6.13.0", @@ -25920,10 +25896,8 @@ "@stacks/profile": "^6.13.1", "@stacks/storage": "^6.13.1", "@stacks/transactions": "^6.13.1", - "buffer": "^6.0.3", "c32check": "^2.0.0", "jsontokens": "^4.0.1", - "triplesec": "^4.0.3", "zone-file": "^2.0.0-beta.3" }, "devDependencies": { @@ -25939,41 +25913,6 @@ "version": "18.17.4", "dev": true, "license": "MIT" - }, - "packages/wallet-sdk/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "packages/wallet-sdk/node_modules/triplesec": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-4.0.3.tgz", - "integrity": "sha512-fug70e1nJoCMxsXQJlETisAALohm84vl++IiTTHEqM7Lgqwz62jrlwqOC/gJEAJjO/ByN127sEcioB56HW3wIw==", - "dependencies": { - "iced-error": ">=0.0.9", - "iced-lock": "^1.0.1", - "iced-runtime": "^1.0.2", - "more-entropy": ">=0.0.7", - "progress": "~1.1.2", - "uglify-js": "^3.1.9" - } } } } diff --git a/package.json b/package.json index 9b69cdce7..2d9fb81b4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint:fix": "eslint \"packages/**/src/**/*.{ts,tsx}\" -f unix --fix", "lint:prettier": "prettier --check \"packages/**/src/**/*.{ts,tsx|!(d.ts)}\" *.js --ignore-path .gitignore", "lint:prettier:fix": "prettier --write \"packages/**/src/**/*.{ts,tsx|!(d.ts)}\" *.js --ignore-path .gitignore", - "madge": "madge --circular --extensions ts --exclude '(triplesec|d).ts' packages/", + "madge": "madge --circular --extensions ts --exclude 'd.ts' packages/", "pack": "lerna run pack", "test": "lerna run test", "typecheck": "lerna run typecheck --parallel --no-bail --stream", diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 29a29eeb3..e7e783aa7 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -37,7 +37,6 @@ "@types/bs58check": "^2.1.0", "@types/elliptic": "^6.4.12", "@types/sha.js": "^2.4.0", - "@types/triplesec": "^3.0.0", "bitcoinjs-lib": "^5.2.0", "bs58check": "^2.1.2", "crypto-browserify": "^3.12.0", @@ -45,8 +44,7 @@ "jsontokens": "^4.0.1", "process": "^0.11.10", "rimraf": "^3.0.2", - "stream-browserify": "^3.0.0", - "triplesec": "^4.0.3" + "stream-browserify": "^3.0.0" }, "sideEffects": false, "typings": "dist/index.d.ts", diff --git a/packages/encryption/src/wallet.ts b/packages/encryption/src/wallet.ts index 6d6e0ccde..6684c56e3 100644 --- a/packages/encryption/src/wallet.ts +++ b/packages/encryption/src/wallet.ts @@ -1,26 +1,16 @@ -// https://github.com/paulmillr/scure-bip39 -// Secure, audited & minimal implementation of BIP39 mnemonic phrases. -import { validateMnemonic, mnemonicToEntropy, entropyToMnemonic } from '@scure/bip39'; +import { entropyToMnemonic, mnemonicToEntropy, validateMnemonic } from '@scure/bip39'; // Word lists not imported by default as that would increase bundle sizes too much as in case of bitcoinjs/bip39 // Use default english world list similiar to bitcoinjs/bip39 // Backward compatible with bitcoinjs/bip39 dependency // Very small in size as compared to bitcoinjs/bip39 wordlist // Reference: https://github.com/paulmillr/scure-bip39 import { wordlist } from '@scure/bip39/wordlists/english'; -import { randomBytes, GetRandomBytes } from './cryptoRandom'; -import { createSha2Hash } from './sha2Hash'; +import { bytesToHex, concatBytes, equals, hexToBytes } from '@stacks/common'; import { createCipher } from './aesCipher'; -import { createPbkdf2 } from './pbkdf2'; -import { TriplesecDecryptSignature } from './cryptoUtils'; -import { - bytesToHex, - bytesToUtf8, - concatBytes, - equals, - hexToBytes, - utf8ToBytes, -} from '@stacks/common'; +import { GetRandomBytes, randomBytes } from './cryptoRandom'; import { hmacSha256 } from './ec'; +import { createPbkdf2 } from './pbkdf2'; +import { createSha2Hash } from './sha2Hash'; /** * Encrypt a raw mnemonic phrase to be password protected @@ -67,8 +57,7 @@ export async function encryptMnemonic( const hmacPayload = concatBytes(salt, cipherText); const hmacDigest = hmacSha256(macKey, hmacPayload); - const payload = concatBytes(salt, hmacDigest, cipherText); - return payload; + return concatBytes(salt, hmacDigest, cipherText); } // Used to distinguish bad password during decrypt vs invalid format @@ -122,41 +111,8 @@ async function decryptMnemonicBytes(dataBytes: Uint8Array, password: string): Pr return mnemonic; } -/** - * Decrypt legacy triplesec keys - * @param {Uint8Array} dataBytes - The encrypted key - * @param {String} password - Password for data - * @return {Promise} Decrypted seed - * @ignore - */ -function decryptLegacy( - dataBytes: Uint8Array, - password: string, - triplesecDecrypt?: TriplesecDecryptSignature -): Promise { - return new Promise((resolve, reject) => { - if (!triplesecDecrypt) { - reject(new Error('The `triplesec.decrypt` function must be provided')); - } - triplesecDecrypt!( - { - key: utf8ToBytes(password), - data: dataBytes, - }, - (err, plaintextBytes) => { - if (!err) { - resolve(plaintextBytes!); - } else { - reject(err); - } - } - ); - }); -} - /** * Decrypt an encrypted mnemonic phrase with a password. - * Legacy triplesec encrypted payloads are also supported. * @param data - Bytes or hex-encoded string of the encrypted mnemonic * @param password - Password for data * @return {string} the raw mnemonic phrase @@ -164,15 +120,8 @@ function decryptLegacy( */ export async function decryptMnemonic( data: string | Uint8Array, - password: string, - triplesecDecrypt?: TriplesecDecryptSignature + password: string ): Promise { const dataBytes = typeof data === 'string' ? hexToBytes(data) : data; - try { - return await decryptMnemonicBytes(dataBytes, password); - } catch (error) { - if (error instanceof PasswordError) throw error; - const data = await decryptLegacy(dataBytes, password, triplesecDecrypt); - return bytesToUtf8(data); - } + return await decryptMnemonicBytes(dataBytes, password); } diff --git a/packages/encryption/tests/encryption.test.ts b/packages/encryption/tests/encryption.test.ts index 729acbe90..458e99c79 100644 --- a/packages/encryption/tests/encryption.test.ts +++ b/packages/encryption/tests/encryption.test.ts @@ -494,28 +494,11 @@ test('encryptMnemonic & decryptMnemonic', async () => { '0579649dfbc9e664073ba614fac180d3dc237b21eba57f9aee5702ba819fe17a0752c4dc7' + '94884c9e75eb60da875f778bbc1aaca1bd373ea3'; - const legacyPhrase = - 'vivid oxygen neutral wheat find thumb cigar wheel ' + 'board kiwi portion business'; - const legacyPassword = 'supersecret'; - const legacyEncrypted = - '1c94d7de0000000304d583f007c71e6e5fef354c046e8c64b1' + - 'adebd6904dcb007a1222f07313643873455ab2a3ab3819e99d518cc7d33c18bde02494aa' + - '74efc35a8970b2007b2fc715f6067cee27f5c92d020b1806b0444994aab80050a6732131' + - 'd2947a51bacb3952fb9286124b3c2b3196ff7edce66dee0dbd9eb59558e0044bddb3a78f' + - '48a66cf8d78bb46bb472bd2d5ec420c831fc384293252459524ee2d668869f33c586a944' + - '67d0ce8671260f4cc2e87140c873b6ca79fb86c6d77d134d7beb2018845a9e71e6c7ecde' + - 'dacd8a676f1f873c5f9c708cc6070642d44d2505aa9cdba26c50ad6f8d3e547fb0cba710' + - 'a7f7be54ff7ea7e98a809ddee5ef85f6f259b3a17a8d8dbaac618b80fe266a1e63ec19e4' + - '76bee9177b51894e'; - - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { triplesecDecrypt } = require('../../wallet-sdk/src/triplesec'); - // Test encryption -> decryption. Can't be done with hard-coded values // due to random salt. await encryptMnemonic(rawPhrase, rawPassword) .then( - encoded => decryptMnemonic(bytesToHex(encoded), rawPassword, triplesecDecrypt), + encoded => decryptMnemonic(bytesToHex(encoded), rawPassword), err => { fail(`Should encrypt mnemonic phrase, instead errored: ${err}`); } @@ -542,26 +525,12 @@ test('encryptMnemonic & decryptMnemonic', async () => { // // Test decryption with mocked randomBytes generator to use same salt try { - const decoded = await decryptMnemonic( - hexToBytes(encryptedPhrase), - rawPassword, - triplesecDecrypt - ); + const decoded = await decryptMnemonic(hexToBytes(encryptedPhrase), rawPassword); expect(decoded === rawPhrase).toEqual(true); } catch (err) { - fail(`Should have decrypted phrase with deterministic salt, instead errored: ${err}`); + throw `Should have decrypted phrase with deterministic salt, instead errored: ${err}`; } - // // Test valid input (No salt, so it's the same every time) - await decryptMnemonic(legacyEncrypted, legacyPassword, triplesecDecrypt).then( - decoded => { - expect(decoded).toBe(legacyPhrase); - }, - err => { - fail(`Should decrypt legacy encrypted phrase, instead errored: ${err}`); - } - ); - const errorCallback = jest.fn(); // // Invalid inputs @@ -571,7 +540,7 @@ test('encryptMnemonic & decryptMnemonic', async () => { expect(errorCallback).toHaveBeenCalledTimes(1); - await decryptMnemonic(preEncryptedPhrase, 'incorrect password', triplesecDecrypt).then(() => { + await decryptMnemonic(preEncryptedPhrase, 'incorrect password').then(() => { fail('Should have thrown on incorrect password for decryption'); }, errorCallback); diff --git a/packages/wallet-sdk/package.json b/packages/wallet-sdk/package.json index b092bd57c..f0c97ede3 100644 --- a/packages/wallet-sdk/package.json +++ b/packages/wallet-sdk/package.json @@ -37,10 +37,8 @@ "@stacks/profile": "^6.13.1", "@stacks/storage": "^6.13.1", "@stacks/transactions": "^6.13.1", - "buffer": "^6.0.3", "c32check": "^2.0.0", "jsontokens": "^4.0.1", - "triplesec": "^4.0.3", "zone-file": "^2.0.0-beta.3" }, "devDependencies": { diff --git a/packages/wallet-sdk/src/encryption.ts b/packages/wallet-sdk/src/encryption.ts index 9224c4019..4dc39959f 100644 --- a/packages/wallet-sdk/src/encryption.ts +++ b/packages/wallet-sdk/src/encryption.ts @@ -1,15 +1,13 @@ -import { encryptMnemonic, decryptMnemonic } from '@stacks/encryption'; -import { triplesecDecrypt } from './triplesec'; +import { decryptMnemonic, encryptMnemonic } from '@stacks/encryption'; /** * Decrypt an encrypted mnemonic phrase with a password. - * Legacy triplesec encrypted payloads are also supported. * @param data - Uint8Array or hex-encoded string of the encrypted mnemonic * @param password - Password for data * @return the raw mnemonic phrase */ export function decrypt(dataBytes: Uint8Array | string, password: string): Promise { - return decryptMnemonic(dataBytes, password, triplesecDecrypt); + return decryptMnemonic(dataBytes, password); } /** diff --git a/packages/wallet-sdk/src/index.ts b/packages/wallet-sdk/src/index.ts index 63cae5701..8cf569f59 100644 --- a/packages/wallet-sdk/src/index.ts +++ b/packages/wallet-sdk/src/index.ts @@ -7,5 +7,4 @@ export * from './models/profile'; export * from './models/wallet-config'; export * from './models/legacy-wallet-config'; export * from './encryption'; -export * from './triplesec'; export * from './utils'; diff --git a/packages/wallet-sdk/src/triplesec.ts b/packages/wallet-sdk/src/triplesec.ts deleted file mode 100644 index d90424653..000000000 --- a/packages/wallet-sdk/src/triplesec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as triplesec from 'triplesec'; - -export function triplesecDecrypt( - arg: { data: Uint8Array; key: Uint8Array }, - cb: (err: Error | null, buff: Uint8Array | null) => void -) { - if (typeof Buffer === 'undefined') - throw Error('Using triplesec currently requires polyfilling `Buffer`'); - - const argBuffer = { - data: Buffer.from(arg.data), - key: Buffer.from(arg.key), - }; - return triplesec.decrypt(argBuffer, (err, buff) => { - return cb(err, buff ? new Uint8Array(buff.buffer) : null); - }); -}