refactor: split web frontend + gateway out to uniweb app (bump 0.13.0)

The SPA (web/) and the web gateway (cmd/webgw) move to a dedicated app
projects/message_bus/apps/uniweb (its own Gitea sub-repo). unibus is now
strictly the bus plane: membership/keys, the client library and demo peers.
uniweb consumes unibus as a Go module via replace => ../unibus.

No capability lost; same SPA and gateway, in their own service folder.
go build/vet/test green after extraction.
This commit is contained in:
2026-06-13 21:21:08 +02:00
parent fadee1a7d0
commit 9661a5ce1f
31166 changed files with 2029366 additions and 3677 deletions
+1
View File
@@ -0,0 +1 @@
../../../@noble+hashes@2.2.0/node_modules/@noble/hashes
+1
View File
@@ -0,0 +1 @@
../../../@scure+base@2.2.0/node_modules/@scure/base
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,141 @@
# scure-bip39
Audited & minimal JS implementation of [BIP39 mnemonic phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
- 🔒 [**Audited**](#security) by an independent security firm
- 🔻 Tree-shakeable: unused code is excluded from your builds
- 🥈 Two implementations: pure JS or friendly WebCrypto wrapper
- ➰ 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)
- 🪶 14KB (gzipped) with one wordlist, 79KB with all of them: much smaller than similar libraries
Check out [scure-bip32](https://github.com/paulmillr/scure-bip32) if you need
hierarchical deterministic wallets ("HD Wallets").
### This library belongs to _scure_
> **scure** — audited micro-libraries.
- 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),
[sr25519](https://github.com/paulmillr/scure-sr25519),
[starknet](https://github.com/paulmillr/scure-starknet)
## Usage
> `npm install @scure/bip39`
> `deno add jsr:@scure/bip39`
We don't provide source maps.
Wordlists are large, including source maps would double package size.
```js
import * as bip39 from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english.js';
// Generate x random words. Uses Cryptographically-Secure Random Number Generator.
const mn = bip39.generateMnemonic(wordlist);
console.log(mn);
// You can customize the strength of the generated mnemonic by passing a value between 128 and 256 as the second argument to the generateMnemonic function.
// This value must be a multiple of 32. Default is 128.
const mn256 = bip39.generateMnemonic(wordlist, 256);
console.log(mn256);
// Reversible: Converts mnemonic string to raw entropy in form of byte array.
const ent = bip39.mnemonicToEntropy(mn, wordlist);
// Reversible: Converts raw entropy in form of byte array to mnemonic string.
bip39.entropyToMnemonic(ent, wordlist);
// Validates mnemonic for being 12-24 words contained in `wordlist`.
bip39.validateMnemonic(mn, wordlist);
bip39.validateMnemonic(mn256, wordlist);
// Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
const seed1 = await bip39.mnemonicToSeed(mn, 'password');
const seed2 = bip39.mnemonicToSeedSync(mn, 'password');
const seed3 = await bip39.mnemonicToSeedWebcrypto(mn, 'password'); // Native, WebCrypto version.
```
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;
function mnemonicToEntropy(mnemonic: string, wordlist: string[]): Uint8Array;
function entropyToMnemonic(entropy: Uint8Array, wordlist: string[]): string;
function validateMnemonic(mnemonic: string, wordlist: string[]): boolean;
function mnemonicToSeed(mnemonic: string, passphrase?: string): Promise<Uint8Array>;
function mnemonicToSeedSync(mnemonic: string, passphrase?: string): Uint8Array;
function mnemonicToSeedWebcrypto(mnemonic: string, passphrase?: string): Promise<Uint8Array>;
```
All wordlists (**warning: non-english wordlists are officially discouraged by bip39**):
```typescript
import { wordlist as czech } from '@scure/bip39/wordlists/czech.js';
import { wordlist as english } from '@scure/bip39/wordlists/english.js';
import { wordlist as french } from '@scure/bip39/wordlists/french.js';
import { wordlist as italian } from '@scure/bip39/wordlists/italian.js';
import { wordlist as japanese } from '@scure/bip39/wordlists/japanese.js';
import { wordlist as korean } from '@scure/bip39/wordlists/korean.js';
import { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese.js';
import { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese.js';
import { wordlist as spanish } from '@scure/bip39/wordlists/spanish.js';
import { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese.js';
```
## Security
The library has been audited:
- at version 2.2.0, in Apr 2026, by ourselves (self-audited)
- Scope: everything
- [Changes since audit](https://github.com/paulmillr/scure-bip39/compare/2.2.0..main)
- 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.
To audit wordlist content, run `node scripts/fetch-wordlist.js`.
### Supply chain security
- **Commits** are signed with PGP keys to prevent forgery. Be sure to verify the commit signatures
- **Releases** are made transparently through token-less GitHub CI and Trusted Publishing. Be sure to verify the [provenance logs](https://docs.npmjs.com/generating-provenance-statements) for authenticity.
- **Rare releasing** is practiced to minimize the need for re-audits by end-users.
- **Dependencies** are minimized and strictly pinned to reduce supply-chain risk.
- We use as few dependencies as possible.
- Version ranges are locked, and changes are checked with npm-diff.
- **Dev dependencies** are excluded from end-user installs; theyre only used for development and build steps.
For this package, there are 2 dependencies; and a few dev dependencies:
- [noble-hashes](https://github.com/paulmillr/noble-hashes) provides cryptographic hashing functionality
- [scure-base](https://github.com/paulmillr/scure-base) provides low-level wordlist utilities
- jsbt is used for benchmarking / testing / build tooling and developed by the same author
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation
## Contributing & testing
- `npm install && npm run build && npm test` will build the code and run tests.
- `npm run lint` / `npm run format` will run linter / fix linter issues.
- `npm run build:release` will build single file
## License
[MIT License](./LICENSE)
Copyright (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com)
@@ -0,0 +1,130 @@
import { type TArg, type TRet } from '@noble/hashes/utils.js';
/**
* Generate x random words. Uses Cryptographically-Secure Random Number Generator.
* @param wordlist - Imported wordlist for a specific language.
* @param strength - Mnemonic strength, from 128 to 256 bits.
* @returns 12-24 word mnemonic phrase.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Generate a new English mnemonic.
* ```ts
* import { generateMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const mnemonic = generateMnemonic(wordlist, 128);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
* ```
*/
export declare function generateMnemonic(wordlist: string[], strength?: number): string;
/**
* Reversible: Converts mnemonic string to raw entropy in form of byte array.
* @param mnemonic - 12-24 words.
* @param wordlist - Imported wordlist for a specific language.
* @returns Raw entropy bytes.
* @throws If the mnemonic shape or checksum is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Decode a mnemonic back into its original entropy bytes.
* ```ts
* import { mnemonicToEntropy } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const entropy = mnemonicToEntropy(mnem, wordlist);
* // Produces the original 16-byte entropy payload.
* new Uint8Array([
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
* ])
* ```
*/
export declare function mnemonicToEntropy(mnemonic: string, wordlist: string[]): TRet<Uint8Array>;
/**
* Reversible: Converts raw entropy in form of byte array to mnemonic string.
* @param entropy - Byte array.
* @param wordlist - Imported wordlist for a specific language.
* @returns 12-24 words.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Convert raw entropy into an English mnemonic.
* ```ts
* import { entropyToMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const ent = new Uint8Array([
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
* ]);
* const mnemonic = entropyToMnemonic(ent, wordlist);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
* ```
*/
export declare function entropyToMnemonic(entropy: TArg<Uint8Array>, wordlist: string[]): string;
/**
* Validates mnemonic for being 12-24 words contained in `wordlist`.
* @param mnemonic - 12-24 words.
* @param wordlist - Imported wordlist for a specific language.
* @returns `true` when mnemonic checksum and words are valid.
* @example
* Validate one English mnemonic.
* ```ts
* import { validateMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const ok = validateMnemonic(
* 'legal winner thank year wave sausage worth useful legal winner thank yellow',
* wordlist
* );
* // => true
* ```
*/
export declare function validateMnemonic(mnemonic: string, wordlist: string[]): boolean;
/**
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed from a mnemonic with the async PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = await mnemonicToSeed(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export declare function mnemonicToSeed(mnemonic: string, passphrase?: string): Promise<TRet<Uint8Array>>;
/**
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed from a mnemonic with the sync PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = mnemonicToSeedSync(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export declare function mnemonicToSeedSync(mnemonic: string, passphrase?: string): TRet<Uint8Array>;
/**
* Uses native, built-in functionality, provided by globalThis.crypto.
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed with the native WebCrypto PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = await mnemonicToSeedWebcrypto(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export declare function mnemonicToSeedWebcrypto(mnemonic: string, passphrase?: string): Promise<TRet<Uint8Array>>;
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,224 @@
/*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2.js';
import { sha256, sha512 } from '@noble/hashes/sha2.js';
import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
import { pbkdf2 as pbkdf2web, sha512 as sha512web } from '@noble/hashes/webcrypto.js';
import { utils as baseUtils } from '@scure/base';
// Japanese wordlist
// The canonical BIP-39 Japanese wordlist starts with あいこくしん.
// Use that sentinel so generated phrases use U+3000 ideographic spaces.
const isJapanese = (wordlist) => wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093';
// 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----
// BIP-39 requires UTF-8 NFKD for localized wordlists and mnemonic sentences.
// It also applies NFKD to the "mnemonic" + passphrase salt.
function nfkd(str) {
if (typeof str !== 'string')
throw new TypeError('invalid mnemonic type: ' + typeof str);
return str.normalize('NFKD');
}
// BIP-39 mnemonics are consumed in NFKD form.
// They must contain 12, 15, 18, 21, or 24 words before checksum validation.
function normalize(str) {
const norm = nfkd(str);
const words = norm.split(' ');
if (![12, 15, 18, 21, 24].includes(words.length))
throw new Error('Invalid mnemonic');
return { nfkd: norm, words };
}
// BIP-39 entropy payloads are 128-256 bits in 32-bit increments, i.e. 16/20/24/28/32 bytes.
function aentropy(ent) {
abytes(ent);
if (![16, 20, 24, 28, 32].includes(ent.length))
throw new RangeError('invalid entropy length');
}
/**
* Generate x random words. Uses Cryptographically-Secure Random Number Generator.
* @param wordlist - Imported wordlist for a specific language.
* @param strength - Mnemonic strength, from 128 to 256 bits.
* @returns 12-24 word mnemonic phrase.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Generate a new English mnemonic.
* ```ts
* import { generateMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const mnemonic = generateMnemonic(wordlist, 128);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
* ```
*/
export function generateMnemonic(wordlist, strength = 128) {
anumber(strength);
if (strength % 32 !== 0 || strength > 256)
throw new RangeError('Invalid entropy');
return entropyToMnemonic(randomBytes(strength / 8), wordlist);
}
const calcChecksum = (entropy) => {
// Checksum is ent.length/4 bits long
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]);
};
function getCoder(wordlist) {
if (!Array.isArray(wordlist) || wordlist.length !== 2048 || typeof wordlist[0] !== 'string')
throw new TypeError('Wordlist: expected array of 2048 strings');
wordlist.forEach((i) => {
if (typeof i !== 'string')
throw new TypeError('wordlist: non-string element: ' + i);
});
// BIP-39 appends checksum bits to entropy.
// It then splits the bitstream into 11-bit indexes for a 2048-word list.
return baseUtils.chain(baseUtils.checksum(1, calcChecksum), baseUtils.radix2(11, true), baseUtils.alphabet(wordlist));
}
/**
* Reversible: Converts mnemonic string to raw entropy in form of byte array.
* @param mnemonic - 12-24 words.
* @param wordlist - Imported wordlist for a specific language.
* @returns Raw entropy bytes.
* @throws If the mnemonic shape or checksum is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Decode a mnemonic back into its original entropy bytes.
* ```ts
* import { mnemonicToEntropy } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const entropy = mnemonicToEntropy(mnem, wordlist);
* // Produces the original 16-byte entropy payload.
* new Uint8Array([
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
* ])
* ```
*/
export function mnemonicToEntropy(mnemonic, wordlist) {
const { words } = normalize(mnemonic);
const entropy = getCoder(wordlist).decode(words);
aentropy(entropy);
return entropy;
}
/**
* Reversible: Converts raw entropy in form of byte array to mnemonic string.
* @param entropy - Byte array.
* @param wordlist - Imported wordlist for a specific language.
* @returns 12-24 words.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Convert raw entropy into an English mnemonic.
* ```ts
* import { entropyToMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const ent = new Uint8Array([
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
* ]);
* const mnemonic = entropyToMnemonic(ent, wordlist);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
* ```
*/
export function entropyToMnemonic(entropy, wordlist) {
aentropy(entropy);
const words = getCoder(wordlist).encode(entropy);
return words.join(isJapanese(wordlist) ? '\u3000' : ' ');
}
/**
* Validates mnemonic for being 12-24 words contained in `wordlist`.
* @param mnemonic - 12-24 words.
* @param wordlist - Imported wordlist for a specific language.
* @returns `true` when mnemonic checksum and words are valid.
* @example
* Validate one English mnemonic.
* ```ts
* import { validateMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const ok = validateMnemonic(
* 'legal winner thank year wave sausage worth useful legal winner thank yellow',
* wordlist
* );
* // => true
* ```
*/
export function validateMnemonic(mnemonic, wordlist) {
try {
mnemonicToEntropy(mnemonic, wordlist);
}
catch (e) {
return false;
}
return true;
}
// BIP-39 salts PBKDF2 with the UTF-8 NFKD string "mnemonic" + passphrase.
const psalt = (passphrase) => nfkd('mnemonic' + passphrase);
/**
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed from a mnemonic with the async PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = await mnemonicToSeed(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
// BIP-39 seed derivation is independent from mnemonic generation.
// These helpers normalize the phrase but do not verify checksum or wordlist membership.
export function mnemonicToSeed(mnemonic, passphrase = '') {
return pbkdf2Async(sha512, normalize(mnemonic).nfkd, psalt(passphrase), {
c: 2048,
dkLen: 64,
});
}
/**
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed from a mnemonic with the sync PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = mnemonicToSeedSync(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export function mnemonicToSeedSync(mnemonic, passphrase = '') {
return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), {
c: 2048,
dkLen: 64,
});
}
/**
* Uses native, built-in functionality, provided by globalThis.crypto.
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed with the native WebCrypto PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = await mnemonicToSeedWebcrypto(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export function mnemonicToSeedWebcrypto(mnemonic, passphrase = '') {
return pbkdf2web(sha512web, normalize(mnemonic).nfkd, psalt(passphrase), {
c: 2048,
dkLen: 64,
});
}
//# sourceMappingURL=index.js.map
@@ -0,0 +1,74 @@
{
"name": "@scure/bip39",
"version": "2.2.0",
"description": "Secure, audited & minimal implementation of BIP39 mnemonic phrases",
"files": [
"index.js",
"index.d.ts",
"wordlists/*.js",
"wordlists/*.d.ts",
"src/index.ts"
],
"dependencies": {
"@noble/hashes": "2.2.0",
"@scure/base": "2.2.0"
},
"devDependencies": {
"@paulmillr/jsbt": "0.5.0",
"prettier": "3.6.2",
"typescript": "6.0.2"
},
"scripts": {
"build": "tsc",
"build:release": "npx --no @paulmillr/jsbt esbuild test/build",
"check": "npx --no @paulmillr/jsbt check package.json",
"check:readme": "npx --no @paulmillr/jsbt readme package.json",
"check:treeshake": "npx --no @paulmillr/jsbt treeshake package.json test/build/out-treeshake",
"check:jsdoc": "npx --no @paulmillr/jsbt tsdoc package.json",
"format": "prettier --write 'src/**/*.ts' 'test/*.test.ts' 'test/scripts/*.js'",
"bench": "node test/benchmark.js",
"test": "node --experimental-strip-types --no-warnings test/index.ts",
"test:bun": "bun test/index.ts",
"test:deno": "deno --allow-env --allow-read test/index.js",
"test:node20": "cd test; npx tsc; node compiled/test/index.js",
"fetch-wordlist": "./test/scripts/fetch-wordlist.js"
},
"exports": {
".": "./index.js",
"./wordlists/czech.js": "./wordlists/czech.js",
"./wordlists/english.js": "./wordlists/english.js",
"./wordlists/french.js": "./wordlists/french.js",
"./wordlists/italian.js": "./wordlists/italian.js",
"./wordlists/japanese.js": "./wordlists/japanese.js",
"./wordlists/korean.js": "./wordlists/korean.js",
"./wordlists/portuguese.js": "./wordlists/portuguese.js",
"./wordlists/simplified-chinese.js": "./wordlists/simplified-chinese.js",
"./wordlists/spanish.js": "./wordlists/spanish.js",
"./wordlists/traditional-chinese.js": "./wordlists/traditional-chinese.js"
},
"keywords": [
"bip39",
"mnemonic",
"phrase",
"code",
"bip0039",
"bip-39",
"wordlist",
"scure",
"noble",
"bitcoin"
],
"homepage": "https://paulmillr.com/noble/#scure",
"funding": "https://paulmillr.com/funding/",
"repository": {
"type": "git",
"url": "git+https://github.com/paulmillr/scure-bip39.git"
},
"type": "module",
"main": "index.js",
"module": "index.js",
"types": "index.d.ts",
"sideEffects": false,
"author": "Paul Miller (https://paulmillr.com)",
"license": "MIT"
}
@@ -0,0 +1,238 @@
/*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2.js';
import { sha256, sha512 } from '@noble/hashes/sha2.js';
import { abytes, anumber, randomBytes, type TArg, type TRet } from '@noble/hashes/utils.js';
import { pbkdf2 as pbkdf2web, sha512 as sha512web } from '@noble/hashes/webcrypto.js';
import { utils as baseUtils } from '@scure/base';
// Japanese wordlist
// The canonical BIP-39 Japanese wordlist starts with あいこくしん.
// Use that sentinel so generated phrases use U+3000 ideographic spaces.
const isJapanese = (wordlist: string[]) => wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093';
// 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----
// BIP-39 requires UTF-8 NFKD for localized wordlists and mnemonic sentences.
// It also applies NFKD to the "mnemonic" + passphrase salt.
function nfkd(str: string) {
if (typeof str !== 'string') throw new TypeError('invalid mnemonic type: ' + typeof str);
return str.normalize('NFKD');
}
// BIP-39 mnemonics are consumed in NFKD form.
// They must contain 12, 15, 18, 21, or 24 words before checksum validation.
function normalize(str: string) {
const norm = nfkd(str);
const words = norm.split(' ');
if (![12, 15, 18, 21, 24].includes(words.length)) throw new Error('Invalid mnemonic');
return { nfkd: norm, words };
}
// BIP-39 entropy payloads are 128-256 bits in 32-bit increments, i.e. 16/20/24/28/32 bytes.
function aentropy(ent: TArg<Uint8Array>) {
abytes(ent);
if (![16, 20, 24, 28, 32].includes(ent.length)) throw new RangeError('invalid entropy length');
}
/**
* Generate x random words. Uses Cryptographically-Secure Random Number Generator.
* @param wordlist - Imported wordlist for a specific language.
* @param strength - Mnemonic strength, from 128 to 256 bits.
* @returns 12-24 word mnemonic phrase.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Generate a new English mnemonic.
* ```ts
* import { generateMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const mnemonic = generateMnemonic(wordlist, 128);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
* ```
*/
export function generateMnemonic(wordlist: string[], strength: number = 128): string {
anumber(strength);
if (strength % 32 !== 0 || strength > 256) throw new RangeError('Invalid entropy');
return entropyToMnemonic(randomBytes(strength / 8), wordlist);
}
const calcChecksum = (entropy: TArg<Uint8Array>) => {
// Checksum is ent.length/4 bits long
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]);
};
function getCoder(wordlist: string[]) {
if (!Array.isArray(wordlist) || wordlist.length !== 2048 || typeof wordlist[0] !== 'string')
throw new TypeError('Wordlist: expected array of 2048 strings');
wordlist.forEach((i) => {
if (typeof i !== 'string') throw new TypeError('wordlist: non-string element: ' + i);
});
// BIP-39 appends checksum bits to entropy.
// It then splits the bitstream into 11-bit indexes for a 2048-word list.
return baseUtils.chain(
baseUtils.checksum(1, calcChecksum),
baseUtils.radix2(11, true),
baseUtils.alphabet(wordlist)
);
}
/**
* Reversible: Converts mnemonic string to raw entropy in form of byte array.
* @param mnemonic - 12-24 words.
* @param wordlist - Imported wordlist for a specific language.
* @returns Raw entropy bytes.
* @throws If the mnemonic shape or checksum is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Decode a mnemonic back into its original entropy bytes.
* ```ts
* import { mnemonicToEntropy } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const entropy = mnemonicToEntropy(mnem, wordlist);
* // Produces the original 16-byte entropy payload.
* new Uint8Array([
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
* ])
* ```
*/
export function mnemonicToEntropy(mnemonic: string, wordlist: string[]): TRet<Uint8Array> {
const { words } = normalize(mnemonic);
const entropy = getCoder(wordlist).decode(words);
aentropy(entropy);
return entropy as TRet<Uint8Array>;
}
/**
* Reversible: Converts raw entropy in form of byte array to mnemonic string.
* @param entropy - Byte array.
* @param wordlist - Imported wordlist for a specific language.
* @returns 12-24 words.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Convert raw entropy into an English mnemonic.
* ```ts
* import { entropyToMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const ent = new Uint8Array([
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
* 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
* ]);
* const mnemonic = entropyToMnemonic(ent, wordlist);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
* ```
*/
export function entropyToMnemonic(entropy: TArg<Uint8Array>, wordlist: string[]): string {
aentropy(entropy);
const words = getCoder(wordlist).encode(entropy);
return words.join(isJapanese(wordlist) ? '\u3000' : ' ');
}
/**
* Validates mnemonic for being 12-24 words contained in `wordlist`.
* @param mnemonic - 12-24 words.
* @param wordlist - Imported wordlist for a specific language.
* @returns `true` when mnemonic checksum and words are valid.
* @example
* Validate one English mnemonic.
* ```ts
* import { validateMnemonic } from '@scure/bip39';
* import { wordlist } from '@scure/bip39/wordlists/english.js';
* const ok = validateMnemonic(
* 'legal winner thank year wave sausage worth useful legal winner thank yellow',
* wordlist
* );
* // => true
* ```
*/
export function validateMnemonic(mnemonic: string, wordlist: string[]): boolean {
try {
mnemonicToEntropy(mnemonic, wordlist);
} catch (e) {
return false;
}
return true;
}
// BIP-39 salts PBKDF2 with the UTF-8 NFKD string "mnemonic" + passphrase.
const psalt = (passphrase: string) => nfkd('mnemonic' + passphrase);
/**
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed from a mnemonic with the async PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = await mnemonicToSeed(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
// BIP-39 seed derivation is independent from mnemonic generation.
// These helpers normalize the phrase but do not verify checksum or wordlist membership.
export function mnemonicToSeed(mnemonic: string, passphrase = ''): Promise<TRet<Uint8Array>> {
return pbkdf2Async(sha512, normalize(mnemonic).nfkd, psalt(passphrase), {
c: 2048,
dkLen: 64,
}) as Promise<TRet<Uint8Array>>;
}
/**
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed from a mnemonic with the sync PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = mnemonicToSeedSync(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export function mnemonicToSeedSync(mnemonic: string, passphrase = ''): TRet<Uint8Array> {
return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), {
c: 2048,
dkLen: 64,
}) as TRet<Uint8Array>;
}
/**
* Uses native, built-in functionality, provided by globalThis.crypto.
* Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
* @param mnemonic - 12-24 words.
* @param passphrase - String that will additionally protect the key.
* @returns 64 bytes of key data.
* @throws If the mnemonic shape is invalid. {@link Error}
* @throws On wrong argument types. {@link TypeError}
* @example
* Derive a seed with the native WebCrypto PBKDF2 helper.
* ```ts
* const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
* const seed = await mnemonicToSeedWebcrypto(mnem, 'password');
* // => new Uint8Array([...64 bytes])
* ```
*/
export function mnemonicToSeedWebcrypto(
mnemonic: string,
passphrase = ''
): Promise<TRet<Uint8Array>> {
return pbkdf2web(sha512web, normalize(mnemonic).nfkd, psalt(passphrase), {
c: 2048,
dkLen: 64,
}) as Promise<TRet<Uint8Array>>;
}
@@ -0,0 +1,3 @@
/** Czech BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=czech.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** English BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=english.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** French BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=french.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Italian BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=italian.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Japanese BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=japanese.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Korean BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=korean.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Portuguese BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=portuguese.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Simplified Chinese BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=simplified-chinese.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Spanish BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=spanish.d.ts.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
/** Traditional Chinese BIP39 wordlist. */
export declare const wordlist: string[];
//# sourceMappingURL=traditional-chinese.d.ts.map
File diff suppressed because it is too large Load Diff