551 lines
21 KiB
Markdown
551 lines
21 KiB
Markdown
|
# noble-hashes
|
||
|
|
||
|
Audited & minimal JS implementation of SHA, RIPEMD, BLAKE, HMAC, HKDF, PBKDF, Scrypt & Argon2.
|
||
|
|
||
|
- 🔒 [**Audited**](#security) by an independent security firm
|
||
|
- 🔻 Tree-shakeable: unused code is excluded from your builds
|
||
|
- 🏎 Fast: hand-optimized for caveats of JS engines
|
||
|
- 🔍 Reliable: chained / sliding window / DoS tests and fuzzing ensure correctness
|
||
|
- 🔁 No unrolled loops: makes it easier to verify and reduces source code size up to 5x
|
||
|
- 🐢 Scrypt supports `N: 2**22`, while other implementations are limited to `2**20`
|
||
|
- 🦘 SHA3 supports Keccak, cSHAKE, KangarooTwelve, MarsupilamiFourteen and TurboSHAKE
|
||
|
- 🪶 89KB (17KB gzipped) for everything, 10KB (2.5KB gzipped) for single-hash build
|
||
|
|
||
|
Take a glance at [GitHub Discussions](https://github.com/paulmillr/noble-hashes/discussions) for questions and support.
|
||
|
The library's initial development was funded by [Ethereum Foundation](https://ethereum.org/).
|
||
|
|
||
|
### This library belongs to _noble_ cryptography
|
||
|
|
||
|
> **noble cryptography** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
||
|
|
||
|
- Zero or minimal dependencies
|
||
|
- Highly readable TypeScript / JS code
|
||
|
- PGP-signed releases and transparent NPM builds
|
||
|
- All libraries:
|
||
|
[ciphers](https://github.com/paulmillr/noble-ciphers),
|
||
|
[curves](https://github.com/paulmillr/noble-curves),
|
||
|
[hashes](https://github.com/paulmillr/noble-hashes),
|
||
|
[post-quantum](https://github.com/paulmillr/noble-post-quantum),
|
||
|
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
|
||
|
[ed25519](https://github.com/paulmillr/noble-ed25519)
|
||
|
- [Check out homepage](https://paulmillr.com/noble/)
|
||
|
for reading resources, documentation and apps built with noble
|
||
|
|
||
|
## Usage
|
||
|
|
||
|
> npm install @noble/hashes
|
||
|
|
||
|
We support all major platforms and runtimes.
|
||
|
For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
|
||
|
For React Native, you may need a [polyfill for getRandomValues](https://github.com/LinusU/react-native-get-random-values).
|
||
|
A standalone file [noble-hashes.js](https://github.com/paulmillr/noble-hashes/releases) is also available.
|
||
|
|
||
|
```js
|
||
|
// import * from '@noble/hashes'; // Error: use sub-imports, to ensure small app size
|
||
|
import { sha256 } from '@noble/hashes/sha2'; // ECMAScript modules (ESM) and Common.js
|
||
|
// import { sha256 } from 'npm:@noble/hashes@1.3.0/sha2'; // Deno
|
||
|
console.log(sha256(new Uint8Array([1, 2, 3]))); // Uint8Array(32) [3, 144, 88, 198, 242...]
|
||
|
// you could also pass strings that will be UTF8-encoded to Uint8Array
|
||
|
console.log(sha256('abc')); // == sha256(new TextEncoder().encode('abc'))
|
||
|
```
|
||
|
|
||
|
- [Implementations](#implementations)
|
||
|
- [sha2: sha256, sha384, sha512 and others](#sha2-sha256-sha384-sha512-and-others)
|
||
|
- [sha3: FIPS, SHAKE, Keccak](#sha3-fips-shake-keccak)
|
||
|
- [sha3-addons: cSHAKE, KMAC, K12, M14, TurboSHAKE](#sha3-addons-cshake-kmac-k12-m14-turboshake)
|
||
|
- [ripemd160](#ripemd160)
|
||
|
- [blake2b, blake2s, blake3](#blake2b-blake2s-blake3)
|
||
|
- [sha1: legacy hash](#sha1-legacy-hash)
|
||
|
- [hmac](#hmac)
|
||
|
- [hkdf](#hkdf)
|
||
|
- [pbkdf2](#pbkdf2)
|
||
|
- [scrypt](#scrypt)
|
||
|
- [argon2](#argon2)
|
||
|
- [utils](#utils)
|
||
|
- [All available imports](#all-available-imports)
|
||
|
- [Security](#security)
|
||
|
- [Constant-timeness](#constant-timeness)
|
||
|
- [Memory dumping](#memory-dumping)
|
||
|
- [Supply chain security](#supply-chain-security)
|
||
|
- [Randomness](#randomness)
|
||
|
- [Speed](#speed)
|
||
|
- [Contributing & testing](#contributing--testing)
|
||
|
- [Resources](#resources)
|
||
|
- [License](#license)
|
||
|
|
||
|
### Implementations
|
||
|
|
||
|
All hash functions:
|
||
|
|
||
|
- receive `Uint8Array` and return `Uint8Array`
|
||
|
- may receive `string`, which is automatically converted to `Uint8Array`
|
||
|
via utf8 encoding **(not hex)**
|
||
|
- support little-endian and big-endian architectures
|
||
|
- can hash up to 4GB per chunk, with any amount of chunks
|
||
|
|
||
|
```ts
|
||
|
function hash(message: Uint8Array | string): Uint8Array;
|
||
|
hash(new Uint8Array([1, 3]));
|
||
|
hash('string') == hash(new TextEncoder().encode('string'));
|
||
|
```
|
||
|
|
||
|
All hash functions can be constructed via `hash.create()` method:
|
||
|
|
||
|
- the result is `Hash` subclass instance, which has `update()` and `digest()` methods
|
||
|
- `digest()` finalizes the hash and makes it no longer usable
|
||
|
|
||
|
```ts
|
||
|
hash
|
||
|
.create()
|
||
|
.update(new Uint8Array([1, 3]))
|
||
|
.digest();
|
||
|
```
|
||
|
|
||
|
_Some_ hash functions can also receive `options` object, which can be either passed as a:
|
||
|
|
||
|
- second argument to hash function: `blake3('abc', { key: 'd', dkLen: 32 })`
|
||
|
- first argument to class initializer: `blake3.create({ context: 'e', dkLen: 32 })`
|
||
|
|
||
|
##### sha2: sha256, sha384, sha512 and others
|
||
|
|
||
|
```typescript
|
||
|
import { sha256, sha384, sha512, sha224, sha512_256, sha512_384 } from '@noble/hashes/sha2';
|
||
|
// also available as aliases:
|
||
|
// import ... from '@noble/hashes/sha256'
|
||
|
// import ... from '@noble/hashes/sha512'
|
||
|
|
||
|
// Variant A:
|
||
|
const h1a = sha256('abc');
|
||
|
|
||
|
// Variant B:
|
||
|
const h1b = sha256
|
||
|
.create()
|
||
|
.update(Uint8Array.from([1, 2, 3]))
|
||
|
.digest();
|
||
|
|
||
|
for (let hash of [sha384, sha512, sha224, sha512_256, sha512_384]) {
|
||
|
const res1 = hash('abc');
|
||
|
const res2 = hash
|
||
|
.create()
|
||
|
.update('def')
|
||
|
.update(Uint8Array.from([1, 2, 3]))
|
||
|
.digest();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
See [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
|
||
|
[the paper on truncated SHA512/256](https://eprint.iacr.org/2010/548.pdf).
|
||
|
|
||
|
##### sha3: FIPS, SHAKE, Keccak
|
||
|
|
||
|
```typescript
|
||
|
import {
|
||
|
sha3_224,
|
||
|
sha3_256,
|
||
|
sha3_384,
|
||
|
sha3_512,
|
||
|
keccak_224,
|
||
|
keccak_256,
|
||
|
keccak_384,
|
||
|
keccak_512,
|
||
|
shake128,
|
||
|
shake256,
|
||
|
} from '@noble/hashes/sha3';
|
||
|
const h5a = sha3_256('abc');
|
||
|
const h5b = sha3_256
|
||
|
.create()
|
||
|
.update(Uint8Array.from([1, 2, 3]))
|
||
|
.digest();
|
||
|
const h6a = keccak_256('abc');
|
||
|
const h7a = shake128('abc', { dkLen: 512 });
|
||
|
const h7b = shake256('abc', { dkLen: 512 });
|
||
|
```
|
||
|
|
||
|
See [FIPS PUB 202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
|
||
|
[Website](https://keccak.team/keccak.html).
|
||
|
|
||
|
Check out [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub)
|
||
|
|
||
|
##### sha3-addons: cSHAKE, KMAC, K12, M14, TurboSHAKE
|
||
|
|
||
|
```typescript
|
||
|
import {
|
||
|
cshake128,
|
||
|
cshake256,
|
||
|
kmac128,
|
||
|
kmac256,
|
||
|
k12,
|
||
|
m14,
|
||
|
turboshake128,
|
||
|
turboshake256,
|
||
|
tuplehash128,
|
||
|
tuplehash256,
|
||
|
parallelhash128,
|
||
|
parallelhash256,
|
||
|
keccakprg,
|
||
|
} from '@noble/hashes/sha3-addons';
|
||
|
const h7c = cshake128('abc', { personalization: 'def' });
|
||
|
const h7d = cshake256('abc', { personalization: 'def' });
|
||
|
const h7e = kmac128('key', 'message');
|
||
|
const h7f = kmac256('key', 'message');
|
||
|
const h7h = k12('abc');
|
||
|
const h7g = m14('abc');
|
||
|
const h7t1 = turboshake128('abc');
|
||
|
const h7t2 = turboshake256('def', { D: 0x05 });
|
||
|
const h7i = tuplehash128(['ab', 'c']); // tuplehash(['ab', 'c']) !== tuplehash(['a', 'bc']) !== tuplehash(['abc'])
|
||
|
// Same as k12/blake3, but without reduced number of rounds. Doesn't speedup anything due lack of SIMD and threading,
|
||
|
// added for compatibility.
|
||
|
const h7j = parallelhash128('abc', { blockLen: 8 });
|
||
|
// pseudo-random generator, first argument is capacity. XKCP recommends 254 bits capacity for 128-bit security strength.
|
||
|
// * with a capacity of 254 bits.
|
||
|
const p = keccakprg(254);
|
||
|
p.feed('test');
|
||
|
const rand1b = p.fetch(1);
|
||
|
```
|
||
|
|
||
|
- Full [NIST SP 800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf):
|
||
|
cSHAKE, KMAC, TupleHash, ParallelHash + XOF variants
|
||
|
- [Reduced-round Keccak](https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/):
|
||
|
- 🦘 K12 aka KangarooTwelve
|
||
|
- M14 aka MarsupilamiFourteen
|
||
|
- TurboSHAKE
|
||
|
- [KeccakPRG](https://keccak.team/files/CSF-0.1.pdf): Pseudo-random generator based on Keccak
|
||
|
|
||
|
##### ripemd160
|
||
|
|
||
|
```typescript
|
||
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
||
|
// function ripemd160(data: Uint8Array): Uint8Array;
|
||
|
const hash8 = ripemd160('abc');
|
||
|
const hash9 = ripemd160
|
||
|
.create()
|
||
|
.update(Uint8Array.from([1, 2, 3]))
|
||
|
.digest();
|
||
|
```
|
||
|
|
||
|
See [RFC 2286](https://datatracker.ietf.org/doc/html/rfc2286),
|
||
|
[Website](https://homes.esat.kuleuven.be/~bosselae/ripemd160.html)
|
||
|
|
||
|
##### blake2b, blake2s, blake3
|
||
|
|
||
|
```typescript
|
||
|
import { blake2b } from '@noble/hashes/blake2b';
|
||
|
import { blake2s } from '@noble/hashes/blake2s';
|
||
|
import { blake3 } from '@noble/hashes/blake3';
|
||
|
|
||
|
const h10a = blake2s('abc');
|
||
|
const b2params = { key: new Uint8Array([1]), personalization: t, salt: t, dkLen: 32 };
|
||
|
const h10b = blake2s('abc', b2params);
|
||
|
const h10c = blake2s
|
||
|
.create(b2params)
|
||
|
.update(Uint8Array.from([1, 2, 3]))
|
||
|
.digest();
|
||
|
|
||
|
// All params are optional
|
||
|
const h11 = blake3('abc', { dkLen: 256, key: 'def', context: 'fji' });
|
||
|
```
|
||
|
|
||
|
See [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693), [Website](https://www.blake2.net).
|
||
|
|
||
|
##### sha1: legacy hash
|
||
|
|
||
|
SHA1 was cryptographically broken, however, it was not broken for cases like HMAC.
|
||
|
|
||
|
See [RFC4226 B.2](https://datatracker.ietf.org/doc/html/rfc4226#appendix-B.2).
|
||
|
|
||
|
Don't use it for a new protocol.
|
||
|
|
||
|
```typescript
|
||
|
import { sha1 } from '@noble/hashes/sha1';
|
||
|
const h12 = sha1('def');
|
||
|
```
|
||
|
|
||
|
##### hmac
|
||
|
|
||
|
```typescript
|
||
|
import { hmac } from '@noble/hashes/hmac';
|
||
|
import { sha256 } from '@noble/hashes/sha2';
|
||
|
const mac1 = hmac(sha256, 'key', 'message');
|
||
|
const mac2 = hmac
|
||
|
.create(sha256, Uint8Array.from([1, 2, 3]))
|
||
|
.update(Uint8Array.from([4, 5, 6]))
|
||
|
.digest();
|
||
|
```
|
||
|
|
||
|
Matches [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104).
|
||
|
|
||
|
##### hkdf
|
||
|
|
||
|
```typescript
|
||
|
import { hkdf } from '@noble/hashes/hkdf';
|
||
|
import { sha256 } from '@noble/hashes/sha2';
|
||
|
import { randomBytes } from '@noble/hashes/utils';
|
||
|
const inputKey = randomBytes(32);
|
||
|
const salt = randomBytes(32);
|
||
|
const info = 'abc';
|
||
|
const dkLen = 32;
|
||
|
const hk1 = hkdf(sha256, inputKey, salt, info, dkLen);
|
||
|
|
||
|
// == same as
|
||
|
import * as hkdf from '@noble/hashes/hkdf';
|
||
|
import { sha256 } from '@noble/hashes/sha2';
|
||
|
const prk = hkdf.extract(sha256, inputKey, salt);
|
||
|
const hk2 = hkdf.expand(sha256, prk, info, dkLen);
|
||
|
```
|
||
|
|
||
|
Matches [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869).
|
||
|
|
||
|
##### pbkdf2
|
||
|
|
||
|
```typescript
|
||
|
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';
|
||
|
import { sha256 } from '@noble/hashes/sha2';
|
||
|
const pbkey1 = pbkdf2(sha256, 'password', 'salt', { c: 32, dkLen: 32 });
|
||
|
const pbkey2 = await pbkdf2Async(sha256, 'password', 'salt', { c: 32, dkLen: 32 });
|
||
|
const pbkey3 = await pbkdf2Async(sha256, Uint8Array.from([1, 2, 3]), Uint8Array.from([4, 5, 6]), {
|
||
|
c: 32,
|
||
|
dkLen: 32,
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Matches [RFC 2898](https://datatracker.ietf.org/doc/html/rfc2898).
|
||
|
|
||
|
##### scrypt
|
||
|
|
||
|
```typescript
|
||
|
import { scrypt, scryptAsync } from '@noble/hashes/scrypt';
|
||
|
const scr1 = scrypt('password', 'salt', { N: 2 ** 16, r: 8, p: 1, dkLen: 32 });
|
||
|
const scr2 = await scryptAsync('password', 'salt', { N: 2 ** 16, r: 8, p: 1, dkLen: 32 });
|
||
|
const scr3 = await scryptAsync(Uint8Array.from([1, 2, 3]), Uint8Array.from([4, 5, 6]), {
|
||
|
N: 2 ** 22,
|
||
|
r: 8,
|
||
|
p: 1,
|
||
|
dkLen: 32,
|
||
|
onProgress(percentage) {
|
||
|
console.log('progress', percentage);
|
||
|
},
|
||
|
maxmem: 2 ** 32 + 128 * 8 * 1, // N * r * p * 128 + (128*r*p)
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Conforms to [RFC 7914](https://datatracker.ietf.org/doc/html/rfc7914),
|
||
|
[Website](https://www.tarsnap.com/scrypt.html)
|
||
|
|
||
|
- `N, r, p` are work factors. To understand them, see [the blog post](https://blog.filippo.io/the-scrypt-parameters/).
|
||
|
- `dkLen` is the length of output bytes
|
||
|
- It is common to use N from `2**10` to `2**22` and `{r: 8, p: 1, dkLen: 32}`
|
||
|
- `onProgress` can be used with async version of the function to report progress to a user.
|
||
|
|
||
|
Memory usage of scrypt is calculated with the formula `N * r * p * 128 + (128 * r * p)`,
|
||
|
which means `{N: 2 ** 22, r: 8, p: 1}` will use 4GB + 1KB of memory. To prevent
|
||
|
DoS, we limit scrypt to `1GB + 1KB` of RAM used, which corresponds to
|
||
|
`{N: 2 ** 20, r: 8, p: 1}`. If you want to use higher values, increase
|
||
|
`maxmem` using the formula above.
|
||
|
|
||
|
_Note:_ noble supports `2**22` (4GB RAM) which is the highest amount amongst JS
|
||
|
libs. Many other implementations don't support it. We cannot support `2**23`,
|
||
|
because there is a limitation in JS engines that makes allocating
|
||
|
arrays bigger than 4GB impossible, but we're looking into other possible solutions.
|
||
|
|
||
|
##### argon2
|
||
|
|
||
|
> [!WARNING]
|
||
|
> Experimental: it may be removed at any time.
|
||
|
|
||
|
Argon2 [RFC 9106](https://datatracker.ietf.org/doc/html/rfc9106) implementation.
|
||
|
|
||
|
```ts
|
||
|
import { argon2d, argon2i, argon2id } from '@noble/hashes/argon2';
|
||
|
const result = argon2id('password', 'salt', { t: 2, m: 65536, p: 1 });
|
||
|
```
|
||
|
|
||
|
##### utils
|
||
|
|
||
|
```typescript
|
||
|
import { bytesToHex as toHex, randomBytes } from '@noble/hashes/utils';
|
||
|
console.log(toHex(randomBytes(32)));
|
||
|
```
|
||
|
|
||
|
- `bytesToHex` will convert `Uint8Array` to a hex string
|
||
|
- `randomBytes(bytes)` will produce cryptographically secure random `Uint8Array` of length `bytes`
|
||
|
|
||
|
##### All available imports
|
||
|
|
||
|
```js
|
||
|
import { sha256, sha384, sha512, sha224, sha512_256, sha512_384 } from '@noble/hashes/sha2';
|
||
|
// prettier-ignore
|
||
|
import {
|
||
|
sha3_224, sha3_256, sha3_384, sha3_512,
|
||
|
keccak_224, keccak_256, keccak_384, keccak_512,
|
||
|
shake128, shake256
|
||
|
} from '@noble/hashes/sha3';
|
||
|
// prettier-ignore
|
||
|
import {
|
||
|
cshake128, cshake256,
|
||
|
turboshake128, turboshake256,
|
||
|
kmac128, kmac256,
|
||
|
tuplehash256, parallelhash256,
|
||
|
k12, m14, keccakprg
|
||
|
} from '@noble/hashes/sha3-addons';
|
||
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
||
|
import { blake3 } from '@noble/hashes/blake3';
|
||
|
import { blake2b } from '@noble/hashes/blake2b';
|
||
|
import { blake2s } from '@noble/hashes/blake2s';
|
||
|
import { hmac } from '@noble/hashes/hmac';
|
||
|
import { hkdf } from '@noble/hashes/hkdf';
|
||
|
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';
|
||
|
import { scrypt, scryptAsync } from '@noble/hashes/scrypt';
|
||
|
|
||
|
import { sha1 } from '@noble/hashes/sha1'; // legacy
|
||
|
|
||
|
// small utility method that converts bytes to hex
|
||
|
import { bytesToHex as toHex } from '@noble/hashes/utils';
|
||
|
console.log(toHex(sha256('abc'))); // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
|
||
|
```
|
||
|
|
||
|
## Security
|
||
|
|
||
|
The library has been independently audited:
|
||
|
|
||
|
- at version 1.0.0, in Jan 2022, by [cure53](https://cure53.de)
|
||
|
- PDFs: [online](https://cure53.de/pentest-report_hashing-libs.pdf), [offline, in-repo](./audit/2022-01-05-cure53-audit-nbl2.pdf)
|
||
|
- [Changes since audit](https://github.com/paulmillr/noble-hashes/compare/1.0.0..main).
|
||
|
- Scope: everything, besides `blake3`, `sha3-addons`, `sha1` and `argon2`, which have not been audited
|
||
|
- The audit has been funded by [Ethereum Foundation](https://ethereum.org/en/) with help of [Nomic Labs](https://nomiclabs.io)
|
||
|
|
||
|
It is tested against property-based, cross-library and Wycheproof vectors,
|
||
|
and has fuzzing by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
|
||
|
|
||
|
If you see anything unusual: investigate and report.
|
||
|
|
||
|
### Constant-timeness
|
||
|
|
||
|
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
|
||
|
achieve [timing attack](https://en.wikipedia.org/wiki/Timing_attack) resistance
|
||
|
in a scripting language. Which means _any other JS library can't have
|
||
|
constant-timeness_. Even statically typed Rust, a language without GC,
|
||
|
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
|
||
|
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones.
|
||
|
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
|
||
|
|
||
|
### Memory dumping
|
||
|
|
||
|
The library shares state buffers between hash
|
||
|
function calls. The buffers are zeroed-out after each call. However, if an attacker
|
||
|
can read application memory, you are doomed in any case:
|
||
|
|
||
|
- At some point, input will be a string and strings are immutable in JS:
|
||
|
there is no way to overwrite them with zeros. For example: deriving
|
||
|
key from `scrypt(password, salt)` where password and salt are strings
|
||
|
- Input from a file will stay in file buffers
|
||
|
- Input / output will be re-used multiple times in application which means it could stay in memory
|
||
|
- `await anything()` will always write all internal variables (including numbers)
|
||
|
to memory. With async functions / Promises there are no guarantees when the code
|
||
|
chunk would be executed. Which means attacker can have plenty of time to read data from memory
|
||
|
- There is no way to guarantee anything about zeroing sensitive data without
|
||
|
complex tests-suite which will dump process memory and verify that there is
|
||
|
no sensitive data left. For JS it means testing all browsers (incl. mobile),
|
||
|
which is complex. And of course it will be useless without using the same
|
||
|
test-suite in the actual application that consumes the library
|
||
|
|
||
|
### Supply chain security
|
||
|
|
||
|
- **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.
|
||
|
- **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs
|
||
|
- **Rare releasing** is followed to ensure less re-audit need for end-users
|
||
|
- **Dependencies** are minimized and locked-down:
|
||
|
- If your app has 500 dependencies, any dep could get hacked and you'll be downloading
|
||
|
malware with every install. We make sure to use as few dependencies as possible
|
||
|
- We prevent automatic dependency updates by locking-down version ranges. Every update is checked with `npm-diff`
|
||
|
- **Dev Dependencies** are only used if you want to contribute to the repo. They are disabled for end-users:
|
||
|
- scure-base, scure-bip32, scure-bip39, micro-bmark and micro-should are developed by the same author and follow identical security practices
|
||
|
- prettier (linter), fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. The packages are big, which makes it hard to audit their source code thoroughly and fully
|
||
|
|
||
|
### Randomness
|
||
|
|
||
|
We're deferring to built-in
|
||
|
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
|
||
|
which is considered cryptographically secure (CSPRNG).
|
||
|
|
||
|
In the past, browsers had bugs that made it weak: it may happen again.
|
||
|
Implementing a userspace CSPRNG to get resilient to the weakness
|
||
|
is even worse: there is no reliable userspace source of quality entropy.
|
||
|
|
||
|
## Speed
|
||
|
|
||
|
Benchmarks measured on Apple M1 with macOS 12.
|
||
|
Note that PBKDF2 and Scrypt are tested with extremely high work factor.
|
||
|
To run benchmarks, execute `npm run bench:install` and then `npm run bench`
|
||
|
|
||
|
```
|
||
|
SHA256 32B x 1,219,512 ops/sec @ 820ns/op ± 2.58% (min: 625ns, max: 4ms)
|
||
|
SHA384 32B x 512,032 ops/sec @ 1μs/op
|
||
|
SHA512 32B x 509,943 ops/sec @ 1μs/op
|
||
|
SHA3-256, keccak256, shake256 32B x 199,600 ops/sec @ 5μs/op
|
||
|
Kangaroo12 32B x 336,360 ops/sec @ 2μs/op
|
||
|
Marsupilami14 32B x 298,418 ops/sec @ 3μs/op
|
||
|
BLAKE2b 32B x 379,794 ops/sec @ 2μs/op
|
||
|
BLAKE2s 32B x 515,995 ops/sec @ 1μs/op ± 1.07% (min: 1μs, max: 4ms)
|
||
|
BLAKE3 32B x 588,235 ops/sec @ 1μs/op ± 1.36% (min: 1μs, max: 5ms)
|
||
|
RIPEMD160 32B x 1,140,250 ops/sec @ 877ns/op ± 3.12% (min: 708ns, max: 6ms)
|
||
|
HMAC-SHA256 32B x 377,358 ops/sec @ 2μs/op
|
||
|
|
||
|
HKDF-SHA256 32B x 108,377 ops/sec @ 9μs/op
|
||
|
PBKDF2-HMAC-SHA256 262144 x 3 ops/sec @ 326ms/op
|
||
|
PBKDF2-HMAC-SHA512 262144 x 1 ops/sec @ 970ms/op
|
||
|
Scrypt r: 8, p: 1, n: 262144 x 1 ops/sec @ 616ms/op
|
||
|
```
|
||
|
|
||
|
Compare to native node.js implementation that uses C bindings instead of pure-js code:
|
||
|
|
||
|
```
|
||
|
SHA256 32B node x 1,302,083 ops/sec @ 768ns/op ± 10.54% (min: 416ns, max: 7ms)
|
||
|
SHA384 32B node x 975,609 ops/sec @ 1μs/op ± 11.32% (min: 625ns, max: 8ms)
|
||
|
SHA512 32B node x 983,284 ops/sec @ 1μs/op ± 11.24% (min: 625ns, max: 8ms)
|
||
|
SHA3-256 32B node x 910,746 ops/sec @ 1μs/op ± 12.19% (min: 666ns, max: 10ms)
|
||
|
keccak, k12, m14 are not implemented
|
||
|
BLAKE2b 32B node x 967,117 ops/sec @ 1μs/op ± 11.26% (min: 625ns, max: 9ms)
|
||
|
BLAKE2s 32B node x 1,055,966 ops/sec @ 947ns/op ± 11.07% (min: 583ns, max: 7ms)
|
||
|
BLAKE3 is not implemented
|
||
|
RIPEMD160 32B node x 1,002,004 ops/sec @ 998ns/op ± 10.66% (min: 625ns, max: 7ms)
|
||
|
HMAC-SHA256 32B node x 919,963 ops/sec @ 1μs/op ± 6.13% (min: 833ns, max: 5ms)
|
||
|
HKDF-SHA256 32 node x 369,276 ops/sec @ 2μs/op ± 13.59% (min: 1μs, max: 9ms)
|
||
|
PBKDF2-HMAC-SHA256 262144 node x 25 ops/sec @ 39ms/op
|
||
|
PBKDF2-HMAC-SHA512 262144 node x 7 ops/sec @ 132ms/op
|
||
|
Scrypt r: 8, p: 1, n: 262144 node x 1 ops/sec @ 523ms/op
|
||
|
```
|
||
|
|
||
|
It is possible to [make this library 4x+ faster](./benchmark/README.md) by
|
||
|
_doing code generation of full loop unrolls_. We've decided against it. Reasons:
|
||
|
|
||
|
- the library must be auditable, with minimum amount of code, and zero dependencies
|
||
|
- most method invocations with the lib are going to be something like hashing 32b to 64kb of data
|
||
|
- hashing big inputs is 10x faster with low-level languages, which means you should probably pick 'em instead
|
||
|
|
||
|
The current performance is good enough when compared to other projects; SHA256 takes only 900 nanoseconds to run.
|
||
|
|
||
|
## Contributing & testing
|
||
|
|
||
|
1. Clone the repository
|
||
|
2. `npm install` to install build dependencies like TypeScript
|
||
|
3. `npm run build` to compile TypeScript code
|
||
|
4. `npm run test` will execute all main tests. See [our approach to testing](./test/README.md)
|
||
|
5. `npm run test:dos` will test against DoS; by measuring function complexity. **Takes ~20 minutes**
|
||
|
6. `npm run test:big` will execute hashing on 4GB inputs,
|
||
|
scrypt with 1024 different `N, r, p` combinations, etc. **Takes several hours**. Using 8-32+ core CPU helps.
|
||
|
7. `npm run format` will fix lint issues
|
||
|
|
||
|
## Resources
|
||
|
|
||
|
Check out [paulmillr.com/noble](https://paulmillr.com/noble/)
|
||
|
for useful resources, articles, documentation and demos
|
||
|
related to the library.
|
||
|
|
||
|
## License
|
||
|
|
||
|
The MIT License (MIT)
|
||
|
|
||
|
Copyright (c) 2022 Paul Miller [(https://paulmillr.com)](https://paulmillr.com)
|
||
|
|
||
|
See LICENSE file.
|