README: update Field documentation, reformat with prettier

This commit is contained in:
Paul Miller 2023-08-11 10:23:19 +00:00
parent 05794c0283
commit d92c9d14ad
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B

@ -166,7 +166,12 @@ edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
// hash-to-curve, ristretto255 // hash-to-curve, ristretto255
import { utf8ToBytes } from '@noble/hashes/utils'; import { utf8ToBytes } from '@noble/hashes/utils';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { hashToCurve, encodeToCurve, RistrettoPoint, hash_to_ristretto255 } from '@noble/curves/ed25519'; import {
hashToCurve,
encodeToCurve,
RistrettoPoint,
hashToRistretto255,
} from '@noble/curves/ed25519';
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee'); const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg); hashToCurve(msg);
@ -179,7 +184,7 @@ RistrettoPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve // pre-hashed hash-to-curve
RistrettoPoint.hashToCurve(sha512(msg)); RistrettoPoint.hashToCurve(sha512(msg));
// full hash-to-curve including domain separation tag // full hash-to-curve including domain separation tag
hash_to_ristretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' }); hashToRistretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' });
``` ```
#### ed448, X448, decaf448 #### ed448, X448, decaf448
@ -207,7 +212,7 @@ edwardsToMontgomeryPub(ed448.getPublicKey(ed448.utils.randomPrivateKey()));
// hash-to-curve, decaf448 // hash-to-curve, decaf448
import { utf8ToBytes } from '@noble/hashes/utils'; import { utf8ToBytes } from '@noble/hashes/utils';
import { shake256 } from '@noble/hashes/sha3'; import { shake256 } from '@noble/hashes/sha3';
import { hashToCurve, encodeToCurve, DecafPoint, hash_to_decaf448 } from '@noble/curves/ed448'; import { hashToCurve, encodeToCurve, DecafPoint, hashToDecaf448 } from '@noble/curves/ed448';
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee'); const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg); hashToCurve(msg);
@ -220,7 +225,7 @@ DecafPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve // pre-hashed hash-to-curve
DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 })); DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 }));
// full hash-to-curve including domain separation tag // full hash-to-curve including domain separation tag
hash_to_decaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' }); hashToDecaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' });
``` ```
Same RFC7748 / RFC8032 / IRTF draft are followed. Same RFC7748 / RFC8032 / IRTF draft are followed.
@ -295,8 +300,9 @@ type CHash = {
``` ```
**Message hash** is expected instead of message itself: **Message hash** is expected instead of message itself:
- `.sign(msgHash, privKey)` is default behavior, you need to do `msgHash = hash(msg)` before
- `.sign(msg, privKey, {prehash: true})` if you want the library to handle hashing for you - `sign(msgHash, privKey)` is default behavior, assuming you pre-hash msg with sha2, or other hash
- `sign(msg, privKey, {prehash: true})` option can be used if you want to pass the message itself
**Weierstrass points:** **Weierstrass points:**
@ -760,26 +766,39 @@ mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
``` ```
Field operations are not constant-time: they are using JS bigints, see [security](#security).
The fact is mostly irrelevant, but the important method to keep in mind is `pow`,
which may leak exponent bits, when used naïvely.
`mod.Field` is always **field over prime**. Non-prime fields aren't supported for now.
We don't test for prime-ness for speed and because algorithms are probabilistic anyway.
Initializing a non-prime field could make your app suspectible to
DoS (infilite loop) on Tonelli-Shanks square root calculation.
Unlike `mod.invert`, `mod.invertBatch` won't throw on `0`: make sure to throw an error yourself.
#### Creating private keys from hashes #### Creating private keys from hashes
Suppose you have `sha256(something)` (e.g. from HMAC) and you want to make a private key from it. You can't simply make a 32-byte private key from a 32-byte hash.
Even though p256 or secp256k1 may have 32-byte private keys, Doing so will make the key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/).
and sha256 output is also 32-byte, you can't just use it and reduce it modulo `CURVE.n`.
Doing so will make the result key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/). It may be tempting to do `sha256(something)` (or pbkdf2 / hmac-sha256),
convert the result to bigint, and modulo-reduce the result output by `CURVE.n`,
but you need more bytes for proper security.
To avoid the bias, we implement FIPS 186 B.4.1, which allows to take arbitrary To make the bias negligible, we follow [FIPS 186-5 A.2](https://csrc.nist.gov/publications/detail/fips/186/5/final)
byte array and produce valid scalars / private keys with bias being neglible. and [h2c standard](https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-hashing-to-a-finite-field).
This means, for 32-byte key, we would need 48-byte hash to get 2^-128 bias, which matches curve security level.
Use [hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points) if you need Use [abstract/hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points) if you need
hashing to **public keys**; the function in the module instead operates on **private keys**. to hash to **public key**. `hashToPrivateScalar()` operates instead on **private keys**.
```ts ```ts
import { p256 } from '@noble/curves/p256'; import { p256 } from '@noble/curves/p256';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { hkdf } from '@noble/hashes/hkdf'; import { hkdf } from '@noble/hashes/hkdf';
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2) const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes const derived = hkdf(sha256, someKey, undefined, 'application', 48); // 48 bytes for 32-byte priv
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n); const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
``` ```
@ -805,18 +824,36 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
## Security ## Security
1. The library has been audited in Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com): 1. The library has been independently audited:
- in Feb 2023 by [Trail of Bits](https://www.trailofbits.com):
[PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf). [PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf).
The audit has been funded by [Ryan Shea](https://www.shea.io). Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`, and top-level modules `_shortw_utils` and `secp256k1`. See [changes since audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main). The audit has been funded by [Ryan Shea](https://www.shea.io).
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. Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`,
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. and top-level modules `_shortw_utils` and `secp256k1`.
See [changes since v0.7.3 audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
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. As for devDependencies used by the library: 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.
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing) are developed by us We consider infrastructure attacks like rogue NPM modules very important;
and follow the same practices such as: minimal library size, auditability, signed releases that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings.
- prettier (linter), fast-check (property-based testing), If your app uses 500 dependencies, any dep could get hacked and you'll be
typescript versions are locked and rarely updated. Every update is checked with `npm-diff`. downloading malware with every `npm install`. Our goal is to minimize this attack vector.
As for devDependencies used by the library:
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing)
are developed by us and follow the same practices such as: minimal library size, auditability,
signed releases
- prettier (linter), fast-check (property-based testing), typescript versions
are locked and rarely updated. Every update is checked with `npm-diff`.
The packages are big, which makes it hard to audit their source code thoroughly and fully. The packages are big, which makes it hard to audit their source code thoroughly and fully.
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them. - They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.