npm package diff

Package: @noble/hashes

Versions: 1.3.2 - 1.3.3

File: package/README.md

Index: package/README.md
===================================================================
--- package/README.md
+++ package/README.md
@@ -1,83 +1,77 @@
 # noble-hashes
 
-Audited & minimal JS implementation of SHA2, SHA3, RIPEMD, BLAKE2/3, HMAC, HKDF, PBKDF2 & Scrypt.
+Audited & minimal JS implementation of SHA, RIPEMD, BLAKE, HMAC, HKDF, PBKDF, Scrypt & Argon2.
 
 - 🔒 [**Audited**](#security) by an independent security firm
 - ðŸ”ŧ Tree-shaking-friendly: use only what's necessary, other code won't be included
 - 🏎 Ultra-fast, hand-optimized for caveats of JS engines
 - 🔍 Unique tests ensure correctness: chained tests, sliding window tests, DoS tests, fuzzing
 - 🔁 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, TupleHash, KangarooTwelve and MarsupilamiFourteen
+- ðŸĶ˜ SHA3 supports Keccak, cSHAKE, KangarooTwelve, MarsupilamiFourteen and TurboSHAKE
 - ðŸŠķ Just 3.4k lines / 17KB gzipped. SHA256-only is 240 lines / 3KB gzipped
 
 The library's initial development was funded by [Ethereum Foundation](https://ethereum.org/).
 
 ### This library belongs to _noble_ crypto
 
 > **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
 
-- No dependencies, protection against supply chain attacks
-- Auditable TypeScript / JS code
-- Supported on all major platforms
-- Releases are signed with PGP keys and built transparently with NPM provenance
-- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
+- 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),
-  4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
-  [ed25519](https://github.com/paulmillr/noble-ed25519)
+  [hashes](https://github.com/paulmillr/noble-hashes)
+- [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/[email protected]/node/npm_specifiers).
 For React Native, you may need a [polyfill for getRandomValues](https://github.com/LinusU/react-native-get-random-values).
-If you don't like NPM, a standalone [noble-hashes.js](https://github.com/paulmillr/noble-hashes/releases) is also available.
+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/sha256'; // ECMAScript modules (ESM) and Common.js
 // import { sha256 } from 'npm:@noble/[email protected]/sha256'; // 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'))
-
-// sha384 is here, because it uses same internals as sha512
-import { sha512, sha512_256, sha384 } from '@noble/hashes/sha512';
-// 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, kmac128, kmac256,
-  k12, m14,
-  tuplehash256, parallelhash256, 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
 ```
 
-## API
+- [Implementations](#implementations)
+  - [sha2: sha256, sha384, sha512, sha512_256](#sha2-sha256-sha384-sha512-sha512_256)
+  - [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:
 
 - can be called directly, with `Uint8Array`.
 - return `Uint8Array`
@@ -107,26 +101,10 @@
 
 - second argument to hash function: `blake3('abc', { key: 'd', dkLen: 32 })`
 - first argument to class initializer: `blake3.create({ context: 'e', dkLen: 32 })`
 
-## Modules
+##### sha2: sha256, sha384, sha512, sha512_256
 
-- [SHA2 (sha256, sha384, sha512, sha512_256)](#sha2-sha256-sha384-sha512-sha512_256)
-- [SHA3 (FIPS, SHAKE, Keccak)](#sha3-fips-shake-keccak)
-- [SHA3 Addons (cSHAKE, KMAC, KangarooTwelve, MarsupilamiFourteen)](#sha3-addons-cshake-kmac-tuplehash-parallelhash-kangarootwelve-marsupilamifourteen)
-- [RIPEMD-160](#ripemd-160)
-- [BLAKE2b, BLAKE2s](#blake2b-blake2s)
-- [BLAKE3](#blake3)
-- [SHA1 (legacy)](#sha1-legacy)
-- [HMAC](#hmac)
-- [HKDF](#hkdf)
-- [PBKDF2](#pbkdf2)
-- [Scrypt](#scrypt)
-- [ESKDF](#eskdf)
-- [utils](#utils)
-
-##### SHA2 (sha256, sha384, sha512, sha512_256)
-
 ```typescript
 import { sha256 } from '@noble/hashes/sha256';
 const h1a = sha256('abc');
 const h1b = sha256
@@ -162,9 +140,9 @@
 
 See [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
 [the paper on SHA512/256](https://eprint.iacr.org/2010/548.pdf).
 
-##### SHA3 (FIPS, SHAKE, Keccak)
+##### sha3: FIPS, SHAKE, Keccak
 
 ```typescript
 import {
   sha3_224,
@@ -192,9 +170,9 @@
 [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, TupleHash, ParallelHash, KangarooTwelve, MarsupilamiFourteen)
+##### sha3-addons: cSHAKE, KMAC, K12, M14, TurboSHAKE
 
 ```typescript
 import {
   cshake128,
@@ -202,8 +180,10 @@
   kmac128,
   kmac256,
   k12,
   m14,
+  turboshake128,
+  turboshake256,
   tuplehash128,
   tuplehash256,
   parallelhash128,
   parallelhash256,
@@ -214,8 +194,10 @@
 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 });
@@ -227,30 +209,30 @@
 ```
 
 - Full [NIST SP 800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf):
   cSHAKE, KMAC, TupleHash, ParallelHash + XOF variants
-- ðŸĶ˜ K12 ([KangarooTwelve Paper](https://keccak.team/files/KangarooTwelve.pdf),
-  [RFC Draft](https://www.ietf.org/archive/id/draft-irtf-cfrg-kangarootwelve-06.txt))
-  and M14 aka MarsupilamiFourteen are basically parallel versions of Keccak with
-  reduced number of rounds (same as Blake3 and ParallelHash).
+- [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
 
-##### RIPEMD-160
+##### ripemd160
 
 ```typescript
 import { ripemd160 } from '@noble/hashes/ripemd160';
 // function ripemd160(data: Uint8Array): Uint8Array;
 const hash8 = ripemd160('abc');
-const hash9 = ripemd160()
+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
+##### blake2b, blake2s, blake3
 
 ```typescript
 import { blake2b } from '@noble/hashes/blake2b';
 import { blake2s } from '@noble/hashes/blake2s';
@@ -260,22 +242,18 @@
 const h10c = blake2s
   .create(b2params)
   .update(Uint8Array.from([1, 2, 3]))
   .digest();
-```
 
-See [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693), [Website](https://www.blake2.net).
-
-##### BLAKE3
-
-```typescript
 import { blake3 } from '@noble/hashes/blake3';
 // All params are optional
 const h11 = blake3('abc', { dkLen: 256, key: 'def', context: 'fji' });
 ```
 
-##### SHA1 (legacy)
+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).
 
@@ -285,20 +263,23 @@
 import { sha1 } from '@noble/hashes/sha1';
 const h12 = sha1('def');
 ```
 
-##### HMAC
+##### hmac
 
 ```typescript
 import { hmac } from '@noble/hashes/hmac';
 import { sha256 } from '@noble/hashes/sha256';
 const mac1 = hmac(sha256, 'key', 'message');
-const mac2 = hmac.create(sha256, Uint8Array.from([1, 2, 3])).update(Uint8Array.from([4, 5, 6])).digest();
+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
+##### hkdf
 
 ```typescript
 import { hkdf } from '@noble/hashes/hkdf';
 import { sha256 } from '@noble/hashes/sha256';
@@ -317,9 +298,9 @@
 ```
 
 Matches [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869).
 
-##### PBKDF2
+##### pbkdf2
 
 ```typescript
 import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';
 import { sha256 } from '@noble/hashes/sha256';
@@ -332,9 +313,9 @@
 ```
 
 Matches [RFC 2898](https://datatracker.ietf.org/doc/html/rfc2898).
 
-##### Scrypt
+##### scrypt
 
 ```typescript
 import { scrypt, scryptAsync } from '@noble/hashes/scrypt';
 const scr1 = scrypt('password', 'salt', { N: 2 ** 16, r: 8, p: 1, dkLen: 32 });
@@ -369,47 +350,17 @@
 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
+##### argon2
 
 Experimental Argon2 RFC 9106 implementation. It may be removed at any time.
 
 ```ts
 import { argon2d, argon2i, argon2id } from '@noble/hashes/argon2';
 const result = argon2id('password', 'salt', { t: 2, m: 65536, p: 1 });
 ```
 
-##### ESKDF
-
-A tiny stretched KDF for various applications like AES key-gen. Takes >= 2 seconds to execute.
-
-Takes following params:
-
-- `username` - username, email, or identifier, min: 8 characters, should have enough entropy
-- `password` - min: 8 characters, should have enough entropy
-
-Produces ESKDF instance that has `deriveChildKey(protocol, accountId[, options])` function.
-
-- `protocol` - 3-15 character protocol name
-- `accountId` - numeric identifier of account
-- `options` - `keyLength: 32` with specified key length (default is 32),
-  or `modulus: 2n ** 221n - 17n` with specified modulus. It will fetch modulus + 64 bits of
-  data, execute modular division. The result will have negligible bias as per FIPS 186 B.4.1.
-  Can be used to generate, for example, elliptic curve keys.
-
-Takes username and password, then takes protocol name and account id.
-
-```typescript
-import { eskdf } from '@noble/hashes/eskdf';
-const kdf = await eskdf('example@university', 'beginning-new-example');
-console.log(kdf.fingerprint);
-const key1 = kdf.deriveChildKey('aes', 0);
-const key2 = kdf.deriveChildKey('aes', 0, { keyLength: 16 });
-const ecc1 = kdf.deriveChildKey('ecc', 0, { modulus: 2n ** 252n - 27742317777372353535851937790883648493n })
-kdf.expire();
-```
-
 ##### utils
 
 ```typescript
 import { bytesToHex as toHex, randomBytes } from '@noble/hashes/utils';
@@ -418,51 +369,111 @@
 
 - `bytesToHex` will convert `Uint8Array` to a hex string
 - `randomBytes(bytes)` will produce cryptographically secure random `Uint8Array` of length `bytes`
 
+##### All available imports
+
+```js
+// sha384 is here, because it uses same internals as sha512
+import { sha512, sha512_256, sha384 } from '@noble/hashes/sha512';
+// 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,
+  k12, m14,
+  turboshake128, turboshake256,
+  kmac128, kmac256,
+  tuplehash256, parallelhash256, 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
 
-Noble is production-ready.
+The library has been independently audited:
 
-1. The library has been audited in Jan 2022 by an independent security firm
-   cure53: [PDF](https://cure53.de/pentest-report_hashing-libs.pdf).
-   No vulnerabilities have been found. The audit has been funded by
-   [Ethereum Foundation](https://ethereum.org/en/) with help of [Nomic Labs](https://nomiclabs.io).
-   Modules `blake3`, `sha3-addons`, `sha1` and `argon2` have not been audited.
-   See [changes since audit](https://github.com/paulmillr/noble-hashes/compare/1.0.0..main).
-2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
-   You can run the fuzzer by yourself to check it.
-3. [Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations:
-   _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
-   achieve 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.
-4. Memory dump considerations: 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
+- 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)
 
-We consider infrastructure attacks like rogue NPM modules very important; that's
-why it's crucial to minimize the amount of 3rd-party dependencies & native bindings.
-If your app uses 500 dependencies, any dep could get hacked and you'll be downloading
-malware with every `npm install`. Our goal is to minimize this attack vector.
+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.
@@ -524,8 +535,14 @@
 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.
 
+## 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)