diff --git a/.github/funding.yml b/.github/funding.yml index 7fffdfe..09d9be7 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1 +1 @@ -custom: https://paulmillr.com/funding/ \ No newline at end of file +github: paulmillr diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 7e2e4d7..379bd22 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,26 +1,26 @@ -name: Node CI - -on: [push] - +name: Run node.js tests +on: + - push + - pull_request jobs: - build: - + test: + name: v${{ matrix.node }} @ ubuntu-latest runs-on: ubuntu-latest - strategy: matrix: - node-version: [12.x, 14.x, 16.x] - + node: + - 14 + - 16 + - 18 + - 20 + - 22 steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: npm install, build, and test - run: | - npm install - npm run build --if-present - npm test - env: - CI: true + - uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4 + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + with: + node-version: ${{ matrix.node }} + - run: npm install + - run: npm run build --if-present + - run: npm test + - run: npm run lint --if-present diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..cd57db8 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,23 @@ +name: Publish package to npm +on: + release: + types: [created] +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + cache: npm + - run: npm install -g npm + - run: npm ci + - run: npm run build + - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} diff --git a/.github/workflows/upload-release.yml b/.github/workflows/upload-release.yml new file mode 100644 index 0000000..b8c2ca9 --- /dev/null +++ b/.github/workflows/upload-release.yml @@ -0,0 +1,28 @@ +name: Upload standalone file to GitHub Releases +on: + release: + types: [created] +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4 + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + cache: npm + - run: npm install -g npm + - run: npm ci + - run: npm run build + - run: | + cd build + npm ci + npm run build:release + cd .. + - run: gh release upload ${{ github.event.release.tag_name }} build/`npx jsbt outfile` + env: + GH_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index ecfc963..2007c5c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,9 @@ /src/index.d.ts /index.js /index.d.ts +/index.d.ts.map /test/*.d.ts /test/*.js +/esm +/build/scure-bip39.js +node_modules diff --git a/.prettierrc.json b/.prettierrc.json index 5ac85e2..789ac2e 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,5 @@ { "printWidth": 100, - "singleQuote": true + "singleQuote": true, + "trailingComma": "es5" } diff --git a/README.md b/README.md index 1416b81..c09d81c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,37 @@ # scure-bip39 -Secure, audited & minimal implementation of BIP39 mnemonic phrases. +Audited & minimal JS implementation of [BIP39 mnemonic phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). -Developed for -[js-ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography). Check out [scure-bip32](https://github.com/paulmillr/scure-bip32) if you need +- 🔒 [**Audited**](#security) by an independent security firm +- 🔻 Tree-shakeable: unused code is excluded from your builds +- 📦 ESM and common.js +- ➰ Only 2 audited dependencies by the same author: + [noble-hashes](https://github.com/paulmillr/noble-hashes) and [scure-base](https://github.com/paulmillr/scure-base) +- 🪶 37KB with all deps bundled and 279KB with wordlists: much smaller than similar libraries + +Check out [scure-bip32](https://github.com/paulmillr/scure-bip32) if you need hierarchical deterministic wallets ("HD Wallets"). -The library has been audited by Cure53 on Jan 5, 2022. Check out the audit [PDF](./audit/2022-01-05-cure53-audit-nbl2.pdf) & [URL](https://cure53.de/pentest-report_hashing-libs.pdf). Before the audit it was called `micro-base39`. +### This library belongs to *scure* -## Usage +> **scure** — audited micro-libraries. -> npm install @scure/bip39 +- Zero or minimal dependencies +- Highly readable TypeScript / JS code +- PGP-signed releases and transparent NPM builds +- Check out [homepage](https://paulmillr.com/noble/#scure) & all libraries: + [base](https://github.com/paulmillr/scure-base), + [bip32](https://github.com/paulmillr/scure-bip32), + [bip39](https://github.com/paulmillr/scure-bip39), + [btc-signer](https://github.com/paulmillr/scure-btc-signer), + [starknet](https://github.com/paulmillr/scure-starknet) -Or +## Usage -> yarn add @scure/bip39 +> npm install @scure/bip39 -## API +We don't provide source maps. +Wordlists are large, including source maps would double package size. ```js import * as bip39 from '@scure/bip39'; @@ -40,7 +55,7 @@ await bip39.mnemonicToSeed(mn, 'password'); bip39.mnemonicToSeedSync(mn, 'password'); ``` -This submodule contains the word lists defined by BIP39 for Czech, English, French, Italian, Japanese, Korean, Simplified and Traditional Chinese, and Spanish. These are not imported by default, as that would increase bundle sizes too much. Instead, you should import and use them explicitly. +This submodule contains the word lists defined by BIP39 for Czech, English, French, Italian, Japanese, Korean, Portuguese, Simplified and Traditional Chinese, and Spanish. These are not imported by default, as that would increase bundle sizes too much. Instead, you should import and use them explicitly. ```typescript function generateMnemonic(wordlist: string[], strength?: number): string; @@ -54,17 +69,34 @@ function mnemonicToSeedSync(mnemonic: string, passphrase?: string): Uint8Array; All wordlists: ```typescript -import { wordlist } from '@scure/bip39/wordlists/czech'; -import { wordlist } from '@scure/bip39/wordlists/english'; -import { wordlist } from '@scure/bip39/wordlists/french'; -import { wordlist } from '@scure/bip39/wordlists/italian'; -import { wordlist } from '@scure/bip39/wordlists/japanese'; -import { wordlist } from '@scure/bip39/wordlists/korean'; -import { wordlist } from '@scure/bip39/wordlists/simplified-chinese'; -import { wordlist } from '@scure/bip39/wordlists/spanish'; -import { wordlist } from '@scure/bip39/wordlists/traditional-chinese'; +import { wordlist as czech } from '@scure/bip39/wordlists/czech'; +import { wordlist as english } from '@scure/bip39/wordlists/english'; +import { wordlist as french } from '@scure/bip39/wordlists/french'; +import { wordlist as italian } from '@scure/bip39/wordlists/italian'; +import { wordlist as japanese } from '@scure/bip39/wordlists/japanese'; +import { wordlist as korean } from '@scure/bip39/wordlists/korean'; +import { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese'; +import { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese'; +import { wordlist as spanish } from '@scure/bip39/wordlists/spanish'; +import { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese'; ``` +## Security + +To audit wordlist content, run `node scripts/fetch-wordlist.js`. + +The library has been independently audited: + +- at version 1.0.0, in Jan 2022, by [cure53](https://cure53.de) + - PDFs: [online](https://cure53.de/pentest-report_hashing-libs.pdf), [offline](./audit/2022-01-05-cure53-audit-nbl2.pdf) + - [Changes since audit](https://github.com/paulmillr/scure-bip39/compare/1.0.0..main). + - The audit has been funded by [Ethereum Foundation](https://ethereum.org/en/) with help of [Nomic Labs](https://nomiclabs.io) + +The library was initially developed for [js-ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography). +At commit [ae00e6d7](https://github.com/ethereum/js-ethereum-cryptography/commit/ae00e6d7d24fb3c76a1c7fe10039f6ecd120b77e), +it was extracted to a separate package called `micro-bip39`. +After the audit we've decided to use `@scure` NPM namespace for security. + ## License [MIT License](./LICENSE) diff --git a/SECURITY.md b/SECURITY.md index 8935c72..8aa8026 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,7 @@ # Security Policy +See [README's Security section](./README.md#security) for detailed description of internal security practices. + ## Supported Versions | Version | Supported | @@ -9,10 +11,10 @@ ## Reporting a Vulnerability -Use maintainer's email specified at https://github.com/paulmillr. +Use maintainer's email specified at https://paulmillr.com It's preferred that you use -PGP key from [pgp proof](https://paulmillr.com/pgp_proof.txt) (current is [46BEEF337A641ABB](https://paulmillr.com/pgp_proof.txt)). +PGP key from [pgp proof](https://paulmillr.com/pgp_proof.txt) (current is [697079DA6878B89B](https://paulmillr.com/pgp_proof.txt)). Ensure the pgp proof page has maintainer's site/github specified. You will get an update as soon as the email is read; a "Security vulnerability" phrase in email's title would help. diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..c1752b2 --- /dev/null +++ b/build/README.md @@ -0,0 +1,7 @@ +# build + +The directory is used to build a single file which contains everything. + +The single file uses iife wrapper and can be used in browsers as-is. + +Don't use it unless you can't use NPM/ESM, which support tree shaking. diff --git a/build/input.js b/build/input.js new file mode 100644 index 0000000..e19861f --- /dev/null +++ b/build/input.js @@ -0,0 +1,11 @@ +export * as bip39 from 'lib'; +export { wordlist as czech } from 'lib/wordlists/czech'; +export { wordlist as english } from 'lib/wordlists/english'; +export { wordlist as french } from 'lib/wordlists/french'; +export { wordlist as italian } from 'lib/wordlists/italian'; +export { wordlist as japanese } from 'lib/wordlists/japanese'; +export { wordlist as korean } from 'lib/wordlists/korean'; +export { wordlist as portuguese } from 'lib/wordlists/portuguese'; +export { wordlist as simplifiedChinese } from 'lib/wordlists/simplified-chinese'; +export { wordlist as spanish } from 'lib/wordlists/spanish'; +export { wordlist as traditionalChinese } from 'lib/wordlists/traditional-chinese'; diff --git a/build/package-lock.json b/build/package-lock.json new file mode 100644 index 0000000..684c98c --- /dev/null +++ b/build/package-lock.json @@ -0,0 +1,445 @@ +{ + "name": "build", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "build", + "version": "1.0.0", + "devDependencies": { + "esbuild": "0.20.1", + "lib": "file:.." + } + }, + "..": { + "name": "@scure/bip39", + "version": "1.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" + }, + "devDependencies": { + "@paulmillr/jsbt": "0.2.1", + "micro-should": "0.4.0", + "prettier": "3.3.2", + "typescript": "5.5.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" + } + }, + "node_modules/lib": { + "resolved": "..", + "link": true + } + } +} diff --git a/build/package.json b/build/package.json new file mode 100644 index 0000000..2378306 --- /dev/null +++ b/build/package.json @@ -0,0 +1,14 @@ +{ + "name": "build", + "private": true, + "version": "1.0.0", + "main": "input.js", + "type": "module", + "devDependencies": { + "lib": "file:..", + "esbuild": "0.20.1" + }, + "scripts": { + "build:release": "npx esbuild --bundle input.js --outfile=`npx jsbt outfile` --global-name=`npx jsbt global`" + } +} diff --git a/esm/package.json b/esm/package.json new file mode 100644 index 0000000..aead43d --- /dev/null +++ b/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fc0d33c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,91 @@ +{ + "name": "@scure/bip39", + "version": "1.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@scure/bip39", + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" + }, + "devDependencies": { + "@paulmillr/jsbt": "0.2.1", + "micro-should": "0.4.0", + "prettier": "3.3.2", + "typescript": "5.5.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paulmillr/jsbt": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@paulmillr/jsbt/-/jsbt-0.2.1.tgz", + "integrity": "sha512-4uC/Fa8L8oYjLNPKBqHKCwLv/47s/Asa4Z/rL584YvViPtlFdxBeCFX6knCZxX5gn6n+2YUdr50Q1sIuUNma5Q==", + "dev": true, + "license": "MIT", + "bin": { + "jsbt": "jsbt.js" + } + }, + "node_modules/@scure/base": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", + "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-should": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/micro-should/-/micro-should-0.4.0.tgz", + "integrity": "sha512-Vclj8yrngSYc9Y3dL2C+AdUlTkyx/syWc4R7LYfk4h7+icfF0DoUBGjjUIaEDzZA19RzoI+Hg8rW9IRoNGP0tQ==", + "dev": true + }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package.json b/package.json index 5709ec8..a903f6e 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,30 @@ { "name": "@scure/bip39", - "version": "1.0.0", + "version": "1.4.0", "description": "Secure, audited & minimal implementation of BIP39 mnemonic phrases", - "main": "index.js", "files": [ "index.js", "index.d.ts", "wordlists/*.js", - "wordlists/*.d.ts" + "wordlists/*.d.ts", + "esm", + "src/index.ts" ], - "types": "index.d.ts", "dependencies": { - "@noble/hashes": "~1.0.0", - "@scure/base": "~1.0.0" + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" }, "devDependencies": { - "@types/mocha": "9.0.0", - "mocha": "9.2.0", - "prettier": "2.4.1", - "typescript": "4.5.4" + "@paulmillr/jsbt": "0.2.1", + "micro-should": "0.4.0", + "prettier": "3.3.2", + "typescript": "5.5.2" }, "author": "Paul Miller (https://paulmillr.com)", "homepage": "https://paulmillr.com/", "repository": { "type": "git", - "url": "https://github.com/paulmillr/scure-bip39.git" + "url": "git+https://github.com/paulmillr/scure-bip39.git" }, "contributors": [ { @@ -38,10 +38,64 @@ ], "license": "MIT", "scripts": { - "build": "tsc", - "lint": "prettier --check 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'", - "format": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'", - "test": "cd test && tsc && mocha bip39.test.js" + "build": "tsc && tsc -p tsconfig.esm.json", + "lint": "prettier --check 'src/**/*.ts' 'test/*.test.ts' 'scripts/*.js'", + "format": "prettier --write 'src/**/*.ts' 'test/*.test.ts' 'scripts/*.js'", + "test": "cd test && tsc && node bip39.test.js", + "fetch-wordlist": "./scripts/fetch-wordlist.js" + }, + "sideEffects": false, + "main": "index.js", + "types": "./index.d.ts", + "exports": { + ".": { + "import": "./esm/index.js", + "require": "./index.js" + }, + "./index": { + "import": "./esm/index.js", + "require": "./index.js" + }, + "./wordlists/czech": { + "import": "./esm/wordlists/czech.js", + "require": "./wordlists/czech.js" + }, + "./wordlists/english": { + "import": "./esm/wordlists/english.js", + "require": "./wordlists/english.js" + }, + "./wordlists/french": { + "import": "./esm/wordlists/french.js", + "require": "./wordlists/french.js" + }, + "./wordlists/italian": { + "import": "./esm/wordlists/italian.js", + "require": "./wordlists/italian.js" + }, + "./wordlists/japanese": { + "import": "./esm/wordlists/japanese.js", + "require": "./wordlists/japanese.js" + }, + "./wordlists/korean": { + "import": "./esm/wordlists/korean.js", + "require": "./wordlists/korean.js" + }, + "./wordlists/portuguese": { + "import": "./esm/wordlists/portuguese.js", + "require": "./wordlists/portuguese.js" + }, + "./wordlists/simplified-chinese": { + "import": "./esm/wordlists/simplified-chinese.js", + "require": "./wordlists/simplified-chinese.js" + }, + "./wordlists/spanish": { + "import": "./esm/wordlists/spanish.js", + "require": "./wordlists/spanish.js" + }, + "./wordlists/traditional-chinese": { + "import": "./esm/wordlists/traditional-chinese.js", + "require": "./wordlists/traditional-chinese.js" + } }, "keywords": [ "bip39", @@ -50,15 +104,9 @@ "code", "bip0039", "bip-39", - "micro", - "scure", "wordlist", + "scure", "noble" ], - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "funding": "https://paulmillr.com/funding/" } diff --git a/scripts/fetch-wordlist.js b/scripts/fetch-wordlist.js new file mode 100755 index 0000000..1a5e481 --- /dev/null +++ b/scripts/fetch-wordlist.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +// dependencies +const path = require('path'); +const fs = require('fs'); +const util = require('util'); +const assert = require('assert'); +const writeFileAsync = util.promisify(fs.writeFile); + +// arguments +const arg = process.argv[2]; +if (arg === undefined) { + console.error('Supply language (lowercased, snakecased) argument.'); + process.exit(); +} + +// parse language argument +const filenameSnakeCased = arg; +const parts = arg.split('_'); +const filenameKebabCased = parts.length > 1 ? `${parts[1]}-${parts[0]}` : arg; + +// fetch, validate and save file +(async () => { + try { + // fetch + const url = `https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/${filenameSnakeCased}.txt`; + const response = await fetch(url); + if (response.ok === false) throw new Error(`Fetch (${url}) failed`); + const txtContent = await response.text(); + + // remove trailing line breaks + const wordlist = txtContent.replace(/\n+$/, ''); + + // validate .txt file content + validateTxtContent(wordlist); + + // write .ts file + const tsContent = `export const wordlist: string[] = \`${wordlist}\`.split('\\n');\n`; + await writeFileAsync( + path.join(__dirname, '..', 'src', 'wordlists', `${filenameKebabCased}.ts`), + tsContent + ); + } catch (err) { + console.error(err); + } +})(); + +// assertions +const validateTxtContent = (txtContent) => { + const words = txtContent.split('\n'); + const emptyLines = words.filter((word) => word.trim() === ''); + assert.equal(emptyLines.length, 0); + assert.equal(words.length, 2048); +}; diff --git a/src/index.ts b/src/index.ts index 207a417..e6afe12 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ /*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */ +import { bytes as assertBytes, number as assertNumber } from '@noble/hashes/_assert'; import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2'; import { sha256 } from '@noble/hashes/sha256'; import { sha512 } from '@noble/hashes/sha512'; -import { assertBytes, assertNumber, randomBytes } from '@noble/hashes/utils'; +import { randomBytes } from '@noble/hashes/utils'; import { utils as baseUtils } from '@scure/base'; // Japanese wordlist @@ -11,6 +12,7 @@ const isJapanese = (wordlist: string[]) => wordlist[0] === '\u3042\u3044\u3053\u // Normalization replaces equivalent sequences of characters // so that any two texts that are equivalent will be reduced // to the same sequence of code points, called the normal form of the original text. +// https://tonsky.me/blog/unicode/#why-is-a---- function nfkd(str: string) { if (typeof str !== 'string') throw new TypeError(`Invalid mnemonic type: ${typeof str}`); return str.normalize('NFKD'); @@ -46,12 +48,12 @@ const calcChecksum = (entropy: Uint8Array) => { const bitsLeft = 8 - entropy.length / 4; // Zero rightmost "bitsLeft" bits in byte // For example: bitsLeft=4 val=10111101 -> 10110000 - return new Uint8Array([(sha256(entropy)[0] >> bitsLeft) << bitsLeft]); + return new Uint8Array([(sha256(entropy)[0]! >> bitsLeft) << bitsLeft]); }; function getCoder(wordlist: string[]) { - if (!Array.isArray(wordlist) || wordlist.length !== 2 ** 11 || typeof wordlist[0] !== 'string') - throw new Error('Worlist: expected array of 2048 strings'); + if (!Array.isArray(wordlist) || wordlist.length !== 2048 || typeof wordlist[0] !== 'string') + throw new Error('Wordlist: expected array of 2048 strings'); wordlist.forEach((i) => { if (typeof i !== 'string') throw new Error(`Wordlist: non-string element: ${i}`); }); diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/src/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/src/wordlists/portuguese.ts b/src/wordlists/portuguese.ts new file mode 100644 index 0000000..a16f09d --- /dev/null +++ b/src/wordlists/portuguese.ts @@ -0,0 +1,2048 @@ +export const wordlist: string[] = `abacate +abaixo +abalar +abater +abduzir +abelha +aberto +abismo +abotoar +abranger +abreviar +abrigar +abrupto +absinto +absoluto +absurdo +abutre +acabado +acalmar +acampar +acanhar +acaso +aceitar +acelerar +acenar +acervo +acessar +acetona +achatar +acidez +acima +acionado +acirrar +aclamar +aclive +acolhida +acomodar +acoplar +acordar +acumular +acusador +adaptar +adega +adentro +adepto +adequar +aderente +adesivo +adeus +adiante +aditivo +adjetivo +adjunto +admirar +adorar +adquirir +adubo +adverso +advogado +aeronave +afastar +aferir +afetivo +afinador +afivelar +aflito +afluente +afrontar +agachar +agarrar +agasalho +agenciar +agilizar +agiota +agitado +agora +agradar +agreste +agrupar +aguardar +agulha +ajoelhar +ajudar +ajustar +alameda +alarme +alastrar +alavanca +albergue +albino +alcatra +aldeia +alecrim +alegria +alertar +alface +alfinete +algum +alheio +aliar +alicate +alienar +alinhar +aliviar +almofada +alocar +alpiste +alterar +altitude +alucinar +alugar +aluno +alusivo +alvo +amaciar +amador +amarelo +amassar +ambas +ambiente +ameixa +amenizar +amido +amistoso +amizade +amolador +amontoar +amoroso +amostra +amparar +ampliar +ampola +anagrama +analisar +anarquia +anatomia +andaime +anel +anexo +angular +animar +anjo +anomalia +anotado +ansioso +anterior +anuidade +anunciar +anzol +apagador +apalpar +apanhado +apego +apelido +apertada +apesar +apetite +apito +aplauso +aplicada +apoio +apontar +aposta +aprendiz +aprovar +aquecer +arame +aranha +arara +arcada +ardente +areia +arejar +arenito +aresta +argiloso +argola +arma +arquivo +arraial +arrebate +arriscar +arroba +arrumar +arsenal +arterial +artigo +arvoredo +asfaltar +asilado +aspirar +assador +assinar +assoalho +assunto +astral +atacado +atadura +atalho +atarefar +atear +atender +aterro +ateu +atingir +atirador +ativo +atoleiro +atracar +atrevido +atriz +atual +atum +auditor +aumentar +aura +aurora +autismo +autoria +autuar +avaliar +avante +avaria +avental +avesso +aviador +avisar +avulso +axila +azarar +azedo +azeite +azulejo +babar +babosa +bacalhau +bacharel +bacia +bagagem +baiano +bailar +baioneta +bairro +baixista +bajular +baleia +baliza +balsa +banal +bandeira +banho +banir +banquete +barato +barbado +baronesa +barraca +barulho +baseado +bastante +batata +batedor +batida +batom +batucar +baunilha +beber +beijo +beirada +beisebol +beldade +beleza +belga +beliscar +bendito +bengala +benzer +berimbau +berlinda +berro +besouro +bexiga +bezerro +bico +bicudo +bienal +bifocal +bifurcar +bigorna +bilhete +bimestre +bimotor +biologia +biombo +biosfera +bipolar +birrento +biscoito +bisneto +bispo +bissexto +bitola +bizarro +blindado +bloco +bloquear +boato +bobagem +bocado +bocejo +bochecha +boicotar +bolada +boletim +bolha +bolo +bombeiro +bonde +boneco +bonita +borbulha +borda +boreal +borracha +bovino +boxeador +branco +brasa +braveza +breu +briga +brilho +brincar +broa +brochura +bronzear +broto +bruxo +bucha +budismo +bufar +bule +buraco +busca +busto +buzina +cabana +cabelo +cabide +cabo +cabrito +cacau +cacetada +cachorro +cacique +cadastro +cadeado +cafezal +caiaque +caipira +caixote +cajado +caju +calafrio +calcular +caldeira +calibrar +calmante +calota +camada +cambista +camisa +camomila +campanha +camuflar +canavial +cancelar +caneta +canguru +canhoto +canivete +canoa +cansado +cantar +canudo +capacho +capela +capinar +capotar +capricho +captador +capuz +caracol +carbono +cardeal +careca +carimbar +carneiro +carpete +carreira +cartaz +carvalho +casaco +casca +casebre +castelo +casulo +catarata +cativar +caule +causador +cautelar +cavalo +caverna +cebola +cedilha +cegonha +celebrar +celular +cenoura +censo +centeio +cercar +cerrado +certeiro +cerveja +cetim +cevada +chacota +chaleira +chamado +chapada +charme +chatice +chave +chefe +chegada +cheiro +cheque +chicote +chifre +chinelo +chocalho +chover +chumbo +chutar +chuva +cicatriz +ciclone +cidade +cidreira +ciente +cigana +cimento +cinto +cinza +ciranda +circuito +cirurgia +citar +clareza +clero +clicar +clone +clube +coado +coagir +cobaia +cobertor +cobrar +cocada +coelho +coentro +coeso +cogumelo +coibir +coifa +coiote +colar +coleira +colher +colidir +colmeia +colono +coluna +comando +combinar +comentar +comitiva +comover +complexo +comum +concha +condor +conectar +confuso +congelar +conhecer +conjugar +consumir +contrato +convite +cooperar +copeiro +copiador +copo +coquetel +coragem +cordial +corneta +coronha +corporal +correio +cortejo +coruja +corvo +cosseno +costela +cotonete +couro +couve +covil +cozinha +cratera +cravo +creche +credor +creme +crer +crespo +criada +criminal +crioulo +crise +criticar +crosta +crua +cruzeiro +cubano +cueca +cuidado +cujo +culatra +culminar +culpar +cultura +cumprir +cunhado +cupido +curativo +curral +cursar +curto +cuspir +custear +cutelo +damasco +datar +debater +debitar +deboche +debulhar +decalque +decimal +declive +decote +decretar +dedal +dedicado +deduzir +defesa +defumar +degelo +degrau +degustar +deitado +deixar +delator +delegado +delinear +delonga +demanda +demitir +demolido +dentista +depenado +depilar +depois +depressa +depurar +deriva +derramar +desafio +desbotar +descanso +desenho +desfiado +desgaste +desigual +deslize +desmamar +desova +despesa +destaque +desviar +detalhar +detentor +detonar +detrito +deusa +dever +devido +devotado +dezena +diagrama +dialeto +didata +difuso +digitar +dilatado +diluente +diminuir +dinastia +dinheiro +diocese +direto +discreta +disfarce +disparo +disquete +dissipar +distante +ditador +diurno +diverso +divisor +divulgar +dizer +dobrador +dolorido +domador +dominado +donativo +donzela +dormente +dorsal +dosagem +dourado +doutor +drenagem +drible +drogaria +duelar +duende +dueto +duplo +duquesa +durante +duvidoso +eclodir +ecoar +ecologia +edificar +edital +educado +efeito +efetivar +ejetar +elaborar +eleger +eleitor +elenco +elevador +eliminar +elogiar +embargo +embolado +embrulho +embutido +emenda +emergir +emissor +empatia +empenho +empinado +empolgar +emprego +empurrar +emulador +encaixe +encenado +enchente +encontro +endeusar +endossar +enfaixar +enfeite +enfim +engajado +engenho +englobar +engomado +engraxar +enguia +enjoar +enlatar +enquanto +enraizar +enrolado +enrugar +ensaio +enseada +ensino +ensopado +entanto +enteado +entidade +entortar +entrada +entulho +envergar +enviado +envolver +enxame +enxerto +enxofre +enxuto +epiderme +equipar +ereto +erguido +errata +erva +ervilha +esbanjar +esbelto +escama +escola +escrita +escuta +esfinge +esfolar +esfregar +esfumado +esgrima +esmalte +espanto +espelho +espiga +esponja +espreita +espumar +esquerda +estaca +esteira +esticar +estofado +estrela +estudo +esvaziar +etanol +etiqueta +euforia +europeu +evacuar +evaporar +evasivo +eventual +evidente +evoluir +exagero +exalar +examinar +exato +exausto +excesso +excitar +exclamar +executar +exemplo +exibir +exigente +exonerar +expandir +expelir +expirar +explanar +exposto +expresso +expulsar +externo +extinto +extrato +fabricar +fabuloso +faceta +facial +fada +fadiga +faixa +falar +falta +familiar +fandango +fanfarra +fantoche +fardado +farelo +farinha +farofa +farpa +fartura +fatia +fator +favorita +faxina +fazenda +fechado +feijoada +feirante +felino +feminino +fenda +feno +fera +feriado +ferrugem +ferver +festejar +fetal +feudal +fiapo +fibrose +ficar +ficheiro +figurado +fileira +filho +filme +filtrar +firmeza +fisgada +fissura +fita +fivela +fixador +fixo +flacidez +flamingo +flanela +flechada +flora +flutuar +fluxo +focal +focinho +fofocar +fogo +foguete +foice +folgado +folheto +forjar +formiga +forno +forte +fosco +fossa +fragata +fralda +frango +frasco +fraterno +freira +frente +fretar +frieza +friso +fritura +fronha +frustrar +fruteira +fugir +fulano +fuligem +fundar +fungo +funil +furador +furioso +futebol +gabarito +gabinete +gado +gaiato +gaiola +gaivota +galega +galho +galinha +galocha +ganhar +garagem +garfo +gargalo +garimpo +garoupa +garrafa +gasoduto +gasto +gata +gatilho +gaveta +gazela +gelado +geleia +gelo +gemada +gemer +gemido +generoso +gengiva +genial +genoma +genro +geologia +gerador +germinar +gesso +gestor +ginasta +gincana +gingado +girafa +girino +glacial +glicose +global +glorioso +goela +goiaba +golfe +golpear +gordura +gorjeta +gorro +gostoso +goteira +governar +gracejo +gradual +grafite +gralha +grampo +granada +gratuito +graveto +graxa +grego +grelhar +greve +grilo +grisalho +gritaria +grosso +grotesco +grudado +grunhido +gruta +guache +guarani +guaxinim +guerrear +guiar +guincho +guisado +gula +guloso +guru +habitar +harmonia +haste +haver +hectare +herdar +heresia +hesitar +hiato +hibernar +hidratar +hiena +hino +hipismo +hipnose +hipoteca +hoje +holofote +homem +honesto +honrado +hormonal +hospedar +humorado +iate +ideia +idoso +ignorado +igreja +iguana +ileso +ilha +iludido +iluminar +ilustrar +imagem +imediato +imenso +imersivo +iminente +imitador +imortal +impacto +impedir +implante +impor +imprensa +impune +imunizar +inalador +inapto +inativo +incenso +inchar +incidir +incluir +incolor +indeciso +indireto +indutor +ineficaz +inerente +infantil +infestar +infinito +inflamar +informal +infrator +ingerir +inibido +inicial +inimigo +injetar +inocente +inodoro +inovador +inox +inquieto +inscrito +inseto +insistir +inspetor +instalar +insulto +intacto +integral +intimar +intocado +intriga +invasor +inverno +invicto +invocar +iogurte +iraniano +ironizar +irreal +irritado +isca +isento +isolado +isqueiro +italiano +janeiro +jangada +janta +jararaca +jardim +jarro +jasmim +jato +javali +jazida +jejum +joaninha +joelhada +jogador +joia +jornal +jorrar +jovem +juba +judeu +judoca +juiz +julgador +julho +jurado +jurista +juro +justa +labareda +laboral +lacre +lactante +ladrilho +lagarta +lagoa +laje +lamber +lamentar +laminar +lampejo +lanche +lapidar +lapso +laranja +lareira +largura +lasanha +lastro +lateral +latido +lavanda +lavoura +lavrador +laxante +lazer +lealdade +lebre +legado +legendar +legista +leigo +leiloar +leitura +lembrete +leme +lenhador +lentilha +leoa +lesma +leste +letivo +letreiro +levar +leveza +levitar +liberal +libido +liderar +ligar +ligeiro +limitar +limoeiro +limpador +linda +linear +linhagem +liquidez +listagem +lisura +litoral +livro +lixa +lixeira +locador +locutor +lojista +lombo +lona +longe +lontra +lorde +lotado +loteria +loucura +lousa +louvar +luar +lucidez +lucro +luneta +lustre +lutador +luva +macaco +macete +machado +macio +madeira +madrinha +magnata +magreza +maior +mais +malandro +malha +malote +maluco +mamilo +mamoeiro +mamute +manada +mancha +mandato +manequim +manhoso +manivela +manobrar +mansa +manter +manusear +mapeado +maquinar +marcador +maresia +marfim +margem +marinho +marmita +maroto +marquise +marreco +martelo +marujo +mascote +masmorra +massagem +mastigar +matagal +materno +matinal +matutar +maxilar +medalha +medida +medusa +megafone +meiga +melancia +melhor +membro +memorial +menino +menos +mensagem +mental +merecer +mergulho +mesada +mesclar +mesmo +mesquita +mestre +metade +meteoro +metragem +mexer +mexicano +micro +migalha +migrar +milagre +milenar +milhar +mimado +minerar +minhoca +ministro +minoria +miolo +mirante +mirtilo +misturar +mocidade +moderno +modular +moeda +moer +moinho +moita +moldura +moleza +molho +molinete +molusco +montanha +moqueca +morango +morcego +mordomo +morena +mosaico +mosquete +mostarda +motel +motim +moto +motriz +muda +muito +mulata +mulher +multar +mundial +munido +muralha +murcho +muscular +museu +musical +nacional +nadador +naja +namoro +narina +narrado +nascer +nativa +natureza +navalha +navegar +navio +neblina +nebuloso +negativa +negociar +negrito +nervoso +neta +neural +nevasca +nevoeiro +ninar +ninho +nitidez +nivelar +nobreza +noite +noiva +nomear +nominal +nordeste +nortear +notar +noticiar +noturno +novelo +novilho +novo +nublado +nudez +numeral +nupcial +nutrir +nuvem +obcecado +obedecer +objetivo +obrigado +obscuro +obstetra +obter +obturar +ocidente +ocioso +ocorrer +oculista +ocupado +ofegante +ofensiva +oferenda +oficina +ofuscado +ogiva +olaria +oleoso +olhar +oliveira +ombro +omelete +omisso +omitir +ondulado +oneroso +ontem +opcional +operador +oponente +oportuno +oposto +orar +orbitar +ordem +ordinal +orfanato +orgasmo +orgulho +oriental +origem +oriundo +orla +ortodoxo +orvalho +oscilar +ossada +osso +ostentar +otimismo +ousadia +outono +outubro +ouvido +ovelha +ovular +oxidar +oxigenar +pacato +paciente +pacote +pactuar +padaria +padrinho +pagar +pagode +painel +pairar +paisagem +palavra +palestra +palheta +palito +palmada +palpitar +pancada +panela +panfleto +panqueca +pantanal +papagaio +papelada +papiro +parafina +parcial +pardal +parede +partida +pasmo +passado +pastel +patamar +patente +patinar +patrono +paulada +pausar +peculiar +pedalar +pedestre +pediatra +pedra +pegada +peitoral +peixe +pele +pelicano +penca +pendurar +peneira +penhasco +pensador +pente +perceber +perfeito +pergunta +perito +permitir +perna +perplexo +persiana +pertence +peruca +pescado +pesquisa +pessoa +petiscar +piada +picado +piedade +pigmento +pilastra +pilhado +pilotar +pimenta +pincel +pinguim +pinha +pinote +pintar +pioneiro +pipoca +piquete +piranha +pires +pirueta +piscar +pistola +pitanga +pivete +planta +plaqueta +platina +plebeu +plumagem +pluvial +pneu +poda +poeira +poetisa +polegada +policiar +poluente +polvilho +pomar +pomba +ponderar +pontaria +populoso +porta +possuir +postal +pote +poupar +pouso +povoar +praia +prancha +prato +praxe +prece +predador +prefeito +premiar +prensar +preparar +presilha +pretexto +prevenir +prezar +primata +princesa +prisma +privado +processo +produto +profeta +proibido +projeto +prometer +propagar +prosa +protetor +provador +publicar +pudim +pular +pulmonar +pulseira +punhal +punir +pupilo +pureza +puxador +quadra +quantia +quarto +quase +quebrar +queda +queijo +quente +querido +quimono +quina +quiosque +rabanada +rabisco +rachar +racionar +radial +raiar +rainha +raio +raiva +rajada +ralado +ramal +ranger +ranhura +rapadura +rapel +rapidez +raposa +raquete +raridade +rasante +rascunho +rasgar +raspador +rasteira +rasurar +ratazana +ratoeira +realeza +reanimar +reaver +rebaixar +rebelde +rebolar +recado +recente +recheio +recibo +recordar +recrutar +recuar +rede +redimir +redonda +reduzida +reenvio +refinar +refletir +refogar +refresco +refugiar +regalia +regime +regra +reinado +reitor +rejeitar +relativo +remador +remendo +remorso +renovado +reparo +repelir +repleto +repolho +represa +repudiar +requerer +resenha +resfriar +resgatar +residir +resolver +respeito +ressaca +restante +resumir +retalho +reter +retirar +retomada +retratar +revelar +revisor +revolta +riacho +rica +rigidez +rigoroso +rimar +ringue +risada +risco +risonho +robalo +rochedo +rodada +rodeio +rodovia +roedor +roleta +romano +roncar +rosado +roseira +rosto +rota +roteiro +rotina +rotular +rouco +roupa +roxo +rubro +rugido +rugoso +ruivo +rumo +rupestre +russo +sabor +saciar +sacola +sacudir +sadio +safira +saga +sagrada +saibro +salada +saleiro +salgado +saliva +salpicar +salsicha +saltar +salvador +sambar +samurai +sanar +sanfona +sangue +sanidade +sapato +sarda +sargento +sarjeta +saturar +saudade +saxofone +sazonal +secar +secular +seda +sedento +sediado +sedoso +sedutor +segmento +segredo +segundo +seiva +seleto +selvagem +semanal +semente +senador +senhor +sensual +sentado +separado +sereia +seringa +serra +servo +setembro +setor +sigilo +silhueta +silicone +simetria +simpatia +simular +sinal +sincero +singular +sinopse +sintonia +sirene +siri +situado +soberano +sobra +socorro +sogro +soja +solda +soletrar +solteiro +sombrio +sonata +sondar +sonegar +sonhador +sono +soprano +soquete +sorrir +sorteio +sossego +sotaque +soterrar +sovado +sozinho +suavizar +subida +submerso +subsolo +subtrair +sucata +sucesso +suco +sudeste +sufixo +sugador +sugerir +sujeito +sulfato +sumir +suor +superior +suplicar +suposto +suprimir +surdina +surfista +surpresa +surreal +surtir +suspiro +sustento +tabela +tablete +tabuada +tacho +tagarela +talher +talo +talvez +tamanho +tamborim +tampa +tangente +tanto +tapar +tapioca +tardio +tarefa +tarja +tarraxa +tatuagem +taurino +taxativo +taxista +teatral +tecer +tecido +teclado +tedioso +teia +teimar +telefone +telhado +tempero +tenente +tensor +tentar +termal +terno +terreno +tese +tesoura +testado +teto +textura +texugo +tiara +tigela +tijolo +timbrar +timidez +tingido +tinteiro +tiragem +titular +toalha +tocha +tolerar +tolice +tomada +tomilho +tonel +tontura +topete +tora +torcido +torneio +torque +torrada +torto +tostar +touca +toupeira +toxina +trabalho +tracejar +tradutor +trafegar +trajeto +trama +trancar +trapo +traseiro +tratador +travar +treino +tremer +trepidar +trevo +triagem +tribo +triciclo +tridente +trilogia +trindade +triplo +triturar +triunfal +trocar +trombeta +trova +trunfo +truque +tubular +tucano +tudo +tulipa +tupi +turbo +turma +turquesa +tutelar +tutorial +uivar +umbigo +unha +unidade +uniforme +urologia +urso +urtiga +urubu +usado +usina +usufruir +vacina +vadiar +vagaroso +vaidoso +vala +valente +validade +valores +vantagem +vaqueiro +varanda +vareta +varrer +vascular +vasilha +vassoura +vazar +vazio +veado +vedar +vegetar +veicular +veleiro +velhice +veludo +vencedor +vendaval +venerar +ventre +verbal +verdade +vereador +vergonha +vermelho +verniz +versar +vertente +vespa +vestido +vetorial +viaduto +viagem +viajar +viatura +vibrador +videira +vidraria +viela +viga +vigente +vigiar +vigorar +vilarejo +vinco +vinheta +vinil +violeta +virada +virtude +visitar +visto +vitral +viveiro +vizinho +voador +voar +vogal +volante +voleibol +voltagem +volumoso +vontade +vulto +vuvuzela +xadrez +xarope +xeque +xeretar +xerife +xingar +zangado +zarpar +zebu +zelador +zombar +zoologia +zumbido`.split('\n'); diff --git a/test/assert.ts b/test/assert.ts index 7f3d3c0..cdb3ff0 100644 --- a/test/assert.ts +++ b/test/assert.ts @@ -1,6 +1,6 @@ -// Minimal assert version to avoid dependecies on node internals -// Allows to verify that none of brwoserify version of node internals is included in resulting build -async function deepStrictEqual(actual: unknown, expected: unknown, message?: string) { +// Minimal assert version to avoid dependencies on node internals +// Allows to verify that none of browserify version of node internals is included in resulting build +function deepStrictEqual(actual: unknown, expected: unknown, message?: string) { const [actualType, expectedType] = [typeof actual, typeof expected]; const err = new Error( `Non-equal values: actual=${actual} (type=${actualType}) expected=${expected} (type=${expectedType})${ diff --git a/test/bip39.test.ts b/test/bip39.test.ts index 9619556..a78a183 100644 --- a/test/bip39.test.ts +++ b/test/bip39.test.ts @@ -9,8 +9,10 @@ import { import { wordlist as englishWordlist } from '../wordlists/english'; import { wordlist as japaneseWordlist } from '../wordlists/japanese'; import { wordlist as spanishWordlist } from '../wordlists/spanish'; -import { bytesToHex as toHex } from '@noble/hashes/utils'; +import { wordlist as portugueseWordlist } from '../wordlists/portuguese'; +import { bytesToHex as toHex, hexToBytes } from '@noble/hashes/utils'; import { deepStrictEqual, throws } from './assert'; +import { should, describe } from 'micro-should'; export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean { if (a.length !== b.length) { @@ -23,31 +25,17 @@ export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean { } return true; } -export function hexToBytes(hex: string): Uint8Array { - if (typeof hex !== 'string') { - throw new TypeError(`hexToBytes: expected string, got ${typeof hex}`); - } - if (hex.length % 2) { - throw new Error('hexToBytes: received invalid unpadded hex'); - } - const array = new Uint8Array(hex.length / 2); - for (let i = 0; i < array.length; i++) { - const j = i * 2; - array[i] = Number.parseInt(hex.slice(j, j + 2), 16); - } - return array; -} describe('BIP39', () => { describe('Mnemonic generation', () => { - it('should create a valid menomic', () => { + should('create a valid menomic', () => { const mnemonic = generateMnemonic(englishWordlist, 128); deepStrictEqual(validateMnemonic(mnemonic, englishWordlist), true); }); }); describe('Mnemonic validation', () => { - it('should accept valid menomics', () => { + should('accept valid menomics', () => { deepStrictEqual( validateMnemonic( 'jump police vessel depth mutual idea cable soap trophy dust hold wink', @@ -63,9 +51,17 @@ describe('BIP39', () => { ), true ); + + deepStrictEqual( + validateMnemonic( + 'grunhido nevasca turbo coeso listagem galinha baronesa refugiar teclado cumprir fragata vinco', + portugueseWordlist + ), + true + ); }); - it('should reject invalid menomics', () => { + should('reject invalid menomics', () => { deepStrictEqual(validateMnemonic('asd', englishWordlist), false); deepStrictEqual( validateMnemonic(generateMnemonic(englishWordlist, 128), spanishWordlist), @@ -76,13 +72,13 @@ describe('BIP39', () => { describe('Entropy-mnemonic convertions', () => { describe('Should convert from mnemonic to entropy and back', () => { - it('should work with the English wodlist', () => { + should('work with the English wodlist', () => { const mnemonic = generateMnemonic(englishWordlist, 128); const entropy = mnemonicToEntropy(mnemonic, englishWordlist); deepStrictEqual(entropyToMnemonic(entropy, englishWordlist), mnemonic); }); - it('should work with the Spanish wodlist', () => { + should('work with the Spanish wodlist', () => { const mnemonic = generateMnemonic(spanishWordlist, 128); const entropy = mnemonicToEntropy(mnemonic, spanishWordlist); deepStrictEqual(entropyToMnemonic(entropy, spanishWordlist), mnemonic); @@ -90,7 +86,7 @@ describe('BIP39', () => { }); }); - describe('Menonic to seed', () => { + describe('Mnemonic to seed', () => { describe('Without passphrase', () => { const MENMONIC = 'koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera'; @@ -100,14 +96,14 @@ describe('BIP39', () => { ); describe('Sync', () => { - it('Should recover the right seed', () => { + should('recover the right seed', () => { const recoveredSeed = mnemonicToSeedSync(MENMONIC); deepStrictEqual(equalsBytes(SEED, recoveredSeed), true); }); }); describe('Async', () => { - it('Should recover the right seed', async () => { + should('recover the right seed', async () => { const recoveredSeed = await mnemonicToSeed(MENMONIC); deepStrictEqual(equalsBytes(SEED, recoveredSeed), true); }); @@ -125,14 +121,14 @@ describe('BIP39', () => { ); describe('Sync', () => { - it('Should recover the right seed', () => { + should('recover the right seed', () => { const recoveredSeed = mnemonicToSeedSync(MENMONIC, PASSPHRASE); deepStrictEqual(SEED, recoveredSeed); }); }); describe('Async', () => { - it('Should recover the right seed', async () => { + should('recover the right seed', async () => { const recoveredSeed = await mnemonicToSeed(MENMONIC, PASSPHRASE); deepStrictEqual(SEED, recoveredSeed); }); @@ -395,7 +391,7 @@ describe('BIP39', () => { i: number ) { const [entropy, mnemonic, seed] = v; - describe(`for ${description} (${i}), ${entropy}`, async () => { + should(`for ${description} (${i}), ${entropy}`, async () => { deepStrictEqual(toHex(mnemonicToEntropy(mnemonic, wordlist)), entropy, 'mnemonicToEntropy'); deepStrictEqual(toHex(mnemonicToSeedSync(mnemonic, password)), seed, 'mnemonicToSeedSync'); const res = await mnemonicToSeed(mnemonic, password); @@ -420,12 +416,12 @@ describe('BIP39', () => { i ); } - describe('Invalid entropy', () => { + should('Invalid entropy', () => { throws(() => entropyToMnemonic(new Uint8Array([]), englishWordlist)); throws(() => entropyToMnemonic(new Uint8Array([0, 0, 0]), englishWordlist)); throws(() => entropyToMnemonic(new Uint8Array(1028), englishWordlist)); }); - describe('UTF8 passwords', () => { + should('UTF8 passwords', () => { for (const [_, mnemonic, seed] of VECTORS.japanese) { const password = '㍍ガバヴァぱばぐゞちぢ十人十色'; const normalizedPassword = 'メートルガバヴァぱばぐゞちぢ十人十色'; @@ -441,14 +437,14 @@ describe('BIP39', () => { ); } }); - describe('generateMnemonic can vary entropy length', () => { + should('generateMnemonic can vary entropy length', () => { deepStrictEqual( generateMnemonic(englishWordlist, 160).split(' ').length, 15, 'can vary generated entropy bit length' ); }); - describe('validateMnemonic', () => { + should('validateMnemonic', () => { deepStrictEqual( validateMnemonic('sleep kitten', englishWordlist), false, @@ -486,3 +482,5 @@ describe('BIP39', () => { }); }); }); + +should.run(); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index ae70e30..0000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---require ts-node/register -test/test-vectors/*.ts diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..b7cc998 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "@paulmillr/jsbt/tsconfig.esm.json", + "compilerOptions": { + "sourceMap": false, + "declarationMap": false, + "outDir": "./esm" + }, + "include": ["index.ts", "src"], + "exclude": ["node_modules", "lib"] +} diff --git a/tsconfig.json b/tsconfig.json index f96ca4e..0e0ce18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,10 @@ { + "extends": "@paulmillr/jsbt/tsconfig.cjs.json", "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "downlevelIteration": true, - "rootDirs": [ - "." - ], - "outDir": ".", "sourceMap": false, - "noUnusedLocals": true, - "declaration": true, - "declarationMap": false + "declarationMap": false, + "outDir": ".", }, - "include": [ - "src" - ], - "exclude": ["node_modules", "./*.d.ts", "./test/*.d.ts"] -} \ No newline at end of file + "include": ["index.ts", "src"], + "exclude": ["node_modules", "lib"] +}