60 Commits
1.1.0 ... 1.2.0

Author SHA1 Message Date
Paul Miller
0d7756dceb Release 1.2.0. 2023-08-23 20:00:32 +02:00
Paul Miller
b716b4603f Update lockfile for 1.2 2023-08-23 19:58:55 +02:00
Paul Miller
d7a139822d Release 1.2.0. 2023-08-23 19:55:35 +02:00
Paul Miller
fb6c379a26 Update README 2023-08-23 19:48:52 +02:00
Paul Miller
eeac255c88 update noble-hashes to 1.3.2 2023-08-23 19:45:47 +02:00
Paul Miller
925fc3f810 modular: adjust getFieldsBytseLength 2023-08-23 19:43:55 +02:00
Paul Miller
eb8e7ec964 hash-to-curve, weierstrass, bls, ed: upgrade h2c comments to rfc 9380 2023-08-23 19:43:14 +02:00
Paul Miller
e7ac5e85d3 poseidon: refactor params 2023-08-21 18:16:40 +02:00
Paul Miller
d285fcce06 modular: Document FpPow 2023-08-21 17:52:21 +02:00
Paul Miller
ef667bb404 poseidon: refactor validateOpts, fix tests 2023-08-21 17:48:34 +02:00
Paul Miller
62749382e7 poseidon: remove default sboxPower: 5 2023-08-21 17:04:58 +02:00
Paul Miller
f90e871725 weierstrass: prohibit (0, 0, 0) in assertValidity 2023-08-21 16:05:53 +02:00
Paul Miller
f049398718 modular: bring back 1.1.0 hashToPrivateScalar for clean diff 2023-08-18 23:14:08 +02:00
Paul Miller
ca99179bd8 bls, modular: lint 2023-08-18 23:09:53 +02:00
Paul Miller
1545230ee5 modular, weierstrass, bls: use new mapHashToField 2023-08-18 23:08:46 +02:00
Paul Miller
2ce3b825f8 readme 2023-08-16 02:36:24 +02:00
Paul Miller
8315fe3580 readme 2023-08-16 02:33:10 +02:00
Paul Miller
9b7889e16f README: improve docs for ecdh 2023-08-16 02:14:41 +02:00
Paul Miller
e8b9509c16 abstract/modular: add more comments everywhere 2023-08-11 12:23:52 +02:00
Paul Miller
d92c9d14ad README: update Field documentation, reformat with prettier 2023-08-11 12:23:19 +02:00
Paul Miller
05794c0283 weierstrass, bls: improve randomPrivateKey security and decrease bias 2023-08-11 12:22:37 +02:00
Paul Miller
ca5583f713 ed25519, ed448: rename hash_to_ristretto to hashToRistretto. And decaf 2023-08-10 20:01:13 +02:00
Paul Miller
8c48abe16a Lint 2023-08-08 15:43:31 +02:00
Paul Miller
08bb00cc8f poseidon: prohibit sBoxPower other than 3, 5, 7 2023-08-08 15:43:14 +02:00
Paul Miller
1ef16033fe readme 2023-08-07 13:54:02 +02:00
Paul Miller
113b6d7c00 readme 2023-08-07 13:48:18 +02:00
Paul Miller
5c3dc0be50 README: more blog posts 2023-08-07 13:45:38 +02:00
Paul Miller
e7d01f4038 Update README.md 2023-08-07 13:11:30 +02:00
Paul Miller
9a39625eda test: lint 2023-08-05 11:25:56 +02:00
Paul Miller
af8462b09e tests/bls12: fix crashes on zero messages 2023-08-05 10:56:52 +02:00
Paul Miller
bfd9ae040d readme: add alt_bn128 2023-08-05 01:19:42 +02:00
Paul Miller
2bd437df4e readme 2023-08-05 00:47:59 +02:00
Paul Miller
b0af0a8977 readme 2023-08-05 00:31:41 +02:00
Paul Miller
aee10c8141 readme 2023-07-18 09:11:24 +02:00
Paul Miller
ff92bafb6f readme 2023-07-18 09:09:01 +02:00
Paul Miller
54679ff788 Usage 2023-07-18 09:08:28 +02:00
Paul Miller
ee4571c7a1 readme: toc 2023-07-18 09:07:11 +02:00
Paul Miller
fe7afdd392 readme 2023-07-16 06:31:52 +02:00
Paul Miller
dba2f0e732 lint 2023-07-12 23:58:30 +02:00
Paul Miller
52c5df0264 utils: add PURE flag 2023-07-12 20:28:45 +02:00
Paul Miller
ebea4a4bcd weierstrass, bls12-381: adjust var names for typescript flag 2023-07-12 20:28:38 +02:00
Paul Miller
33a53006f7 build: update esbuild 2023-07-12 20:28:11 +02:00
Paul Miller
549e286ef0 package.json: declare side-effects free 2023-07-12 20:26:12 +02:00
Paul Miller
3f0c0b59f1 readme 2023-07-11 19:00:56 +02:00
Paul Miller
62205347e1 readme for finalExponentiate 2023-07-11 18:59:40 +02:00
Paul Miller
476e75104f Merge pull request #62 from steveluscher/pure-and-twisted
Add pure annotation to all calls to `twistedEdwards`
2023-07-01 04:27:31 +02:00
steveluscher
413725cfb3 Add pure annotation to all calls to twistedEdwards
This PR makes it so that if you only use _one_ export:

