├── test ├── .npmrc ├── tsconfig.json ├── karma.webpack.conf.js ├── karma.parcel.conf.js ├── karma.rollup.conf.js ├── karma.browserify.conf.js ├── package.json ├── rollup.config.js ├── test-vectors │ ├── ripemd160.ts │ ├── secp256k1.ts │ ├── random.ts │ ├── sha256.ts │ ├── sha512.ts │ ├── math.ts │ ├── pbkdf2.ts │ ├── blake2b.ts │ ├── scrypt.ts │ ├── assert.ts │ ├── bn.ts │ ├── bls.ts │ ├── keccak.ts │ ├── aes.ts │ ├── secp256k1-compat.ts │ ├── hdkey.ts │ └── bip39.ts ├── test-imports-2.mjs └── test-imports-1.mjs ├── esm └── package.json ├── src ├── bn.ts ├── bls.ts ├── hdkey.ts ├── secp256k1.ts ├── bip39 │ ├── wordlists │ │ ├── czech.ts │ │ ├── english.ts │ │ ├── french.ts │ │ ├── italian.ts │ │ ├── japanese.ts │ │ ├── korean.ts │ │ ├── spanish.ts │ │ ├── simplified-chinese.ts │ │ └── traditional-chinese.ts │ └── index.ts ├── index.ts ├── math.ts ├── sha256.ts ├── sha512.ts ├── ripemd160.ts ├── random.ts ├── blake2b.ts ├── keccak.ts ├── scrypt.ts ├── pbkdf2.ts ├── aes.ts ├── utils.ts └── secp256k1-compat.ts ├── audit ├── 2022-01-05-cure53-audit-nbl2.pdf └── README.md ├── .vscode └── settings.json ├── .editorconfig ├── .mocharc.json ├── .gitignore ├── tsconfig.prod.json ├── tsconfig.prod.esm.json ├── .eslintrc ├── tsconfig.json ├── tslint.json ├── scripts ├── build-browser-tests.sh └── validate-wordlists.js ├── .github └── workflows │ ├── publish-npm.yml │ └── nodejs.yml ├── LICENSE ├── package.json └── README.md /test/.npmrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } -------------------------------------------------------------------------------- /src/bn.ts: -------------------------------------------------------------------------------- 1 | export { bn254 } from "@noble/curves/bn254.js"; 2 | -------------------------------------------------------------------------------- /src/bls.ts: -------------------------------------------------------------------------------- 1 | export { bls12_381 } from "@noble/curves/bls12-381.js"; 2 | -------------------------------------------------------------------------------- /src/hdkey.ts: -------------------------------------------------------------------------------- 1 | export { HARDENED_OFFSET, HDKey } from "@scure/bip32"; 2 | -------------------------------------------------------------------------------- /src/secp256k1.ts: -------------------------------------------------------------------------------- 1 | export { secp256k1 } from "@noble/curves/secp256k1.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/czech.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/czech.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/english.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/english.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/french.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/french.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/italian.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/italian.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/japanese.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/japanese.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/korean.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/korean.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/spanish.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/spanish.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/simplified-chinese.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/simplified-chinese.js"; 2 | -------------------------------------------------------------------------------- /src/bip39/wordlists/traditional-chinese.ts: -------------------------------------------------------------------------------- 1 | export { wordlist } from "@scure/bip39/wordlists/traditional-chinese.js"; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | throw new Error( 2 | "This package has no entry-point. Please consult the README.md to learn how to use it." 3 | ); 4 | -------------------------------------------------------------------------------- /audit/2022-01-05-cure53-audit-nbl2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/js-ethereum-cryptography/HEAD/audit/2022-01-05-cure53-audit-nbl2.pdf -------------------------------------------------------------------------------- /src/math.ts: -------------------------------------------------------------------------------- 1 | import { pow, invert } from "@noble/curves/abstract/modular.js"; 2 | 3 | export const modPow = pow; 4 | export const modInvert = invert; 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "*.{js,d.ts,js.map,d.ts.map}": true, 4 | "esm/*.{js,d.ts,js.map,d.ts.map}": true 5 | } 6 | } -------------------------------------------------------------------------------- /src/sha256.ts: -------------------------------------------------------------------------------- 1 | import { sha256 as _sha256 } from "@noble/hashes/sha2.js"; 2 | import { wrapHash } from "./utils.js"; 3 | 4 | export const sha256 = wrapHash(_sha256); 5 | -------------------------------------------------------------------------------- /src/sha512.ts: -------------------------------------------------------------------------------- 1 | import { sha512 as _sha512 } from "@noble/hashes/sha2.js"; 2 | import { wrapHash } from "./utils.js"; 3 | 4 | export const sha512 = wrapHash(_sha512); 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /src/ripemd160.ts: -------------------------------------------------------------------------------- 1 | import { ripemd160 as _ripemd160 } from "@noble/hashes/legacy.js"; 2 | import { wrapHash } from "./utils.js"; 3 | 4 | export const ripemd160 = wrapHash(_ripemd160); 5 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require" : [ 3 | "ts-node/register" 4 | ], 5 | "spec": "./test/test-vectors/*.ts", 6 | "package": "./package.json", 7 | "ui": "bdd" 8 | } 9 | -------------------------------------------------------------------------------- /src/bip39/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | generateMnemonic, 3 | mnemonicToEntropy, 4 | entropyToMnemonic, 5 | validateMnemonic, 6 | mnemonicToSeed, 7 | mnemonicToSeedSync 8 | } from "@scure/bip39"; 9 | -------------------------------------------------------------------------------- /audit/README.md: -------------------------------------------------------------------------------- 1 | # Audit 2 | 3 | The PDF was saved from cure53.de site: [URL](https://cure53.de/pentest-report_hashing-libs.pdf). See information about audit and fuzzing in root [README](../README.md). 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.js 2 | /*.js.map 3 | /*.d.ts 4 | /*.d.ts.map 5 | /bip39 6 | /test-builds 7 | /node_modules 8 | .parcel-cache 9 | /esm 10 | /test/node_modules 11 | /test/package-lock.json 12 | /test/test-builds 13 | -------------------------------------------------------------------------------- /src/random.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from "@noble/hashes/utils.js"; 2 | 3 | export function getRandomBytesSync(bytes: number): Uint8Array { 4 | return randomBytes(bytes); 5 | } 6 | 7 | export async function getRandomBytes(bytes: number): Promise { 8 | return randomBytes(bytes); 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "rootDir": "src", 8 | "outDir": ".", 9 | "declaration": true 10 | }, 11 | "include": [ 12 | "src/**/*.ts" 13 | ], 14 | "exclude": [] 15 | } -------------------------------------------------------------------------------- /tsconfig.prod.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["es2020", "dom"], 5 | "module": "es6", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "declaration": true, 9 | "rootDir": "src", 10 | "outDir": "esm" 11 | }, 12 | "include": [ 13 | "src/**/*.ts" 14 | ], 15 | "exclude": [] 16 | } -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "strict": true, 6 | "downlevelIteration": true, 7 | "rootDirs": [ 8 | "./" 9 | ], 10 | "outDir": "./test-builds/tsc", 11 | "noUnusedLocals": true, 12 | "declaration": true 13 | }, 14 | "include": [ 15 | "./**/*.ts", 16 | ] 17 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint", 6 | "prettier" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "rules": { 14 | "prettier/prettier": "error" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/blake2b.ts: -------------------------------------------------------------------------------- 1 | import { blake2b as _blake2b } from "@noble/hashes/blake2.js"; 2 | import { assertBytes } from "./utils.js"; 3 | 4 | export const blake2b = (msg: Uint8Array, outputLength = 64): Uint8Array => { 5 | assertBytes(msg); 6 | if (outputLength <= 0 || outputLength > 64) { 7 | throw Error("Invalid outputLength"); 8 | } 9 | return _blake2b(msg, { dkLen: outputLength }); 10 | }; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "strict": true, 6 | "downlevelIteration": true, 7 | "rootDirs": [ 8 | "./src", 9 | "./test" 10 | ], 11 | "outDir": "./test-builds/tsc", 12 | "noUnusedLocals": true, 13 | "declaration": true 14 | }, 15 | "include": [ 16 | "src/**/*.ts", 17 | "test/**/*.ts" 18 | ] 19 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest", 4 | "tslint-config-prettier", 5 | "tslint-plugin-prettier" 6 | ], 7 | "rules": { 8 | "prettier": true, 9 | "no-submodule-imports": false, 10 | "only-arrow-functions": false, 11 | "no-implicit-dependencies": [ 12 | true, 13 | [ 14 | "chai" 15 | ] 16 | ], 17 | "no-var-requires": false, 18 | "object-literal-sort-keys": false, 19 | "interface-name": false, 20 | "no-bitwise": false 21 | } 22 | } -------------------------------------------------------------------------------- /test/karma.webpack.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ["mocha"], 4 | files: ["./test-builds/main.js"], 5 | colors: true, 6 | logLevel: config.LOG_INFO, 7 | browsers: ["ChromeHeadless"], 8 | browserDisconnectTimeout: 100000, 9 | browserDisconnectTolerance: 3, 10 | browserNoActivityTimeout: 100000, 11 | autoWatch: false, 12 | concurrency: Infinity, 13 | reporters: ["mocha"], 14 | client: { 15 | captureConsole: true, 16 | }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /test/karma.parcel.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ["mocha"], 4 | files: ["./test-builds/parcel/*.js"], 5 | colors: true, 6 | logLevel: config.LOG_INFO, 7 | browsers: ["ChromeHeadless"], 8 | browserDisconnectTimeout: 100000, 9 | browserDisconnectTolerance: 3, 10 | browserNoActivityTimeout: 100000, 11 | autoWatch: false, 12 | concurrency: Infinity, 13 | reporters: ["mocha"], 14 | client: { 15 | captureConsole: true, 16 | }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /test/karma.rollup.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ["mocha"], 4 | files: ["./test-builds/rollup/*.js"], 5 | colors: true, 6 | logLevel: config.LOG_INFO, 7 | browsers: ["ChromeHeadless"], 8 | browserDisconnectTimeout: 100000, 9 | browserDisconnectTolerance: 3, 10 | browserNoActivityTimeout: 100000, 11 | autoWatch: false, 12 | concurrency: Infinity, 13 | reporters: ["mocha"], 14 | client: { 15 | captureConsole: true, 16 | }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /test/karma.browserify.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ["mocha"], 4 | files: ["./test-builds/browserify-build.js"], 5 | colors: true, 6 | logLevel: config.LOG_INFO, 7 | browsers: ["ChromeHeadless"], 8 | browserDisconnectTimeout: 100000, 9 | browserDisconnectTolerance: 3, 10 | browserNoActivityTimeout: 100000, 11 | autoWatch: false, 12 | concurrency: Infinity, 13 | reporters: ["mocha"], 14 | client: { 15 | captureConsole: true, 16 | }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /src/keccak.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Keccak, 3 | keccak_224, 4 | keccak_256, 5 | keccak_384, 6 | keccak_512, 7 | } from "@noble/hashes/sha3.js"; 8 | import { Hash } from "@noble/hashes/utils.js"; 9 | import { wrapHash } from "./utils.js"; 10 | 11 | // Expose create only for keccak256 12 | interface K256 { 13 | (data: Uint8Array): Uint8Array; 14 | create(): Hash; 15 | } 16 | 17 | export const keccak224 = wrapHash(keccak_224); 18 | export const keccak256: K256 = (() => { 19 | const k: any = wrapHash(keccak_256); 20 | k.create = keccak_256.create; 21 | return k; 22 | })(); 23 | export const keccak384 = wrapHash(keccak_384); 24 | export const keccak512 = wrapHash(keccak_512); 25 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "karma.browserify.conf.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ethereum-cryptography": "file:.." 14 | }, 15 | "devDependencies": { 16 | "@parcel/watcher": "2.4.1", 17 | "@rollup/plugin-commonjs": "22.0.1", 18 | "@rollup/plugin-node-resolve": "13.3.0", 19 | "browserify": "17.0.0", 20 | "parcel": "2.12.0", 21 | "rollup": "2.79.1", 22 | "webpack": "5.94.0", 23 | "webpack-cli": "4.10.0" 24 | }, 25 | "targets": { 26 | "parcel_tests": { 27 | "context": "browser" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | import resolve from "@rollup/plugin-node-resolve"; 5 | 6 | const TESTS_DIR = "./test-builds/tsc/test/test-vectors"; 7 | const testFiles = fs 8 | .readdirSync(TESTS_DIR) 9 | .filter(name => name.endsWith(".js")) 10 | .map(name => path.join(TESTS_DIR, name)); 11 | 12 | export default testFiles.map(test => ({ 13 | input: test, 14 | output: { 15 | file: `./test-builds/rollup/${path.basename(test)}`, 16 | format: "iife", 17 | name: "tests", 18 | sourcemap: true, 19 | exports: "named" 20 | }, 21 | plugins: [ 22 | commonjs(), 23 | resolve({ 24 | browser: true, 25 | preferBuiltins: false 26 | }) 27 | ] 28 | })); 29 | -------------------------------------------------------------------------------- /scripts/build-browser-tests.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd ./test/ 4 | echo "Install package to tests" 5 | # Cleanup old module build 6 | rm -rf ./node_modules 7 | npm install --production=false 8 | 9 | export PATH="${PWD}/node_modules/.bin:${PATH}" 10 | 11 | echo "Building tests with TypeScript" 12 | tsc --project ./tsconfig.json 13 | 14 | echo "Building tests with Parcel" 15 | parcel build --no-cache --no-optimize ./test-builds/tsc/test/test-vectors/*.js --dist-dir ./test-builds/parcel --target parcel_tests 16 | 17 | #echo "Building tests with Browserify" 18 | #browserify ./test-builds/tsc/test/test-vectors/*.js > ./test-builds/browserify-build.js 19 | 20 | echo "Building tests with webpack" 21 | webpack --mode development ./test-builds/tsc/test/test-vectors/*.js --output-path ./test-builds 22 | 23 | echo "Building tests with Rollup" 24 | rollup -c ./rollup.config.js 25 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish package to npm 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | name: "Publish to NPM" 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4 14 | with: 15 | persist-credentials: false 16 | - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 17 | with: 18 | node-version: 20 19 | registry-url: 'https://registry.npmjs.org' 20 | cache: npm 21 | - run: npm install -g npm@9.x 22 | - run: npm ci 23 | - run: npm run build 24 | - run: npm publish --provenance --access public 25 | env: 26 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 27 | -------------------------------------------------------------------------------- /src/scrypt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | scrypt as _sync, 3 | scryptAsync as _async, 4 | } from "@noble/hashes/scrypt.js"; 5 | import { assertBytes } from "./utils.js"; 6 | 7 | type OnProgressCallback = (progress: number) => void; 8 | 9 | export async function scrypt( 10 | password: Uint8Array, 11 | salt: Uint8Array, 12 | n: number, 13 | p: number, 14 | r: number, 15 | dkLen: number, 16 | onProgress?: OnProgressCallback 17 | ): Promise { 18 | assertBytes(password); 19 | assertBytes(salt); 20 | return _async(password, salt, { N: n, r, p, dkLen, onProgress }); 21 | } 22 | 23 | export function scryptSync( 24 | password: Uint8Array, 25 | salt: Uint8Array, 26 | n: number, 27 | p: number, 28 | r: number, 29 | dkLen: number, 30 | onProgress?: OnProgressCallback 31 | ): Uint8Array { 32 | assertBytes(password); 33 | assertBytes(salt); 34 | return _sync(password, salt, { N: n, r, p, dkLen, onProgress }); 35 | } 36 | -------------------------------------------------------------------------------- /test/test-vectors/ripemd160.ts: -------------------------------------------------------------------------------- 1 | import { ripemd160 } from "ethereum-cryptography/ripemd160"; 2 | import { toHex, utf8ToBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | const TEST_VECTORS = [ 6 | { 7 | input: utf8ToBytes(""), 8 | output: "9c1185a5c5e9fc54612808977ee8f548b2258d31" 9 | }, 10 | { 11 | input: utf8ToBytes("a"), 12 | output: "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe" 13 | }, 14 | { 15 | input: utf8ToBytes("abc"), 16 | output: "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc" 17 | }, 18 | { 19 | input: utf8ToBytes("message digest"), 20 | output: "5d0689ef49d2fae572b881b123a85ffa21595f36" 21 | } 22 | ]; 23 | 24 | describe("ripemd160", function() { 25 | for (const [i, vector] of TEST_VECTORS.entries()) { 26 | it(`Should return the right hash for the test ${i}`, async function() { 27 | deepStrictEqual(toHex(ripemd160(vector.input)), vector.output); 28 | }); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /test/test-vectors/secp256k1.ts: -------------------------------------------------------------------------------- 1 | import { secp256k1 } from "ethereum-cryptography/secp256k1"; 2 | import { hexToBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | describe("secp256k1", () => { 6 | it("should verify msg bb5a...", async () => { 7 | const msg = 8 | hexToBytes("bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023"); 9 | const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n; 10 | const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; 11 | const r = 432420386565659656852420866390673177323n; 12 | const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; 13 | const pub = new secp256k1.Point(x, y, 1n); 14 | const sig = new secp256k1.Signature(r, s).toBytes(); 15 | deepStrictEqual(secp256k1.verify(sig, msg, pub.toBytes(), { lowS: false, prehash: false }), true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/test-vectors/random.ts: -------------------------------------------------------------------------------- 1 | import { getRandomBytes, getRandomBytesSync } from "ethereum-cryptography/random"; 2 | import { equalsBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | describe("Random number generation", () => { 6 | describe("Sync version", () => { 7 | it("Returns a Uint8Array of the right size", () => { 8 | deepStrictEqual(getRandomBytesSync(32) instanceof Uint8Array, true); 9 | deepStrictEqual(getRandomBytesSync(32).length, 32); 10 | deepStrictEqual( 11 | equalsBytes(getRandomBytesSync(32), new Uint8Array(32)), 12 | false 13 | ); 14 | }); 15 | }); 16 | 17 | describe("Async version", () => { 18 | it("Returns a Promise of Uint8Array of the right size", async () => { 19 | deepStrictEqual(getRandomBytes(32) instanceof Promise, true); 20 | deepStrictEqual((await getRandomBytes(32)) instanceof Uint8Array, true); 21 | deepStrictEqual((await getRandomBytes(32)).length, 32); 22 | deepStrictEqual( 23 | equalsBytes(await getRandomBytes(32), new Uint8Array(32)), 24 | false 25 | ); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/test-vectors/sha256.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from "ethereum-cryptography/sha256"; 2 | import { toHex, utf8ToBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | const TEST_VECTORS = [ 6 | { 7 | input: utf8ToBytes(""), 8 | output: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 9 | }, 10 | { 11 | input: utf8ToBytes("abc"), 12 | output: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" 13 | }, 14 | { 15 | input: utf8ToBytes( 16 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 17 | ), 18 | output: "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" 19 | }, 20 | { 21 | input: utf8ToBytes( 22 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 23 | ), 24 | output: "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" 25 | } 26 | ]; 27 | 28 | describe("sha256", function() { 29 | for (const [i, vector] of TEST_VECTORS.entries()) { 30 | it(`Should return the right hash for the test ${i}`, async function() { 31 | deepStrictEqual(toHex(sha256(vector.input)), vector.output); 32 | }); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Patricio Palladino, Paul Miller, ethereum-cryptography contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/pbkdf2.ts: -------------------------------------------------------------------------------- 1 | import { 2 | pbkdf2 as _pbkdf2, 3 | pbkdf2Async as _pbkdf2Async, 4 | } from "@noble/hashes/pbkdf2.js"; 5 | import { sha256, sha512 } from "@noble/hashes/sha2.js"; 6 | import { assertBytes } from "./utils.js"; 7 | 8 | export async function pbkdf2( 9 | password: Uint8Array, 10 | salt: Uint8Array, 11 | iterations: number, 12 | keylen: number, 13 | digest: string 14 | ): Promise { 15 | if (!["sha256", "sha512"].includes(digest)) { 16 | throw new Error("Only sha256 and sha512 are supported"); 17 | } 18 | assertBytes(password); 19 | assertBytes(salt); 20 | return _pbkdf2Async(digest === "sha256" ? sha256 : sha512, password, salt, { 21 | c: iterations, 22 | dkLen: keylen, 23 | }); 24 | } 25 | 26 | export function pbkdf2Sync( 27 | password: Uint8Array, 28 | salt: Uint8Array, 29 | iterations: number, 30 | keylen: number, 31 | digest: string 32 | ): Uint8Array { 33 | if (!["sha256", "sha512"].includes(digest)) { 34 | throw new Error("Only sha256 and sha512 are supported"); 35 | } 36 | assertBytes(password); 37 | assertBytes(salt); 38 | return _pbkdf2(digest === "sha256" ? sha256 : sha512, password, salt, { 39 | c: iterations, 40 | dkLen: keylen, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | env: 5 | FORCE_COLOR: 2 6 | jobs: 7 | test: 8 | name: v${{ matrix.node }} @ ${{ matrix.os }} 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | # @actions/setup-node does not support Node.js v14 for macOS 15 | - node: 14 16 | os: ubuntu-latest 17 | node: 18 | - 16.0.0 19 | - 16 20 | - 18.0.0 21 | - 18 22 | - 20 23 | - 22 24 | os: 25 | - ubuntu-latest 26 | - macOS-latest 27 | steps: 28 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 29 | with: 30 | persist-credentials: false 31 | - name: Use Node.js ${{ matrix.node }} 32 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 33 | with: 34 | node-version: ${{ matrix.node }} 35 | registry-url: "https://registry.npmjs.org" 36 | - run: npm install -g npm@9.x 37 | - run: npm ci 38 | - run: npm run build --if-present 39 | - run: npm test 40 | - run: npm run browser-tests 41 | - run: npm run lint --if-present 42 | -------------------------------------------------------------------------------- /src/aes.ts: -------------------------------------------------------------------------------- 1 | import { cbc, ctr } from "@noble/ciphers/aes.js"; 2 | import type { CipherWithOutput } from "@noble/ciphers/utils.js"; 3 | 4 | function getCipher( 5 | key: Uint8Array, 6 | iv: Uint8Array, 7 | mode: string, 8 | pkcs7PaddingEnabled = true 9 | ): CipherWithOutput { 10 | if (!mode.startsWith("aes-")) { 11 | throw new Error("AES: unsupported mode"); 12 | } 13 | const len = key.length; 14 | if ( 15 | (mode.startsWith("aes-128") && len !== 16) || 16 | (mode.startsWith("aes-256") && len !== 32) 17 | ) { 18 | throw new Error("AES: wrong key length"); 19 | } 20 | if (iv.length !== 16) { 21 | throw new Error("AES: wrong IV length"); 22 | } 23 | if (["aes-128-cbc", "aes-256-cbc"].includes(mode)) { 24 | return cbc(key, iv, { disablePadding: !pkcs7PaddingEnabled }); 25 | } 26 | if (["aes-128-ctr", "aes-256-ctr"].includes(mode)) { 27 | return ctr(key, iv); 28 | } 29 | throw new Error("AES: unsupported mode"); 30 | } 31 | 32 | export function encrypt( 33 | msg: Uint8Array, 34 | key: Uint8Array, 35 | iv: Uint8Array, 36 | mode = "aes-128-ctr", 37 | pkcs7PaddingEnabled = true 38 | ): Uint8Array { 39 | return getCipher(key, iv, mode, pkcs7PaddingEnabled).encrypt(msg); 40 | } 41 | 42 | export function decrypt( 43 | ciphertext: Uint8Array, 44 | key: Uint8Array, 45 | iv: Uint8Array, 46 | mode = "aes-128-ctr", 47 | pkcs7PaddingEnabled = true 48 | ): Uint8Array { 49 | return getCipher(key, iv, mode, pkcs7PaddingEnabled).decrypt(ciphertext); 50 | } 51 | -------------------------------------------------------------------------------- /test/test-vectors/sha512.ts: -------------------------------------------------------------------------------- 1 | import { sha512 } from "ethereum-cryptography/sha512"; 2 | import { toHex, utf8ToBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | const TEST_VECTORS = [ 6 | { 7 | input: utf8ToBytes(""), 8 | output: 9 | "cf83e1357eefb8bd f1542850d66d8007 d620e4050b5715dc 83f4a921d36ce9ce 47d0d13c5d85f2b0 ff8318d2877eec2f 63b931bd47417a81 a538327af927da3e" 10 | }, 11 | { 12 | input: utf8ToBytes("abc"), 13 | output: 14 | "ddaf35a193617aba cc417349ae204131 12e6fa4e89a97ea2 0a9eeee64b55d39a 2192992a274fc1a8 36ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" 15 | }, 16 | { 17 | input: utf8ToBytes( 18 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 19 | ), 20 | output: 21 | "204a8fc6dda82f0a 0ced7beb8e08a416 57c16ef468b228a8 279be331a703c335 96fd15c13b1b07f9 aa1d3bea57789ca0 31ad85c7a71dd703 54ec631238ca3445" 22 | }, 23 | { 24 | input: utf8ToBytes( 25 | "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" 26 | ), 27 | output: 28 | "8e959b75dae313da 8cf4f72814fc143f 8f7779c6eb9f7fa1 7299aeadb6889018 501d289e4900f7e4 331b99dec4b5433a c7d329eeb6dd2654 5e96e55b874be909" 29 | } 30 | ]; 31 | 32 | describe("sha512", function() { 33 | for (const [i, vector] of TEST_VECTORS.entries()) { 34 | it(`Should return the right hash for the test ${i}`, async function() { 35 | deepStrictEqual( 36 | toHex(sha512(vector.input)), 37 | vector.output.replace(/ /g, "") 38 | ); 39 | }); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | abool as assertBool, 3 | abytes as assertBytes, 4 | } from "@noble/curves/utils.js"; 5 | import { hexToBytes as _hexToBytes } from "@noble/hashes/utils.js"; 6 | 7 | export { 8 | bytesToHex, 9 | concatBytes, 10 | createView, 11 | bytesToHex as toHex, 12 | utf8ToBytes, 13 | } from "@noble/hashes/utils.js"; 14 | export { assertBool, assertBytes }; 15 | 16 | // buf.toString('hex') -> toHex(buf) 17 | 18 | // Global symbols in both browsers and Node.js since v11 19 | // See https://github.com/microsoft/TypeScript/issues/31535 20 | declare const TextEncoder: any; 21 | declare const TextDecoder: any; 22 | 23 | // buf.toString('utf8') -> bytesToUtf8(buf) 24 | export function bytesToUtf8(data: Uint8Array): string { 25 | if (!(data instanceof Uint8Array)) { 26 | throw new TypeError(`bytesToUtf8 expected Uint8Array, got ${typeof data}`); 27 | } 28 | return new TextDecoder().decode(data); 29 | } 30 | 31 | export function hexToBytes(data: string): Uint8Array { 32 | const sliced = data.startsWith("0x") ? data.substring(2) : data; 33 | return _hexToBytes(sliced); 34 | } 35 | 36 | // buf.equals(buf2) -> equalsBytes(buf, buf2) 37 | export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean { 38 | if (a.length !== b.length) { 39 | return false; 40 | } 41 | for (let i = 0; i < a.length; i++) { 42 | if (a[i] !== b[i]) { 43 | return false; 44 | } 45 | } 46 | return true; 47 | } 48 | 49 | // Internal utils 50 | export function wrapHash(hash: (msg: Uint8Array) => Uint8Array) { 51 | return (msg: Uint8Array): Uint8Array => { 52 | assertBytes(msg); 53 | return hash(msg); 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /scripts/validate-wordlists.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const https = require("https"); 3 | const assert = require("assert"); 4 | 5 | const WORDLIST_NAMES = { 6 | czech: "czech", 7 | english: "english", 8 | french: "french", 9 | italian: "italian", 10 | japanese: "japanese", 11 | korean: "korean", 12 | "simplified-chinese": "chinese_simplified", 13 | spanish: "spanish", 14 | "traditional-chinese": "chinese_traditional" 15 | }; 16 | 17 | const download = name => 18 | new Promise((resolve, reject) => { 19 | { 20 | name = WORDLIST_NAMES[name]; 21 | const url = `https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/${name}.txt`; 22 | const req = https.get(url, { encoding: "utf8" }, res => { 23 | const chunks = []; 24 | res.on("data", chunk => chunks.push(chunk)); 25 | res.on("end", () => { 26 | resolve( 27 | Buffer.concat(chunks) 28 | .toString("utf8") 29 | .trim() 30 | .split("\n") 31 | ); 32 | }); 33 | }); 34 | req.on("error", reject); 35 | req.end(); 36 | } 37 | }); 38 | 39 | const files = fs 40 | .readdirSync(__dirname + "/../bip39/wordlists") 41 | .filter(f => f.endsWith(".js")); 42 | 43 | (async () => { 44 | try { 45 | for (const file of files) { 46 | const actual = require(`../bip39/wordlists/${file}`).wordlist; 47 | let expected = await download(file.slice(0, -3)); 48 | assert.deepStrictEqual(actual, expected); 49 | console.log(`CHECKED: ${file}`); 50 | } 51 | } catch (e) { 52 | console.log("ERROR", e); 53 | process.exit(1); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /test/test-imports-2.mjs: -------------------------------------------------------------------------------- 1 | // Hashes 2 | import { sha256 } from "ethereum-cryptography/sha256"; 3 | import { keccak256 } from "ethereum-cryptography/keccak"; 4 | import { ripemd160 } from "ethereum-cryptography/ripemd160"; 5 | import { blake2b } from "ethereum-cryptography/blake2b"; 6 | 7 | // KDFs 8 | import { pbkdf2Sync } from "ethereum-cryptography/pbkdf2"; 9 | import { scryptSync } from "ethereum-cryptography/scrypt"; 10 | 11 | // Random 12 | import { getRandomBytesSync } from "ethereum-cryptography/random"; 13 | 14 | // AES encryption 15 | import { encrypt } from "ethereum-cryptography/aes"; 16 | 17 | // secp256k1 elliptic curve operations 18 | import { secp256k1 } from "ethereum-cryptography/secp256k1"; 19 | 20 | // BIP32 HD Keygen, BIP39 Mnemonic Phrases 21 | import { HDKey } from "ethereum-cryptography/hdkey"; 22 | import { generateMnemonic as gm1 } from "ethereum-cryptography/bip39"; 23 | import { generateMnemonic as gm2 } from "ethereum-cryptography/bip39/index"; 24 | import { wordlist } from "ethereum-cryptography/bip39/wordlists/english"; 25 | 26 | // utilities 27 | import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils"; 28 | 29 | import * as w1 from 'ethereum-cryptography/bip39/wordlists/czech'; 30 | import * as w2 from 'ethereum-cryptography/bip39/wordlists/english'; 31 | import * as w3 from 'ethereum-cryptography/bip39/wordlists/french'; 32 | import * as w4 from 'ethereum-cryptography/bip39/wordlists/italian'; 33 | import * as w5 from 'ethereum-cryptography/bip39/wordlists/japanese'; 34 | import * as w6 from 'ethereum-cryptography/bip39/wordlists/korean'; 35 | import * as w7 from 'ethereum-cryptography/bip39/wordlists/simplified-chinese'; 36 | import * as w8 from 'ethereum-cryptography/bip39/wordlists/spanish'; 37 | import * as w9 from 'ethereum-cryptography/bip39/wordlists/traditional-chinese'; 38 | -------------------------------------------------------------------------------- /test/test-imports-1.mjs: -------------------------------------------------------------------------------- 1 | // Hashes 2 | import { sha256 } from "ethereum-cryptography/sha256.js"; 3 | import { keccak256 } from "ethereum-cryptography/keccak.js"; 4 | import { ripemd160 } from "ethereum-cryptography/ripemd160.js"; 5 | import { blake2b } from "ethereum-cryptography/blake2b.js"; 6 | 7 | // KDFs 8 | import { pbkdf2Sync } from "ethereum-cryptography/pbkdf2.js"; 9 | import { scryptSync } from "ethereum-cryptography/scrypt.js"; 10 | 11 | // Random 12 | import { getRandomBytesSync } from "ethereum-cryptography/random.js"; 13 | 14 | // AES encryption 15 | import { encrypt } from "ethereum-cryptography/aes.js"; 16 | 17 | // secp256k1 elliptic curve operations 18 | import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; 19 | 20 | // BIP32 HD Keygen, BIP39 Mnemonic Phrases 21 | import { HDKey } from "ethereum-cryptography/hdkey.js"; 22 | import { generateMnemonic as gm1 } from "ethereum-cryptography/bip39.js"; 23 | import { generateMnemonic as gm2 } from "ethereum-cryptography/bip39/index.js"; 24 | import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; 25 | 26 | // utilities 27 | import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils.js"; 28 | 29 | import * as w1 from 'ethereum-cryptography/bip39/wordlists/czech.js'; 30 | import * as w2 from 'ethereum-cryptography/bip39/wordlists/english.js'; 31 | import * as w3 from 'ethereum-cryptography/bip39/wordlists/french.js'; 32 | import * as w4 from 'ethereum-cryptography/bip39/wordlists/italian.js'; 33 | import * as w5 from 'ethereum-cryptography/bip39/wordlists/japanese.js'; 34 | import * as w6 from 'ethereum-cryptography/bip39/wordlists/korean.js'; 35 | import * as w7 from 'ethereum-cryptography/bip39/wordlists/simplified-chinese.js'; 36 | import * as w8 from 'ethereum-cryptography/bip39/wordlists/spanish.js'; 37 | import * as w9 from 'ethereum-cryptography/bip39/wordlists/traditional-chinese.js'; 38 | -------------------------------------------------------------------------------- /test/test-vectors/math.ts: -------------------------------------------------------------------------------- 1 | import { modPow, modInvert } from "ethereum-cryptography/math"; 2 | import { deepStrictEqual, throws } from "./assert"; 3 | 4 | describe("math", () => { 5 | it("pow", () => { 6 | deepStrictEqual(modPow(123n, 456n, 789n), 699n); 7 | deepStrictEqual(modPow(123n, 0n, 789n), 1n); 8 | deepStrictEqual(modPow(2n, 5n, 789n), 32n); 9 | deepStrictEqual(modPow(123n, 456n, 1n), 0n); 10 | deepStrictEqual( 11 | modPow( 12 | 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn, 13 | 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n, 14 | 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn 15 | ), 16 | 436876548127983101943682984672944697156319180922804969694242509081341800477678465744409641708610886843942671311740n 17 | ); 18 | throws(() => modPow(123n, 456n, 0n)); 19 | throws(() => modPow(123n, 456n, -1n)); 20 | throws(() => modPow(123n, -1n, 789n)); 21 | }); 22 | it("invert", () => { 23 | // basic 24 | deepStrictEqual(modInvert(3n, 11n), 4n); 25 | deepStrictEqual(modInvert(10n, 17n), 12n); 26 | deepStrictEqual(modInvert(22n, 5n), 3n); // bigger than modulo 27 | deepStrictEqual( 28 | modInvert( 29 | 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn, 30 | 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n 31 | ), 32 | 291256306712195702191844365537370801710916620404828242975224254728724473918830780018590872057480493707327142420858n 33 | ); // big 34 | // zero 35 | throws(() => modInvert(0n, 5n)); 36 | throws(() => modInvert(5n, 0n)); 37 | deepStrictEqual(modInvert(-1n, 5n), 4n); 38 | throws(() => modInvert(5n, -1n)); 39 | throws(() => modInvert(2n, 4n)); // gcd is not 1 40 | throws(() => modInvert(7n, 7n)); // number and modulo same 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/test-vectors/pbkdf2.ts: -------------------------------------------------------------------------------- 1 | import { pbkdf2 as pbkdf2Async, pbkdf2Sync } from "ethereum-cryptography/pbkdf2"; 2 | import { toHex, utf8ToBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | const TEST_VECTORS = [ 6 | { 7 | password: utf8ToBytes("password"), 8 | salt: utf8ToBytes("salt"), 9 | iterations: 1, 10 | keylen: 32, 11 | digest: "sha256", 12 | result: "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b" 13 | }, 14 | { 15 | password: utf8ToBytes("passwordPASSWORDpassword"), 16 | salt: utf8ToBytes("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 17 | iterations: 4096, 18 | keylen: 40, 19 | digest: "sha256", 20 | result: 21 | "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9" 22 | }, 23 | { 24 | password: utf8ToBytes("password"), 25 | salt: utf8ToBytes("salt"), 26 | iterations: 4096, 27 | keylen: 32, 28 | digest: "sha256", 29 | result: "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a" 30 | } 31 | ]; 32 | 33 | describe("pbkdf2", function() { 34 | describe("pbkdf2 sync", function() { 35 | for (let i = 0; i < TEST_VECTORS.length; i++) { 36 | it(`Should process the test ${i} correctly`, function() { 37 | const vector = TEST_VECTORS[i]; 38 | 39 | const derived = pbkdf2Sync( 40 | vector.password, 41 | vector.salt, 42 | vector.iterations, 43 | vector.keylen, 44 | vector.digest 45 | ); 46 | 47 | deepStrictEqual(toHex(derived), vector.result); 48 | }); 49 | } 50 | }); 51 | 52 | describe("pbkdf2 async", function() { 53 | for (let i = 0; i < TEST_VECTORS.length; i++) { 54 | it(`Should process the test ${i} correctly`, async function() { 55 | const vector = TEST_VECTORS[i]; 56 | 57 | const derived = await pbkdf2Async( 58 | vector.password, 59 | vector.salt, 60 | vector.iterations, 61 | vector.keylen, 62 | vector.digest 63 | ); 64 | 65 | deepStrictEqual(toHex(derived), vector.result); 66 | }); 67 | } 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/test-vectors/blake2b.ts: -------------------------------------------------------------------------------- 1 | import { blake2b } from "ethereum-cryptography/blake2b"; 2 | import { hexToBytes, toHex } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual, throws } from "./assert"; 4 | // Vectors extracted from https://github.com/emilbayes/blake2b/blob/f0a7c7b550133eca5f5fc3b751ccfd2335ce736f/test-vectors.json 5 | const TEST_VECTORS = [ 6 | { 7 | outlen: 64, 8 | out: 9 | "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", 10 | input: "" 11 | }, 12 | { 13 | outlen: 64, 14 | out: 15 | "2fa3f686df876995167e7c2e5d74c4c7b6e48f8068fe0e44208344d480f7904c36963e44115fe3eb2a3ac8694c28bcb4f5a0f3276f2e79487d8219057a506e4b", 16 | input: "00" 17 | }, 18 | { 19 | outlen: 64, 20 | out: 21 | "1c08798dc641aba9dee435e22519a4729a09b2bfe0ff00ef2dcd8ed6f8a07d15eaf4aee52bbf18ab5608a6190f70b90486c8a7d4873710b1115d3debbb4327b5", 22 | input: "0001" 23 | }, 24 | { 25 | outlen: 64, 26 | out: 27 | "40a374727302d9a4769c17b5f409ff32f58aa24ff122d7603e4fda1509e919d4107a52c57570a6d94e50967aea573b11f86f473f537565c66f7039830a85d186", 28 | input: "000102" 29 | }, 30 | { 31 | outlen: 64, 32 | out: 33 | "77ddf4b14425eb3d053c1e84e3469d92c4cd910ed20f92035e0c99d8a7a86cecaf69f9663c20a7aa230bc82f60d22fb4a00b09d3eb8fc65ef547fe63c8d3ddce", 34 | input: "00010203" 35 | }, 36 | { 37 | outlen: 64, 38 | out: 39 | "cbaa0ba7d482b1f301109ae41051991a3289bc1198005af226c5e4f103b66579f461361044c8ba3439ff12c515fb29c52161b7eb9c2837b76a5dc33f7cb2e2e8", 40 | input: "0001020304" 41 | } 42 | ]; 43 | 44 | describe("blake2b", function() { 45 | for (const [i, vector] of TEST_VECTORS.entries()) { 46 | it(`Should return the right hash for the test ${i}`, function() { 47 | const actual = blake2b(hexToBytes(vector.input), vector.outlen); 48 | deepStrictEqual(toHex(actual), vector.out); 49 | }); 50 | } 51 | 52 | it("throws if the outputLength is <= 0", function() { 53 | throws(() => blake2b(hexToBytes(""), 0)); 54 | throws(() => blake2b(hexToBytes(""), -1)); 55 | }); 56 | 57 | it("throws if the outputLength is > 64", function() { 58 | throws(() => blake2b(hexToBytes(""), 65)); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/test-vectors/scrypt.ts: -------------------------------------------------------------------------------- 1 | import { scrypt as scryptAsync, scryptSync } from "ethereum-cryptography/scrypt"; 2 | import { hexToBytes, toHex } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | const TEST_VECTORS = [ 6 | { 7 | password: "", 8 | salt: "", 9 | N: 16, 10 | p: 1, 11 | r: 1, 12 | dkLen: 64, 13 | derivedKey: 14 | "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906" 15 | }, 16 | { 17 | password: "70617373776f7264", 18 | salt: "4e61436c", 19 | N: 1024, 20 | p: 16, 21 | r: 8, 22 | dkLen: 64, 23 | derivedKey: 24 | "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640" 25 | }, 26 | { 27 | password: "706c656173656c65746d65696e", 28 | salt: "536f6469756d43686c6f72696465", 29 | N: 16384, 30 | p: 1, 31 | r: 8, 32 | dkLen: 64, 33 | derivedKey: 34 | "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887" 35 | } 36 | ]; 37 | 38 | describe("scrypt", function() { 39 | describe("scrypt sync", function() { 40 | for (let i = 0; i < TEST_VECTORS.length; i++) { 41 | it(`Should process the test ${i} correctly`, function() { 42 | this.timeout(10000) 43 | 44 | const vector = TEST_VECTORS[i]; 45 | 46 | const derived = scryptSync( 47 | hexToBytes(vector.password), 48 | hexToBytes(vector.salt), 49 | +vector.N, 50 | +vector.p, 51 | +vector.r, 52 | +vector.dkLen 53 | ); 54 | 55 | deepStrictEqual(toHex(derived), vector.derivedKey); 56 | }); 57 | } 58 | }); 59 | 60 | describe("scrypt async", function() { 61 | for (let i = 0; i < TEST_VECTORS.length; i++) { 62 | it(`Should process the test ${i} correctly`, async function() { 63 | this.timeout(10000); 64 | 65 | const vector = TEST_VECTORS[i]; 66 | 67 | const derived = await scryptAsync( 68 | hexToBytes(vector.password), 69 | hexToBytes(vector.salt), 70 | +vector.N, 71 | +vector.p, 72 | +vector.r, 73 | +vector.dkLen 74 | ); 75 | 76 | deepStrictEqual(toHex(derived), vector.derivedKey); 77 | }); 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/test-vectors/assert.ts: -------------------------------------------------------------------------------- 1 | // Minimal assert version to avoid dependecies on node internals 2 | // Allows to verify that none of brwoserify version of node internals is included in resulting build 3 | function deepStrictEqual(actual: unknown, expected: unknown, message?: string) { 4 | const [actualType, expectedType] = [typeof actual, typeof expected]; 5 | const err = new Error( 6 | `Non-equal values: actual=${actual} (type=${actualType}) expected=${expected} (type=${expectedType})${ 7 | message ? `. Message: ${message}` : "" 8 | }` 9 | ); 10 | if (actualType !== expectedType) { 11 | throw err; 12 | } 13 | // Primitive types 14 | if ( 15 | ["string", "number", "bigint", "undefined", "boolean"].includes(actualType) 16 | ) { 17 | if (actual !== expected) { 18 | throw err; 19 | } 20 | return; 21 | } 22 | if (actual instanceof Uint8Array && expected instanceof Uint8Array) { 23 | if (actual.length !== expected.length) { 24 | throw err; 25 | } 26 | for (let i = 0; i < actual.length; i++) { 27 | if (actual[i] !== expected[i]) { 28 | throw err; 29 | } 30 | } 31 | return; 32 | } 33 | if (Array.isArray(actual) && Array.isArray(expected)) { 34 | if (actual.length !== expected.length) { 35 | throw err; 36 | } 37 | for (let i = 0; i < actual.length; i++) { 38 | deepStrictEqual(actual[i], expected[i], message); 39 | } 40 | return; 41 | } 42 | if (actual === null && expected === null) { 43 | return; 44 | } 45 | if (actualType === "object") { 46 | const [actualKeys, expectedKeys] = [ 47 | Object.keys(actual as object), 48 | Object.keys(expected as object), 49 | ]; 50 | deepStrictEqual(actualKeys, expectedKeys, message); 51 | for (const key of actualKeys) { 52 | deepStrictEqual((actual as any)[key], (expected as any)[key], message); 53 | } 54 | return; 55 | } 56 | throw err; 57 | } 58 | 59 | function throws(cb: () => any) { 60 | try { 61 | cb(); 62 | } catch (e) { 63 | return; 64 | } 65 | throw new Error("Missing expected exception."); 66 | } 67 | 68 | async function rejects(cb: () => Promise): Promise { 69 | try { 70 | await cb(); 71 | } catch (e) { 72 | return; 73 | } 74 | throw new Error("Missing expected rejection."); 75 | } 76 | 77 | // Run tests with node assert: 78 | // import { deepStrictEqual, throws } from "assert"; 79 | 80 | export { deepStrictEqual, throws, rejects }; 81 | -------------------------------------------------------------------------------- /test/test-vectors/bn.ts: -------------------------------------------------------------------------------- 1 | import { bn254 } from "ethereum-cryptography/bn"; 2 | import { deepStrictEqual } from "./assert"; 3 | 4 | describe("bn-254", () => { 5 | const PointG1 = bn254.G1.Point; 6 | const PointG2 = bn254.G2.Point; 7 | const { Fp12 } = bn254.fields; 8 | 9 | it("basic", () => { 10 | const g1 = 11 | PointG1.BASE.multiply( 12 | 18097487326282793650237947474982649264364522469319914492172746413872781676n 13 | ); 14 | g1.assertValidity(); 15 | deepStrictEqual(g1.toAffine(), { 16 | x: 0x16f7535f91f50bb2227f483b54850a63b38206f28e0a1a65c83d0c90762442a9n, 17 | y: 0x0b46dd0c40725b6b4a298576629d77b41a545060adb4358eabec939e80691a05n, 18 | }); 19 | const g2 = 20 | PointG2.BASE.multiply( 21 | 20390255904278144451778773028944684152769293537511418234311120800877067946n 22 | ); 23 | g2.assertValidity(); 24 | deepStrictEqual(g2.toAffine(), { 25 | x: { 26 | c0: 0x1ecfd2dff2aad18798b64bdb0c2b50c9d73e6c05619e04cbf5b448fd98726880n, 27 | c1: 0x0e16c8d96362720af0916592be1b839a26f5e6b710f3ede0d8840d9a70eaf97fn, 28 | }, 29 | y: { 30 | c0: 0x2aa778acda9e7d4925c60ad84c12fb3b4f2b9539d5699934b0e6fdd10cc2c0e1n, 31 | c1: 0x1e8f2c1f441fed039bb46d6bfb91236cf7ba240c75080cedbe40e049c46b26ben, 32 | }, 33 | }); 34 | }); 35 | it("pairing", () => { 36 | const g1 = 37 | PointG1.BASE.multiply( 38 | 18097487326282793650237947474982649264364522469319914492172746413872781676n 39 | ); 40 | const g2 = 41 | PointG2.BASE.multiply( 42 | 20390255904278144451778773028944684152769293537511418234311120800877067946n 43 | ); 44 | deepStrictEqual( 45 | bn254.pairing(g1, g2, true), 46 | // @ts-ignore 47 | Fp12.fromBigTwelve([ 48 | 7520311483001723614143802378045727372643587653754534704390832890681688842501n, 49 | 20265650864814324826731498061022229653175757397078253377158157137251452249882n, 50 | 11942254371042183455193243679791334797733902728447312943687767053513298221130n, 51 | 759657045325139626991751731924144629256296901790485373000297868065176843620n, 52 | 16045761475400271697821392803010234478356356448940805056528536884493606035236n, 53 | 4715626119252431692316067698189337228571577552724976915822652894333558784086n, 54 | 14901948363362882981706797068611719724999331551064314004234728272909570402962n, 55 | 11093203747077241090565767003969726435272313921345853819385060670210834379103n, 56 | 17897835398184801202802503586172351707502775171934235751219763553166796820753n, 57 | 1344517825169318161285758374052722008806261739116142912817807653057880346554n, 58 | 11123896897251094532909582772961906225000817992624500900708432321664085800838n, 59 | 17453370448280081813275586256976217762629631160552329276585874071364454854650n, 60 | ]) 61 | ); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/test-vectors/bls.ts: -------------------------------------------------------------------------------- 1 | import { bls12_381 } from "ethereum-cryptography/bls"; 2 | import { deepStrictEqual } from "./assert"; 3 | import { hexToBytes } from "ethereum-cryptography/utils"; 4 | 5 | describe("bls12-381", () => { 6 | const PointG1 = bls12_381.G1.Point; 7 | const PointG2 = bls12_381.G2.Point; 8 | const { Fp, Fp12 } = bls12_381.fields; 9 | 10 | it("basic", () => { 11 | const a = PointG1.fromAffine({ 12 | x: Fp.create( 13 | 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn 14 | ), 15 | y: Fp.create( 16 | 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n 17 | ), 18 | }); 19 | a.assertValidity(); 20 | }); 21 | it("sign", () => { 22 | const [priv, msg] = 23 | "0d1bd9077705325666408124339dca98c0c842b35a90bc3cea8e0c36f2d35583:c43623:94f60dc44a4dbb2505befe346c0c143190fc877ded5e877418f0f890b8ae357a40e8fcc189139aaa509d2b6500f623a5".split( 24 | ":" 25 | ); 26 | const hashed = bls12_381.shortSignatures.hash(hexToBytes(msg)); 27 | const sig = bls12_381.shortSignatures.sign(hashed, hexToBytes(priv)); 28 | const pub = bls12_381.shortSignatures.getPublicKey(hexToBytes(priv)); 29 | const res = bls12_381.shortSignatures.verify(sig, hashed, pub); 30 | deepStrictEqual(res, true, `${priv}-${msg}`); 31 | }); 32 | it("pairing", () => { 33 | const p1 = bls12_381.pairing(PointG1.BASE, PointG2.BASE); 34 | deepStrictEqual( 35 | p1, 36 | // @ts-ignore 37 | Fp12.fromBigTwelve([ 38 | 0x1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6n, 39 | 0x089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170fn, 40 | 0x1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87n, 41 | 0x193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934fn, 42 | 0x01b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5n, 43 | 0x018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b6n, 44 | 0x19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89dn, 45 | 0x06fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1an, 46 | 0x11b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba57n, 47 | 0x03350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a2n, 48 | 0x04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3efn, 49 | 0x0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631n, 50 | ]) 51 | ); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/test-vectors/keccak.ts: -------------------------------------------------------------------------------- 1 | import { keccak224, keccak256, keccak384, keccak512 } from "ethereum-cryptography/keccak"; 2 | import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual } from "./assert"; 4 | 5 | export const keccak224Vectors = [ 6 | { 7 | input: utf8ToBytes(""), 8 | output: "f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd" 9 | }, 10 | { 11 | input: hexToBytes("41"), 12 | output: "ef40b16ff375c834e91412489889f36538748c5454f4b02ba750b65e" 13 | }, 14 | { 15 | input: utf8ToBytes("asd"), 16 | output: "c8cc732c0fa9004eb33d5d833ca22fbd27f21f1c53ef5670bc6779ca" 17 | } 18 | ]; 19 | 20 | export const keccak256Vectors = [ 21 | { 22 | input: utf8ToBytes(""), 23 | output: "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" 24 | }, 25 | { 26 | input: hexToBytes("41"), 27 | output: "03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760" 28 | }, 29 | { 30 | input: utf8ToBytes("asd"), 31 | output: "87c2d362de99f75a4f2755cdaaad2d11bf6cc65dc71356593c445535ff28f43d" 32 | } 33 | ]; 34 | 35 | export const keccak384Vectors = [ 36 | { 37 | input: utf8ToBytes(""), 38 | output: 39 | "2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff" 40 | }, 41 | { 42 | input: hexToBytes("41"), 43 | output: 44 | "5c744cf4b4e3fb8967189e9744261a74f0ef31cdd8850554c737803585ac109039b73c22c50ea866c94debf1061f37a4" 45 | }, 46 | { 47 | input: utf8ToBytes("asd"), 48 | output: 49 | "50efbfa7d5aa41e132c3cfba2bc503d0014eb5bf6d214420851bff0f284bc9a5383a49327600e2efc3ad9db3621decaf" 50 | } 51 | ]; 52 | 53 | export const keccak512Vectors = [ 54 | { 55 | input: utf8ToBytes(""), 56 | output: 57 | "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e" 58 | }, 59 | { 60 | input: hexToBytes("41"), 61 | output: 62 | "421a35a60054e5f383b6137e43d44e998f496748cc77258240ccfaa8730b51f40cf47c1bc09c728a8cd4f096731298d51463f15af89543fed478053346260c38" 63 | }, 64 | { 65 | input: utf8ToBytes("asd"), 66 | output: 67 | "3fb67c8b512d8ce73324db02dda2d19ebfb9d6a923c48fb503be3e0c7c752eb84e4da0818665133a27638dce8e9e8696a51b64b6b247354764609f22b4e65d35" 68 | } 69 | ]; 70 | 71 | const VECTORS: Array<[typeof keccak224, typeof keccak224Vectors]> = [ 72 | [keccak224, keccak224Vectors], 73 | [keccak256, keccak256Vectors], 74 | [keccak384, keccak384Vectors], 75 | [keccak512, keccak512Vectors] 76 | ]; 77 | 78 | describe("keccak", function() { 79 | for (const [hash, vectors] of VECTORS) { 80 | describe(hash.name, function() { 81 | for (const [i, vector] of vectors.entries()) { 82 | it(`Should return the correct hash for test ${i}`, function() { 83 | deepStrictEqual(toHex(hash(vector.input)), vector.output); 84 | }); 85 | } 86 | }); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /test/test-vectors/aes.ts: -------------------------------------------------------------------------------- 1 | import { decrypt, encrypt } from "ethereum-cryptography/aes"; 2 | import { hexToBytes, toHex } from "ethereum-cryptography/utils"; 3 | import { deepStrictEqual, throws } from "./assert"; 4 | // Test vectors taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf 5 | const TEST_VECTORS = [ 6 | { 7 | mode: "aes-128-ctr", 8 | key: "2b7e151628aed2a6abf7158809cf4f3c", 9 | iv: "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 10 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 11 | cypherText: 12 | "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", 13 | pkcs7PaddingEnabled: false, 14 | }, 15 | // CTR uses no padding, so we test that here 16 | { 17 | mode: "aes-128-ctr", 18 | key: "2b7e151628aed2a6abf7158809cf4f3c", 19 | iv: "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 20 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 21 | cypherText: 22 | "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", 23 | pkcs7PaddingEnabled: true, 24 | }, 25 | // Same as the previous one, but with default params 26 | { 27 | mode: undefined, 28 | key: "2b7e151628aed2a6abf7158809cf4f3c", 29 | iv: "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 30 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 31 | cypherText: 32 | "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", 33 | pkcs7PaddingEnabled: undefined, 34 | }, 35 | // CBC uses padding, but the NIST test vectors don't 36 | { 37 | mode: "aes-128-cbc", 38 | key: "2b7e151628aed2a6abf7158809cf4f3c", 39 | iv: "000102030405060708090a0b0c0d0e0f", 40 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 41 | cypherText: 42 | "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7", 43 | pkcs7PaddingEnabled: false, 44 | }, 45 | // We test that the padding is in fact PKCS#7 by first entrypting with its 46 | // corresponding padding adding manually, and then with automatic padding 47 | { 48 | mode: "aes-128-cbc", 49 | key: "2b7e151628aed2a6abf7158809cf4f3c", 50 | iv: "000102030405060708090a0b0c0d0e0f", 51 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c371010101010101010101010101010101010", 52 | cypherText: 53 | "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a78cb82807230e1321d3fae00d18cc2012", 54 | pkcs7PaddingEnabled: false, 55 | }, 56 | { 57 | mode: "aes-128-cbc", 58 | key: "2b7e151628aed2a6abf7158809cf4f3c", 59 | iv: "000102030405060708090a0b0c0d0e0f", 60 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 61 | cypherText: 62 | "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a78cb82807230e1321d3fae00d18cc2012", 63 | pkcs7PaddingEnabled: true, 64 | }, 65 | // Same applies for aes-256-cbc 66 | { 67 | mode: "aes-256-cbc", 68 | key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 69 | iv: "000102030405060708090a0b0c0d0e0f", 70 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 71 | cypherText: 72 | "f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b", 73 | pkcs7PaddingEnabled: false, 74 | }, 75 | { 76 | mode: "aes-256-cbc", 77 | key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 78 | iv: "000102030405060708090a0b0c0d0e0f", 79 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c371010101010101010101010101010101010", 80 | cypherText: 81 | "f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644", 82 | pkcs7PaddingEnabled: false, 83 | }, 84 | { 85 | mode: "aes-256-cbc", 86 | key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 87 | iv: "000102030405060708090a0b0c0d0e0f", 88 | msg: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", 89 | cypherText: 90 | "f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644", 91 | pkcs7PaddingEnabled: true, 92 | }, 93 | ]; 94 | 95 | describe("aes", () => { 96 | for (const [i, vector] of TEST_VECTORS.entries()) { 97 | it(`Should encrypt the test ${i} correctly`, () => { 98 | const encrypted = encrypt( 99 | hexToBytes(vector.msg), 100 | hexToBytes(vector.key), 101 | hexToBytes(vector.iv), 102 | vector.mode, 103 | vector.pkcs7PaddingEnabled 104 | ); 105 | 106 | deepStrictEqual(toHex(encrypted), vector.cypherText); 107 | }); 108 | 109 | it(`Should decrypt the test ${i} correctly`, () => { 110 | const decrypted = decrypt( 111 | hexToBytes(vector.cypherText), 112 | hexToBytes(vector.key), 113 | hexToBytes(vector.iv), 114 | vector.mode, 115 | vector.pkcs7PaddingEnabled 116 | ); 117 | 118 | deepStrictEqual(toHex(decrypted), vector.msg); 119 | }); 120 | } 121 | 122 | it("Should throw when not padding automatically and the message isn't the right size", () => { 123 | throws(() => 124 | encrypt( 125 | hexToBytes("abcd"), 126 | hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), 127 | hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), 128 | "aes-128-cbc", 129 | false 130 | ) 131 | ); 132 | }); 133 | 134 | it("Should throw when trying to use non-aes modes", () => { 135 | throws(() => 136 | encrypt( 137 | hexToBytes("abcd"), 138 | hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), 139 | hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), 140 | "asd-128-cbc", 141 | false 142 | ) 143 | ); 144 | }); 145 | 146 | it("aes-ctr bug (browser/node result mismatch)", () => { 147 | // NOTE: full 0xff iv causes difference on counter overflow in CTR mode 148 | const iv = "ffffffffffffffffffffffffffffffff"; 149 | const vectors = [ 150 | { 151 | msg: "efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378", 152 | key: "ccc0b35ea59c51a1e45af00502966237", 153 | iv, 154 | mode: "aes-128-ctr", 155 | result: 156 | "15e356c67d266d3ca85cff4f6d92d11720aae32cdd28d5d9885836dacb1d213b", 157 | }, 158 | { 159 | msg: "efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378", 160 | key: "ccc0b35ea59c51a1e45af00502966237ccc0b35ea59c51a1e45af00502966237", 161 | iv, 162 | mode: "aes-256-ctr", 163 | result: 164 | "010bb6dc10ea201bf2d586de4741309373c07b6ddf30ad8502adf4dd0bda2d23", 165 | }, 166 | { 167 | msg: "efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378", 168 | key: "ccc0b35ea59c51a1e45af00502966237", 169 | iv, 170 | mode: "aes-128-ctr", 171 | result: 172 | "15e356c67d266d3ca85cff4f6d92d11720aae32cdd28d5d9885836dacb1d213b55f347e68f72acf46234d495f579fb45f9dcfc7dc688a9174f566d137ffc626c", 173 | }, 174 | { 175 | msg: "efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378", 176 | key: "ccc0b35ea59c51a1e45af00502966237ccc0b35ea59c51a1e45af00502966237", 177 | iv, 178 | mode: "aes-256-ctr", 179 | result: 180 | "010bb6dc10ea201bf2d586de4741309373c07b6ddf30ad8502adf4dd0bda2d23c436b35e5dfa0a0088dcb6ae7328f1ec66212099222ee1c18983b58513cf5f4c", 181 | }, 182 | ]; 183 | for (const v of vectors) { 184 | const msg = hexToBytes(v.msg); 185 | const key = hexToBytes(v.key); 186 | const iv = hexToBytes(v.iv); 187 | const res = encrypt(msg, key, iv, v.mode); 188 | deepStrictEqual(toHex(res), v.result); 189 | const clearText = decrypt(res, key, iv, v.mode); 190 | deepStrictEqual(clearText, msg); 191 | } 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /test/test-vectors/secp256k1-compat.ts: -------------------------------------------------------------------------------- 1 | import * as secp from "ethereum-cryptography/secp256k1-compat"; 2 | import { sha256 } from "ethereum-cryptography/sha256"; 3 | import { concatBytes, hexToBytes, toHex } from "ethereum-cryptography/utils"; 4 | import { deepStrictEqual, throws } from "./assert"; 5 | import { VECTORS } from "./secp256k1_lib_vectors"; 6 | 7 | describe("secp256k1", function () { 8 | it("should create valid private keys", async function () { 9 | const asyncPk = await secp.createPrivateKey(); 10 | const syncPk = secp.createPrivateKeySync(); 11 | 12 | deepStrictEqual(secp.privateKeyVerify(asyncPk), true); 13 | deepStrictEqual(secp.privateKeyVerify(syncPk), true); 14 | }); 15 | 16 | it("Should sign correctly", function () { 17 | // This test has been adapted from ethereumjs-util 18 | // https://github.com/ethereumjs/ethereumjs-util/blob/3b1085059194b02354177d334f89cd82a5187883/test/index.js#L531 19 | const msgHash = hexToBytes( 20 | "82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28" 21 | ); 22 | const privateKey = hexToBytes( 23 | "3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1" 24 | ); 25 | const signature = secp.ecdsaSign(msgHash, privateKey); 26 | const sig = { 27 | r: signature.signature.slice(0, 32), 28 | s: signature.signature.slice(32, 64), 29 | v: signature.recid, 30 | }; 31 | deepStrictEqual( 32 | secp.signatureImport(secp.signatureExport(signature.signature)), 33 | signature.signature 34 | ); 35 | deepStrictEqual( 36 | sig.r, 37 | hexToBytes( 38 | "99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9" 39 | ) 40 | ); 41 | deepStrictEqual( 42 | sig.s, 43 | hexToBytes( 44 | "129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66" 45 | ) 46 | ); 47 | deepStrictEqual(sig.v, 0); 48 | }); 49 | 50 | it("Should recover signatures correctly", function () { 51 | const echash = hexToBytes( 52 | "82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28" 53 | ); 54 | const recid = 0; 55 | const r = hexToBytes( 56 | "99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9" 57 | ); 58 | const s = hexToBytes( 59 | "129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66" 60 | ); 61 | const expected = hexToBytes( 62 | "b4ac68eff3a82d86db5f0489d66f91707e99943bf796ae6a2dcb2205c9522fa7915428b5ac3d3b9291e62142e7246d85ad54504fabbdb2bae5795161f8ddf259" 63 | ); 64 | const signature = concatBytes(r, s); 65 | const senderPubKey = secp.ecdsaRecover(signature, recid, echash); 66 | const recovered = secp.publicKeyConvert(senderPubKey, false).slice(1); 67 | deepStrictEqual(recovered, expected); 68 | deepStrictEqual( 69 | secp.signatureImport(secp.signatureExport(signature)), 70 | signature 71 | ); 72 | }); 73 | it("ecdh with hashfn", () => { 74 | /* GENERATED BY: 75 | const secp256k1 = require('secp256k1'); 76 | const crypto = require('crypto'); 77 | const privateKey = hexToBytes('3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1'); 78 | const publicKey = hexToBytes( 79 | '04b4ac68eff3a82d86db5f0489d66f91707e99943bf796ae6a2dcb2205c9522fa7915428b5ac3d3b9291e62142e7246d85ad54504fabbdb2bae5795161f8ddf259' 80 | ); 81 | const output = new Uint8Array(32); 82 | const output2 = new Uint8Array(33); 83 | const xbuf = new Uint8Array(32); 84 | const ybuf = new Uint8Array(32); 85 | const hashfn = (x, y, data) => 86 | crypto.createHash('sha256').update(x).update(y).update(data).digest(); 87 | 88 | const hashfn2 = (x, y, data) => new Uint8Array(33); 89 | const data = new Uint8Array([1, 2, 3, 4, 5]); 90 | // 9461b13c2191424a2adafc45f8043504454dc37199b73a999bd12463b6904eeb 91 | console.log('1', toHex(secp256k1.ecdh(publicKey, privateKey, {}))); 92 | // 9461b13c2191424a2adafc45f8043504454dc37199b73a999bd12463b6904eeb 93 | console.log('2', toHex(secp256k1.ecdh(publicKey, privateKey, { data }))); 94 | // Error: Expected output to be an Uint8Array 95 | // console.log('3', toHex(secp256k1.ecdh(publicKey, privateKey, { data, hashfn }))); 96 | // Error: Scalar was invalid (zero or overflow) 97 | // console.log('4', toHex(secp256k1.ecdh(publicKey, privateKey, { data, hashfn }, output2))); 98 | // 6945269332a7e162866138faaffc09636e78ca742edbb555a2c274f7446cf6fb 99 | console.log('4', toHex(secp256k1.ecdh(publicKey, privateKey, { data, hashfn }, output))); 100 | // 6945269332a7e162866138faaffc09636e78ca742edbb555a2c274f7446cf6fb 101 | console.log('4.1', toHex(output)); 102 | // 6945269332a7e162866138faaffc09636e78ca742edbb555a2c274f7446cf6fb 103 | console.log( 104 | '5', 105 | toHex(secp256k1.ecdh(publicKey, privateKey, { data, hashfn, xbuf, ybuf }, output)) 106 | ); 107 | // 6945269332a7e162866138faaffc09636e78ca742edbb555a2c274f7446cf6fb 108 | console.log('5.1', toHex(output)); 109 | // 8b2983e19282e35890fa04abc0ca4a1981723c50413db91a1387c43caae4888f 110 | console.log('5.2', toHex(xbuf)); 111 | // 6a4ee6fcc9e1403ab90236594278dfe158cd79608f4434f27506c5dd39776f97 112 | console.log('5.3', toHex(ybuf)); 113 | // Error: Scalar was invalid (zero or overflow) 114 | // console.log( 115 | // '6', 116 | // toHex(secp256k1.ecdh(publicKey, privateKey, { data, hashfn: hashfn2, xbuf, ybuf }, output)) 117 | // ); 118 | */ 119 | const privateKey = hexToBytes( 120 | "3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1" 121 | ); 122 | const publicKey = hexToBytes( 123 | "04b4ac68eff3a82d86db5f0489d66f91707e99943bf796ae6a2dcb2205c9522fa7915428b5ac3d3b9291e62142e7246d85ad54504fabbdb2bae5795161f8ddf259" 124 | ); 125 | const output = new Uint8Array(32); 126 | const output2 = new Uint8Array(33); 127 | const xbuf = new Uint8Array(32); 128 | const ybuf = new Uint8Array(32); 129 | const hashfn = (x: Uint8Array, y: Uint8Array, data: Uint8Array) => 130 | sha256(concatBytes(x, y, data)); 131 | const hashfn2 = (x: Uint8Array, y: Uint8Array, data: Uint8Array) => 132 | new Uint8Array(33); 133 | const d = new Uint8Array([1, 2, 3, 4, 5]); 134 | 135 | const exp1 = 136 | "9461b13c2191424a2adafc45f8043504454dc37199b73a999bd12463b6904eeb"; 137 | const exp2 = 138 | "6945269332a7e162866138faaffc09636e78ca742edbb555a2c274f7446cf6fb"; 139 | deepStrictEqual(toHex(secp.ecdh(publicKey, privateKey, {})), exp1); 140 | deepStrictEqual(toHex(secp.ecdh(publicKey, privateKey, { data: d })), exp1); 141 | throws(() => secp.ecdh(publicKey, privateKey, { data: d, hashfn })); 142 | throws(() => 143 | secp.ecdh(publicKey, privateKey, { data: d, hashfn }, output2) 144 | ); 145 | deepStrictEqual( 146 | toHex(secp.ecdh(publicKey, privateKey, { data: d, hashfn }, output)), 147 | exp2 148 | ); 149 | deepStrictEqual(toHex(output), exp2); 150 | deepStrictEqual( 151 | toHex( 152 | secp.ecdh( 153 | publicKey, 154 | privateKey, 155 | { data: d, hashfn, xbuf, ybuf }, 156 | output 157 | ) 158 | ), 159 | exp2 160 | ); 161 | deepStrictEqual(toHex(output), exp2); 162 | deepStrictEqual( 163 | toHex(xbuf), 164 | "8b2983e19282e35890fa04abc0ca4a1981723c50413db91a1387c43caae4888f" 165 | ); 166 | deepStrictEqual( 167 | toHex(ybuf), 168 | "6a4ee6fcc9e1403ab90236594278dfe158cd79608f4434f27506c5dd39776f97" 169 | ); 170 | throws(() => 171 | secp.ecdh( 172 | publicKey, 173 | privateKey, 174 | { data: d, hashfn: hashfn2, xbuf, ybuf }, 175 | output 176 | ) 177 | ); 178 | }); 179 | 180 | describe("Test vectors against library", () => { 181 | const unserialize = (obj: any): any => { 182 | if (Array.isArray(obj)) { 183 | return obj.map((i) => unserialize(i)); 184 | } 185 | if (typeof obj === "object" && obj !== null) { 186 | if (obj.__BigInt__) { 187 | return BigInt(`0x${obj.__BigInt__}`); 188 | } else if (obj.__Buffer__) { 189 | return hexToBytes(obj.__Buffer__); 190 | } else if (obj.__Bytes__) { 191 | return hexToBytes(obj.__Bytes__); 192 | } 193 | const res: Record = {}; 194 | for (const key of Object.keys(obj)) { 195 | res[key] = unserialize(obj[key]); 196 | } 197 | return res; 198 | } 199 | return obj; 200 | }; 201 | const genTest = (vectors: any, name: string, fn: any) => { 202 | for (let i = 0; i < vectors[name].length; i++) { 203 | const vector = vectors[name][i]; 204 | it(`${name} (${i})${vector.err ? ` ERR: ${vector.err}` : ""}`, () => { 205 | if (vector.err) { 206 | throws(() => fn(...vector.args)); 207 | } else { 208 | deepStrictEqual(fn(...vector.args), vector.res); 209 | } 210 | deepStrictEqual(vector.args, vector.argsAfter); 211 | }); 212 | } 213 | }; 214 | const genTests = ( 215 | vectors: Record, 216 | library: Record 217 | ) => { 218 | for (const k of Object.keys(vectors)) { 219 | const path = k.split("."); 220 | let cur = library; 221 | for (const i of path) { 222 | if (!cur || typeof cur !== "object") { 223 | throw new Error(`genTests: not object: ${k}, ${i}, ${typeof cur}`); 224 | } 225 | cur = cur[i]; 226 | } 227 | if (typeof cur !== "function") { 228 | throw new Error(`genTests: not function ${k}`); 229 | } 230 | genTest(vectors, k, cur); 231 | } 232 | }; 233 | genTests(unserialize(VECTORS), { secp256k1: secp }); 234 | }); 235 | }); 236 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethereum-cryptography", 3 | "version": "3.2.0", 4 | "description": "All the cryptographic primitives used in Ethereum.", 5 | "repository": "https://github.com/ethereum/js-ethereum-cryptography", 6 | "license": "MIT", 7 | "main": "./index.js", 8 | "engines": { 9 | "node": "^14.21.3 || >=16", 10 | "npm": ">=9" 11 | }, 12 | "files": [ 13 | "bip39/*.js", 14 | "bip39/*.d.ts", 15 | "bip39/wordlists/*.js", 16 | "bip39/wordlists/*.d.ts", 17 | "*.js", 18 | "*.d.ts", 19 | "esm" 20 | ], 21 | "dependencies": { 22 | "@noble/ciphers": "2.1.1", 23 | "@noble/curves": "2.0.1", 24 | "@noble/hashes": "2.0.1", 25 | "@scure/bip32": "2.0.1", 26 | "@scure/bip39": "2.0.1" 27 | }, 28 | "exports": { 29 | ".": { 30 | "import": "./esm/index.js", 31 | "require": "./index.js" 32 | }, 33 | "./aes": { 34 | "import": "./esm/aes.js", 35 | "require": "./aes.js" 36 | }, 37 | "./bip39": { 38 | "import": "./esm/bip39/index.js", 39 | "require": "./bip39/index.js" 40 | }, 41 | "./blake2b": { 42 | "import": "./esm/blake2b.js", 43 | "require": "./blake2b.js" 44 | }, 45 | "./bls": { 46 | "import": "./esm/bls.js", 47 | "require": "./bls.js" 48 | }, 49 | "./bn": { 50 | "import": "./esm/bn.js", 51 | "require": "./bn.js" 52 | }, 53 | "./hdkey": { 54 | "import": "./esm/hdkey.js", 55 | "require": "./hdkey.js" 56 | }, 57 | "./index": { 58 | "import": "./esm/index.js", 59 | "require": "./index.js" 60 | }, 61 | "./math": { 62 | "import": "./esm/math.js", 63 | "require": "./math.js" 64 | }, 65 | "./keccak": { 66 | "import": "./esm/keccak.js", 67 | "require": "./keccak.js" 68 | }, 69 | "./pbkdf2": { 70 | "import": "./esm/pbkdf2.js", 71 | "require": "./pbkdf2.js" 72 | }, 73 | "./random": { 74 | "import": "./esm/random.js", 75 | "require": "./random.js" 76 | }, 77 | "./ripemd160": { 78 | "import": "./esm/ripemd160.js", 79 | "require": "./ripemd160.js" 80 | }, 81 | "./scrypt": { 82 | "import": "./esm/scrypt.js", 83 | "require": "./scrypt.js" 84 | }, 85 | "./secp256k1-compat": { 86 | "import": "./esm/secp256k1-compat.js", 87 | "require": "./secp256k1-compat.js" 88 | }, 89 | "./secp256k1": { 90 | "import": "./esm/secp256k1.js", 91 | "require": "./secp256k1.js" 92 | }, 93 | "./sha256": { 94 | "import": "./esm/sha256.js", 95 | "require": "./sha256.js" 96 | }, 97 | "./sha512": { 98 | "import": "./esm/sha512.js", 99 | "require": "./sha512.js" 100 | }, 101 | "./utils": { 102 | "import": "./esm/utils.js", 103 | "require": "./utils.js" 104 | }, 105 | "./bip39/index": { 106 | "import": "./esm/bip39/index.js", 107 | "require": "./bip39/index.js" 108 | }, 109 | "./bip39/wordlists/czech": { 110 | "import": "./esm/bip39/wordlists/czech.js", 111 | "require": "./bip39/wordlists/czech.js" 112 | }, 113 | "./bip39/wordlists/english": { 114 | "import": "./esm/bip39/wordlists/english.js", 115 | "require": "./bip39/wordlists/english.js" 116 | }, 117 | "./bip39/wordlists/french": { 118 | "import": "./esm/bip39/wordlists/french.js", 119 | "require": "./bip39/wordlists/french.js" 120 | }, 121 | "./bip39/wordlists/italian": { 122 | "import": "./esm/bip39/wordlists/italian.js", 123 | "require": "./bip39/wordlists/italian.js" 124 | }, 125 | "./bip39/wordlists/japanese": { 126 | "import": "./esm/bip39/wordlists/japanese.js", 127 | "require": "./bip39/wordlists/japanese.js" 128 | }, 129 | "./bip39/wordlists/korean": { 130 | "import": "./esm/bip39/wordlists/korean.js", 131 | "require": "./bip39/wordlists/korean.js" 132 | }, 133 | "./bip39/wordlists/portuguese": { 134 | "import": "./esm/bip39/wordlists/portuguese.js", 135 | "require": "./bip39/wordlists/portuguese.js" 136 | }, 137 | "./bip39/wordlists/simplified-chinese": { 138 | "import": "./esm/bip39/wordlists/simplified-chinese.js", 139 | "require": "./bip39/wordlists/simplified-chinese.js" 140 | }, 141 | "./bip39/wordlists/spanish": { 142 | "import": "./esm/bip39/wordlists/spanish.js", 143 | "require": "./bip39/wordlists/spanish.js" 144 | }, 145 | "./bip39/wordlists/traditional-chinese": { 146 | "import": "./esm/bip39/wordlists/traditional-chinese.js", 147 | "require": "./bip39/wordlists/traditional-chinese.js" 148 | }, 149 | "./aes.js": { 150 | "import": "./esm/aes.js", 151 | "require": "./aes.js" 152 | }, 153 | "./bip39.js": { 154 | "import": "./esm/bip39/index.js", 155 | "require": "./bip39/index.js" 156 | }, 157 | "./blake2b.js": { 158 | "import": "./esm/blake2b.js", 159 | "require": "./blake2b.js" 160 | }, 161 | "./bls.js": { 162 | "import": "./esm/bls.js", 163 | "require": "./bls.js" 164 | }, 165 | "./bn.js": { 166 | "import": "./esm/bn.js", 167 | "require": "./bn.js" 168 | }, 169 | "./hdkey.js": { 170 | "import": "./esm/hdkey.js", 171 | "require": "./hdkey.js" 172 | }, 173 | "./index.js": { 174 | "import": "./esm/index.js", 175 | "require": "./index.js" 176 | }, 177 | "./math.js": { 178 | "import": "./esm/math.js", 179 | "require": "./math.js" 180 | }, 181 | "./keccak.js": { 182 | "import": "./esm/keccak.js", 183 | "require": "./keccak.js" 184 | }, 185 | "./pbkdf2.js": { 186 | "import": "./esm/pbkdf2.js", 187 | "require": "./pbkdf2.js" 188 | }, 189 | "./random.js": { 190 | "import": "./esm/random.js", 191 | "require": "./random.js" 192 | }, 193 | "./ripemd160.js": { 194 | "import": "./esm/ripemd160.js", 195 | "require": "./ripemd160.js" 196 | }, 197 | "./scrypt.js": { 198 | "import": "./esm/scrypt.js", 199 | "require": "./scrypt.js" 200 | }, 201 | "./secp256k1-compat.js": { 202 | "import": "./esm/secp256k1-compat.js", 203 | "require": "./secp256k1-compat.js" 204 | }, 205 | "./secp256k1.js": { 206 | "import": "./esm/secp256k1.js", 207 | "require": "./secp256k1.js" 208 | }, 209 | "./sha256.js": { 210 | "import": "./esm/sha256.js", 211 | "require": "./sha256.js" 212 | }, 213 | "./sha512.js": { 214 | "import": "./esm/sha512.js", 215 | "require": "./sha512.js" 216 | }, 217 | "./utils.js": { 218 | "import": "./esm/utils.js", 219 | "require": "./utils.js" 220 | }, 221 | "./bip39/index.js": { 222 | "import": "./esm/bip39/index.js", 223 | "require": "./bip39/index.js" 224 | }, 225 | "./bip39/wordlists/czech.js": { 226 | "import": "./esm/bip39/wordlists/czech.js", 227 | "require": "./bip39/wordlists/czech.js" 228 | }, 229 | "./bip39/wordlists/english.js": { 230 | "import": "./esm/bip39/wordlists/english.js", 231 | "require": "./bip39/wordlists/english.js" 232 | }, 233 | "./bip39/wordlists/french.js": { 234 | "import": "./esm/bip39/wordlists/french.js", 235 | "require": "./bip39/wordlists/french.js" 236 | }, 237 | "./bip39/wordlists/italian.js": { 238 | "import": "./esm/bip39/wordlists/italian.js", 239 | "require": "./bip39/wordlists/italian.js" 240 | }, 241 | "./bip39/wordlists/japanese.js": { 242 | "import": "./esm/bip39/wordlists/japanese.js", 243 | "require": "./bip39/wordlists/japanese.js" 244 | }, 245 | "./bip39/wordlists/korean.js": { 246 | "import": "./esm/bip39/wordlists/korean.js", 247 | "require": "./bip39/wordlists/korean.js" 248 | }, 249 | "./bip39/wordlists/simplified-chinese.js": { 250 | "import": "./esm/bip39/wordlists/simplified-chinese.js", 251 | "require": "./bip39/wordlists/simplified-chinese.js" 252 | }, 253 | "./bip39/wordlists/spanish.js": { 254 | "import": "./esm/bip39/wordlists/spanish.js", 255 | "require": "./bip39/wordlists/spanish.js" 256 | }, 257 | "./bip39/wordlists/traditional-chinese.js": { 258 | "import": "./esm/bip39/wordlists/traditional-chinese.js", 259 | "require": "./bip39/wordlists/traditional-chinese.js" 260 | } 261 | }, 262 | "browser": { 263 | "crypto": false 264 | }, 265 | "sideEffects": false, 266 | "scripts": { 267 | "prepare": "npm run build", 268 | "build": "npm-run-all build:tsc", 269 | "build:tsc": "tsc --project tsconfig.prod.json && tsc --project tsconfig.prod.esm.json", 270 | "test": "npm-run-all test:node", 271 | "test:node": "cd test && npm install && cd .. && NODE_OPTIONS='--loader ts-node/esm' mocha", 272 | "clean": "rm -rf test/test-builds bip39 *.js *.js.map *.d.ts *.d.ts.map src/**/*.js", 273 | "lint": "eslint", 274 | "lint:fix": "eslint --fix", 275 | "browser-tests": "npm-run-all browser-tests:build browser-tests:test", 276 | "browser-tests:build": "bash -x ./scripts/build-browser-tests.sh", 277 | "browser-tests:test": "npm-run-all browser-tests:test-parcel browser-tests:test-webpack browser-tests:test-rollup", 278 | "browser-tests:test-parcel": "karma start --single-run --browsers ChromeHeadless test/karma.parcel.conf.js", 279 | "browser-tests:test-browserify": "karma start --single-run --browsers ChromeHeadless test/karma.browserify.conf.js", 280 | "browser-tests:test-webpack": "karma start --single-run --browsers ChromeHeadless test/karma.webpack.conf.js", 281 | "browser-tests:test-rollup": "karma start --single-run --browsers ChromeHeadless test/karma.rollup.conf.js" 282 | }, 283 | "devDependencies": { 284 | "@types/estree": "1.0.0", 285 | "@types/mocha": "10.0.7", 286 | "@types/node": "22.14.1", 287 | "@typescript-eslint/eslint-plugin": "5.30.6", 288 | "@typescript-eslint/parser": "5.30.6", 289 | "eslint": "8.38.0", 290 | "eslint-plugin-prettier": "4.2.1", 291 | "karma": "6.4.4", 292 | "karma-chrome-launcher": "3.1.1", 293 | "karma-mocha": "2.0.1", 294 | "karma-mocha-reporter": "2.2.5", 295 | "mocha": "10.7.3", 296 | "npm-run-all": "4.1.5", 297 | "prettier": "2.7.1", 298 | "ts-node": "10.9.2", 299 | "typescript": "5.8.3" 300 | }, 301 | "packageManager": "npm@9.9.4", 302 | "keywords": [ 303 | "ethereum", 304 | "cryptography", 305 | "digital signature", 306 | "hash", 307 | "encryption", 308 | "prng", 309 | "keccak", 310 | "scrypt", 311 | "pbkdf2", 312 | "sha-256", 313 | "ripemd-160", 314 | "blake2b", 315 | "aes", 316 | "advanced encryption standard", 317 | "secp256k1", 318 | "ecdsa", 319 | "bip32", 320 | "hierarchical deterministic keys", 321 | "hdwallet", 322 | "hdkeys" 323 | ], 324 | "contributors": [ 325 | { 326 | "name": "Patricio Palladino", 327 | "email": "patricio@nomiclabs.io" 328 | }, 329 | { 330 | "name": "Paul Miller", 331 | "url": "https://paulmillr.com" 332 | } 333 | ], 334 | "targets": { 335 | "parcel_tests": { 336 | "context": "browser" 337 | } 338 | } 339 | } -------------------------------------------------------------------------------- /src/secp256k1-compat.ts: -------------------------------------------------------------------------------- 1 | import { mod } from "@noble/curves/abstract/modular.js"; 2 | import { sha256 } from "@noble/hashes/sha2.js"; 3 | import { secp256k1 } from "./secp256k1.js"; 4 | import { assertBool, assertBytes, hexToBytes, toHex } from "./utils.js"; 5 | 6 | // Use `secp256k1` module directly. 7 | // This is a legacy compatibility layer for the npm package `secp256k1` via noble-secp256k1 8 | 9 | const Point = secp256k1.Point; 10 | 11 | function hexToNumber(hex: string): bigint { 12 | if (typeof hex !== "string") { 13 | throw new TypeError("hexToNumber: expected string, got " + typeof hex); 14 | } 15 | return BigInt(`0x${hex}`); 16 | } 17 | 18 | // Copy-paste from secp256k1, maybe export it? 19 | const bytesToNumber = (bytes: Uint8Array) => hexToNumber(toHex(bytes)); 20 | const numberToHex = (num: number | bigint) => 21 | num.toString(16).padStart(64, "0"); 22 | const numberToBytes = (num: number | bigint) => hexToBytes(numberToHex(num)); 23 | 24 | const ORDER = Point.Fn.ORDER; 25 | 26 | type Output = Uint8Array | ((len: number) => Uint8Array); 27 | interface Signature { 28 | signature: Uint8Array; 29 | recid: number; 30 | } 31 | 32 | function output( 33 | out: Output = (len: number): Uint8Array => new Uint8Array(len), 34 | length: number, 35 | value?: Uint8Array 36 | ): Uint8Array { 37 | if (typeof out === "function") { 38 | out = out(length); 39 | } 40 | assertBytes(out, length); 41 | if (value) { 42 | out.set(value); 43 | } 44 | return out; 45 | } 46 | 47 | function getSignature(signature: Uint8Array) { 48 | assertBytes(signature, 64); 49 | return secp256k1.Signature.fromBytes(signature, "compact"); 50 | } 51 | 52 | export function createPrivateKeySync(): Uint8Array { 53 | return secp256k1.utils.randomSecretKey(); 54 | } 55 | 56 | export async function createPrivateKey(): Promise { 57 | return createPrivateKeySync(); 58 | } 59 | 60 | export function privateKeyVerify(privateKey: Uint8Array): boolean { 61 | assertBytes(privateKey, 32); 62 | return secp256k1.utils.isValidSecretKey(privateKey); 63 | } 64 | 65 | export function publicKeyCreate( 66 | privateKey: Uint8Array, 67 | compressed = true, 68 | out?: Output 69 | ): Uint8Array { 70 | assertBytes(privateKey, 32); 71 | assertBool(compressed, "compressed"); 72 | const res = secp256k1.getPublicKey(privateKey, compressed); 73 | return output(out, compressed ? 33 : 65, res); 74 | } 75 | 76 | export function publicKeyVerify(publicKey: Uint8Array): boolean { 77 | assertBytes(publicKey); 78 | if (![33, 65].includes(publicKey.length)) 79 | throw new Error( 80 | "Expected public key to be an Uint8Array with length [33, 65]" 81 | ); 82 | try { 83 | Point.fromBytes(publicKey); 84 | return true; 85 | } catch (e) { 86 | return false; 87 | } 88 | } 89 | 90 | export function publicKeyConvert( 91 | publicKey: Uint8Array, 92 | compressed = true, 93 | out?: Output 94 | ): Uint8Array { 95 | assertBytes(publicKey); 96 | if (![33, 65].includes(publicKey.length)) 97 | throw new Error( 98 | "Expected public key to be an Uint8Array with length [33, 65]" 99 | ); 100 | assertBool(compressed, "compressed"); 101 | const res = Point.fromBytes(publicKey).toBytes(compressed); 102 | return output(out, compressed ? 33 : 65, res); 103 | } 104 | 105 | export function ecdsaSign( 106 | msgHash: Uint8Array, 107 | privateKey: Uint8Array, 108 | options = { noncefn: undefined, data: undefined }, 109 | out?: Output 110 | ): Signature { 111 | assertBytes(msgHash, 32); 112 | assertBytes(privateKey, 32); 113 | if (typeof options !== "object" || options === null) { 114 | throw new TypeError("secp256k1.ecdsaSign: options should be object"); 115 | } 116 | // noble-secp256k1 uses hmac instead of hmac-drbg here 117 | if ( 118 | options && 119 | (options.noncefn !== undefined || options.data !== undefined) 120 | ) { 121 | throw new Error("Secp256k1: noncefn && data is unsupported"); 122 | } 123 | const sig = secp256k1.sign(msgHash, privateKey, { 124 | prehash: false, 125 | format: "recovered", 126 | }); 127 | const sigObj = secp256k1.Signature.fromBytes(sig, "recovered"); 128 | const recid = sigObj.recovery!; 129 | return { signature: output(out, 64, sigObj.toBytes("compact")), recid }; 130 | } 131 | 132 | export function ecdsaRecover( 133 | signature: Uint8Array, 134 | recid: number, 135 | msgHash: Uint8Array, 136 | compressed = true, 137 | out?: Output 138 | ): Uint8Array { 139 | assertBytes(msgHash, 32); 140 | assertBool(compressed, "compressed"); 141 | const sign = getSignature(signature); 142 | const signBytes = sign.addRecoveryBit(recid).toBytes("recovered"); 143 | const recovered = secp256k1.recoverPublicKey(signBytes, msgHash, { 144 | prehash: false, 145 | }); 146 | return publicKeyConvert(recovered, compressed, out); 147 | } 148 | 149 | export function ecdsaVerify( 150 | signature: Uint8Array, 151 | msgHash: Uint8Array, 152 | publicKey: Uint8Array 153 | ) { 154 | assertBytes(signature, 64); 155 | assertBytes(msgHash, 32); 156 | assertBytes(publicKey); 157 | if (![33, 65].includes(publicKey.length)) 158 | throw new Error( 159 | "Expected public key to be an Uint8Array with length [33, 65]" 160 | ); 161 | assertBytes(signature, 64); 162 | const r = bytesToNumber(signature.slice(0, 32)); 163 | const s = bytesToNumber(signature.slice(32, 64)); 164 | if (r >= ORDER || s >= ORDER) { 165 | throw new Error("Cannot parse signature"); 166 | } 167 | const pub = Point.fromBytes(publicKey); // can throw error 168 | pub; // typescript 169 | let sig; 170 | try { 171 | sig = getSignature(signature); 172 | } catch (error) { 173 | return false; 174 | } 175 | return secp256k1.verify(sig.toBytes(), msgHash, publicKey, { 176 | prehash: false, 177 | }); 178 | } 179 | 180 | export function privateKeyTweakAdd( 181 | privateKey: Uint8Array, 182 | tweak: Uint8Array 183 | ): Uint8Array { 184 | assertBytes(privateKey, 32); 185 | assertBytes(tweak, 32); 186 | let t = bytesToNumber(tweak); 187 | if (t === 0n) { 188 | throw new Error("Tweak must not be zero"); 189 | } 190 | if (t >= ORDER) { 191 | throw new Error("Tweak bigger than curve order"); 192 | } 193 | t += bytesToNumber(privateKey); 194 | if (t >= ORDER) { 195 | t -= ORDER; 196 | } 197 | if (t === 0n) { 198 | throw new Error( 199 | "The tweak was out of range or the resulted private key is invalid" 200 | ); 201 | } 202 | privateKey.set(hexToBytes(numberToHex(t))); 203 | return privateKey; 204 | } 205 | 206 | export function privateKeyNegate(privateKey: Uint8Array): Uint8Array { 207 | assertBytes(privateKey, 32); 208 | const bn = mod(-bytesToNumber(privateKey), ORDER); 209 | privateKey.set(hexToBytes(numberToHex(bn))); 210 | return privateKey; 211 | } 212 | 213 | export function publicKeyNegate( 214 | publicKey: Uint8Array, 215 | compressed = true, 216 | out?: Output 217 | ): Uint8Array { 218 | assertBytes(publicKey); 219 | assertBool(compressed, "compressed"); 220 | const point = Point.fromBytes(publicKey).negate(); 221 | return output(out, compressed ? 33 : 65, point.toBytes(compressed)); 222 | } 223 | 224 | export function publicKeyCombine( 225 | publicKeys: Uint8Array[], 226 | compressed = true, 227 | out?: Output 228 | ): Uint8Array { 229 | if (!Array.isArray(publicKeys) || !publicKeys.length) { 230 | throw new TypeError( 231 | `Expected array with one or more items, not ${publicKeys}` 232 | ); 233 | } 234 | for (const publicKey of publicKeys) { 235 | assertBytes(publicKey); 236 | } 237 | assertBool(compressed, "compressed"); 238 | const combined = publicKeys 239 | .map((pub) => Point.fromBytes(pub)) 240 | .reduce((res, curr) => res.add(curr), Point.ZERO); 241 | // Prohibit returning ZERO point 242 | if (combined.equals(Point.ZERO)) { 243 | throw new Error("Combined result must not be zero"); 244 | } 245 | return output(out, compressed ? 33 : 65, combined.toBytes(compressed)); 246 | } 247 | 248 | export function publicKeyTweakAdd( 249 | publicKey: Uint8Array, 250 | tweak: Uint8Array, 251 | compressed = true, 252 | out?: Output 253 | ): Uint8Array { 254 | assertBytes(publicKey); 255 | if (![33, 65].includes(publicKey.length)) 256 | throw new Error( 257 | "Expected public key to be an Uint8Array with length [33, 65]" 258 | ); 259 | assertBytes(tweak, 32); 260 | assertBool(compressed, "compressed"); 261 | const p1 = Point.fromBytes(publicKey); 262 | const p2 = Point.BASE.multiply(Point.Fn.fromBytes(tweak)); 263 | const point = p1.add(p2); 264 | if (p2.equals(Point.ZERO) || point.equals(Point.ZERO)) { 265 | throw new Error("Tweak must not be zero"); 266 | } 267 | return output(out, compressed ? 33 : 65, point.toBytes(compressed)); 268 | } 269 | 270 | export function publicKeyTweakMul( 271 | publicKey: Uint8Array, 272 | tweak: Uint8Array, 273 | compressed = true, 274 | out?: Output 275 | ): Uint8Array { 276 | assertBytes(publicKey); 277 | if (![33, 65].includes(publicKey.length)) 278 | throw new Error( 279 | "Expected public key to be an Uint8Array with length [33, 65]" 280 | ); 281 | assertBytes(tweak, 32); 282 | assertBool(compressed, "compressed"); 283 | const bn = bytesToNumber(tweak); 284 | if (bn === 0n) { 285 | throw new Error("Tweak must not be zero"); 286 | } 287 | if (bn <= 1 || bn >= ORDER) { 288 | throw new Error("Tweak is zero or bigger than curve order"); 289 | } 290 | const point = Point.fromBytes(publicKey).multiply(bn); 291 | return output(out, compressed ? 33 : 65, point.toBytes(compressed)); 292 | } 293 | 294 | export function privateKeyTweakMul( 295 | privateKey: Uint8Array, 296 | tweak: Uint8Array 297 | ): Uint8Array { 298 | assertBytes(privateKey, 32); 299 | assertBytes(tweak, 32); 300 | const bn = bytesToNumber(tweak); 301 | if (bn <= 1 || bn >= ORDER) { 302 | throw new Error("Tweak is zero or bigger than curve order"); 303 | } 304 | const res = mod(bn * bytesToNumber(privateKey), ORDER); 305 | if (res === 0n) { 306 | throw new Error( 307 | "The tweak was out of range or the resulted private key is invalid" 308 | ); 309 | } 310 | privateKey.set(hexToBytes(numberToHex(res))); 311 | return privateKey; 312 | } 313 | // internal -> DER 314 | export function signatureExport( 315 | signature: Uint8Array, 316 | out?: Output 317 | ): Uint8Array { 318 | const res = getSignature(signature).toBytes("der"); 319 | return output(out, 72, res.slice()).slice(0, res.length); 320 | } 321 | // DER -> internal 322 | export function signatureImport( 323 | signature: Uint8Array, 324 | out?: Output 325 | ): Uint8Array { 326 | assertBytes(signature); 327 | const sig = secp256k1.Signature.fromBytes(signature, "der"); 328 | return output(out, 64, sig.toBytes("compact")); 329 | } 330 | 331 | export function signatureNormalize(signature: Uint8Array): Uint8Array { 332 | const res = getSignature(signature); 333 | if (res.s > ORDER / 2n) { 334 | signature.set(numberToBytes(ORDER - res.s), 32); 335 | } 336 | return signature; 337 | } 338 | 339 | export function ecdh( 340 | publicKey: Uint8Array, 341 | privateKey: Uint8Array, 342 | options: { 343 | xbuf?: Uint8Array; 344 | ybuf?: Uint8Array; 345 | data?: Uint8Array; 346 | hashfn?: (x: Uint8Array, y: Uint8Array, data: Uint8Array) => Uint8Array; 347 | } = {}, 348 | out?: Output 349 | ): Uint8Array { 350 | assertBytes(publicKey); 351 | if (![33, 65].includes(publicKey.length)) 352 | throw new Error( 353 | "Expected public key to be an Uint8Array with length [33, 65]" 354 | ); 355 | assertBytes(privateKey, 32); 356 | if (typeof options !== "object" || options === null) { 357 | throw new TypeError("secp256k1.ecdh: options should be object"); 358 | } 359 | if (options.data !== undefined) { 360 | assertBytes(options.data); 361 | } 362 | const point = Point.fromBytes( 363 | secp256k1.getSharedSecret(privateKey, publicKey) 364 | ); 365 | if (options.hashfn === undefined) { 366 | return output(out, 32, sha256(point.toBytes(true))); 367 | } 368 | if (typeof options.hashfn !== "function") { 369 | throw new TypeError("secp256k1.ecdh: options.hashfn should be function"); 370 | } 371 | if (options.xbuf !== undefined) { 372 | assertBytes(options.xbuf, 32); 373 | } 374 | if (options.ybuf !== undefined) { 375 | assertBytes(options.ybuf, 32); 376 | } 377 | assertBytes(out as Uint8Array, 32); 378 | const { x, y } = point.toAffine(); 379 | const xbuf = options.xbuf || new Uint8Array(32); 380 | xbuf.set(numberToBytes(x)); 381 | const ybuf = options.ybuf || new Uint8Array(32); 382 | ybuf.set(numberToBytes(y)); 383 | const hash = options.hashfn(xbuf, ybuf, options.data!); 384 | if (!(hash instanceof Uint8Array) || hash.length !== 32) { 385 | throw new Error("secp256k1.ecdh: invalid options.hashfn output"); 386 | } 387 | return output(out, 32, hash); 388 | } 389 | 390 | export function contextRandomize(seed: Uint8Array) { 391 | if (seed !== null) { 392 | assertBytes(seed, 32); 393 | } 394 | // There is no context to randomize 395 | } 396 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ethereum-cryptography 2 | 3 | [Audited](#security) pure JS library containing all Ethereum-related cryptographic primitives. Implemented with 6 [noble & scure](https://paulmillr.com/noble/) dependencies. 4 | 5 | Check out [Changelog / Upgrading](#upgrading) and an article about the library: 6 | [A safer, smaller, and faster Ethereum cryptography stack](https://medium.com/nomic-labs-blog/a-safer-smaller-and-faster-ethereum-cryptography-stack-5eeb47f62d79). 7 | 8 | ## Usage 9 | 10 | ```shell 11 | npm install ethereum-cryptography 12 | ``` 13 | 14 | We explicitly support major browsers and Node.js on x86 and arm64. Other major runtimes and platforms are supported on a best-effort basis. 15 | Refer to `engines` field of `package.json` for runtime support information for each version. 16 | Tests are being ran with Webpack, Rollup, Parcel and Browserify. 17 | 18 | This package has no single entry-point, but submodule for each cryptographic 19 | primitive. The reason for this is that importing everything from a single file will lead to huge bundles when using this package for the web. This could be 20 | avoided through tree-shaking, but the possibility of it not working properly 21 | on one of [the supported bundlers](#browser-usage) is too high. 22 | 23 | - [Usage](#usage) 24 | - [Dependencies](#dependencies) 25 | - [hashes: sha256, sha512, keccak, ripemd160, blake2b](#hashes-sha256-sha512-keccak-ripemd160-blake2b) 26 | - [kdfs: pbkdf2, scrypt](#kdfs-pbkdf2-scrypt) 27 | - [random: secure randomness](#random-secure-randomness) 28 | - [secp256k1: curve operations](#secp256k1-curve-operations) 29 | - [bn: pairing-friendly curve](#bn-pairing-friendly-curve) 30 | - [bls: pairing-friendly curve](#bls-pairing-friendly-curve) 31 | - [aes: encryption](#aes-encryption) 32 | - [hdkey: bip32 HD wallets](#hdkey-bip32-hd-wallets) 33 | - [bip39: mnemonic phrases](#bip39-mnemonic-phrases) 34 | - [math: utilities](#math-utilities) 35 | - [utils: generic utilities](#utils-generic-utilities) 36 | - [secp256k1-compat: compatibility layer with other libraries](#secp256k1-compat-compatibility-layer-with-other-libraries) 37 | - [All imports](#all-imports) 38 | - [Caveats](#caveats) 39 | - [Browser usage: Rollup setup](#browser-usage-rollup-setup) 40 | - [AES](#aes) 41 | - [Encrypting with passwords](#encrypting-with-passwords) 42 | - [Operation modes](#operation-modes) 43 | - [Padding plaintext messages](#padding-plaintext-messages) 44 | - [How to use the IV parameter](#how-to-use-the-iv-parameter) 45 | - [How to handle errors with this module](#how-to-handle-errors-with-this-module) 46 | - [Upgrading](#upgrading) 47 | - [Changelog](#changelog) 48 | - [From v2 to v3](#from-v2-to-v3) 49 | - [From v1 to v2](#from-v1-to-v2) 50 | - [From v0.1 to v1](#from-v01-to-v1) 51 | - [Security](#security) 52 | - [License](#license) 53 | 54 | ### Dependencies 55 | 56 | All functionality of the module is simple 57 | re-export of 6 audited [noble & scure libraries](https://paulmillr.com/noble/): 58 | 59 | - noble-curves, noble-ciphers, noble-hashes 60 | - scure-base, scure-bip32, scure-bip39 61 | 62 | ethereum-cryptography pins versions of the libraries to ensure good 63 | protection against supply chain attacks. Ideally, your app would also 64 | pin version of ethereum-cryptography. That means, no `^3.1.0` - use `3.1.0` instead. 65 | 66 | ### hashes: sha256, sha512, keccak, ripemd160, blake2b 67 | 68 | ```js 69 | import { sha256 } from "ethereum-cryptography/sha256.js"; 70 | import { sha512 } from "ethereum-cryptography/sha512.js"; 71 | import { 72 | keccak256, 73 | keccak224, 74 | keccak384, 75 | keccak512, 76 | } from "ethereum-cryptography/keccak.js"; 77 | import { ripemd160 } from "ethereum-cryptography/ripemd160.js"; 78 | import { blake2b } from "ethereum-cryptography/blake2b.js"; 79 | sha256(Uint8Array.from([1, 2, 3])); // A: buffers 80 | 81 | import { utf8ToBytes } from "ethereum-cryptography/utils.js"; 82 | sha256(utf8ToBytes("abc")); // B: strings 83 | 84 | import { bytesToHex as toHex } from "ethereum-cryptography/utils.js"; 85 | toHex(sha256(utf8ToBytes("abc"))); // C: hex 86 | ``` 87 | 88 | ### kdfs: pbkdf2, scrypt 89 | 90 | ```js 91 | import { pbkdf2, pbkdf2Sync } from "ethereum-cryptography/pbkdf2.js"; 92 | import { scrypt, scryptSync } from "ethereum-cryptography/scrypt.js"; 93 | import { utf8ToBytes } from "ethereum-cryptography/utils.js"; 94 | 95 | // Pass Uint8Array, or convert strings to Uint8Array 96 | const pass = utf8ToBytes("password"); 97 | const salt = utf8ToBytes("salt"); 98 | const iters = 131072; 99 | const outLength = 32; 100 | console.log(await pbkdf2(pass, salt, iters, outLength, "sha256")); 101 | 102 | const N = 262144; 103 | const r = 8; 104 | const p = 1; 105 | const outLengths = 32; 106 | console.log(await scrypt(pass, salt, N, r, p, outLengths)); 107 | ``` 108 | 109 | The `pbkdf2` submodule has two functions implementing the PBKDF2 key 110 | derivation algorithm in synchronous and asynchronous ways. This algorithm is 111 | very slow, and using the synchronous version in the browser is not recommended, 112 | as it will block its main thread and hang your UI. The KDF supports `sha256` and `sha512` digests. 113 | 114 | The `scrypt` submodule has two functions implementing the Scrypt key 115 | derivation algorithm in synchronous and asynchronous ways. This algorithm is 116 | very slow, and using the synchronous version in the browser is not recommended, 117 | as it will block its main thread and hang your UI. 118 | 119 | Encoding passwords is a frequent source of errors. Please read 120 | [notes](https://github.com/ricmoo/scrypt-js/tree/0eb70873ddf3d24e34b53e0d9a99a0cef06a79c0#encoding-notes) 121 | before using these submodules. 122 | 123 | ### random: secure randomness 124 | 125 | ```js 126 | import { getRandomBytesSync } from "ethereum-cryptography/random.js"; 127 | console.log(getRandomBytesSync(32)); 128 | ``` 129 | 130 | The `random` submodule has functions to generate cryptographically strong 131 | pseudo-random data in synchronous and asynchronous ways. Backed by [`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) in browser and by [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback) in node.js. If backends are somehow not available, the module would throw an error and won't work, as keeping them working would be insecure. 132 | 133 | ### secp256k1: curve operations 134 | 135 | ```js 136 | import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; 137 | // You pass either a hex string, or Uint8Array 138 | const privateKey = 139 | "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; 140 | const messageHash = 141 | "a33321f98e4ff1c283c76998f14f57447545d339b3db534c6d886decb4209f28"; 142 | const publicKey = secp256k1.getPublicKey(privateKey); 143 | const signature = secp256k1.sign(messageHash, privateKey); 144 | const isSigned = secp256k1.verify(signature, messageHash, publicKey); 145 | ``` 146 | 147 | Elliptic curve operations on the curve secp256k1. Check out [noble-curves docs](https://github.com/paulmillr/noble-curves) for more info. 148 | 149 | secp256k1 private keys need to be cryptographically secure random numbers with 150 | certain characteristics. If this is not the case, the security of secp256k1 is 151 | compromised. 152 | 153 | ### bn: pairing-friendly curve 154 | 155 | ```js 156 | import { bn } from "ethereum-cryptography/bls.js"; 157 | 158 | console.log(bn254.G1, bn254.G2, bn254.pairing); 159 | ``` 160 | 161 | For example usage, check out [the implementation of bn254 EVM precompiles](https://github.com/paulmillr/noble-curves/blob/3ed792f8ad9932765b84d1064afea8663a255457/test/bn254.test.js#L697). 162 | 163 | ### bls: pairing-friendly curve 164 | 165 | ```js 166 | import { bls12_381 as bls } from "ethereum-cryptography/bls.js"; 167 | 168 | // G1 keys, G2 signatures 169 | const privateKey = 170 | "67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c"; 171 | const message = "64726e3da8"; 172 | const publicKey = bls.getPublicKey(privateKey); 173 | const signature = bls.sign(message, privateKey); 174 | const isValid = bls.verify(signature, message, publicKey); 175 | console.log({ publicKey, signature, isValid }); 176 | 177 | // G2 signatures, G1 keys 178 | // getPublicKeyForShortSignatures(privateKey) 179 | // signShortSignature(message, privateKey) 180 | // verifyShortSignature(signature, message, publicKey) 181 | // aggregateShortSignatures(signatures) 182 | 183 | // Custom DST 184 | const htfEthereum = { DST: "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" }; 185 | const signatureEth = bls.sign(message, privateKey, htfEthereum); 186 | const isValidEth = bls.verify(signature, message, publicKey, htfEthereum); 187 | 188 | // Aggregation 189 | const aggregatedKey = bls.aggregatePublicKeys([ 190 | bls.getPublicKey(bls.utils.randomPrivateKey()), 191 | bls.getPublicKey(bls.utils.randomPrivateKey()), 192 | ]); 193 | // const aggregatedSig = bls.aggregateSignatures(sigs) 194 | 195 | // Pairings, with and without final exponentiation 196 | // bls.pairing(PointG1, PointG2); 197 | // bls.pairing(PointG1, PointG2, false); 198 | // bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(PointG1, PointG2)); 199 | 200 | // Others 201 | // bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE; 202 | // bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr; 203 | ``` 204 | 205 | For example usage, check out [the implementation of BLS EVM precompiles](https://github.com/ethereumjs/ethereumjs-monorepo/blob/361f4edbc239e795a411ac2da7e5567298b9e7e5/packages/evm/src/precompiles/bls12_381/noble.ts). 206 | 207 | ### aes: encryption 208 | 209 | ```js 210 | import * as aes from "ethereum-cryptography/aes.js"; 211 | import { hexToBytes, utf8ToBytes } from "ethereum-cryptography/utils.js"; 212 | 213 | console.log( 214 | aes.encrypt( 215 | utf8ToBytes("message"), 216 | hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), 217 | hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") 218 | ) 219 | ); 220 | // const mode = "aes-128-ctr"; // "aes-128-cbc", "aes-256-ctr", "aes-256-cbc" 221 | // function encrypt(msg: Uint8Array, key: Uint8Array, iv: Uint8Array, mode = "aes-128-ctr", pkcs7PaddingEnabled = true): Uint8Array; 222 | // function decrypt(cipherText: Uint8Array, key: Uint8Array, iv: Uint8Array, mode = "aes-128-ctr", pkcs7PaddingEnabled = true): Uint8Array; 223 | ``` 224 | 225 | ### hdkey: bip32 HD wallets 226 | 227 | ```js 228 | import { HDKey } from "ethereum-cryptography/hdkey.js"; 229 | const hdkey1 = HDKey.fromMasterSeed(seed); 230 | const hdkey2 = HDKey.fromExtendedKey(base58key); 231 | const hdkey3 = HDKey.fromJSON({ xpriv: string }); 232 | 233 | // props 234 | [hdkey1.depth, hdkey1.index, hdkey1.chainCode]; 235 | console.log(hdkey2.privateKey, hdkey2.publicKey); 236 | console.log(hdkey3.derive("m/0/2147483647'/1")); 237 | const sig = hdkey3.sign(hash); 238 | hdkey3.verify(hash, sig); 239 | ``` 240 | 241 | Hierarchical deterministic (HD) wallets that conform to 242 | [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki). 243 | 244 | ### bip39: mnemonic phrases 245 | 246 | ```js 247 | import * as bip39 from "ethereum-cryptography/bip39/index.js"; 248 | import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; 249 | 250 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/czech.js"; 251 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; 252 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/french.js"; 253 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/italian.js"; 254 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/japanese.js"; 255 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/korean.js"; 256 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/portuguese.js"; 257 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/simplified-chinese.js"; 258 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/spanish.js"; 259 | // import { wordlist } from "ethereum-cryptography/bip39/wordlists/traditional-chinese.js"; 260 | 261 | // Generate x random words. Uses Cryptographically-Secure Random Number Generator. 262 | const mn = bip39.generateMnemonic(wordlist); 263 | console.log(mn); 264 | 265 | // Reversible: Converts mnemonic string to raw entropy in form of byte array. 266 | const ent = bip39.mnemonicToEntropy(mn, wordlist); 267 | 268 | // Reversible: Converts raw entropy in form of byte array to mnemonic string. 269 | bip39.entropyToMnemonic(ent, wordlist); 270 | 271 | // Validates mnemonic for being 12-24 words contained in `wordlist`. 272 | bip39.validateMnemonic(mn, wordlist); 273 | 274 | // Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password. 275 | await bip39.mnemonicToSeed(mn, "password"); 276 | bip39.mnemonicToSeedSync(mn, "password"); 277 | ``` 278 | 279 | The `bip39` submodule provides functions to generate, validate and use seed 280 | recovery phrases according to [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). 281 | 282 | Wordlists for different languages are not imported by default, 283 | as that would increase bundle sizes too much. Instead, you should import and use them explicitly. 284 | 285 | ### math: utilities 286 | 287 | ```js 288 | import { modPow, modInvert } from "ethereum-cryptography/math.js"; 289 | modPow(123n, 456n, 789n); 290 | modInvert(22n, 5n); 291 | ``` 292 | 293 | ### utils: generic utilities 294 | 295 | ```js 296 | import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils.js"; 297 | ``` 298 | 299 | ### secp256k1-compat: compatibility layer with other libraries 300 | 301 | ```js 302 | import { 303 | createPrivateKeySync, 304 | ecdsaSign, 305 | } from "ethereum-cryptography/secp256k1-compat"; 306 | const msgHash = Uint8Array.from( 307 | "82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28", 308 | "hex" 309 | ); 310 | const privateKey = createPrivateKeySync(); 311 | console.log(Uint8Array.from(ecdsaSign(msgHash, privateKey).signature)); 312 | ``` 313 | 314 | **Warning:** use `secp256k1` instead. This module is only for users who upgraded 315 | from ethereum-cryptography v0.1. It could be removed in the future. 316 | 317 | The API of `secp256k1-compat` is the same as [secp256k1-node](https://github.com/cryptocoinjs/secp256k1-node): 318 | 319 | ### All imports 320 | 321 | ```js 322 | import { sha256 } from "ethereum-cryptography/sha256.js"; 323 | import { sha512 } from "ethereum-cryptography/sha512.js"; 324 | import { 325 | keccak256, 326 | keccak224, 327 | keccak384, 328 | keccak512, 329 | } from "ethereum-cryptography/keccak.js"; 330 | import { ripemd160 } from "ethereum-cryptography/ripemd160.js"; 331 | import { blake2b } from "ethereum-cryptography/blake2b.js"; 332 | 333 | import { pbkdf2Sync } from "ethereum-cryptography/pbkdf2.js"; 334 | import { scryptSync } from "ethereum-cryptography/scrypt.js"; 335 | 336 | import { getRandomBytesSync } from "ethereum-cryptography/random.js"; 337 | 338 | import { encrypt } from "ethereum-cryptography/aes.js"; 339 | import { modPow, modInvert } from "ethereum-cryptography/math.js"; 340 | 341 | import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; 342 | import { bls12_381 } from "ethereum-cryptography/bls.js"; 343 | import { bn254 } from "ethereum-cryptography/bn.js"; 344 | 345 | import { HDKey } from "ethereum-cryptography/hdkey.js"; 346 | import { generateMnemonic } from "ethereum-cryptography/bip39/index.js"; 347 | import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; 348 | 349 | import { modPow, modInvert } from "ethereum-cryptography/math.js"; 350 | import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils.js"; 351 | ``` 352 | 353 | ## Caveats 354 | 355 | ### Browser usage: Rollup setup 356 | 357 | Using this library with Rollup requires the following plugins: 358 | 359 | - [`@rollup/plugin-commonjs`](https://www.npmjs.com/package/@rollup/plugin-commonjs) 360 | - [`@rollup/plugin-node-resolve`](https://www.npmjs.com/package/@rollup/plugin-node-resolve) 361 | 362 | These can be used by setting your `plugins` array like this: 363 | 364 | ```js 365 | plugins: [ 366 | commonjs(), 367 | resolve({ 368 | browser: true, 369 | preferBuiltins: false, 370 | }), 371 | ]; 372 | ``` 373 | 374 | ### AES 375 | 376 | #### Encrypting with passwords 377 | 378 | AES is not supposed to be used directly with a password. Doing that will 379 | compromise your users' security. 380 | 381 | The `key` parameters in this submodule are meant to be strong cryptographic 382 | keys. If you want to obtain such a key from a password, please use a 383 | [key derivation function](https://en.wikipedia.org/wiki/Key_derivation_function) 384 | like [pbkdf2](#kdfs-pbkdf2-scrypt) or [scrypt](#kdfs-pbkdf2-scrypt). 385 | 386 | #### Operation modes 387 | 388 | This submodule works with different [block cipher modes of operation](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation). If you are using this module in a new 389 | application, we recommend using the default. 390 | 391 | While this module may work with any mode supported by OpenSSL, we only test it 392 | with `aes-128-ctr`, `aes-128-cbc`, and `aes-256-cbc`. If you use another module 393 | a warning will be printed in the console. 394 | 395 | We only recommend using `aes-128-cbc` and `aes-256-cbc` to decrypt already 396 | encrypted data. 397 | 398 | #### Padding plaintext messages 399 | 400 | Some operation modes require the plaintext message to be a multiple of `16`. If 401 | that isn't the case, your message has to be padded. 402 | 403 | By default, this module automatically pads your messages according to [PKCS#7](https://tools.ietf.org/html/rfc2315). 404 | Note that this padding scheme always adds at least 1 byte of padding. If you 405 | are unsure what anything of this means, we **strongly** recommend you to use 406 | the defaults. 407 | 408 | If you need to encrypt without padding or want to use another padding scheme, 409 | you can disable PKCS#7 padding by passing `false` as the last argument and 410 | handling padding yourself. Note that if you do this and your operation mode 411 | requires padding, `encrypt` will throw if your plaintext message isn't a 412 | multiple of `16`. 413 | 414 | This option is only present to enable the decryption of already encrypted data. 415 | To encrypt new data, we recommend using the default. 416 | 417 | #### How to use the IV parameter 418 | 419 | The `iv` parameter of the `encrypt` function must be unique, or the security 420 | of the encryption algorithm can be compromised. 421 | 422 | You can generate a new `iv` using the `random` module. 423 | 424 | Note that to decrypt a value, you have to provide the same `iv` used to encrypt 425 | it. 426 | 427 | #### How to handle errors with this module 428 | 429 | Sensitive information can be leaked via error messages when using this module. 430 | To avoid this, you should make sure that the errors you return don't 431 | contain the exact reason for the error. Instead, errors must report general 432 | encryption/decryption failures. 433 | 434 | Note that implementing this can mean catching all errors that can be thrown 435 | when calling on of this module's functions, and just throwing a new generic 436 | exception. 437 | 438 | ## Upgrading 439 | 440 | ### Changelog 441 | 442 | - v3.0 (Sep 2024): new modules `bls`, `bn`, `math` 443 | change async AES to non-native sync, 444 | improve typescript compatibility, new dependency [noble-ciphers](https://github.com/paulmillr/noble-ciphers) 445 | - v2.0 (Apr 2023): switched 446 | [noble-secp256k1](https://github.com/paulmillr/noble-secp256k1) to 447 | [noble-curves](https://github.com/paulmillr/noble-curves), 448 | which changes re-exported api of `secp256k1` submodule. 449 | - v1.0 (Jan 2022): rewritten the library from 450 | scratch and [audited](#security) it. It became **6x smaller:** ~5,000 lines of 451 | code instead of ~24,000 (with all deps); 650KB instead of 10.2MB. 452 | 5 dependencies by 1 author are now used, instead of 38 by 5 authors. 453 | 454 | ### From v2 to v3 455 | 456 | 1. utils: `crypto` var had been removed 457 | 2. aes: async methods became sync 458 | 459 | ### From v1 to v2 460 | 461 | 1. `secp256k1` module was changed massively: 462 | before, it was using [noble-secp256k1 1.7](https://github.com/paulmillr/noble-secp256k1); 463 | now it uses safer [noble-curves](https://github.com/paulmillr/noble-curves). Please refer 464 | to [upgrading section from curves README](https://github.com/paulmillr/noble-curves#upgrading). 465 | Main changes to keep in mind: a) `sign` now returns `Signature` instance 466 | b) `recoverPublicKey` got moved onto a `Signature` instance 467 | 2. node.js 14 and older support was dropped. Upgrade to node.js 16 or later. 468 | 469 | ### From v0.1 to v1 470 | 471 | All old APIs remain the same except for the breaking changes: 472 | 473 | 1. We return `Uint8Array` from all methods that worked with `Buffer` before. 474 | `Buffer` has never been supported in browsers, while `Uint8Array`s are supported natively in both 475 | browsers and node.js. 476 | 2. We target runtimes with [bigint](https://caniuse.com/bigint) support, 477 | which is Chrome 67+, Edge 79+, Firefox 68+, Safari 14+, node.js 10+. If you need to support older runtimes, use `ethereum-cryptography@0.1` 478 | 3. If you've used `secp256k1`, [rename it to `secp256k1-compat`](#legacy-secp256k1-compatibility-layer) 479 | 480 | ```js 481 | import { sha256 } from "ethereum-cryptography/sha256.js"; 482 | 483 | // Old usage 484 | const hasho = sha256(Buffer.from("string", "utf8")).toString("hex"); 485 | 486 | // New usage 487 | import { toHex } from "ethereum-cryptography/utils.js"; 488 | const hashn = toHex(sha256("string")); 489 | 490 | // If you have `Buffer` module and want to preserve it: 491 | const hashb = Buffer.from(sha256("string")); 492 | const hashbo = hashb.toString("hex"); 493 | ``` 494 | 495 | ## Security 496 | 497 | 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). 498 | 499 | Dependencies are having separate regular audits: check out their documentation for more info. 500 | 501 | ## License 502 | 503 | `ethereum-cryptography` is released under The MIT License (MIT) 504 | 505 | Copyright (c) 2021 Patricio Palladino, Paul Miller, ethereum-cryptography contributors 506 | 507 | See [LICENSE](./LICENSE) file. 508 | 509 | `hdkey` is loosely based on [hdkey](https://github.com/cryptocoinjs/hdkey), 510 | which had [MIT License](https://github.com/cryptocoinjs/hdkey/blob/3f3c0b5cedb98f971835b5116ebea05b3c09422a/LICENSE) 511 | 512 | Copyright (c) 2018 cryptocoinjs 513 | 514 | [1]: https://www.npmjs.com/package/ethereum-cryptography 515 | [2]: https://github.com/ethereum/js-ethereum-cryptography/blob/master/packages/ethereum-cryptography/LICENSE 516 | -------------------------------------------------------------------------------- /test/test-vectors/hdkey.ts: -------------------------------------------------------------------------------- 1 | import { secp256k1 as secp } from "@noble/curves/secp256k1.js"; 2 | import { HARDENED_OFFSET, HDKey } from "ethereum-cryptography/hdkey"; 3 | import { hexToBytes, toHex } from "ethereum-cryptography/utils"; 4 | import { deepStrictEqual, throws } from "./assert"; 5 | // https://github.com/cryptocoinjs/hdkey/blob/42637e381bdef0c8f785b14f5b66a80dad969514/test/fixtures/hdkey.json 6 | const fixtures = [ 7 | { 8 | seed: "000102030405060708090a0b0c0d0e0f", 9 | path: "m", 10 | public: 11 | "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", 12 | private: 13 | "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", 14 | }, 15 | { 16 | seed: "000102030405060708090a0b0c0d0e0f", 17 | path: "m/0'", 18 | public: 19 | "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", 20 | private: 21 | "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", 22 | }, 23 | { 24 | seed: "000102030405060708090a0b0c0d0e0f", 25 | path: "m/0'/1", 26 | public: 27 | "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", 28 | private: 29 | "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", 30 | }, 31 | { 32 | seed: "000102030405060708090a0b0c0d0e0f", 33 | path: "m/0'/1/2'", 34 | public: 35 | "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", 36 | private: 37 | "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", 38 | }, 39 | { 40 | seed: "000102030405060708090a0b0c0d0e0f", 41 | path: "m/0'/1/2'/2", 42 | public: 43 | "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", 44 | private: 45 | "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", 46 | }, 47 | { 48 | seed: "000102030405060708090a0b0c0d0e0f", 49 | path: "m/0'/1/2'/2/1000000000", 50 | public: 51 | "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", 52 | private: 53 | "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", 54 | }, 55 | { 56 | seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 57 | path: "m", 58 | public: 59 | "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", 60 | private: 61 | "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", 62 | }, 63 | { 64 | seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 65 | path: "m/0", 66 | public: 67 | "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", 68 | private: 69 | "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", 70 | }, 71 | { 72 | seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 73 | path: "m/0/2147483647'", 74 | public: 75 | "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", 76 | private: 77 | "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", 78 | }, 79 | { 80 | seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 81 | path: "m/0/2147483647'/1", 82 | public: 83 | "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", 84 | private: 85 | "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", 86 | }, 87 | { 88 | seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 89 | path: "m/0/2147483647'/1/2147483646'", 90 | public: 91 | "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", 92 | private: 93 | "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", 94 | }, 95 | { 96 | seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 97 | path: "m/0/2147483647'/1/2147483646'/2", 98 | public: 99 | "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", 100 | private: 101 | "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", 102 | }, 103 | ]; 104 | 105 | describe("hdkey", () => { 106 | it("Should derive private key correctly", () => { 107 | const seed = 108 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; 109 | const hdkey = HDKey.fromMasterSeed(hexToBytes(seed)); 110 | const childkey = hdkey.derive("m/0/2147483647'/1"); 111 | deepStrictEqual( 112 | childkey.privateExtendedKey, 113 | "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" 114 | ); 115 | // Should throw on 2**32 childs 116 | throws(() => hdkey.deriveChild(2 ** 32)); 117 | }); 118 | 119 | it("Should derive public key correctly", () => { 120 | const seed = 121 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; 122 | const hdkey = HDKey.fromMasterSeed(hexToBytes(seed)); 123 | const expected = hdkey.derive("m/0/2147483647'/1"); 124 | const parentkey = hdkey.derive("m/0/2147483647'"); 125 | parentkey.wipePrivateData(); 126 | const childkey = parentkey.derive("m/1"); 127 | deepStrictEqual(childkey.publicExtendedKey, expected.publicExtendedKey); 128 | }); 129 | 130 | // Ported from https://github.com/cryptocoinjs/hdkey/blob/42637e381bdef0c8f785b14f5b66a80dad969514/test/hdkey.test.js 131 | describe("+ fromMasterSeed", () => { 132 | for (const f of fixtures) { 133 | it("should properly derive the chain path: " + f.path, () => { 134 | const hdkey = HDKey.fromMasterSeed(hexToBytes(f.seed)); 135 | const childkey = hdkey.derive(f.path); 136 | deepStrictEqual(childkey.privateExtendedKey, f.private); 137 | deepStrictEqual(childkey.publicExtendedKey, f.public); 138 | }); 139 | 140 | describe("> " + f.path + " toJSON() / fromJSON()", () => { 141 | it("should return an object read for JSON serialization", () => { 142 | const hdkey = HDKey.fromMasterSeed(hexToBytes(f.seed)); 143 | const childkey = hdkey.derive(f.path); 144 | const obj = { 145 | xpriv: f.private, 146 | xpub: f.public, 147 | }; 148 | deepStrictEqual(childkey.toJSON(), obj); 149 | const newKey = HDKey.fromJSON(obj); 150 | deepStrictEqual(newKey.privateExtendedKey, f.private); 151 | deepStrictEqual(newKey.publicExtendedKey, f.public); 152 | }); 153 | }); 154 | } 155 | }); 156 | 157 | describe("- privateKey", () => { 158 | it("should throw an error if incorrect key size", () => { 159 | const seed = 160 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; 161 | const hdkey = HDKey.fromMasterSeed(hexToBytes(seed)); 162 | // const hdkey = new HDKey.HDKey(); 163 | throws(() => { 164 | "use strict"; // parcel strips strict-mode by some reasons. 165 | // @ts-ignore 166 | hdkey.privateKey = new Uint8Array([1, 2, 3, 4]); 167 | }); 168 | }); 169 | }); 170 | 171 | describe("- publicKey", () => { 172 | it("should throw an error if incorrect key size", () => { 173 | throws(() => { 174 | "use strict"; // parcel strips strict-mode by some reasons. 175 | const seed = 176 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; 177 | const hdkey = HDKey.fromMasterSeed(hexToBytes(seed)); 178 | // @ts-ignore 179 | hdkey.publicKey = new Uint8Array([1, 2, 3, 4]); 180 | }); 181 | }); 182 | 183 | it("should not throw if key is 33 bytes (compressed)", () => { 184 | const pub = secp.getPublicKey(secp.utils.randomSecretKey(), true); 185 | deepStrictEqual(pub.length, 33); 186 | const seed = 187 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; 188 | const hdkey = HDKey.fromMasterSeed(hexToBytes(seed)); 189 | throws(() => { 190 | "use strict"; // parcel strips strict-mode by some reasons. 191 | // @ts-ignore 192 | hdkey.publicKey = pub; 193 | }); 194 | }); 195 | 196 | it("should not throw if key is 65 bytes (not compressed)", () => { 197 | const pub = secp.getPublicKey(secp.utils.randomSecretKey(), false); 198 | deepStrictEqual(pub.length, 65); 199 | const seed = 200 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; 201 | const hdkey = HDKey.fromMasterSeed(hexToBytes(seed)); 202 | throws(() => { 203 | "use strict"; // parcel strips strict-mode by some reasons. 204 | // @ts-ignore 205 | hdkey.publicKey = pub; 206 | }); 207 | }); 208 | }); 209 | 210 | describe("+ fromExtendedKey()", () => { 211 | describe("> when private", () => { 212 | it("should parse it", () => { 213 | // m/0/2147483647'/1/2147483646'/2 214 | const key = 215 | "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"; 216 | const hdkey = HDKey.fromExtendedKey(key); 217 | deepStrictEqual(hdkey.versions.private, 0x0488ade4); 218 | deepStrictEqual(hdkey.versions.public, 0x0488b21e); 219 | deepStrictEqual(hdkey.depth, 5); 220 | deepStrictEqual(hdkey.parentFingerprint, 0x31a507b8); 221 | deepStrictEqual(hdkey.index, 2); 222 | deepStrictEqual( 223 | toHex(hdkey.chainCode!), 224 | "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271" 225 | ); 226 | deepStrictEqual( 227 | toHex(hdkey.privateKey!), 228 | "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23" 229 | ); 230 | deepStrictEqual( 231 | toHex(hdkey.publicKey!), 232 | "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c" 233 | ); 234 | deepStrictEqual( 235 | toHex(hdkey.identifier!), 236 | "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220" 237 | ); 238 | }); 239 | }); 240 | 241 | describe("> when public", () => { 242 | it("should parse it", () => { 243 | // m/0/2147483647'/1/2147483646'/2 244 | const key = 245 | "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"; 246 | const hdkey = HDKey.fromExtendedKey(key); 247 | deepStrictEqual(hdkey.versions.private, 0x0488ade4); 248 | deepStrictEqual(hdkey.versions.public, 0x0488b21e); 249 | deepStrictEqual(hdkey.depth, 5); 250 | deepStrictEqual(hdkey.parentFingerprint, 0x31a507b8); 251 | deepStrictEqual(hdkey.index, 2); 252 | deepStrictEqual( 253 | toHex(hdkey.chainCode!), 254 | "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271" 255 | ); 256 | deepStrictEqual(hdkey.privateKey, null); 257 | deepStrictEqual( 258 | toHex(hdkey.publicKey!), 259 | "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c" 260 | ); 261 | deepStrictEqual( 262 | toHex(hdkey.identifier!), 263 | "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220" 264 | ); 265 | }); 266 | }); 267 | }); 268 | 269 | describe("> when signing", () => { 270 | it("should work", () => { 271 | const key = 272 | "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"; 273 | const hdkey = HDKey.fromExtendedKey(key); 274 | 275 | const ma = new Uint8Array(32); 276 | const mb = new Uint8Array(32).fill(8); 277 | const a = hdkey.sign(ma); 278 | const b = hdkey.sign(mb); 279 | deepStrictEqual( 280 | toHex(a), 281 | "6ba4e554457ce5c1f1d7dbd10459465e39219eb9084ee23270688cbe0d49b52b7905d5beb28492be439a3250e9359e0390f844321b65f1a88ce07960dd85da06" 282 | ); 283 | deepStrictEqual( 284 | toHex(b), 285 | "dfae85d39b73c9d143403ce472f7c4c8a5032c13d9546030044050e7d39355e47a532e5c0ae2a25392d97f5e55ab1288ef1e08d5c034bad3b0956fbbab73b381" 286 | ); 287 | // TODO: noble-secp256k1 incompat 288 | // assert.equal(hdkey.verify(ma, a), true); 289 | deepStrictEqual(hdkey.verify(mb, b), true); 290 | deepStrictEqual( 291 | hdkey.verify(new Uint8Array(32), new Uint8Array(64)), 292 | false 293 | ); 294 | deepStrictEqual(hdkey.verify(ma, b), false); 295 | deepStrictEqual(hdkey.verify(mb, a), false); 296 | 297 | throws(() => hdkey.verify(new Uint8Array(99), a)); 298 | throws(() => hdkey.verify(ma, new Uint8Array(99))); 299 | }); 300 | }); 301 | 302 | describe("> when deriving public key", () => { 303 | it("should work", () => { 304 | const key = 305 | "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; 306 | const hdkey = HDKey.fromExtendedKey(key); 307 | 308 | const path = "m/3353535/2223/0/99424/4/33"; 309 | const derivedHDKey = hdkey.derive(path); 310 | 311 | const expected = 312 | "xpub6JdKdVJtdx6sC3nh87pDvnGhotXuU5Kz6Qy7Piy84vUAwWSYShsUGULE8u6gCivTHgz7cCKJHiXaaMeieB4YnoFVAsNgHHKXJ2mN6jCMbH1"; 313 | deepStrictEqual(derivedHDKey.publicExtendedKey, expected); 314 | }); 315 | }); 316 | 317 | describe("> when private key integer is less than 32 bytes", () => { 318 | it("should work", () => { 319 | const seed = "000102030405060708090a0b0c0d0e0f"; 320 | const masterKey = HDKey.fromMasterSeed(hexToBytes(seed)); 321 | 322 | const newKey = masterKey.derive("m/44'/6'/4'"); 323 | const expected = 324 | "xprv9ymoag6W7cR6KBcJzhCM6qqTrb3rRVVwXKzwNqp1tDWcwierEv3BA9if3ARHMhMPh9u2jNoutcgpUBLMfq3kADDo7LzfoCnhhXMRGX3PXDx"; 325 | deepStrictEqual(newKey.privateExtendedKey, expected); 326 | }); 327 | }); 328 | 329 | describe("HARDENED_OFFSET", () => { 330 | it("should be set", () => { 331 | deepStrictEqual(!!HARDENED_OFFSET, true); 332 | }); 333 | }); 334 | 335 | describe("> when private key has leading zeros", () => { 336 | it("will include leading zeros when hashing to derive child", () => { 337 | const key = 338 | "xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr"; 339 | const hdkey = HDKey.fromExtendedKey(key); 340 | deepStrictEqual( 341 | toHex(hdkey.privateKey!), 342 | "00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd" 343 | ); 344 | const derived = hdkey.derive("m/44'/0'/0'/0/0'"); 345 | deepStrictEqual( 346 | toHex(derived.privateKey!), 347 | "3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb" 348 | ); 349 | }); 350 | }); 351 | 352 | describe("> when private key is null", () => { 353 | it("privateExtendedKey should return null and not throw", () => { 354 | const seed = "000102030405060708090a0b0c0d0e0f"; 355 | const masterKey = HDKey.fromMasterSeed(hexToBytes(seed)); 356 | 357 | deepStrictEqual(!!masterKey.privateExtendedKey, true, "xpriv is truthy"); 358 | throws(() => { 359 | "use strict"; // parcel strips strict-mode by some reasons. 360 | (masterKey as any).privateKey = undefined; 361 | }); 362 | 363 | // throws(() => masterKey.privateExtendedKey); 364 | }); 365 | }); 366 | 367 | describe(" - when the path given to derive contains only the master extended key", () => { 368 | const hdKeyInstance = HDKey.fromMasterSeed(hexToBytes(fixtures[0].seed)); 369 | 370 | it("should return the same hdkey instance", () => { 371 | deepStrictEqual(hdKeyInstance.derive("m"), hdKeyInstance); 372 | deepStrictEqual(hdKeyInstance.derive("M"), hdKeyInstance); 373 | deepStrictEqual(hdKeyInstance.derive("m'"), hdKeyInstance); 374 | deepStrictEqual(hdKeyInstance.derive("M'"), hdKeyInstance); 375 | }); 376 | }); 377 | 378 | describe(" - when the path given to derive does not begin with master extended key", () => { 379 | it("should throw an error", () => { 380 | throws(() => HDKey.prototype.derive("123")); 381 | }); 382 | }); 383 | 384 | describe("- after wipePrivateData()", () => { 385 | it("should not have private data", () => { 386 | const hdkey = HDKey.fromMasterSeed( 387 | hexToBytes(fixtures[6].seed) 388 | ).wipePrivateData(); 389 | deepStrictEqual(hdkey.privateKey, null); 390 | throws(() => hdkey.privateExtendedKey); 391 | throws(() => hdkey.sign(new Uint8Array(32))); 392 | const childKey = hdkey.derive("m/0"); 393 | deepStrictEqual(childKey.publicExtendedKey, fixtures[7].public); 394 | deepStrictEqual(childKey.privateKey, null); 395 | throws(() => childKey.privateExtendedKey); 396 | }); 397 | 398 | it("should have correct data", () => { 399 | // m/0/2147483647'/1/2147483646'/2 400 | const key = 401 | "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"; 402 | const hdkey = HDKey.fromExtendedKey(key).wipePrivateData(); 403 | deepStrictEqual(hdkey.versions.private, 0x0488ade4); 404 | deepStrictEqual(hdkey.versions.public, 0x0488b21e); 405 | deepStrictEqual(hdkey.depth, 5); 406 | deepStrictEqual(hdkey.parentFingerprint, 0x31a507b8); 407 | deepStrictEqual(hdkey.index, 2); 408 | deepStrictEqual( 409 | toHex(hdkey.chainCode!), 410 | "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271" 411 | ); 412 | deepStrictEqual( 413 | toHex(hdkey.publicKey!), 414 | "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c" 415 | ); 416 | deepStrictEqual( 417 | toHex(hdkey.identifier!), 418 | "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220" 419 | ); 420 | }); 421 | 422 | it("should be able to verify signatures", () => { 423 | const fullKey = HDKey.fromMasterSeed(hexToBytes(fixtures[0].seed)); 424 | // using JSON methods to clone before mutating 425 | const wipedKey = HDKey.fromJSON(fullKey.toJSON()).wipePrivateData(); 426 | const hash = new Uint8Array(32).fill(8); 427 | deepStrictEqual(!!wipedKey.verify(hash, fullKey.sign(hash)), true); 428 | }); 429 | 430 | it("should not throw if called on hdkey without private data", () => { 431 | const hdkey = HDKey.fromExtendedKey(fixtures[0].public); 432 | hdkey.wipePrivateData(); 433 | deepStrictEqual(hdkey.publicExtendedKey, fixtures[0].public); 434 | }); 435 | }); 436 | it("should throw on derive of wrong indexes", () => { 437 | const hdkey = HDKey.fromExtendedKey(fixtures[0].public); 438 | const invalid = [ 439 | "m/0/ 1 /2", 440 | "m/0/1.5/2", 441 | "m/0/331e100/2", 442 | "m/0/3e/2", 443 | "m/0/'/2", 444 | ]; 445 | for (const t of invalid) { 446 | throws(() => hdkey.derive(t)); 447 | } 448 | }); 449 | // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 450 | describe("Spec test vectors", () => { 451 | it("Test Vector 1", () => { 452 | const master = HDKey.fromMasterSeed( 453 | hexToBytes("000102030405060708090a0b0c0d0e0f") 454 | ); 455 | deepStrictEqual(master.derive("m").toJSON(), { 456 | xpriv: 457 | "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", 458 | xpub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", 459 | }); 460 | deepStrictEqual(master.derive("m/0'").toJSON(), { 461 | xpriv: 462 | "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", 463 | xpub: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", 464 | }); 465 | deepStrictEqual(master.derive("m/0'/1").toJSON(), { 466 | xpriv: 467 | "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", 468 | xpub: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", 469 | }); 470 | deepStrictEqual(master.derive("m/0'/1/2'").toJSON(), { 471 | xpriv: 472 | "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", 473 | xpub: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", 474 | }); 475 | deepStrictEqual(master.derive("m/0'/1/2'/2").toJSON(), { 476 | xpriv: 477 | "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", 478 | xpub: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", 479 | }); 480 | deepStrictEqual(master.derive("m/0'/1/2'/2/1000000000").toJSON(), { 481 | xpriv: 482 | "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", 483 | xpub: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", 484 | }); 485 | }); 486 | it("Test Vector 2", () => { 487 | const master = HDKey.fromMasterSeed( 488 | hexToBytes( 489 | "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" 490 | ) 491 | ); 492 | deepStrictEqual(master.derive("m").toJSON(), { 493 | xpriv: 494 | "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", 495 | xpub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", 496 | }); 497 | deepStrictEqual(master.derive("m/0").toJSON(), { 498 | xpriv: 499 | "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", 500 | xpub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", 501 | }); 502 | deepStrictEqual(master.derive("m/0/2147483647'").toJSON(), { 503 | xpriv: 504 | "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", 505 | xpub: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", 506 | }); 507 | deepStrictEqual(master.derive("m/0/2147483647'/1").toJSON(), { 508 | xpriv: 509 | "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", 510 | xpub: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", 511 | }); 512 | deepStrictEqual(master.derive("m/0/2147483647'/1/2147483646'").toJSON(), { 513 | xpriv: 514 | "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", 515 | xpub: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", 516 | }); 517 | deepStrictEqual( 518 | master.derive("m/0/2147483647'/1/2147483646'/2").toJSON(), 519 | { 520 | xpriv: 521 | "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", 522 | xpub: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", 523 | } 524 | ); 525 | }); 526 | it("Test Vector 3", () => { 527 | const master = HDKey.fromMasterSeed( 528 | hexToBytes( 529 | "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be" 530 | ) 531 | ); 532 | deepStrictEqual(master.derive("m").toJSON(), { 533 | xpriv: 534 | "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", 535 | xpub: "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", 536 | }); 537 | deepStrictEqual(master.derive("m/0'").toJSON(), { 538 | xpriv: 539 | "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", 540 | xpub: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", 541 | }); 542 | }); 543 | it("Test Vector 4", () => { 544 | const master = HDKey.fromMasterSeed( 545 | hexToBytes( 546 | "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678" 547 | ) 548 | ); 549 | deepStrictEqual(master.derive("m").toJSON(), { 550 | xpriv: 551 | "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv", 552 | xpub: "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa", 553 | }); 554 | deepStrictEqual(master.derive("m/0'").toJSON(), { 555 | xpriv: 556 | "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G", 557 | xpub: "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m", 558 | }); 559 | deepStrictEqual(master.derive("m/0'/1'").toJSON(), { 560 | xpriv: 561 | "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1", 562 | xpub: "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt", 563 | }); 564 | }); 565 | it("Test Vector 5", () => { 566 | const keys = [ 567 | "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm", // (pubkey version / prvkey mismatch) 568 | "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH", // (prvkey version / pubkey mismatch) 569 | "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn", // (invalid pubkey prefix 04) 570 | "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ", // (invalid prvkey prefix 04) 571 | "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4", // (invalid pubkey prefix 01) 572 | "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J", // (invalid prvkey prefix 01) 573 | "xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv", // (zero depth with non-zero parent fingerprint) 574 | "xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ", // (zero depth with non-zero parent fingerprint) 575 | "xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN", // (zero depth with non-zero index) 576 | "xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8", // (zero depth with non-zero index) 577 | "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4", // (unknown extended key version) 578 | "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9", // (unknown extended key version) 579 | "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx", // (private key 0 not in 1..n-1) 580 | "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G", // (private key n not in 1..n-1) 581 | "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY", // (invalid pubkey 020000000000000000000000000000000000000000000000000000000000000007) 582 | "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL", // (invalid checksum) 583 | ]; 584 | for (const c of keys) { 585 | throws(() => HDKey.fromExtendedKey(c)); 586 | } 587 | }); 588 | }); 589 | }); 590 | -------------------------------------------------------------------------------- /test/test-vectors/bip39.ts: -------------------------------------------------------------------------------- 1 | import { 2 | entropyToMnemonic, 3 | generateMnemonic, 4 | mnemonicToEntropy, 5 | mnemonicToSeed, 6 | mnemonicToSeedSync, 7 | validateMnemonic 8 | } from "../../src/bip39"; 9 | import { wordlist as englishWordlist } from "../../src/bip39/wordlists/english"; 10 | import { wordlist as japaneseWordlist } from "../../src/bip39/wordlists/japanese"; 11 | import { wordlist as spanishWordlist } from "../../src/bip39/wordlists/spanish"; 12 | import { equalsBytes, hexToBytes, toHex } from "../../src/utils"; 13 | import { deepStrictEqual, throws } from "./assert"; 14 | 15 | describe("BIP39", () => { 16 | describe("Mnemonic generation", () => { 17 | it("should create a valid menomic", () => { 18 | const mnemonic = generateMnemonic(englishWordlist, 128); 19 | deepStrictEqual(validateMnemonic(mnemonic, englishWordlist), true); 20 | }); 21 | }); 22 | 23 | describe("Mnemonic validation", () => { 24 | it("should accept valid menomics", () => { 25 | deepStrictEqual( 26 | validateMnemonic( 27 | "jump police vessel depth mutual idea cable soap trophy dust hold wink", 28 | englishWordlist 29 | ), 30 | true 31 | ); 32 | 33 | deepStrictEqual( 34 | validateMnemonic( 35 | "koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera", 36 | spanishWordlist 37 | ), 38 | true 39 | ); 40 | }); 41 | 42 | it("should reject invalid menomics", () => { 43 | deepStrictEqual(validateMnemonic("asd", englishWordlist), false); 44 | deepStrictEqual( 45 | validateMnemonic( 46 | generateMnemonic(englishWordlist, 128), 47 | spanishWordlist 48 | ), 49 | false 50 | ); 51 | }); 52 | }); 53 | 54 | describe("Entropy-mnemonic conversions", () => { 55 | describe("Should convert from mnemonic to entropy and back", () => { 56 | it("should work with the English wodlist", () => { 57 | const mnemonic = generateMnemonic(englishWordlist, 128); 58 | const entropy = mnemonicToEntropy(mnemonic, englishWordlist); 59 | deepStrictEqual(entropyToMnemonic(entropy, englishWordlist), mnemonic); 60 | }); 61 | 62 | it("should work with the Spanish wodlist", () => { 63 | const mnemonic = generateMnemonic(spanishWordlist, 128); 64 | const entropy = mnemonicToEntropy(mnemonic, spanishWordlist); 65 | deepStrictEqual(entropyToMnemonic(entropy, spanishWordlist), mnemonic); 66 | }); 67 | }); 68 | }); 69 | 70 | describe("Menonic to seed", () => { 71 | describe("Without passphrase", () => { 72 | const MENMONIC = 73 | "koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera"; 74 | 75 | const SEED = hexToBytes( 76 | "e9dc495a155c2c5b577847874323853efc11e2379cc25fdcd26f3ad2ecca8b05d2a0e995fb6738dbcf65760e571863e0e8f518b5626b7865ac74f2ab814c050f" 77 | ); 78 | 79 | describe("Sync", () => { 80 | it("Should recover the right seed", () => { 81 | const recoveredSeed = mnemonicToSeedSync(MENMONIC); 82 | deepStrictEqual(equalsBytes(SEED, recoveredSeed), true); 83 | }); 84 | }); 85 | 86 | describe("Async", () => { 87 | it("Should recover the right seed", async () => { 88 | const recoveredSeed = await mnemonicToSeed(MENMONIC); 89 | deepStrictEqual(equalsBytes(SEED, recoveredSeed), true); 90 | }); 91 | }); 92 | }); 93 | 94 | describe("With passphrase", () => { 95 | const MENMONIC = 96 | "koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera"; 97 | 98 | const PASSPHRASE = "passphrase"; 99 | 100 | const SEED = hexToBytes( 101 | "c54939df37bf94ab65973c6c1b8e5eaf855dc4ab200698961c398685714c99f9d9e5c8518769619ba606bdaf5254d4ef34c0789089a2e4f21e357a1aae906f9d" 102 | ); 103 | 104 | describe("Sync", () => { 105 | it("Should recover the right seed", () => { 106 | const recoveredSeed = mnemonicToSeedSync(MENMONIC, PASSPHRASE); 107 | deepStrictEqual(SEED, recoveredSeed); 108 | }); 109 | }); 110 | 111 | describe("Async", () => { 112 | it("Should recover the right seed", async () => { 113 | const recoveredSeed = await mnemonicToSeed(MENMONIC, PASSPHRASE); 114 | deepStrictEqual(SEED, recoveredSeed); 115 | }); 116 | }); 117 | }); 118 | }); 119 | // Based on https://github.com/bitcoinjs/bip39/blob/cfea218ee2e6c3157baabb1e2ec684d36cce89c5/test/index.js 120 | const VECTORS: Record> = { 121 | english: [ 122 | [ 123 | "00000000000000000000000000000000", 124 | "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", 125 | "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04" 126 | ], 127 | [ 128 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 129 | "legal winner thank year wave sausage worth useful legal winner thank yellow", 130 | "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607" 131 | ], 132 | [ 133 | "80808080808080808080808080808080", 134 | "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", 135 | "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8" 136 | ], 137 | [ 138 | "ffffffffffffffffffffffffffffffff", 139 | "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", 140 | "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069" 141 | ], 142 | [ 143 | "000000000000000000000000000000000000000000000000", 144 | "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", 145 | "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa" 146 | ], 147 | [ 148 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 149 | "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", 150 | "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd" 151 | ], 152 | [ 153 | "808080808080808080808080808080808080808080808080", 154 | "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", 155 | "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65" 156 | ], 157 | [ 158 | "ffffffffffffffffffffffffffffffffffffffffffffffff", 159 | "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", 160 | "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528" 161 | ], 162 | [ 163 | "0000000000000000000000000000000000000000000000000000000000000000", 164 | "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", 165 | "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8" 166 | ], 167 | [ 168 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 169 | "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", 170 | "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87" 171 | ], 172 | [ 173 | "8080808080808080808080808080808080808080808080808080808080808080", 174 | "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", 175 | "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f" 176 | ], 177 | [ 178 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 179 | "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", 180 | "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad" 181 | ], 182 | [ 183 | "77c2b00716cec7213839159e404db50d", 184 | "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge", 185 | "b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f672a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff" 186 | ], 187 | [ 188 | "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", 189 | "renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap", 190 | "9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5" 191 | ], 192 | [ 193 | "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", 194 | "dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic", 195 | "ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da20af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67" 196 | ], 197 | [ 198 | "0460ef47585604c5660618db2e6a7e7f", 199 | "afford alter spike radar gate glance object seek swamp infant panel yellow", 200 | "65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4" 201 | ], 202 | [ 203 | "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", 204 | "indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left", 205 | "3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349dbe2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba" 206 | ], 207 | [ 208 | "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", 209 | "clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste", 210 | "fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449" 211 | ], 212 | [ 213 | "eaebabb2383351fd31d703840b32e9e2", 214 | "turtle front uncle idea crush write shrug there lottery flower risk shell", 215 | "bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c404f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c" 216 | ], 217 | [ 218 | "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", 219 | "kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment", 220 | "ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79" 221 | ], 222 | [ 223 | "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", 224 | "exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top", 225 | "095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c" 226 | ], 227 | [ 228 | "18ab19a9f54a9274f03e5209a2ac8a91", 229 | "board flee heavy tunnel powder denial science ski answer betray cargo cat", 230 | "6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe005831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8" 231 | ], 232 | [ 233 | "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", 234 | "board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief", 235 | "f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf07c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9" 236 | ], 237 | [ 238 | "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", 239 | "beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", 240 | "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd" 241 | ] 242 | ], 243 | japanese: [ 244 | [ 245 | "00000000000000000000000000000000", 246 | "あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら", 247 | "a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55" 248 | ], 249 | [ 250 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 251 | "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかめ", 252 | "aee025cbe6ca256862f889e48110a6a382365142f7d16f2b9545285b3af64e542143a577e9c144e101a6bdca18f8d97ec3366ebf5b088b1c1af9bc31346e60d9" 253 | ], 254 | [ 255 | "80808080808080808080808080808080", 256 | "そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あかちゃん", 257 | "e51736736ebdf77eda23fa17e31475fa1d9509c78f1deb6b4aacfbd760a7e2ad769c714352c95143b5c1241985bcb407df36d64e75dd5a2b78ca5d2ba82a3544" 258 | ], 259 | [ 260 | "ffffffffffffffffffffffffffffffff", 261 | "われる われる われる われる われる われる われる われる われる われる われる ろんぶん", 262 | "4cd2ef49b479af5e1efbbd1e0bdc117f6a29b1010211df4f78e2ed40082865793e57949236c43b9fe591ec70e5bb4298b8b71dc4b267bb96ed4ed282c8f7761c" 263 | ], 264 | [ 265 | "000000000000000000000000000000000000000000000000", 266 | "あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あらいぐま", 267 | "d99e8f1ce2d4288d30b9c815ae981edd923c01aa4ffdc5dee1ab5fe0d4a3e13966023324d119105aff266dac32e5cd11431eeca23bbd7202ff423f30d6776d69" 268 | ], 269 | [ 270 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 271 | "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れいぎ", 272 | "eaaf171efa5de4838c758a93d6c86d2677d4ccda4a064a7136344e975f91fe61340ec8a615464b461d67baaf12b62ab5e742f944c7bd4ab6c341fbafba435716" 273 | ], 274 | [ 275 | "808080808080808080808080808080808080808080808080", 276 | "そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら いきなり", 277 | "aec0f8d3167a10683374c222e6e632f2940c0826587ea0a73ac5d0493b6a632590179a6538287641a9fc9df8e6f24e01bf1be548e1f74fd7407ccd72ecebe425" 278 | ], 279 | [ 280 | "ffffffffffffffffffffffffffffffffffffffffffffffff", 281 | "われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる りんご", 282 | "f0f738128a65b8d1854d68de50ed97ac1831fc3a978c569e415bbcb431a6a671d4377e3b56abd518daa861676c4da75a19ccb41e00c37d086941e471a4374b95" 283 | ], 284 | [ 285 | "0000000000000000000000000000000000000000000000000000000000000000", 286 | "あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん いってい", 287 | "23f500eec4a563bf90cfda87b3e590b211b959985c555d17e88f46f7183590cd5793458b094a4dccc8f05807ec7bd2d19ce269e20568936a751f6f1ec7c14ddd" 288 | ], 289 | [ 290 | "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", 291 | "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかす りくつ ばいか ろせん まんきつ", 292 | "cd354a40aa2e241e8f306b3b752781b70dfd1c69190e510bc1297a9c5738e833bcdc179e81707d57263fb7564466f73d30bf979725ff783fb3eb4baa86560b05" 293 | ], 294 | [ 295 | "8080808080808080808080808080808080808080808080808080808080808080", 296 | "そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あこがれる いくぶん けいけん あたえる うめる", 297 | "6b7cd1b2cdfeeef8615077cadd6a0625f417f287652991c80206dbd82db17bf317d5c50a80bd9edd836b39daa1b6973359944c46d3fcc0129198dc7dc5cd0e68" 298 | ], 299 | [ 300 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 301 | "われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる らいう", 302 | "a44ba7054ac2f9226929d56505a51e13acdaa8a9097923ca07ea465c4c7e294c038f3f4e7e4b373726ba0057191aced6e48ac8d183f3a11569c426f0de414623" 303 | ], 304 | [ 305 | "77c2b00716cec7213839159e404db50d", 306 | "せまい うちがわ あずき かろう めずらしい だんち ますく おさめる ていぼう あたる すあな えしゃく", 307 | "344cef9efc37d0cb36d89def03d09144dd51167923487eec42c487f7428908546fa31a3c26b7391a2b3afe7db81b9f8c5007336b58e269ea0bd10749a87e0193" 308 | ], 309 | [ 310 | "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", 311 | "ぬすむ ふっかつ うどん こうりつ しつじ りょうり おたがい せもたれ あつめる いちりゅう はんしゃ ごますり そんけい たいちょう らしんばん ぶんせき やすみ ほいく", 312 | "b14e7d35904cb8569af0d6a016cee7066335a21c1c67891b01b83033cadb3e8a034a726e3909139ecd8b2eb9e9b05245684558f329b38480e262c1d6bc20ecc4" 313 | ], 314 | [ 315 | "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", 316 | "くのう てぬぐい そんかい すろっと ちきゅう ほあん とさか はくしゅ ひびく みえる そざい てんすう たんぴん くしょう すいようび みけん きさらぎ げざん ふくざつ あつかう はやい くろう おやゆび こすう", 317 | "32e78dce2aff5db25aa7a4a32b493b5d10b4089923f3320c8b287a77e512455443298351beb3f7eb2390c4662a2e566eec5217e1a37467af43b46668d515e41b" 318 | ], 319 | [ 320 | "0460ef47585604c5660618db2e6a7e7f", 321 | "あみもの いきおい ふいうち にげる ざんしょ じかん ついか はたん ほあん すんぽう てちがい わかめ", 322 | "0acf902cd391e30f3f5cb0605d72a4c849342f62bd6a360298c7013d714d7e58ddf9c7fdf141d0949f17a2c9c37ced1d8cb2edabab97c4199b142c829850154b" 323 | ], 324 | [ 325 | "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", 326 | "すろっと にくしみ なやむ たとえる へいこう すくう きない けってい とくべつ ねっしん いたみ せんせい おくりがな まかい とくい けあな いきおい そそぐ", 327 | "9869e220bec09b6f0c0011f46e1f9032b269f096344028f5006a6e69ea5b0b8afabbb6944a23e11ebd021f182dd056d96e4e3657df241ca40babda532d364f73" 328 | ], 329 | [ 330 | "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", 331 | "かほご きうい ゆたか みすえる もらう がっこう よそう ずっと ときどき したうけ にんか はっこう つみき すうじつ よけい くげん もくてき まわり せめる げざい にげる にんたい たんそく ほそく", 332 | "713b7e70c9fbc18c831bfd1f03302422822c3727a93a5efb9659bec6ad8d6f2c1b5c8ed8b0b77775feaf606e9d1cc0a84ac416a85514ad59f5541ff5e0382481" 333 | ], 334 | [ 335 | "eaebabb2383351fd31d703840b32e9e2", 336 | "めいえん さのう めだつ すてる きぬごし ろんぱ はんこ まける たいおう さかいし ねんいり はぶらし", 337 | "06e1d5289a97bcc95cb4a6360719131a786aba057d8efd603a547bd254261c2a97fcd3e8a4e766d5416437e956b388336d36c7ad2dba4ee6796f0249b10ee961" 338 | ], 339 | [ 340 | "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", 341 | "せんぱい おしえる ぐんかん もらう きあい きぼう やおや いせえび のいず じゅしん よゆう きみつ さといも ちんもく ちわわ しんせいじ とめる はちみつ", 342 | "1fef28785d08cbf41d7a20a3a6891043395779ed74503a5652760ee8c24dfe60972105ee71d5168071a35ab7b5bd2f8831f75488078a90f0926c8e9171b2bc4a" 343 | ], 344 | [ 345 | "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", 346 | "こころ いどう きあつ そうがんきょう へいあん せつりつ ごうせい はいち いびき きこく あんい おちつく きこえる けんとう たいこ すすめる はっけん ていど はんおん いんさつ うなぎ しねま れいぼう みつかる", 347 | "43de99b502e152d4c198542624511db3007c8f8f126a30818e856b2d8a20400d29e7a7e3fdd21f909e23be5e3c8d9aee3a739b0b65041ff0b8637276703f65c2" 348 | ], 349 | [ 350 | "18ab19a9f54a9274f03e5209a2ac8a91", 351 | "うりきれ さいせい じゆう むろん とどける ぐうたら はいれつ ひけつ いずれ うちあわせ おさめる おたく", 352 | "3d711f075ee44d8b535bb4561ad76d7d5350ea0b1f5d2eac054e869ff7963cdce9581097a477d697a2a9433a0c6884bea10a2193647677977c9820dd0921cbde" 353 | ], 354 | [ 355 | "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", 356 | "うりきれ うねる せっさたくま きもち めんきょ へいたく たまご ぜっく びじゅつかん さんそ むせる せいじ ねくたい しはらい せおう ねんど たんまつ がいけん", 357 | "753ec9e333e616e9471482b4b70a18d413241f1e335c65cd7996f32b66cf95546612c51dcf12ead6f805f9ee3d965846b894ae99b24204954be80810d292fcdd" 358 | ], 359 | [ 360 | "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", 361 | "うちゅう ふそく ひしょ がちょう うけもつ めいそう みかん そざい いばる うけとる さんま さこつ おうさま ぱんつ しひょう めした たはつ いちぶ つうじょう てさぎょう きつね みすえる いりぐち かめれおん", 362 | "346b7321d8c04f6f37b49fdf062a2fddc8e1bf8f1d33171b65074531ec546d1d3469974beccb1a09263440fc92e1042580a557fdce314e27ee4eabb25fa5e5fe" 363 | ] 364 | ] 365 | }; 366 | describe("BIP39-lib vectors", () => { 367 | function testVector( 368 | description: string, 369 | wordlist: string[], 370 | password: string, 371 | v: [string, string, string], 372 | i: number 373 | ) { 374 | const [entropy, mnemonic, seed] = v; 375 | describe(`for ${description} (${i}), ${entropy}`, async () => { 376 | deepStrictEqual( 377 | toHex(mnemonicToEntropy(mnemonic, wordlist)), 378 | entropy, 379 | "mnemonicToEntropy" 380 | ); 381 | deepStrictEqual( 382 | toHex(mnemonicToSeedSync(mnemonic, password)), 383 | seed, 384 | "mnemonicToSeedSync" 385 | ); 386 | const res = await mnemonicToSeed(mnemonic, password); 387 | deepStrictEqual(toHex(res), seed, "mnemonicToSeed"); 388 | deepStrictEqual( 389 | entropyToMnemonic(hexToBytes(entropy), wordlist), 390 | mnemonic, 391 | "entropyToMnemonic" 392 | ); 393 | deepStrictEqual( 394 | validateMnemonic(mnemonic, wordlist), 395 | true, 396 | "validateMnemonic" 397 | ); 398 | }); 399 | } 400 | for (let i = 0; i < VECTORS.english.length; i++) { 401 | testVector("English", englishWordlist, "TREZOR", VECTORS.english[i], i); 402 | } 403 | for (let i = 0; i < VECTORS.japanese.length; i++) { 404 | testVector( 405 | "Japanese", 406 | japaneseWordlist, 407 | "㍍ガバヴァぱばぐゞちぢ十人十色", 408 | VECTORS.japanese[i], 409 | i 410 | ); 411 | } 412 | describe("Invalid entropy", () => { 413 | throws(() => entropyToMnemonic(new Uint8Array([]), englishWordlist)); 414 | throws(() => 415 | entropyToMnemonic(new Uint8Array([0, 0, 0]), englishWordlist) 416 | ); 417 | throws(() => entropyToMnemonic(new Uint8Array(1028), englishWordlist)); 418 | }); 419 | describe("UTF8 passwords", () => { 420 | for (const [_, mnemonic, seed] of VECTORS.japanese) { 421 | const password = "㍍ガバヴァぱばぐゞちぢ十人十色"; 422 | const normalizedPassword = 423 | "メートルガバヴァぱばぐゞちぢ十人十色"; 424 | deepStrictEqual( 425 | toHex(mnemonicToSeedSync(mnemonic, password)), 426 | seed, 427 | "mnemonicToSeedSync normalizes passwords" 428 | ); 429 | deepStrictEqual( 430 | toHex(mnemonicToSeedSync(mnemonic, normalizedPassword)), 431 | seed, 432 | "mnemonicToSeedSync leaves normalizes passwords as-is" 433 | ); 434 | } 435 | }); 436 | describe("generateMnemonic can vary entropy length", () => { 437 | deepStrictEqual( 438 | generateMnemonic(englishWordlist, 160).split(" ").length, 439 | 15, 440 | "can vary generated entropy bit length" 441 | ); 442 | }); 443 | describe("validateMnemonic", () => { 444 | deepStrictEqual( 445 | validateMnemonic("sleep kitten", englishWordlist), 446 | false, 447 | "fails for a mnemonic that is too short" 448 | ); 449 | deepStrictEqual( 450 | validateMnemonic( 451 | "sleep kitten sleep kitten sleep kitten", 452 | englishWordlist 453 | ), 454 | false, 455 | "fails for a mnemonic that is too short" 456 | ); 457 | deepStrictEqual( 458 | validateMnemonic( 459 | "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about end grace oxygen maze bright face loan ticket trial leg cruel lizard bread worry reject journey perfect chef section caught neither install industry", 460 | englishWordlist 461 | ), 462 | false, 463 | "fails for a mnemonic that is too long" 464 | ); 465 | deepStrictEqual( 466 | validateMnemonic( 467 | "turtle front uncle idea crush write shrug there lottery flower risky shell", 468 | englishWordlist 469 | ), 470 | false, 471 | "fails if mnemonic words are not in the word list" 472 | ); 473 | deepStrictEqual( 474 | validateMnemonic( 475 | "sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten", 476 | englishWordlist 477 | ), 478 | false, 479 | "fails for invalid checksum" 480 | ); 481 | }); 482 | }); 483 | }); 484 | --------------------------------------------------------------------------------