```ts
import { ed25519 } from '@noble/curves`;
```

…then only the `twistedEdwards` call that constructs that export will remain after bundling and tree-shaking.

Before this change, the compiled bundle contains all the code that constructs `ed25519ph` and `ed25519ctx` remains.

```js
var ed25519 = twistedEdwards(ed25519Defaults);
function ed25519_domain(data, ctx, phflag) {
  if (ctx.length > 255)
    throw new Error("Context is too big");
  return concatBytes(utf8ToBytes("SigEd25519 no Ed25519 collisions"), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data);
}
twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
twistedEdwards({
  ...ed25519Defaults,
  domain: ed25519_domain,
  prehash: sha512
});
```

```js
var ed25519 = twistedEdwards(ed25519Defaults);
```
2023-06-30 17:36:16 +00:00
Paul Miller
cf17f7fe01 readme 2023-06-28 17:33:13 +02:00
Paul Miller
49fb90ae9a Add README link to new library noble-ciphers 2023-06-28 16:04:09 +02:00
Paul Miller
309d29a084 Merge pull request #56 from CoinSpace/extraentropy
fix: check extraEntropy according to the spec
2023-06-28 14:44:56 +02:00
Evgeny Vlasenko
d3aa051770 feat: tests for extraEntropy 2023-06-28 16:12:44 +04:00
Paul Miller
5609ec7644 Adjust readme docs on sig key recovery 2023-06-27 01:38:02 +02:00
Paul Miller
af8c1eebee Merge pull request #59 from stknob/decaf448
Add decaf448
2023-06-27 00:32:53 +02:00
Stefan Knoblich
08ea57ce5c Expand ristretto255 and ed448 + decaf448 README section
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Stefan Knoblich
ee3d3815b4 Add benchmarks for hash_to_ristretto255 and hash_to_decaf448
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Stefan Knoblich
f471405798 Add benchmarks for ristretto255 and decaf448
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Stefan Knoblich
e3a4bbffe9 Add decaf448
Based on draft-irtf-cfrg-ristretto255-decaf448-07,
draft-irtf-cfrg-hash-to-curve-16 and the ristretto255 implementation.

Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Paul Miller
c2edc97868 Merge pull request #58 from sublimator/nd-validate-dst-as-stringoruint8array-closes-57-2023-06-22
fix: validate hash_to_field DST as stringOrUint8Array (closes #57)
2023-06-23 00:27:24 +02:00
Nicholas Dudfield
bf70ba9776 fix: validate hash_to_field DST as stringOrUint8Array (closes #57) 2023-06-22 07:19:08 +07:00
Evgeny Vlasenko
c71920722c fix: check extraEntropy according to the spec 2023-06-16 19:43:12 +04:00
25 changed files with 945 additions and 352 deletions

435
README.md
View File

@@ -9,26 +9,23 @@ Audited & minimal JS implementation of elliptic curve cryptography.
- ➰ Short Weierstrass, Edwards, Montgomery curves
- ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
- 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
- #Hash-to-curve
for encoding or hashing an arbitrary string to an elliptic curve point
- #hash-to-curve for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ Poseidon ZK-friendly hash
Check out [Upgrading](#upgrading) if you've previously used single-feature noble
packages. See [Resources](#resources) for articles and real-world software that uses curves.
### 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 in all major browsers and stable node.js versions
- All releases are signed with PGP keys
- 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:
[curves](https://github.com/paulmillr/noble-curves)
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1),
[ed25519](https://github.com/paulmillr/noble-ed25519)),
[hashes](https://github.com/paulmillr/noble-hashes)
[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)
## Usage
@@ -39,33 +36,52 @@ For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/m
For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available.
The library is tree-shaking-friendly and does not expose root entry point as
`@noble/curves`. Instead, you need to import specific primitives.
This is done to ensure small size of your apps.
The package consists of two parts:
* [Implementations](#implementations), utilizing one dependency [noble-hashes](https://github.com/paulmillr/noble-hashes),
providing ready-to-use:
- NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521
- SECG curve secp256k1
- ed25519 / curve25519 / x25519 / ristretto255, edwards448 / curve448 / x448
- pairing-friendly curves bls12-381, bn254
- [pasta](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) curves
2. [Abstract](#abstract-api), zero-dependency elliptic curve algorithms
- [Implementations](#implementations)
- [ECDSA signature scheme](#ecdsa-signature-scheme)
- [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy)
- [ECDH (Elliptic Curve Diffie-Hellman)](#ecdh-elliptic-curve-diffie-hellman)
- [Schnorr signatures over secp256k1, BIP340](#schnorr-signatures-over-secp256k1-bip340)
- [ed25519, X25519, ristretto255](#ed25519-x25519-ristretto255)
- [ed448, X448, decaf448](#ed448-x448-decaf448)
- [bls12-381](#bls12-381)
- [All available imports](#all-available-imports)
- [Accessing a curve's variables](#accessing-a-curves-variables)
- [Abstract API](#abstract-api)
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
- [Creating private keys from hashes](#creating-private-keys-from-hashes)
- [abstract/utils: Useful utilities](#abstractutils-useful-utilities)
- [Security](#security)
- [Speed](#speed)
- [Contributing & testing](#contributing--testing)
- [Upgrading](#upgrading)
- [Resources](#resources)
- [Demos](#demos)
- [Projects using curves](#projects-using-curves)
- [License](#license)
### Implementations
#### Generic example for all curves, secp256k1
Implementations are utilizing [noble-hashes](https://github.com/paulmillr/noble-hashes).
[Abstract API](#abstract-api) doesn't depend on them: you can use a different hashing library.
#### ECDSA signature scheme
Generic example that works for all curves, shown for secp256k1:
```ts
// Each curve has similar methods
// import * from '@noble/curves'; // Error: use sub-imports, to ensure small app size
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv);
const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv);
const msg = new Uint8Array(32).fill(1); // message hash (not message) in ecdsa
const sig = secp256k1.sign(msg, priv); // `{prehash: true}` option is available
const isValid = secp256k1.verify(sig, msg, pub) === true;
// hex strings are also supported besides Uint8Arrays:
@@ -73,29 +89,22 @@ const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c12623
const pub2 = secp256k1.getPublicKey(privHex);
```
#### All imports
```typescript
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { p256 } from '@noble/curves/p256';
import { p384 } from '@noble/curves/p384';
import { p521 } from '@noble/curves/p521';
import { pallas, vesta } from '@noble/curves/pasta';
import { bls12_381 } from '@noble/curves/bls12-381';
import { bn254 } from '@noble/curves/bn254';
import { jubjub } from '@noble/curves/jubjub';
```
#### ECDSA public key recovery & ECDH
#### ECDSA public key recovery & extra entropy
```ts
sig.recoverPublicKey(msg).toRawBytes(); // === pub; // public key recovery
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
sig.recoverPublicKey(msg) === pub; // public key recovery
```
#### ECDH (Elliptic Curve Diffie-Hellman)
```ts
// 1. The output includes parity byte. Strip it using shared.slice(1)
// 2. The output is not hashed. More secure way is sha256(shared) or hkdf(shared)
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH
const shared = secp256k1.getSharedSecret(priv, someonesPub);
```
#### Schnorr signatures over secp256k1 (BIP340)
@@ -129,7 +138,6 @@ It has SUF-CMA (strong unforgeability under chosen message attacks).
and additionally provides non-repudiation with SBS [(Strongly Binding Signatures)](https://eprint.iacr.org/2020/1244).
X25519 follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
```ts
// Variants from RFC8032: with context, prehashed
@@ -147,17 +155,36 @@ x25519.getPublicKey(x25519.utils.randomPrivateKey());
import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
```
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
```ts
// hash-to-curve, ristretto255
import { hashToCurve, encodeToCurve, RistrettoPoint } from '@noble/curves/ed25519';
import { utf8ToBytes } from '@noble/hashes/utils';
import { sha512 } from '@noble/hashes/sha512';
import {
hashToCurve,
encodeToCurve,
RistrettoPoint,
hashToRistretto255,
} from '@noble/curves/ed25519';
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg);
const rp = RistrettoPoint.fromHex(
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
);
RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso coffee');
// also has add(), equals(), multiply(), toRawBytes() methods
RistrettoPoint.BASE.multiply(2n).add(rp).subtract(RistrettoPoint.BASE).toRawBytes();
RistrettoPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve
RistrettoPoint.hashToCurve(sha512(msg));
// full hash-to-curve including domain separation tag
hashToRistretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' });
```
#### ed448, X448
#### ed448, X448, decaf448
```ts
import { ed448 } from '@noble/curves/ed448';
@@ -167,17 +194,65 @@ const msg = new TextEncoder().encode('whatsup');
const sig = ed448.sign(msg, priv);
ed448.verify(sig, msg, pub);
import { ed448ph, ed448ctx, x448, hashToCurve, encodeToCurve } from '@noble/curves/ed448';
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
// Variants from RFC8032: prehashed
import { ed448ph } from '@noble/curves/ed448';
```
Same RFC7748 / RFC8032 are followed.
ECDH using Curve448 aka X448, follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
```ts
import { x448 } from '@noble/curves/ed448';
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
// ed448 => x448 conversion
import { edwardsToMontgomeryPub } from '@noble/curves/ed448';
edwardsToMontgomeryPub(ed448.getPublicKey(ed448.utils.randomPrivateKey()));
```
decaf448 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
```ts
import { utf8ToBytes } from '@noble/hashes/utils';
import { shake256 } from '@noble/hashes/sha3';
import { hashToCurve, encodeToCurve, DecafPoint, hashToDecaf448 } from '@noble/curves/ed448';
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg);
const dp = DecafPoint.fromHex(
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75'
);
DecafPoint.BASE.multiply(2n).add(dp).subtract(DecafPoint.BASE).toRawBytes();
DecafPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve
DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 }));
// full hash-to-curve including domain separation tag
hashToDecaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' });
```
Same RFC7748 / RFC8032 / IRTF draft are followed.
#### bls12-381
See [abstract/bls](#abstractbls-barreto-lynn-scott-curves).
#### All available imports
```typescript
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { p256 } from '@noble/curves/p256';
import { p384 } from '@noble/curves/p384';
import { p521 } from '@noble/curves/p521';
import { pallas, vesta } from '@noble/curves/pasta';
import { bls12_381 } from '@noble/curves/bls12-381';
import { bn254 } from '@noble/curves/bn254'; // also known as alt_bn128
import { jubjub } from '@noble/curves/jubjub';
import { bytesToHex, hexToBytes, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
```
#### Accessing a curve's variables
```ts
@@ -199,17 +274,6 @@ Precomputes are enabled for weierstrass and edwards BASE points of a curve. You
could precompute any other point (e.g. for ECDH) using `utils.precompute()`
method: check out examples.
There are following zero-dependency algorithms:
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
- [abstract/utils: General utilities](#abstractutils-general-utilities)
### abstract/weierstrass: Short Weierstrass curve
```ts
@@ -233,7 +297,7 @@ const secq256k1 = weierstrass({
randomBytes,
});
// Replace weierstrass with weierstrassPoints if you don't need ECDSA, hash, hmac, randomBytes
// Replace weierstrass() with weierstrassPoints() if you don't need ECDSA, hash, hmac, randomBytes
```
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass`
@@ -254,6 +318,11 @@ type CHash = {
};
```
**Message hash** is expected instead of message itself:
- `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:**
1. Exported as `ProjectivePoint`
@@ -349,6 +418,7 @@ More examples:
const priv = secq256k1.utils.randomPrivateKey();
secq256k1.getPublicKey(priv); // Convert private key to public.
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
const sig2 = secq256k1.sign(msg, priv, { prehash: true }); // hash(msg)
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
const Point = secq256k1.ProjectivePoint;
@@ -496,6 +566,8 @@ use aggregated, batch-verifiable
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
using Boneh-Lynn-Shacham signature scheme.
The module doesn't expose `CURVE` property: use `G1.CURVE`, `G2.CURVE` instead.
Main methods and properties are:
- `getPublicKey(privateKey)`
@@ -539,7 +611,12 @@ const aggSignature3 = bls.aggregateSignatures(signatures3);
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// bls.pairing(PointG1, PointG2) // pairings
// Pairings, with and without final exponentiation
// bls.pairing(PointG1, PointG2);
// bls.pairing(PointG1, PointG2, false);
// bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(eGS, ePHm));
// Others
// bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE
// bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr
@@ -598,7 +675,7 @@ utils: {
### abstract/hash-to-curve: Hashing strings to curve points
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v16](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16).
The module allows to hash arbitrary strings to elliptic curve points. Implements [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security:
@@ -614,19 +691,17 @@ bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
```
If you need low-level methods from spec:
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
Low-level methods from the spec:
```ts
// produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
function expand_message_xmd(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: CHash
H: CHash // For CHash see abstract/weierstrass docs section
): Uint8Array;
// produces a uniformly random byte string using an extendable-output function (XOF) H.
function expand_message_xof(
msg: Uint8Array,
DST: Uint8Array,
@@ -634,13 +709,9 @@ function expand_message_xof(
k: number,
H: CHash
): Uint8Array;
```
// Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
`hash_to_field(msg, count, options)`
[(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
```ts
/**
* * `DST` is a domain separation tag, defined in section 2.2.5
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
@@ -651,23 +722,13 @@ hashes arbitrary-length byte strings to a list of one or more elements of a fini
*/
type UnicodeOrBytes = string | Uint8Array;
type Opts = {
DST: UnicodeOrBytes;
p: bigint;
m: number;
k: number;
expand?: 'xmd' | 'xof';
hash: CHash;
DST: UnicodeOrBytes;
p: bigint;
m: number;
k: number;
expand?: 'xmd' | 'xof';
hash: CHash;
};
/**
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
* @param msg a byte string containing the message to hash
* @param count the number of elements of F to output
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
```
### abstract/poseidon: Poseidon hash
@@ -710,30 +771,40 @@ mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
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
Suppose you have `sha256(something)` (e.g. from HMAC) and you want to make a private key from it.
Even though p256 or secp256k1 may have 32-byte private keys,
and sha256 output is also 32-byte, you can't just use it and reduce it modulo `CURVE.n`.
You can't simply make a 32-byte private key from a 32-byte hash.
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/).
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/).
To make the bias negligible, we follow [FIPS 186-5 A.2](https://csrc.nist.gov/publications/detail/fips/186/5/final)
and [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-5.2).
This means, for 32-byte key, we would need 48-byte hash to get 2^-128 bias, which matches curve security level.
To avoid the bias, we implement FIPS 186 B.4.1, which allows to take arbitrary
byte array and produce valid scalars / private keys with bias being neglible.
Use [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**.
`hashToPrivateScalar()` that hashes to **private key** was created for this purpose.
Use [abstract/hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
if you need to hash to **public key**.
```ts
import { p256 } from '@noble/curves/p256';
import { sha256 } from '@noble/hashes/sha256';
import { hkdf } from '@noble/hashes/hkdf';
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);
```
### abstract/utils: General utilities
### abstract/utils: Useful utilities
```ts
import * as utils from '@noble/curves/abstract/utils';
@@ -755,18 +826,36 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
## Security
1. The library has been audited in Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com):
[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).
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.
1. The library has been independently audited:
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:
- 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).
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 v0.7.3 audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
- `@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`.
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.
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:
- `@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.
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
@@ -865,35 +954,37 @@ ed448 x 1,247 ops/sec @ 801μs/op
## Upgrading
Previously, the library was split into single-feature packages
noble-secp256k1, noble-ed25519 and noble-bls12-381.
[noble-secp256k1](https://github.com/paulmillr/noble-secp256k1),
[noble-ed25519](https://github.com/paulmillr/noble-ed25519) and
[noble-bls12-381](https://github.com/paulmillr/noble-bls12-381).
Curves continue their original work. The single-feature packages changed their
direction towards providing minimal 4kb implementations of cryptography,
which means they have less features.
Upgrading from @noble/secp256k1 2.0 or @noble/ed25519 2.0: no changes, libraries are compatible.
Upgrading from noble-secp256k1 2.0 or noble-ed25519 2.0: no changes, libraries are compatible.
Upgrading from [@noble/secp256k1](https://github.com/paulmillr/noble-secp256k1) 1.7:
Upgrading from noble-secp256k1 1.7:
- `getPublicKey`
- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getPublicKey(priv, false)`
- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getPublicKey(priv, false)`
- `sign`
- is now sync; use `signAsync` for async version
- now returns `Signature` instance with `{ r, s, recovery }` properties
- `canonical` option was renamed to `lowS`
- `recovered` option has been removed because recovery bit is always returned now
- `der` option has been removed. There are 2 options:
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
2. If you must use DER encoding, switch to noble-curves (see above).
- is now sync; use `signAsync` for async version
- now returns `Signature` instance with `{ r, s, recovery }` properties
- `canonical` option was renamed to `lowS`
- `recovered` option has been removed because recovery bit is always returned now
- `der` option has been removed. There are 2 options:
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
2. If you must use DER encoding, switch to noble-curves (see above).
- `verify`
- `strict` option was renamed to `lowS`
- `strict` option was renamed to `lowS`
- `getSharedSecret`
- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
- `number` type for private keys have been removed: use `bigint` instead
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
@@ -914,53 +1005,65 @@ Upgrading from [@noble/ed25519](https://github.com/paulmillr/noble-ed25519) 1.7:
Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
- Methods and classes were renamed:
- PointG1 -> G1.Point, PointG2 -> G2.Point
- PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
- PointG1 -> G1.Point, PointG2 -> G2.Point
- PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
- Fp2 ORDER was corrected
## Resources
Useful documentation and articles about the library or its primitives:
- [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
- [Taming the many EdDSAs](https://csrc.nist.gov/csrc/media/Presentations/2023/crclub-2023-03-08/images-media/20230308-crypto-club-slides--taming-the-many-EdDSAs.pdf)
that describes concepts of Strong UnForgeability under Chosen Message Attacks and Strongly Binding Signatures
- EdDSA
- [A Deep dive into Ed25519 Signatures](https://cendyne.dev/posts/2022-03-06-ed25519-signatures.html)
- [Ed25519 Deep Dive Addendum](https://cendyne.dev/posts/2022-09-11-ed25519-deep-dive-addendum.html)
- [Its 255:19AM. Do you know what your validation criteria are?](https://hdevalence.ca/blog/2020-10-04-its-25519am)
- [Taming the many EdDSAs](https://csrc.nist.gov/csrc/media/Presentations/2023/crclub-2023-03-08/images-media/20230308-crypto-club-slides--taming-the-many-EdDSAs.pdf)
that describes concepts of Strong UnForgeability under Chosen Message Attacks and Strongly Binding Signatures
- [Cofactor Explained: Clearing Elliptic Curves dirty little secret](https://loup-vaillant.fr/tutorials/cofactor)
- [Surrounded by Elligators](https://loup-vaillant.fr/articles/implementing-elligator)
- Pairings and BLS
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
- Pairing over bls12-381:
[part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
[part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
[part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
- [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
- Pairing over bls12-381:
[fields](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
[curves](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
[pairings](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
- [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
Online demos:
### Demos
- [Elliptic Curve Calculator](https://paulmillr.com/noble): add / multiply points, sign messages
- [BLS threshold signatures](https://genthresh.com)
Projects using noble-curves:
### Projects using curves
- [scure-bip32](https://github.com/paulmillr/scure-bip32) and separate [bip32](https://github.com/bitcoinjs/bip32) HDkey libraries
- HDkey libraries: [scure-bip32](https://github.com/paulmillr/scure-bip32), [bip32](https://github.com/bitcoinjs/bip32)
- Social networks: [nostr](https://github.com/nbd-wtf/nostr-tools), [bluesky](https://github.com/bluesky-social/atproto)
- Ethereum libraries:
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
- [@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer)
- [ethers](https://github.com/ethers-io/ethers.js) (old noble-secp256k1 for now)
- [viem.sh](https://viem.sh)
- [metamask's eth-sig-util](https://github.com/MetaMask/eth-sig-util)
- [gridplus lattice sdk](https://github.com/GridPlus/lattice-eth2-utils)
- Bitcoin libraries: [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer)
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer),
[ethers](https://github.com/ethers-io/ethers.js) (old noble),
[viem.sh](https://viem.sh),
[@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
- [metamask's eth-sig-util](https://github.com/MetaMask/eth-sig-util)
- [gridplus lattice sdk](https://github.com/GridPlus/lattice-eth2-utils)
- Bitcoin libraries:
- [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer)
- [tapscript](https://github.com/cmdruid/tapscript)
- Solana libraries: [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [solana-web3.js](https://github.com/solana-labs/solana-web3.js)
- [polkadot.js](https://github.com/polkadot-js/common), [micro-starknet](https://github.com/paulmillr/micro-starknet)
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble-ed25519 for now)
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js), [nostr-tools](https://github.com/nbd-wtf/nostr-tools)
- Other web3 stuff:
- [scure-starknet](https://github.com/paulmillr/scure-starknet)
- [aztec](https://github.com/AztecProtocol/aztec-packages)
- [polkadot.js](https://github.com/polkadot-js/common), [drand-client](https://github.com/drand/drand-client), [moneroj](https://github.com/beritani/moneroj), [tronlib](https://github.com/CoinSpace/tronlib)
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble for now)
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js),
[js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise)
- [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation
- [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
- [BLS BBS signatures](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)
- [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony)
- See [full list of projects on GitHub](https://github.com/paulmillr/noble-curves/network/dependents).
## License

18
benchmark/decaf448.js Normal file
View File

@@ -0,0 +1,18 @@
import { run, mark, utils } from 'micro-bmark';
import { shake256 } from '@noble/hashes/sha3';
import * as mod from '../abstract/modular.js';
import { ed448, DecafPoint } from '../ed448.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36mdecaf448\x1b[0m`);
const priv = mod.hashToPrivateScalar(shake256(ed448.utils.randomPrivateKey(), { dkLen: 112 }), ed448.CURVE.n);
const pub = DecafPoint.BASE.multiply(priv);
const encoded = pub.toRawBytes();
await mark('add', 1000000, () => pub.add(DecafPoint.BASE));
await mark('multiply', 1000, () => DecafPoint.BASE.multiply(priv));
await mark('encode', 10000, () => DecafPoint.BASE.toRawBytes());
await mark('decode', 10000, () => DecafPoint.fromHex(encoded));
if (RAM) utils.logMem();
});

View File

@@ -8,8 +8,8 @@ import { hashToCurve as secp256k1 } from '../secp256k1.js';
import { hashToCurve as p256 } from '../p256.js';
import { hashToCurve as p384 } from '../p384.js';
import { hashToCurve as p521 } from '../p521.js';
import { hashToCurve as ed25519 } from '../ed25519.js';
import { hashToCurve as ed448 } from '../ed448.js';
import { hashToCurve as ed25519, hash_to_ristretto255 } from '../ed25519.js';
import { hashToCurve as ed448, hash_to_decaf448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
@@ -26,4 +26,7 @@ run(async () => {
for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) {
await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
}
await mark('hash_to_ristretto255', 1000, () => hash_to_ristretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' }));
await mark('hash_to_decaf448', 1000, () => hash_to_decaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' }));
});

18
benchmark/ristretto255.js Normal file
View File

@@ -0,0 +1,18 @@
import { run, mark, utils } from 'micro-bmark';
import { sha512 } from '@noble/hashes/sha512';
import * as mod from '../abstract/modular.js';
import { ed25519, RistrettoPoint } from '../ed25519.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36mristretto255\x1b[0m`);
const priv = mod.hashToPrivateScalar(sha512(ed25519.utils.randomPrivateKey()), ed25519.CURVE.n);
const pub = RistrettoPoint.BASE.multiply(priv);
const encoded = pub.toRawBytes();
await mark('add', 1000000, () => pub.add(RistrettoPoint.BASE));
await mark('multiply', 10000, () => RistrettoPoint.BASE.multiply(priv));
await mark('encode', 10000, () => RistrettoPoint.BASE.toRawBytes());
await mark('decode', 10000, () => RistrettoPoint.fromHex(encoded));
if (RAM) utils.logMem();
});

View File

@@ -10,7 +10,7 @@
"license": "MIT",
"devDependencies": {
"@noble/curves": "..",
"esbuild": "0.17.19"
"esbuild": "0.18.11"
},
"scripts": {
"build": "npx esbuild --bundle input.js --outfile=noble-curves.js --global-name=nobleCurves"

View File

@@ -1,7 +1,4 @@
{
"type": "module",
"browser": {
"crypto": false,
"./crypto": "./esm/crypto.js"
}
"sideEffects": false
}

12
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "@noble/curves",
"version": "1.1.0",
"version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "1.1.0",
"version": "1.2.0",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.1"
"@noble/hashes": "1.3.2"
},
"devDependencies": {
"fast-check": "3.0.0",
@@ -23,9 +23,9 @@
}
},
"node_modules/@noble/hashes": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"engines": {
"node": ">= 16"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@noble/curves",
"version": "1.1.0",
"version": "1.2.0",
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [
"abstract",
@@ -12,7 +12,7 @@
"*.d.ts.map"
],
"scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js",
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js; node ristretto255.js; node decaf448.js",
"build": "tsc && tsc -p tsconfig.esm.json",
"build:release": "rollup -c rollup.config.js",
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
@@ -28,7 +28,7 @@
},
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.1"
"@noble/hashes": "1.3.2"
},
"devDependencies": {
"fast-check": "3.0.0",
@@ -37,6 +37,7 @@
"prettier": "2.8.4",
"typescript": "5.0.2"
},
"sideEffects": false,
"main": "index.js",
"exports": {
".": {

View File

@@ -12,7 +12,7 @@
* Some projects may prefer to swap this relation, it is not supported for now.
*/
import { AffinePoint } from './curve.js';
import { IField, hashToPrivateScalar } from './modular.js';
import { IField, getMinHashLength, mapHashToField } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js';
import {
@@ -122,7 +122,6 @@ export function bls<Fp2, Fp6, Fp12>(
// Fields are specific for curve, so for now we'll need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
const BLS_X_LEN = bitLen(CURVE.params.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients
@@ -189,7 +188,8 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = {
randomPrivateKey: (): Uint8Array => {
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.params.r));
const length = getMinHashLength(Fr.ORDER);
return mapHashToField(CURVE.randomBytes(length), Fr.ORDER);
},
calcPairingPrecomputes,
};

View File

@@ -59,7 +59,7 @@ function isNum(item: unknown): void {
}
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1
export function expand_message_xmd(
msg: Uint8Array,
DST: Uint8Array,
@@ -69,7 +69,7 @@ export function expand_message_xmd(
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const ell = Math.ceil(lenInBytes / b_in_bytes);
@@ -88,6 +88,11 @@ export function expand_message_xmd(
return pseudo_random_bytes.slice(0, lenInBytes);
}
// Produces a uniformly random byte string using an extendable-output function (XOF) H.
// 1. The collision resistance of H MUST be at least k bits.
// 2. H MUST be an XOF that has been proved indifferentiable from
// a random oracle under a reasonable cryptographic assumption.
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2
export function expand_message_xof(
msg: Uint8Array,
DST: Uint8Array,
@@ -98,7 +103,7 @@ export function expand_message_xof(
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8);
@@ -119,7 +124,7 @@ export function expand_message_xof(
/**
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
* https://www.rfc-editor.org/rfc/rfc9380#section-5.2
* @param msg a byte string containing the message to hash
* @param count the number of elements of F to output
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
@@ -127,7 +132,7 @@ export function expand_message_xof(
*/
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
validateObject(options, {
DST: 'string',
DST: 'stringOrUint8Array',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
@@ -201,8 +206,8 @@ export function createHasher<T>(
) {
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
// Encodes byte string to elliptic curve.
// hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const u0 = Point.fromAffine(mapToCurve(u[0]));
@@ -212,7 +217,8 @@ export function createHasher<T>(
return P;
},
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
// Encodes byte string to elliptic curve.
// encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();

View File

@@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
return mod(x, modulo);
}
// Tonelli-Shanks algorithm
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
/**
* Tonelli-Shanks square root search algorithm.
* 1. https://eprint.iacr.org/2012/685.pdf (page 12)
* 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
* Will start an infinite loop if field order P is not prime.
* @param P field order
* @returns function that takes field Fp (created from P) and number n
*/
export function tonelliShanks(P: bigint) {
// Legendre constant: used to calculate Legendre symbol (a | p),
// which denotes the value of a^((p-1)/2) (mod p).
@@ -198,7 +203,7 @@ export function FpSqrt(P: bigint) {
// Little-endian check for first LE bit (last BE bit);
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m
// Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
export interface IField<T> {
ORDER: bigint;
BYTES: number;
@@ -228,7 +233,8 @@ export interface IField<T> {
sqrN(num: T): T;
// Optional
// Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/
// Should be same as sgn0 function in
// [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
// legendre?(num: T): T;
@@ -260,6 +266,11 @@ export function validateField<T>(field: IField<T>) {
}
// Generic field functions
/**
* Same as `pow` but for Fp: non-constant-time.
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
*/
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
// Should have same speed as pow for bigints
// TODO: benchmark!
@@ -276,7 +287,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
return p;
}
// 0 is non-invertible: non-batched version will throw on 0
/**
* Efficiently invert an array of Field elements.
* `inv(0)` will return `undefined` here: make sure to throw an error.
*/
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p
@@ -319,12 +333,12 @@ export function nLength(n: bigint, nBitLength?: number) {
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
/**
* Initializes a galois field over prime. Non-primes are not supported for now.
* Do not init in loop: slow. Very fragile: always run a benchmark on change.
* Major performance gains:
* a) non-normalized operations like mulN instead of mul
* b) `Object.freeze`
* c) Same object shape: never add or remove keys
* Initializes a finite field over prime. **Non-primes are not supported.**
* Do not init in loop: slow. Very fragile: always run a benchmark on a change.
* Major performance optimizations:
* * a) denormalized operations like mulN instead of mul
* * b) same object shape: never add or remove keys
* * c) Object.freeze
* @param ORDER prime positive bigint
* @param bitLen how many bits the field consumes
* @param isLE (def: false) if encoding / decoding should be in little-endian
@@ -336,7 +350,7 @@ export function Field(
isLE = false,
redef: Partial<IField<bigint>> = {}
): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
const sqrtP = FpSqrt(ORDER);
@@ -400,15 +414,10 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
}
/**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
* @param isLE interpret hash bytes as LE num
* @returns valid private scalar
* "Constant-time" private key generation utility.
* Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
* Which makes it slightly more biased, less secure.
* @deprecated use mapKeyToField instead
*/
export function hashToPrivateScalar(
hash: string | Uint8Array,
@@ -423,3 +432,53 @@ export function hashToPrivateScalar(
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod(num, groupOrder - _1n) + _1n;
}
/**
* Returns total number of bytes consumed by the field element.
* For example, 32 bytes for usual 256-bit weierstrass curve.
* @param fieldOrder number of field elements, usually CURVE.n
* @returns byte length of field
*/
export function getFieldBytesLength(fieldOrder: bigint): number {
if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
const bitLength = fieldOrder.toString(2).length;
return Math.ceil(bitLength / 8);
}
/**
* Returns minimal amount of bytes that can be safely reduced
* by field order.
* Should be 2^-128 for 128-bit curve such as P256.
* @param fieldOrder number of field elements, usually CURVE.n
* @returns byte length of target hash
*/
export function getMinHashLength(fieldOrder: bigint): number {
const length = getFieldBytesLength(fieldOrder);
return length + Math.ceil(length / 2);
}
/**
* "Constant-time" private key generation utility.
* Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 48 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
* RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
* @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
* @param isLE interpret hash bytes as LE num
* @returns valid private scalar
*/
export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
const len = key.length;
const fieldLen = getFieldBytesLength(fieldOrder);
const minLen = getMinHashLength(fieldOrder);
// No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
if (len < 16 || len < minLen || len > 1024)
throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
const reduced = mod(num, fieldOrder - _1n) + _1n;
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
}

View File

@@ -15,34 +15,36 @@ export type PoseidonOpts = {
};
export function validateOpts(opts: PoseidonOpts) {
const { Fp } = opts;
const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
const { roundsFull, roundsPartial, sboxPower, t } = opts;
validateField(Fp);
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
}
if (opts.reversePartialPowIdx !== undefined && typeof opts.reversePartialPowIdx !== 'boolean')
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${opts.reversePartialPowIdx}`);
// Default is 5, but by some reasons stark uses 3
let sboxPower = opts.sboxPower;
if (sboxPower === undefined) sboxPower = 5;
if (typeof sboxPower !== 'number' || !Number.isSafeInteger(sboxPower))
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
const _sboxPower = BigInt(sboxPower);
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
// Unwrapped sbox power for common cases (195->142μs)
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
// MDS is TxT matrix
if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: wrong MDS matrix');
const _mds = mds.map((mdsRow) => {
if (!Array.isArray(mdsRow) || mdsRow.length !== t)
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
return mdsRow.map((i) => {
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
return Fp.create(i);
});
});
if (opts.roundsFull % 2 !== 0)
throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`);
const rounds = opts.roundsFull + opts.roundsPartial;
if (rev !== undefined && typeof rev !== 'boolean')
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
if (!Array.isArray(opts.roundConstants) || opts.roundConstants.length !== rounds)
if (roundsFull % 2 !== 0) throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
const rounds = roundsFull + roundsPartial;
if (!Array.isArray(rc) || rc.length !== rounds)
throw new Error('Poseidon: wrong round constants');
const roundConstants = opts.roundConstants.map((rc) => {
if (!Array.isArray(rc) || rc.length !== opts.t)
const roundConstants = rc.map((rc) => {
if (!Array.isArray(rc) || rc.length !== t)
throw new Error(`Poseidon wrong round constants: ${rc}`);
return rc.map((i) => {
if (typeof i !== 'bigint' || !Fp.isValid(i))
@@ -50,18 +52,16 @@ export function validateOpts(opts: PoseidonOpts) {
return Fp.create(i);
});
});
// MDS is TxT matrix
if (!Array.isArray(opts.mds) || opts.mds.length !== opts.t)
throw new Error('Poseidon: wrong MDS matrix');
const mds = opts.mds.map((mdsRow) => {
if (!Array.isArray(mdsRow) || mdsRow.length !== opts.t)
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
return mdsRow.map((i) => {
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
return Fp.create(i);
});
});
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds });
if (!sboxPower || ![3, 5, 7].includes(sboxPower))
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
const _sboxPower = BigInt(sboxPower);
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
// Unwrapped sbox power for common cases (195->142μs)
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
}
export function splitConstants(rc: bigint[], t: number) {
@@ -80,18 +80,17 @@ export function splitConstants(rc: bigint[], t: number) {
}
export function poseidon(opts: PoseidonOpts) {
const { t, Fp, rounds, sboxFn, reversePartialPowIdx } = validateOpts(opts);
const halfRoundsFull = Math.floor(opts.roundsFull / 2);
const partialIdx = reversePartialPowIdx ? t - 1 : 0;
const _opts = validateOpts(opts);
const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
const halfRoundsFull = _opts.roundsFull / 2;
const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
values = values.map((i, j) => Fp.add(i, opts.roundConstants[idx][j]));
values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
if (isFull) values = values.map((i) => sboxFn(i));
else values[partialIdx] = sboxFn(values[partialIdx]);
// Matrix multiplication
values = opts.mds.map((i) =>
i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO)
);
values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
return values;
};
const poseidonHash = function poseidonHash(values: bigint[]) {
@@ -105,7 +104,7 @@ export function poseidon(opts: PoseidonOpts) {
// Apply r_f/2 full rounds.
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
// Apply r_p partial rounds.
for (let i = 0; i < opts.roundsPartial; i++) values = poseidonRound(values, false, round++);
for (let i = 0; i < roundsPartial; i++) values = poseidonRound(values, false, round++);
// Apply r_f/2 full rounds.
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
@@ -114,6 +113,6 @@ export function poseidon(opts: PoseidonOpts) {
return values;
};
// For verification in tests
poseidonHash.roundConstants = opts.roundConstants;
poseidonHash.roundConstants = roundConstants;
return poseidonHash;
}

View File

@@ -17,7 +17,9 @@ export type CHash = {
};
export type FHash = (message: Uint8Array | string) => Uint8Array;
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
i.toString(16).padStart(2, '0')
);
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
@@ -246,6 +248,7 @@ const validatorFns = {
function: (val: any) => typeof val === 'function',
boolean: (val: any) => typeof val === 'boolean',
string: (val: any) => typeof val === 'string',
stringOrUint8Array: (val: any) => typeof val === 'string' || val instanceof Uint8Array,
isSafeInteger: (val: any) => Number.isSafeInteger(val),
array: (val: any) => Array.isArray(val),
field: (val: any, object: any) => (object as any).Fp.isValid(val),

View File

@@ -193,7 +193,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const toBytes =
CURVE.toBytes ||
((c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => {
((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
const a = point.toAffine();
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
});
@@ -333,9 +333,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// A point on curve is valid if it conforms to equation.
assertValidity(): void {
// Zero is valid point too!
if (this.is0()) {
if (CURVE.allowInfinityPoint) return;
// (0, 1, 0) aka ZERO is invalid in most contexts.
// In BLS, ZERO can be serialized, so we allow it.
// (0, 0, 0) is wrong representation of ZERO and is always invalid.
if (CURVE.allowInfinityPoint && !Fp.is0(this.py)) return;
throw new Error('bad point: ZERO');
}
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
@@ -707,7 +709,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
isWithinCurveOrder,
} = weierstrassPoints({
...CURVE,
toBytes(c, point, isCompressed: boolean): Uint8Array {
toBytes(_c, point, isCompressed: boolean): Uint8Array {
const a = point.toAffine();
const x = Fp.toBytes(a.x);
const cat = ut.concatBytes;
@@ -845,13 +847,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
normPrivateKeyToScalar: normPrivateKeyToScalar,
/**
* Produces cryptographically secure private key from random of size (nBitLength+64)
* as per FIPS 186 B.4.1 with modulo bias being neglible.
* Produces cryptographically secure private key from random of size
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/
randomPrivateKey: (): Uint8Array => {
const rand = CURVE.randomBytes(Fp.BYTES + 8);
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
return ut.numberToBytesBE(num, CURVE.nByteLength);
const length = mod.getMinHashLength(CURVE.n);
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
},
/**
@@ -964,7 +965,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (ent != null) {
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
}
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
@@ -1170,7 +1171,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
return sqrtRatio;
}
/**
* From draft-irtf-cfrg-hash-to-curve-16
* Simplified Shallue-van de Woestijne-Ulas Method
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
*/
export function mapToCurveSimpleSWU<T>(
Fp: mod.IField<T>,

View File

@@ -8,8 +8,8 @@
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
// [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
// [bls-sigs-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12).
// [bls-sigs-04](https:/cfrg-hash-to/tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf--curve-12).
//
// ### Summary
// 1. BLS Relies on Bilinear Pairing (expensive)
@@ -177,7 +177,7 @@ const Fp2: mod.IField<Fp2> & Fp2Utils = {
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
return x2;
},
// Same as sgn0_fp2 in draft-irtf-cfrg-hash-to-curve-16
// Same as sgn0_m_eq_2 in RFC 9380
isOdd: (x: Fp2) => {
const { re: x0, im: x1 } = Fp2.reim(x);
const sign_0 = x0 % _2n;
@@ -780,8 +780,7 @@ const FP12_FROBENIUS_COEFFICIENTS = [
// HashToCurve
// 3-isogeny map from E' to E
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-E.3
// 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
const isogenyMapG2 = isogenyMap(
Fp2,
[
@@ -985,7 +984,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
//
// Parameter definitions are in section 5.3 of the spec unless otherwise noted.
// Parameter values come from section 8.8.2 of the spec.
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.2
// https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2
//
// Base field F is GF(p^m)
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
@@ -1040,7 +1039,7 @@ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
}
// To verify curve parameters, see pairing-friendly-curves spec:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-11
// Basic math is done over finite fields over p.
// More complicated math is done over polynominal extension fields.
// To simplify calculations in Fp12, we construct extension tower:
@@ -1108,7 +1107,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
},
// Clear cofactor of G1
// https://eprint.iacr.org/2019/403
clearCofactor: (c, point) => {
clearCofactor: (_c, point) => {
// return this.multiplyUnsafe(CURVE.h);
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
},

View File

@@ -123,7 +123,7 @@ const ed25519Defaults = {
uvRatio,
} as const;
export const ed25519 = twistedEdwards(ed25519Defaults);
export const ed25519 = /* @__PURE__ */ twistedEdwards(ed25519Defaults);
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
if (ctx.length > 255) throw new Error('Context is too big');
@@ -135,8 +135,11 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
);
}
export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
export const ed25519ph = twistedEdwards({
export const ed25519ctx = /* @__PURE__ */ twistedEdwards({
...ed25519Defaults,
domain: ed25519_domain,
});
export const ed25519ph = /* @__PURE__ */ twistedEdwards({
...ed25519Defaults,
domain: ed25519_domain,
prehash: sha512,
@@ -475,12 +478,12 @@ export const RistrettoPoint = /* @__PURE__ */ (() => {
return RistPoint;
})();
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
// Appendix B. Hashing to ristretto255
export const hash_to_ristretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
// Hashing to ristretto255. https://www.rfc-editor.org/rfc/rfc9380#appendix-B
export const hashToRistretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
const P = RistPoint.hashToCurve(uniform_bytes);
return P;
};
export const hash_to_ristretto255 = hashToRistretto255; // legacy

View File

@@ -1,14 +1,25 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field } from './abstract/modular.js';
import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field, isNegativeLE } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js';
import { createHasher } from './abstract/hash-to-curve.js';
import { createHasher, htfBasicOpts, expand_message_xof } from './abstract/hash-to-curve.js';
import {
bytesToHex,
bytesToNumberLE,
ensureBytes,
equalBytes,
Hex,
numberToBytesLE,
} from './abstract/utils.js';
import { AffinePoint } from './abstract/curve.js';
/**
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
* * X448 ECDH
* - X448 ECDH
* - Decaf cofactor elimination
* - Elligator hash-to-group / point indistinguishability
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
*/
@@ -18,15 +29,16 @@ const ed448P = BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
);
// prettier-ignore
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4), _11n = BigInt(11);
// prettier-ignore
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
// Used for efficient square root calculation.
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
function ed448_pow_Pminus3div4(x: bigint): bigint {
const P = ed448P;
// prettier-ignore
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _11n = BigInt(11);
// prettier-ignore
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
const b2 = (x * x * x) % P;
const b3 = (b2 * b2 * x) % P;
const b6 = (pow2(b3, _3n, P) * b3) % P;
@@ -53,8 +65,29 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
return bytes;
}
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
// Uses algo from RFC8032 5.1.3.
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
const P = ed448P;
// https://www.rfc-editor.org/rfc/rfc8032#section-5.2.3
// To compute the square root of (u/v), the first step is to compute the
// candidate root x = (u/v)^((p+1)/4). This can be done using the
// following trick, to use a single modular powering for both the
// inversion of v and the square root:
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
const u2v = mod(u * u * v, P); // u²v
const u3v = mod(u2v * u, P); // u³v
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
const root = ed448_pow_Pminus3div4(u5v3);
const x = mod(u3v * root, P);
// Verify that root is exists
const x2 = mod(x * x, P); // x²
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
}
const Fp = Field(ed448P, 456, true);
const _4n = BigInt(4);
const ED448_DEF = {
// Param: a
@@ -94,33 +127,12 @@ const ED448_DEF = {
data
);
},
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
// Uses algo from RFC8032 5.1.3.
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
const P = ed448P;
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
// To compute the square root of (u/v), the first step is to compute the
// candidate root x = (u/v)^((p+1)/4). This can be done using the
// following trick, to use a single modular powering for both the
// inversion of v and the square root:
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
const u2v = mod(u * u * v, P); // u²v
const u3v = mod(u2v * u, P); // u³v
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
const root = ed448_pow_Pminus3div4(u5v3);
const x = mod(u3v * root, P);
// Verify that root is exists
const x2 = mod(x * x, P); // x²
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
},
uvRatio,
} as const;
export const ed448 = twistedEdwards(ED448_DEF);
export const ed448 = /* @__PURE__ */ twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const ed448ph = /* @__PURE__ */ twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const x448 = /* @__PURE__ */ (() =>
montgomery({
@@ -245,3 +257,209 @@ const htf = /* @__PURE__ */ (() =>
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
function assertDcfPoint(other: unknown) {
if (!(other instanceof DcfPoint)) throw new Error('DecafPoint expected');
}
// 1-d
const ONE_MINUS_D = BigInt('39082');
// 1-2d
const ONE_MINUS_TWO_D = BigInt('78163');
// √(-d)
const SQRT_MINUS_D = BigInt(
'98944233647732219769177004876929019128417576295529901074099889598043702116001257856802131563896515373927712232092845883226922417596214'
);
// 1 / √(-d)
const INVSQRT_MINUS_D = BigInt(
'315019913931389607337177038330951043522456072897266928557328499619017160722351061360252776265186336876723201881398623946864393857820716'
);
// Calculates 1/√(number)
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
const MAX_448B = BigInt(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
);
const bytes448ToNumberLE = (bytes: Uint8Array) =>
ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
type ExtendedPoint = ExtPointType;
// Computes Elligator map for Decaf
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
function calcElligatorDecafMap(r0: bigint): ExtendedPoint {
const { d } = ed448.CURVE;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const r = mod(-(r0 * r0)); // 1
const u0 = mod(d * (r - _1n)); // 2
const u1 = mod((u0 + _1n) * (u0 - r)); // 3
const { isValid: was_square, value: v } = uvRatio(ONE_MINUS_TWO_D, mod((r + _1n) * u1)); // 4
let v_prime = v; // 5
if (!was_square) v_prime = mod(r0 * v);
let sgn = _1n; // 6
if (!was_square) sgn = mod(-_1n);
const s = mod(v_prime * (r + _1n)); // 7
let s_abs = s;
if (isNegativeLE(s, P)) s_abs = mod(-s);
const s2 = s * s;
const W0 = mod(s_abs * _2n); // 8
const W1 = mod(s2 + _1n); // 9
const W2 = mod(s2 - _1n); // 10
const W3 = mod(v_prime * s * (r - _1n) * ONE_MINUS_TWO_D + sgn); // 11
return new ed448.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
}
/**
* Each ed448/ExtendedPoint has 4 different equivalent points. This can be
* a source of bugs for protocols like ring signatures. Decaf was created to solve this.
* Decaf point operates in X:Y:Z:T extended coordinates like ExtendedPoint,
* but it should work in its own namespace: do not combine those two.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
*/
class DcfPoint {
static BASE: DcfPoint;
static ZERO: DcfPoint;
// Private property to discourage combining ExtendedPoint + DecafPoint
// Always use Decaf encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {}
static fromAffine(ap: AffinePoint<bigint>) {
return new DcfPoint(ed448.ExtendedPoint.fromAffine(ap));
}
/**
* Takes uniform output of 112-byte hash function like shake256 and converts it to `DecafPoint`.
* The hash-to-group operation applies Elligator twice and adds the results.
* **Note:** this is one-way map, there is no conversion from point to hash.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
* @param hex 112-byte output of a hash function
*/
static hashToCurve(hex: Hex): DcfPoint {
hex = ensureBytes('decafHash', hex, 112);
const r1 = bytes448ToNumberLE(hex.slice(0, 56));
const R1 = calcElligatorDecafMap(r1);
const r2 = bytes448ToNumberLE(hex.slice(56, 112));
const R2 = calcElligatorDecafMap(r2);
return new DcfPoint(R1.add(R2));
}
/**
* Converts decaf-encoded string to decaf point.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-decode-2
* @param hex Decaf-encoded 56 bytes. Not every 56-byte string is valid decaf encoding
*/
static fromHex(hex: Hex): DcfPoint {
hex = ensureBytes('decafHex', hex, 56);
const { d } = ed448.CURVE;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const emsg = 'DecafPoint.fromHex: the hex is not valid encoding of DecafPoint';
const s = bytes448ToNumberLE(hex);
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
// 2. Check that s is non-negative, or else abort
if (!equalBytes(numberToBytesLE(s, 56), hex) || isNegativeLE(s, P)) throw new Error(emsg);
const s2 = mod(s * s); // 1
const u1 = mod(_1n + s2); // 2
const u1sq = mod(u1 * u1);
const u2 = mod(u1sq - _4n * d * s2); // 3
const { isValid, value: invsqrt } = invertSqrt(mod(u2 * u1sq)); // 4
let u3 = mod((s + s) * invsqrt * u1 * SQRT_MINUS_D); // 5
if (isNegativeLE(u3, P)) u3 = mod(-u3);
const x = mod(u3 * invsqrt * u2 * INVSQRT_MINUS_D); // 6
const y = mod((_1n - s2) * invsqrt * u1); // 7
const t = mod(x * y); // 8
if (!isValid) throw new Error(emsg);
return new DcfPoint(new ed448.ExtendedPoint(x, y, _1n, t));
}
/**
* Encodes decaf point to Uint8Array.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-encode-2
*/
toRawBytes(): Uint8Array {
let { ex: x, ey: _y, ez: z, et: t } = this.ep;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const u1 = mod(mod(x + t) * mod(x - t)); // 1
const x2 = mod(x * x);
const { value: invsqrt } = invertSqrt(mod(u1 * ONE_MINUS_D * x2)); // 2
let ratio = mod(invsqrt * u1 * SQRT_MINUS_D); // 3
if (isNegativeLE(ratio, P)) ratio = mod(-ratio);
const u2 = mod(INVSQRT_MINUS_D * ratio * z - t); // 4
let s = mod(ONE_MINUS_D * invsqrt * x * u2); // 5
if (isNegativeLE(s, P)) s = mod(-s);
return numberToBytesLE(s, 56);
}
toHex(): string {
return bytesToHex(this.toRawBytes());
}
toString(): string {
return this.toHex();
}
// Compare one point to another.
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-equals-2
equals(other: DcfPoint): boolean {
assertDcfPoint(other);
const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = other.ep;
const mod = ed448.CURVE.Fp.create;
// (x1 * y2 == y1 * x2)
return mod(X1 * Y2) === mod(Y1 * X2);
}
add(other: DcfPoint): DcfPoint {
assertDcfPoint(other);
return new DcfPoint(this.ep.add(other.ep));
}
subtract(other: DcfPoint): DcfPoint {
assertDcfPoint(other);
return new DcfPoint(this.ep.subtract(other.ep));
}
multiply(scalar: bigint): DcfPoint {
return new DcfPoint(this.ep.multiply(scalar));
}
multiplyUnsafe(scalar: bigint): DcfPoint {
return new DcfPoint(this.ep.multiplyUnsafe(scalar));
}
}
export const DecafPoint = /* @__PURE__ */ (() => {
// decaf448 base point is ed448 base x 2
// https://github.com/dalek-cryptography/curve25519-dalek/blob/59837c6ecff02b77b9d5ff84dbc239d0cf33ef90/vendor/ristretto.sage#L699
if (!DcfPoint.BASE) DcfPoint.BASE = new DcfPoint(ed448.ExtendedPoint.BASE).multiply(_2n);
if (!DcfPoint.ZERO) DcfPoint.ZERO = new DcfPoint(ed448.ExtendedPoint.ZERO);
return DcfPoint;
})();
// Hashing to decaf448. https://www.rfc-editor.org/rfc/rfc9380#appendix-C
export const hashToDecaf448 = (msg: Uint8Array, options: htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = expand_message_xof(msg, DST, 112, 224, shake256);
const P = DcfPoint.hashToCurve(uniform_bytes);
return P;
};
export const hash_to_decaf448 = hashToDecaf448; // legacy

View File

@@ -11,7 +11,7 @@ import { Field } from './abstract/modular.js';
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
*/
export const jubjub = twistedEdwards({
export const jubjub = /* @__PURE__ */ twistedEdwards({
// Params: a, d
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),

View File

@@ -37,6 +37,11 @@ const B_192_40 = '40'.padEnd(192, '0');
const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
const getPubKey = (priv) => bls.getPublicKey(priv);
function replaceZeroPoint(item) {
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
const ones = '1000000000000000000000000000000000000000000000000000000000000001';
return item === zeros ? ones : item;
}
function equal(a, b, comment) {
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
@@ -1234,6 +1239,7 @@ describe('verify()', () => {
should('verify multi-signature as simple signature', () => {
fc.assert(
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures);
@@ -1245,6 +1251,7 @@ describe('verify()', () => {
should('not verify wrong multi-signature as simple signature', () => {
fc.assert(
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures);

117
test/ed448-addons.test.js Normal file
View File

@@ -0,0 +1,117 @@
import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import { bytesToNumberLE } from '../esm/abstract/utils.js';
import { ed448, DecafPoint } from '../esm/ed448.js';
describe('decaf448', () => {
should('follow the byte encodings of small multiples', () => {
const encodingsOfSmallMultiples = [
// This is the identity point
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
// This is the basepoint
'6666666666666666666666666666666666666666666666666666666633333333333333333333333333333333333333333333333333333333',
// These are small multiples of the basepoint
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75',
'a0c09bf2ba7208fda0f4bfe3d0f5b29a543012306d43831b5adc6fe7f8596fa308763db15468323b11cf6e4aeb8c18fe44678f44545a69bc',
'b46f1836aa287c0a5a5653f0ec5ef9e903f436e21c1570c29ad9e5f596da97eeaf17150ae30bcb3174d04bc2d712c8c7789d7cb4fda138f4',
'1c5bbecf4741dfaae79db72dface00eaaac502c2060934b6eaaeca6a20bd3da9e0be8777f7d02033d1b15884232281a41fc7f80eed04af5e',
'86ff0182d40f7f9edb7862515821bd67bfd6165a3c44de95d7df79b8779ccf6460e3c68b70c16aaa280f2d7b3f22d745b97a89906cfc476c',
'502bcb6842eb06f0e49032bae87c554c031d6d4d2d7694efbf9c468d48220c50f8ca28843364d70cee92d6fe246e61448f9db9808b3b2408',
'0c9810f1e2ebd389caa789374d78007974ef4d17227316f40e578b336827da3f6b482a4794eb6a3975b971b5e1388f52e91ea2f1bcb0f912',
'20d41d85a18d5657a29640321563bbd04c2ffbd0a37a7ba43a4f7d263ce26faf4e1f74f9f4b590c69229ae571fe37fa639b5b8eb48bd9a55',
'e6b4b8f408c7010d0601e7eda0c309a1a42720d6d06b5759fdc4e1efe22d076d6c44d42f508d67be462914d28b8edce32e7094305164af17',
'be88bbb86c59c13d8e9d09ab98105f69c2d1dd134dbcd3b0863658f53159db64c0e139d180f3c89b8296d0ae324419c06fa87fc7daaf34c1',
'a456f9369769e8f08902124a0314c7a06537a06e32411f4f93415950a17badfa7442b6217434a3a05ef45be5f10bd7b2ef8ea00c431edec5',
'186e452c4466aa4383b4c00210d52e7922dbf9771e8b47e229a9b7b73c8d10fd7ef0b6e41530f91f24a3ed9ab71fa38b98b2fe4746d51d68',
'4ae7fdcae9453f195a8ead5cbe1a7b9699673b52c40ab27927464887be53237f7f3a21b938d40d0ec9e15b1d5130b13ffed81373a53e2b43',
'841981c3bfeec3f60cfeca75d9d8dc17f46cf0106f2422b59aec580a58f342272e3a5e575a055ddb051390c54c24c6ecb1e0aceb075f6056',
];
let B = DecafPoint.BASE;
let P = DecafPoint.ZERO;
for (const encoded of encodingsOfSmallMultiples) {
deepStrictEqual(P.toHex(), encoded);
deepStrictEqual(DecafPoint.fromHex(encoded).toHex(), encoded);
P = P.add(B);
}
});
should('not convert bad bytes encoding', () => {
const badEncodings = [
// These are all bad because they're non-canonical field encodings.
'8e24f838059ee9fef1e209126defe53dcd74ef9b6304601c6966099effffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'86fcc7212bd4a0b980928666dc28c444a605ef38e09fb569e28d4443ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'866d54bd4c4ff41a55d4eefdbeca73cbd653c7bd3135b383708ec0bdffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'4a380ccdab9c86364a89e77a464d64f9157538cfdfa686adc0d5ece4ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'f22d9d4c945dd44d11e0b1d3d3d358d959b4844d83b08c44e659d79fffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'8cdffc681aa99e9c818c8ef4c3808b58e86acdef1ab68c8477af185bffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'0e1c12ac7b5920effbd044e897c57634e2d05b5c27f8fa3df8a086a1ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
// These are all bad because they're negative field elements.
'15141bd2121837ef71a0016bd11be757507221c26542244f23806f3fd3496b7d4c36826276f3bf5deea2c60c4fa4cec69946876da497e795',
'455d380238434ab740a56267f4f46b7d2eb2dd8ee905e51d7b0ae8a6cb2bae501e67df34ab21fa45946068c9f233939b1d9521a998b7cb93',
'810b1d8e8bf3a9c023294bbfd3d905a97531709bdc0f42390feedd7010f77e98686d400c9c86ed250ceecd9de0a18888ffecda0f4ea1c60d',
'd3af9cc41be0e5de83c0c6273bedcb9351970110044a9a41c7b9b2267cdb9d7bf4dc9c2fdb8bed32878184604f1d9944305a8df4274ce301',
'9312bcab09009e4330ff89c4bc1e9e000d863efc3c863d3b6c507a40fd2cdefde1bf0892b4b5ed9780b91ed1398fb4a7344c605aa5efda74',
'53d11bce9e62a29d63ed82ae93761bdd76e38c21e2822d6ebee5eb1c5b8a03eaf9df749e2490eda9d8ac27d1f71150de93668074d18d1c3a',
'697c1aed3cd8858515d4be8ac158b229fe184d79cb2b06e49210a6f3a7cd537bcd9bd390d96c4ab6a4406da5d93640726285370cfa95df80',
// These are all bad because they give a nonsquare x².
'58ad48715c9a102569b68b88362a4b0645781f5a19eb7e59c6a4686fd0f0750ff42e3d7af1ab38c29d69b670f31258919c9fdbf6093d06c0',
'8ca37ee2b15693f06e910cf43c4e32f1d5551dda8b1e48cb6ddd55e440dbc7b296b601919a4e4069f59239ca247ff693f7daa42f086122b1',
'982c0ec7f43d9f97c0a74b36db0abd9ca6bfb98123a90782787242c8a523cdc76df14a910d54471127e7662a1059201f902940cd39d57af5',
'baa9ab82d07ca282b968a911a6c3728d74bf2fe258901925787f03ee4be7e3cb6684fd1bcfe5071a9a974ad249a4aaa8ca81264216c68574',
'2ed9ffe2ded67a372b181ac524996402c42970629db03f5e8636cbaf6074b523d154a7a8c4472c4c353ab88cd6fec7da7780834cc5bd5242',
'f063769e4241e76d815800e4933a3a144327a30ec40758ad3723a788388399f7b3f5d45b6351eb8eddefda7d5bff4ee920d338a8b89d8b63',
'5a0104f1f55d152ceb68bc138182499891d90ee8f09b40038ccc1e07cb621fd462f781d045732a4f0bda73f0b2acf94355424ff0388d4b9c',
];
for (const badBytes of badEncodings) {
const b = hexToBytes(badBytes);
throws(() => DecafPoint.fromHex(b), badBytes);
}
});
should('create right points from uniform hash', () => {
const hashes = [
'cbb8c991fd2f0b7e1913462d6463e4fd2ce4ccdd28274dc2ca1f4165d5ee6cdccea57be3416e166fd06718a31af45a2f8e987e301be59ae6673e963001dbbda80df47014a21a26d6c7eb4ebe0312aa6fffb8d1b26bc62ca40ed51f8057a635a02c2b8c83f48fa6a2d70f58a1185902c0',
'b6d8da654b13c3101d6634a231569e6b85961c3f4b460a08ac4a5857069576b64428676584baa45b97701be6d0b0ba18ac28d443403b45699ea0fbd1164f5893d39ad8f29e48e399aec5902508ea95e33bc1e9e4620489d684eb5c26bc1ad1e09aba61fabc2cdfee0b6b6862ffc8e55a',
'36a69976c3e5d74e4904776993cbac27d10f25f5626dd45c51d15dcf7b3e6a5446a6649ec912a56895d6baa9dc395ce9e34b868d9fb2c1fc72eb6495702ea4f446c9b7a188a4e0826b1506b0747a6709f37988ff1aeb5e3788d5076ccbb01a4bc6623c92ff147a1e21b29cc3fdd0e0f4',
'd5938acbba432ecd5617c555a6a777734494f176259bff9dab844c81aadcf8f7abd1a9001d89c7008c1957272c1786a4293bb0ee7cb37cf3988e2513b14e1b75249a5343643d3c5e5545a0c1a2a4d3c685927c38bc5e5879d68745464e2589e000b31301f1dfb7471a4f1300d6fd0f99',
'4dec58199a35f531a5f0a9f71a53376d7b4bdd6bbd2904234a8ea65bbacbce2a542291378157a8f4be7b6a092672a34d85e473b26ccfbd4cdc6739783dc3f4f6ee3537b7aed81df898c7ea0ae89a15b5559596c2a5eeacf8b2b362f3db2940e3798b63203cae77c4683ebaed71533e51',
'df2aa1536abb4acab26efa538ce07fd7bca921b13e17bc5ebcba7d1b6b733deda1d04c220f6b5ab35c61b6bcb15808251cab909a01465b8ae3fc770850c66246d5a9eae9e2877e0826e2b8dc1bc08009590bc6778a84e919fbd28e02a0f9c49b48dc689eb5d5d922dc01469968ee81b5',
'e9fb440282e07145f1f7f5ecf3c273212cd3d26b836b41b02f108431488e5e84bd15f2418b3d92a3380dd66a374645c2a995976a015632d36a6c2189f202fc766e1c82f50ad9189be190a1f0e8f9b9e69c9c18cc98fdd885608f68bf0fdedd7b894081a63f70016a8abf04953affbefa',
];
const encodedHashToPoints = [
'0c709c9607dbb01c94513358745b7c23953d03b33e39c7234e268d1d6e24f34014ccbc2216b965dd231d5327e591dc3c0e8844ccfd568848',
'76ab794e28ff1224c727fa1016bf7f1d329260b7218a39aea2fdb17d8bd9119017b093d641cedf74328c327184dc6f2a64bd90eddccfcdab',
'c8d7ac384143500e50890a1c25d643343accce584caf2544f9249b2bf4a6921082be0e7f3669bb5ec24535e6c45621e1f6dec676edd8b664',
'62beffc6b8ee11ccd79dbaac8f0252c750eb052b192f41eeecb12f2979713b563caf7d22588eca5e80995241ef963e7ad7cb7962f343a973',
'f4ccb31d263731ab88bed634304956d2603174c66da38742053fa37dd902346c3862155d68db63be87439e3d68758ad7268e239d39c4fd3b',
'7e79b00e8e0a76a67c0040f62713b8b8c6d6f05e9c6d02592e8a22ea896f5deacc7c7df5ed42beae6fedb9000285b482aa504e279fd49c32',
'20b171cb16be977f15e013b9752cf86c54c631c4fc8cbf7c03c4d3ac9b8e8640e7b0e9300b987fe0ab5044669314f6ed1650ae037db853f1',
];
for (let i = 0; i < hashes.length; i++) {
const hash = hexToBytes(hashes[i]);
const point = DecafPoint.hashToCurve(hash);
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
}
});
should('have proper equality testing', () => {
const MAX_448B = BigInt(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
);
const bytes448ToNumberLE = (bytes) => ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
const priv = new Uint8Array([
23, 211, 149, 179, 209, 108, 78, 37, 229, 45, 122, 220, 85, 38, 192, 182, 96, 40, 168, 63,
175, 194, 73, 202, 14, 175, 78, 15, 117, 175, 40, 32, 218, 221, 151, 58, 158, 91, 250, 141,
18, 175, 191, 119, 152, 124, 223, 101, 54, 218, 76, 158, 43, 112, 151, 32,
]);
const pub = DecafPoint.BASE.multiply(bytes448ToNumberLE(priv));
deepStrictEqual(pub.equals(DecafPoint.ZERO), false);
});
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -4,6 +4,7 @@ import { should } from 'micro-should';
import './basic.test.js';
import './nist.test.js';
import './ed448.test.js';
import './ed448-addons.test.js';
import './ed25519.test.js';
import './ed25519-addons.test.js';
import './secp256k1.test.js';

View File

@@ -163,6 +163,7 @@ should('poseidonperm_x5_255_3', () => {
t,
roundsFull: 8,
roundsPartial: 57,
sboxPower: 5,
mds,
roundConstants,
});
@@ -229,6 +230,7 @@ should('poseidonperm_x5_255_5', () => {
t,
roundsFull: 8,
roundsPartial: 60,
sboxPower: 5,
mds,
roundConstants,
});
@@ -280,6 +282,7 @@ should('poseidonperm_x5_254_3', () => {
t,
roundsFull: 8,
roundsPartial: 57,
sboxPower: 5,
mds,
roundConstants,
});
@@ -347,6 +350,7 @@ should('poseidonperm_x5_254_5', () => {
t,
roundsFull: 8,
roundsPartial: 60,
sboxPower: 5,
mds,
roundConstants,
});

View File

@@ -268,6 +268,33 @@ describe('secp256k1', () => {
deepStrictEqual(sign(ent5), e.extraEntropyMax);
}
});
should('handle one byte {extraData}', () => {
const extraEntropy = '01';
const privKey = hexToBytes(
'0101010101010101010101010101010101010101010101010101010101010101'
);
const msg = 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b';
const res = secp.sign(msg, privKey, { extraEntropy }).toCompactHex();
deepStrictEqual(
res,
'a250ec23a54bfdecf0e924cbf484077c5044410f915cdba86731cb2e4e925aaa5b1e4e3553d88be2c48a9a0d8d849ce2cc5720d25b2f97473e02f2550abe9545'
);
});
should('handle 48 bytes {extraData}', () => {
const extraEntropy =
'000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000001';
const privKey = hexToBytes(
'0101010101010101010101010101010101010101010101010101010101010101'
);
const msg = 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b';
const res = secp.sign(msg, privKey, { extraEntropy }).toCompactHex();
deepStrictEqual(
res,
'2bdf40f42ac0e42ee12750d03bb12b75306dae58eb3c961c5a80d78efae93e595295b66e8eb28f1eb046bb129a976340312159ec0c20b97342667572e4a8379a'
);
});
});
describe('verify()', () => {

View File

@@ -1,20 +1,24 @@
{
"compilerOptions": {
"strict": true,
"outDir": "esm",
"target": "es2020",
"module": "es6",
"moduleResolution": "node16",
"noUnusedLocals": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": [ "src/crypto" ]
},
"compilerOptions": {
"outDir": "esm",
"target": "es2020",
"module": "es6",
"moduleResolution": "node16",
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": ["src/crypto"]
},
"include": ["src"],
"exclude": [
"node_modules",
"lib",
],
}
"sourceMap": true,
"strict": true,
"allowSyntheticDefaultImports": false,
"allowUnreachableCode": false,
"esModuleInterop": false,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
},
"include": ["src"],
"exclude": ["node_modules", "lib"]
}

View File

@@ -1,20 +1,24 @@
{
"compilerOptions": {
"strict": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": ".",
"target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM
"module": "commonjs",
"moduleResolution": "node",
"noUnusedLocals": true,
"baseUrl": ".",
},
"include": ["src"],
"exclude": [
"node_modules",
"*.d.ts"
],
}
"compilerOptions": {
"outDir": ".",
"target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM
"module": "commonjs",
"moduleResolution": "node",
"baseUrl": ".",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"strict": true,
"allowSyntheticDefaultImports": false,
"allowUnreachableCode": false,
"esModuleInterop": false,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": false,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src"],
"exclude": ["node_modules", "*.d.ts"]
}