18 Commits
0.8.1 ... 0.9.0

Author SHA1 Message Date
Paul Miller
0ffa38db6b Release 0.9.0. 2023-03-24 11:12:02 +01:00
Paul Miller
c4c580edc0 Bump devdeps 2023-03-24 11:06:48 +01:00
Paul Miller
abe8adac7b README 2023-03-24 10:25:03 +01:00
Paul Miller
4fd2ae82b6 readme 2023-03-21 07:27:45 +01:00
Paul Miller
e2411f7dfd modular: add comment 2023-03-21 07:25:09 +01:00
Paul Miller
cb61e4f292 readme 2023-03-21 07:25:01 +01:00
Paul Miller
bb875791bd docs 2023-03-21 07:11:17 +01:00
Paul Miller
3df2553ced Docs 2023-03-21 07:02:07 +01:00
Paul Miller
8fabc7ff06 All files: rename Fp to Field 2023-03-21 06:51:18 +01:00
Paul Miller
f3c21eb347 weierstrass: make weierstrassPoints fromBytes / toBytes optional 2023-03-21 05:51:10 +01:00
Paul Miller
a8b8192714 Add CURVE.p param 2023-03-21 03:06:06 +01:00
Paul Miller
1c6aa07ff7 Release 0.8.3. 2023-03-16 19:41:20 +01:00
Paul Miller
e110237298 readme 2023-03-16 19:17:34 +01:00
Paul Miller
45393db807 Bump docs 2023-03-16 19:05:33 +01:00
Paul Miller
acc3a9dc4d Bump devdep types/node 2023-03-16 18:52:03 +01:00
Paul Miller
9295b0dbae Upgrade to Typescript 5 2023-03-16 18:49:48 +01:00
Paul Miller
5784ef23f6 Release 0.8.2. 2023-03-14 00:44:02 +01:00
Paul Miller
ef55efe842 Fix common.js build 2023-03-14 00:42:40 +01:00
27 changed files with 398 additions and 301 deletions

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"files.exclude": {
"*.{js,d.ts,js.map,d.ts.map}": true,
"esm/*.{js,d.ts,js.map,d.ts.map}": true
}
}

410
README.md
View File

@@ -1,28 +1,21 @@
# noble-curves
[Audited](#security) & minimal JS implementation of elliptic curve cryptography.
Audited & minimal JS implementation of elliptic curve cryptography.
- **noble** family, zero dependencies
- Short Weierstrass, Edwards, Montgomery curves
- ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
- 🔒 [**Audited**](#security) by an independent security firm
- #⃣ [hash to curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
- 🔍 Unique tests ensure correctness with Wycheproof vectors and [cryptofuzz](https://github.com/guidovranken/cryptofuzz) differential fuzzing
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
- 🔍 Unique tests ensure correctness with Wycheproof vectors and
[cryptofuzz](https://github.com/guidovranken/cryptofuzz) differential fuzzing
- 🔻 Tree-shaking-friendly: use only what's necessary, other code won't be included
Package consists of two parts:
1. [Abstract](#abstract-api), zero-dependency EC algorithms
2. [Implementations](#implementations), utilizing one dependency `@noble/hashes`, providing ready-to-use:
- NIST curves secp256r1/P256, secp384r1/P384, secp521r1/P521
- SECG curve secp256k1
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 [RFC7748](https://www.rfc-editor.org/rfc/rfc7748) / [RFC8032](https://www.rfc-editor.org/rfc/rfc8032) / [ZIP215](https://zips.z.cash/zip-0215) stuff
- pairing-friendly curves bls12-381, bn254
Check out [Upgrading](#upgrading) if you've previously used single-feature noble packages
([secp256k1](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519)).
Check out [Upgrading](#upgrading) if you've previously used single-feature noble
packages ([secp256k1](https://github.com/paulmillr/noble-secp256k1),
[ed25519](https://github.com/paulmillr/noble-ed25519)).
See [Resources](#resources) for articles and real-world software that uses curves.
### This library belongs to _noble_ crypto
@@ -30,33 +23,50 @@ See [Resources](#resources) for articles and real-world software that uses curve
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
- No dependencies, protection against supply chain attacks
- Easily auditable TypeScript/JS code
- Auditable TypeScript / JS code
- Supported in all major browsers and stable node.js versions
- All releases are signed with PGP keys
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
[curves](https://github.com/paulmillr/noble-curves)
([secp256k1](https://github.com/paulmillr/noble-secp256k1),
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1),
[ed25519](https://github.com/paulmillr/noble-ed25519)),
[hashes](https://github.com/paulmillr/noble-hashes)
## Usage
Use NPM for browser / node.js:
Browser, deno and node.js are supported:
> npm install @noble/curves
For [Deno](https://deno.land), use it with [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers). In browser, you could also include the single file from
For [Deno](https://deno.land), use it with
[npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
In browser, you could also include the single file from
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases).
The library is tree-shaking-friendly and does not expose root entry point as `import * from '@noble/curves'`.
Instead, you need to import specific primitives. This is done to ensure small size of your apps.
The library is tree-shaking-friendly and does not expose root entry point as
`import * from '@noble/curves'`. Instead, you need to import specific primitives.
This is done to ensure small size of your apps.
Package consists of two parts:
1. [Implementations](#implementations), utilizing one dependency `@noble/hashes`,
providing ready-to-use:
- NIST curves secp256r1/P256, secp384r1/P384, secp521r1/P521
- SECG curve secp256k1
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448
implementing
[RFC7748](https://www.rfc-editor.org/rfc/rfc7748) /
[RFC8032](https://www.rfc-editor.org/rfc/rfc8032) /
[ZIP215](https://zips.z.cash/zip-0215) standards
- pairing-friendly curves bls12-381, bn254
2. [Abstract](#abstract-api), zero-dependency EC algorithms
### Implementations
Each curve can be used in the following way:
```ts
import { secp256k1 } from '@noble/curves/secp256k1'; // ECMAScript Modules (ESM) and Common.js
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);
@@ -64,8 +74,9 @@ const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv);
secp256k1.verify(sig, msg, pub) === true;
// hex strings are also supported besides Uint8Arrays:
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings
const pub2 = secp256k1.getPublicKey(privHex);
```
All curves:
@@ -90,7 +101,7 @@ Weierstrass curves feature recovering public keys from signatures and ECDH key a
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
sig.recoverPublicKey(msg) === pub; // public key recovery
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH (elliptic curve diffie-hellman)
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH
```
secp256k1 has schnorr signature implementation which follows
@@ -103,7 +114,6 @@ const pub = schnorr.getPublicKey(priv);
const msg = new TextEncoder().encode('hello');
const sig = schnorr.sign(msg, priv);
const isValid = schnorr.verify(sig, msg, pub);
console.log(isValid);
```
ed25519 module has ed25519ctx / ed25519ph variants,
@@ -134,18 +144,27 @@ RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso
// also has add(), equals(), multiply(), toRawBytes() methods
```
ed448 module is basically the same:
ed448 is similar:
```ts
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448';
ed448.getPublicKey(ed448.utils.randomPrivateKey());
```
Every curve has params:
```ts
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
console.log(secp256k1.CURVE.p, secp256k1.CURVE.n, secp256k1.CURVE.a, secp256k1.CURVE.b);
```
BLS12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to
construct [zk-SNARKs](https://z.cash/technology/zksnarks/) at the 128-bit security
and use aggregated, batch-verifiable
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
using Boneh-Lynn-Shacham signature scheme.
using Boneh-Lynn-Shacham signature scheme. Compatible with ETH and others,
just make sure to provide correct DST (domain separation tag argument).
```ts
import { bls12_381 as bls } from '@noble/curves/bls12-381';
@@ -182,10 +201,13 @@ console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
## Abstract API
Abstract API allows to define custom curves. All arithmetics is done with JS bigints over finite fields,
which is defined from `modular` sub-module. For scalar multiplication, we use [precomputed tables with w-ary non-adjacent form (wNAF)](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/).
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.
Abstract API allows to define custom curves. All arithmetics is done with JS
bigints over finite fields, which is defined from `modular` sub-module. For
scalar multiplication, we use
[precomputed tables with w-ary non-adjacent form (wNAF)](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/).
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:
@@ -201,14 +223,36 @@ There are following zero-dependency algorithms:
```ts
import { weierstrass } from '@noble/curves/abstract/weierstrass';
import { Field } from '@noble/curves/abstract/modular'; // finite field for mod arithmetics
import { sha256 } from '@noble/hashes/sha256'; // 3rd-party sha256() of type utils.CHash
import { hmac } from '@noble/hashes/hmac'; // 3rd-party hmac() that will accept sha256()
import { concatBytes, randomBytes } from '@noble/hashes/utils'; // 3rd-party utilities
const secq256k1 = weierstrass({
// secq256k1: cycle of secp256k1 with Fp/N flipped.
// https://personaelabs.org/posts/spartan-ecdsa
// https://zcash.github.io/halo2/background/curves.html#cycles-of-curves
a: 0n,
b: 7n,
Fp: Field(2n ** 256n - 432420386565659656852420866394968145599n),
n: 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n,
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
hash: sha256,
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
randomBytes,
});
// weierstrassPoints can also be used if you don't need ECDSA, hash, hmac, randomBytes
```
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass` expects arguments `a`, `b`, field `Fp`, curve order `n`, cofactor `h`
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass`
expects arguments `a`, `b`, field `Fp`, curve order `n`, cofactor `h`
and coordinates `Gx`, `Gy` of generator point.
**`k` generation** is done deterministically, following [RFC6979](https://www.rfc-editor.org/rfc/rfc6979).
For this you will need `hmac` & `hash`, which in our implementations is provided by noble-hashes.
If you're using different hashing library, make sure to wrap it in the following interface:
**`k` generation** is done deterministically, following
[RFC6979](https://www.rfc-editor.org/rfc/rfc6979). For this you will need
`hmac` & `hash`, which in our implementations is provided by noble-hashes. If
you're using different hashing library, make sure to wrap it in the following interface:
```ts
type CHash = {
@@ -224,11 +268,36 @@ type CHash = {
1. Exported as `ProjectivePoint`
2. Represented in projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z)
3. Use complete exception-free formulas for addition and doubling
4. Can be decoded/encoded from/to Uint8Array / hex strings using `ProjectivePoint.fromHex` and `ProjectivePoint#toRawBytes()`
4. Can be decoded/encoded from/to Uint8Array / hex strings using
`ProjectivePoint.fromHex` and `ProjectivePoint#toRawBytes()`
5. Have `assertValidity()` which checks for being on-curve
6. Have `toAffine()` and `x` / `y` getters which convert to 2d xy affine coordinates
```ts
// `weierstrassPoints()` returns `CURVE` and `ProjectivePoint`
// `weierstrass()` returns `CurveFn`
type SignOpts = { lowS?: boolean; prehash?: boolean; extraEntropy: boolean | Uint8Array };
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
verify: (
signature: Hex | SignatureType,
msgHash: Hex,
publicKey: Hex,
opts?: { lowS?: boolean; prehash?: boolean }
) => boolean;
ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor;
utils: {
normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(key: PrivKey): boolean;
randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
};
};
// T is usually bigint, but can be something else like complex numbers in BLS curves
interface ProjPointType<T> extends Group<ProjPointType<T>> {
readonly px: T;
@@ -254,7 +323,8 @@ interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
}
```
**ECDSA signatures** are represented by `Signature` instances and can be described by the interface:
**ECDSA signatures** are represented by `Signature` instances and can be
described by the interface:
```ts
interface SignatureType {
@@ -279,28 +349,9 @@ type SignatureConstructor = {
};
```
Example implementing [secq256k1](https://personaelabs.org/posts/spartan-ecdsa) (NOT secp256k1)
[cycle](https://zcash.github.io/halo2/background/curves.html#cycles-of-curves) of secp256k1 with Fp/N flipped.
More examples:
```typescript
import { weierstrass } from '@noble/curves/abstract/weierstrass';
import { Field } from '@noble/curves/abstract/modular'; // finite field, mod arithmetics done over it
import { sha256 } from '@noble/hashes/sha256'; // 3rd-party sha256() of type utils.CHash, with blockLen/outputLen
import { hmac } from '@noble/hashes/hmac'; // 3rd-party hmac() that will accept sha256()
import { concatBytes, randomBytes } from '@noble/hashes/utils'; // 3rd-party utilities
const secq256k1 = weierstrass({
// secq256k1: cycle of secp256k1 with Fp/N flipped.
a: 0n,
b: 7n,
Fp: Field(2n ** 256n - 432420386565659656852420866394968145599n),
n: 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n,
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
hash: sha256,
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
randomBytes,
});
// All curves expose same generic interface.
const priv = secq256k1.utils.randomPrivateKey();
secq256k1.getPublicKey(priv); // Convert private key to public.
@@ -318,6 +369,7 @@ point.assertValidity(); // Checks for being on-curve
point.toAffine(); // Converts to 2d affine xy coordinates
secq256k1.CURVE.n;
secq256k1.CURVE.p;
secq256k1.CURVE.Fp.mod();
secq256k1.CURVE.hash();
@@ -326,33 +378,34 @@ const fast = secq256k1.utils.precompute(8, Point.fromHex(someonesPubKey));
fast.multiply(privKey); // much faster ECDH now
```
`weierstrass()` returns `CurveFn`:
### abstract/edwards: Twisted Edwards curve
```ts
type SignOpts = { lowS?: boolean; prehash?: boolean; extraEntropy: boolean | Uint8Array };
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
verify: (
signature: Hex | SignatureType,
msgHash: Hex,
publicKey: Hex,
opts?: { lowS?: boolean; prehash?: boolean }
) => boolean;
ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor;
utils: {
normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(key: PrivKey): boolean;
randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
};
};
```
import { twistedEdwards } from '@noble/curves/abstract/edwards';
import { Field } from '@noble/curves/abstract/modular';
import { sha512 } from '@noble/hashes/sha512';
import { randomBytes } from '@noble/hashes/utils';
### abstract/edwards: Twisted Edwards curve
const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({
a: -1n,
d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
Fp: Fp,
n: 2n ** 252n + 27742317777372353535851937790883648493n,
h: 8n,
Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
hash: sha512,
randomBytes,
adjustScalarBytes(bytes) {
// optional; but mandatory in ed25519
bytes[0] &= 248;
bytes[31] &= 127;
bytes[31] |= 64;
return bytes;
},
} as const);
```
Twisted Edwards curve's formula is `ax² + y² = 1 + dx²y²`. You must specify `a`, `d`, field `Fp`, order `n`, cofactor `h`
and coordinates `Gx`, `Gy` of generator point.
@@ -370,6 +423,25 @@ For EdDSA signatures, `hash` param required. `adjustScalarBytes` which instructs
7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions
```ts
// `twistedEdwards()` returns `CurveFn` of following type:
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex, context?: Hex) => Uint8Array;
verify: (sig: SigType, message: Hex, publicKey: Hex, context?: Hex) => boolean;
ExtendedPoint: ExtPointConstructor;
utils: {
randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => {
head: Uint8Array;
prefix: Uint8Array;
scalar: bigint;
point: PointType;
pointBytes: Uint8Array;
};
};
};
interface ExtPointType extends Group<ExtPointType> {
readonly ex: bigint;
readonly ey: bigint;
@@ -392,69 +464,16 @@ interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
}
```
Example implementing edwards25519:
```ts
import { twistedEdwards } from '@noble/curves/abstract/edwards';
import { Field, div } from '@noble/curves/abstract/modular';
import { sha512 } from '@noble/hashes/sha512';
const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({
a: -1n,
d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
Fp,
n: 2n ** 252n + 27742317777372353535851937790883648493n,
h: 8n,
Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
hash: sha512,
randomBytes,
adjustScalarBytes(bytes) {
// optional; but mandatory in ed25519
bytes[0] &= 248;
bytes[31] &= 127;
bytes[31] |= 64;
return bytes;
},
} as const);
```
`twistedEdwards()` returns `CurveFn` of following type:
```ts
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex, context?: Hex) => Uint8Array;
verify: (sig: SigType, message: Hex, publicKey: Hex, context?: Hex) => boolean;
ExtendedPoint: ExtPointConstructor;
utils: {
randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => {
head: Uint8Array;
prefix: Uint8Array;
scalar: bigint;
point: PointType;
pointBytes: Uint8Array;
};
};
};
```
### abstract/montgomery: Montgomery curve
The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet.
You must specify curve params `Fp`, `a`, `Gu` coordinate of u, `montgomeryBits` and `nByteLength`.
```typescript
import { montgomery } from '@noble/curves/abstract/montgomery';
import { Field } from '@noble/curves/abstract/modular';
const x25519 = montgomery({
Fp: Field(2n ** 255n - 19n),
a: 486662n,
Gu: 9n,
Fp: Field(2n ** 255n - 19n),
montgomeryBits: 255,
nByteLength: 32,
// Optional param
@@ -467,6 +486,11 @@ const x25519 = montgomery({
});
```
The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748.
Proper Elliptic Curve Points are not implemented yet.
You must specify curve params `Fp`, `a`, `Gu` coordinate of u, `montgomeryBits` and `nByteLength`.
### 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).
@@ -507,19 +531,37 @@ function expand_message_xof(
): Uint8Array;
```
`hash_to_field(msg, count, options)` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
`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.
- `msg` a byte string containing the message to hash
- `count` the number of elements of F to output
- `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`.
- `p` is field prime, m=field extension (1 for prime fields)
- `k` is security target in bits (e.g. 128).
- `expand` should be `xmd` for SHA2, SHA3, BLAKE; `xof` for SHAKE, BLAKE-XOF
- `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
- Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
```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
* * `m` is extension degree (1 for prime fields)
* * `k` is the target security target in bits (e.g. 128), from section 5.1
* * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
* * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
*/
type UnicodeOrBytes = string | Uint8Array;
type Opts = {
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[][];
```
@@ -704,6 +746,13 @@ hashToCurve
└─ed448 x 1,045 ops/sec @ 956μs/op
```
## Contributing & testing
1. Clone the repository
2. `npm install` to install build dependencies like TypeScript
3. `npm run build` to compile TypeScript code
4. `npm run test` will execute all main tests
## Resources
Article about some of library's features: [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
@@ -719,45 +768,52 @@ Projects using the library:
- Threshold sigs demo [genthresh.com](https://genthresh.com)
- BBS signatures [github.com/Wind4Greg/BBS-Draft-Checks](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)
- Others
- All curves demo: Elliptic curve calculator [paulmillr.com/ecc](https://paulmillr.com/ecc)
- All curves demo: Elliptic curve calculator [paulmillr.com/noble](https://paulmillr.com/noble)
- [micro-starknet](https://github.com/paulmillr/micro-starknet) for stark-friendly elliptic curve.
## Upgrading
If you're coming from single-feature noble packages, the following changes need to be kept in mind:
Previously, the library was split into single-feature packages
noble-secp256k1 and noble-ed25519. curves can be thought as a continuation of their
original work. The libraries now changed their direction towards providing
minimal 4kb implementations of cryptography and are not as feature-complete.
- 2d affine (x, y) points have been removed to reduce complexity and improve speed
- Removed `number` support as a type for private keys, `bigint` is still supported
- `mod`, `invert` are no longer present in `utils`: use `@noble/curves/abstract/modular`
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)`
- `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).
- `verify`
- `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)`
- `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)
- `utils` were split into `utils` (same api as in noble-curves) and
`etc` (`hmacSha256Sync` and others)
- Compressed (33-byte) public keys are now returned by default, instead of uncompressed
- Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
- `sign()`
- `der`, `recovered` options were removed
- `canonical` was renamed to `lowS`
- Return type is now `{ r: bigint, s: bigint, recovery: number }` instance of `Signature`
- `verify()`
- `strict` was renamed to `lowS`
- `recoverPublicKey()`: moved to sig instance `Signature#recoverPublicKey(msgHash)`
- `Point` was removed: use `ProjectivePoint` in xyz coordinates
- `utils`: Many methods were removed, others were moved to `schnorr` namespace
Upgrading from [@noble/ed25519](https://github.com/paulmillr/noble-ed25519) 1.7:
Upgrading from @noble/ed25519 1.7:
- Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
- ed25519ph, ed25519ctx
- `Point` was removed: use `ExtendedPoint` in xyzt coordinates
- `Signature` was removed
- `getSharedSecret` was removed: use separate x25519 sub-module
- Methods are now sync by default
- `bigint` is no longer allowed in `getPublicKey`, `sign`, `verify`. Reason: ed25519 is LE, can lead to bugs
## Contributing & testing
1. Clone the repository
2. `npm install` to install build dependencies like TypeScript
3. `npm run build` to compile TypeScript code
4. `npm run test` will execute all main tests
- `Point` (2d xy) has been changed to `ExtendedPoint` (xyzt)
- `Signature` was removed: just use raw bytes or hex now
- `utils` were split into `utils` (same api as in noble-curves) and
`etc` (`sha512Sync` and others)
- `getSharedSecret` was moved to `x25519` module
## License

View File

@@ -1,6 +1,6 @@
import { run, mark } from 'micro-bmark';
import { secp256k1 } from '../secp256k1.js';
import { Fp } from '../abstract/modular.js';
import { Field as Fp } from '../abstract/modular.js';
run(async () => {
console.log(`\x1b[36mmodular, secp256k1 field\x1b[0m`);

77
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@noble/curves",
"version": "0.8.0",
"version": "0.8.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "0.8.0",
"version": "0.8.3",
"funding": [
{
"type": "individual",
@@ -15,35 +15,38 @@
],
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.2.0"
"@noble/hashes": "1.3.0"
},
"devDependencies": {
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"@scure/bip32": "~1.2.0",
"@scure/bip39": "~1.2.0",
"@types/node": "18.11.18",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.3",
"typescript": "4.7.3"
"prettier": "2.8.4",
"typescript": "5.0.2"
}
},
"node_modules/@noble/hashes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz",
"integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==",
"node_modules/@noble/curves": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-0.8.3.tgz",
"integrity": "sha512-OqaOf4RWDaCRuBKJLDURrgVxjLmneGsiCXGuzYB5y95YithZMA6w4uk34DHSm0rKMrrYiaeZj48/81EvaAScLQ==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
],
"dependencies": {
"@noble/hashes": "1.3.0"
}
},
"node_modules/@noble/secp256k1": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"dev": true,
"node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
@@ -64,9 +67,9 @@
]
},
"node_modules/@scure/bip32": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz",
"integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.2.0.tgz",
"integrity": "sha512-O+vT/hBVk+ag2i6j2CDemwd1E1MtGt+7O1KzrPNsaNvSsiEK55MyPIxJIMI2PS8Ijj464B2VbQlpRoQXxw1uHg==",
"dev": true,
"funding": [
{
@@ -75,15 +78,15 @@
}
],
"dependencies": {
"@noble/hashes": "~1.2.0",
"@noble/secp256k1": "~1.7.0",
"@noble/curves": "~0.8.3",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@scure/bip39": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz",
"integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"dev": true,
"funding": [
{
@@ -92,14 +95,14 @@
}
],
"dependencies": {
"@noble/hashes": "~1.2.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@types/node": {
"version": "18.11.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz",
"integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==",
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
"dev": true
},
"node_modules/fast-check": {
@@ -131,9 +134,9 @@
"dev": true
},
"node_modules/prettier": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
"integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
@@ -162,16 +165,16 @@
]
},
"node_modules/typescript": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
"node": ">=12.20"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@noble/curves",
"version": "0.8.1",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"version": "0.9.0",
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [
"abstract",
"esm",
@@ -28,17 +28,17 @@
},
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.2.0"
"@noble/hashes": "1.3.0"
},
"devDependencies": {
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"@scure/bip32": "~1.2.0",
"@scure/bip39": "~1.2.0",
"@types/node": "18.11.18",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.3",
"typescript": "4.7.3"
"prettier": "2.8.4",
"typescript": "5.0.2"
},
"main": "index.js",
"exports": {
@@ -171,7 +171,7 @@
"bn254",
"pasta",
"bls",
"nist",
"noble",
"ecc",
"ecdsa",
"eddsa",

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 { Field, hashToPrivateScalar } from './modular.js';
import { IField, hashToPrivateScalar } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js';
import {
@@ -41,15 +41,15 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
htfDefaults: htf.Opts;
};
x: bigint;
Fp: Field<Fp>;
Fr: Field<bigint>;
Fp2: Field<Fp2> & {
Fp: IField<Fp>;
Fr: IField<bigint>;
Fp2: IField<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2;
};
Fp6: Field<Fp6>;
Fp12: Field<Fp12> & {
Fp6: IField<Fp6>;
Fp12: IField<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12;
@@ -62,11 +62,11 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>;
Fr: Field<bigint>;
Fp: Field<Fp>;
Fp2: Field<Fp2>;
Fp6: Field<Fp6>;
Fp12: Field<Fp12>;
Fr: IField<bigint>;
Fp: IField<Fp>;
Fp2: IField<Fp2>;
Fp6: IField<Fp6>;
Fp12: IField<Fp12>;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;

View File

@@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Abelian group utilities
import { Field, validateField, nLength } from './modular.js';
import { IField, validateField, nLength } from './modular.js';
import { validateObject } from './utils.js';
const _0n = BigInt(0);
const _1n = BigInt(1);
@@ -168,7 +168,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
// Though generator can be different (Fp2 / Fp6 for BLS).
export type BasicCurve<T> = {
Fp: Field<T>; // Field over which we'll do calculations (Fp)
Fp: IField<T>; // Field over which we'll do calculations (Fp)
n: bigint; // Curve order, total count of valid points in the field
nBitLength?: number; // bit length of curve order
nByteLength?: number; // byte length of curve order
@@ -195,5 +195,9 @@ export function validateBasic<FP, T>(curve: BasicCurve<FP> & T) {
}
);
// Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
return Object.freeze({
...nLength(curve.n, curve.nBitLength),
...curve,
...{ p: curve.Fp.ORDER },
} as const);
}

View File

@@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import { mod, Field } from './modular.js';
import { mod, IField } from './modular.js';
import { bytesToNumberBE, CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
/**
@@ -163,7 +163,7 @@ export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bi
return u;
}
export function isogenyMap<T, F extends Field<T>>(field: F, map: [T[], T[], T[], T[]]) {
export function isogenyMap<T, F extends IField<T>>(field: F, map: [T[], T[], T[], T[]]) {
// Make same order as in spec
const COEFF = map.map((i) => Array.from(i).reverse());
return (x: T, y: T) => {

View File

@@ -97,7 +97,7 @@ export function tonelliShanks(P: bigint) {
// Fast-path
if (S === 1) {
const p1div4 = (P + _1n) / _4n;
return function tonelliFast<T>(Fp: Field<T>, n: T) {
return function tonelliFast<T>(Fp: IField<T>, n: T) {
const root = Fp.pow(n, p1div4);
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root;
@@ -106,7 +106,7 @@ export function tonelliShanks(P: bigint) {
// Slow-path
const Q1div2 = (Q + _1n) / _2n;
return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
return function tonelliSlow<T>(Fp: IField<T>, n: T): T {
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');
let r = S;
@@ -146,7 +146,7 @@ export function FpSqrt(P: bigint) {
// 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn;
// const NUM = 72057594037927816n;
const p1div4 = (P + _1n) / _4n;
return function sqrt3mod4<T>(Fp: Field<T>, n: T) {
return function sqrt3mod4<T>(Fp: IField<T>, n: T) {
const root = Fp.pow(n, p1div4);
// Throw if root**2 != n
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
@@ -157,7 +157,7 @@ export function FpSqrt(P: bigint) {
// Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10)
if (P % _8n === _5n) {
const c1 = (P - _5n) / _8n;
return function sqrt5mod8<T>(Fp: Field<T>, n: T) {
return function sqrt5mod8<T>(Fp: IField<T>, n: T) {
const n2 = Fp.mul(n, _2n);
const v = Fp.pow(n2, c1);
const nv = Fp.mul(n, v);
@@ -203,7 +203,7 @@ export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) &
// - unreadable mess: addition, multiply, square, squareRoot, inversion, divide, power, equals, subtract
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m
export interface Field<T> {
export interface IField<T> {
ORDER: bigint;
BYTES: number;
BITS: number;
@@ -249,7 +249,7 @@ const FIELD_FIELDS = [
'eql', 'add', 'sub', 'mul', 'pow', 'div',
'addN', 'subN', 'mulN', 'sqrN'
] as const;
export function validateField<T>(field: Field<T>) {
export function validateField<T>(field: IField<T>) {
const initial = {
ORDER: 'bigint',
MASK: 'bigint',
@@ -264,7 +264,7 @@ export function validateField<T>(field: Field<T>) {
}
// Generic field functions
export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
// Should have same speed as pow for bigints
// TODO: benchmark!
if (power < _0n) throw new Error('Expected power > 0');
@@ -280,7 +280,8 @@ export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
return p;
}
export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
// 0 is non-invertible: non-batched version will throw on 0
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
const lastMultiplied = nums.reduce((acc, num, i) => {
@@ -299,12 +300,12 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
return tmp;
}
export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T {
export function FpDiv<T>(f: IField<T>, lhs: T, rhs: T | bigint): T {
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
}
// This function returns True whenever the value x is a square in the field F.
export function FpIsSquare<T>(f: Field<T>) {
export function FpIsSquare<T>(f: IField<T>) {
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
return (x: T): boolean => {
const p = f.pow(x, legendreConst);
@@ -320,16 +321,24 @@ export function nLength(n: bigint, nBitLength?: number) {
return { nBitLength: _nBitLength, nByteLength };
}
// NOTE: very fragile, always bench. Major performance points:
// - NonNormalized ops
// - Object.freeze
// - same shape of object (don't add/remove keys)
type FpField = Field<bigint> & Required<Pick<Field<bigint>, 'isOdd'>>;
export function Fp(
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
* @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
* @param redef optional faster redefinitions of sqrt and other methods
*/
export function Field(
ORDER: bigint,
bitLen?: number,
isLE = false,
redef: Partial<Field<bigint>> = {}
redef: Partial<IField<bigint>> = {}
): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
@@ -382,13 +391,13 @@ export function Fp(
return Object.freeze(f);
}
export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) {
export function FpSqrtOdd<T>(Fp: IField<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? root : Fp.neg(root);
}
export function FpSqrtEven<T>(Fp: Field<T>, elm: T) {
export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? Fp.neg(root) : root;

View File

@@ -1,10 +1,10 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
import { Field, FpPow, validateField } from './modular.js';
import { IField, FpPow, validateField } from './modular.js';
// We don't provide any constants, since different implementations use different constants.
// For reference constants see './test/poseidon.test.js'.
export type PoseidonOpts = {
Fp: Field<bigint>;
Fp: IField<bigint>;
t: number;
roundsFull: number;
roundsPartial: number;

View File

@@ -82,8 +82,8 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
export type CurvePointsType<T> = BasicWCurve<T> & {
// Bytes
fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array;
fromBytes?: (bytes: Uint8Array) => AffinePoint<T>;
toBytes?: (c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => Uint8Array;
};
function validatePointOpts<T>(curve: CurvePointsType<T>) {
@@ -93,8 +93,6 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
{
a: 'field',
b: 'field',
fromBytes: 'function',
toBytes: 'function',
},
{
allowedPrivateKeyLengths: 'array',
@@ -102,6 +100,8 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
isTorsionFree: 'function',
clearCofactor: 'function',
allowInfinityPoint: 'boolean',
fromBytes: 'function',
toBytes: 'function',
}
);
const { endo, Fp, a } = opts;
@@ -184,6 +184,23 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const CURVE = validatePointOpts(opts);
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
const toBytes =
CURVE.toBytes ||
((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));
});
const fromBytes =
CURVE.fromBytes ||
((bytes: Uint8Array) => {
// const head = bytes[0];
const tail = bytes.subarray(1);
// if (head !== 0x04) throw new Error('Only non-compressed encoding is supported');
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
return { x, y };
});
/**
* y² = x³ + ax + b: Short weierstrass curve formula
* @returns y²
@@ -280,7 +297,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param hex short/long ECDSA hex
*/
static fromHex(hex: Hex): Point {
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes('pointHex', hex)));
const P = Point.fromAffine(fromBytes(ensureBytes('pointHex', hex)));
P.assertValidity();
return P;
}
@@ -563,7 +580,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
toRawBytes(isCompressed = true): Uint8Array {
this.assertValidity();
return CURVE.toBytes(Point, this, isCompressed);
return toBytes(Point, this, isCompressed);
}
toHex(isCompressed = true): string {
@@ -574,6 +591,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const wnaf = wNAF(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
return {
CURVE,
ProjectivePoint: Point as ProjConstructor<T>,
normPrivateKeyToScalar,
weierstrassEquation,
@@ -652,8 +670,7 @@ export type CurveFn = {
export function weierstrass(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const CURVE_ORDER = CURVE.n;
const Fp = CURVE.Fp;
const { Fp, n: CURVE_ORDER } = CURVE;
const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
@@ -1055,11 +1072,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve
// TODO: check if there is a way to merge this with uvRatio in Edwards && move to modular?
// b = True and y = sqrt(u / v) if (u / v) is square in F, and
// b = False and y = sqrt(Z * (u / v)) otherwise.
export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// Generic implementation
const q = Fp.ORDER;
let l = 0n;
@@ -1124,7 +1140,7 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
}
// From draft-irtf-cfrg-hash-to-curve-16
export function mapToCurveSimpleSWU<T>(
Fp: mod.Field<T>,
Fp: mod.IField<T>,
opts: {
A: T;
B: T;

View File

@@ -72,13 +72,13 @@ import { isogenyMap } from './abstract/hash-to-curve.js';
// CURVE FIELDS
// Finite field over p.
const Fp =
mod.Fp(
mod.Field(
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn
);
type Fp = bigint;
// Finite field over r.
// This particular field is not used anywhere in bls12-381, but it is still useful.
const Fr = mod.Fp(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n);
const Fr = mod.Field(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n);
// Fp₂ over complex plane
type BigintTuple = [bigint, bigint];
@@ -124,7 +124,7 @@ const FP2_ORDER =
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn **
2n;
const Fp2: mod.Field<Fp2> & Fp2Utils = {
const Fp2: mod.IField<Fp2> & Fp2Utils = {
ORDER: FP2_ORDER,
BITS: bitLen(FP2_ORDER),
BYTES: Math.ceil(bitLen(FP2_ORDER) / 8),
@@ -333,7 +333,7 @@ type Fp6Utils = {
multiplyByFp2(lhs: Fp6, rhs: Fp2): Fp6;
};
const Fp6: mod.Field<Fp6> & Fp6Utils = {
const Fp6: mod.IField<Fp6> & Fp6Utils = {
ORDER: Fp2.ORDER, // TODO: unused, but need to verify
BITS: 3 * Fp2.BITS,
BYTES: 3 * Fp2.BYTES,
@@ -545,7 +545,7 @@ type Fp12Utils = {
_cyclotomicExp(num: Fp12, n: bigint): Fp12;
};
const Fp12: mod.Field<Fp12> & Fp12Utils = {
const Fp12: mod.IField<Fp12> & Fp12Utils = {
ORDER: Fp2.ORDER, // TODO: unused, but need to verify
BITS: 2 * Fp2.BITS,
BYTES: 2 * Fp2.BYTES,

View File

@@ -2,7 +2,7 @@
import { sha256 } from '@noble/hashes/sha256';
import { weierstrass } from './abstract/weierstrass.js';
import { getHash } from './_shortw_utils.js';
import { Fp } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
/**
* bn254 pairing-friendly curve.
* Previously known as alt_bn_128, when it had 128-bit security.
@@ -12,7 +12,7 @@ import { Fp } from './abstract/modular.js';
export const bn254 = weierstrass({
a: BigInt(0),
b: BigInt(3),
Fp: Fp(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')),
Fp: Field(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')),
n: BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001'),
Gx: BigInt(1),
Gy: BigInt(2),

View File

@@ -3,7 +3,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtPointType } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
import { mod, pow2, isNegativeLE, Field, FpSqrtEven } from './abstract/modular.js';
import {
equalBytes,
bytesToHex,

View File

@@ -2,7 +2,7 @@
import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Fp as Field } from './abstract/modular.js';
import { mod, pow2, Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js';

View File

@@ -3,7 +3,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js';
import { blake2s } from '@noble/hashes/blake2s';
import { Fp } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
/**
* jubjub Twisted Edwards curve.
@@ -17,7 +17,7 @@ export const jubjub = twistedEdwards({
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
// Finite field 𝔽p over which we'll do calculations
// Same value as bls12-381 Fr (not Fp)
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
Fp: Field(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
// Subgroup order: how many points curve has
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
// Cofactor

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha384 } from '@noble/hashes/sha512';
import { Fp as Field } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512';
import { Fp as Field } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';

View File

@@ -11,7 +11,7 @@ export const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46e
export const pallas = weierstrass({
a: BigInt(0),
b: BigInt(5),
Fp: mod.Fp(p),
Fp: mod.Field(p),
n: q,
Gx: mod.mod(BigInt(-1), p),
Gy: BigInt(2),
@@ -22,7 +22,7 @@ export const pallas = weierstrass({
export const vesta = weierstrass({
a: BigInt(0),
b: BigInt(5),
Fp: mod.Fp(q),
Fp: mod.Field(q),
n: p,
Gx: mod.mod(BigInt(-1), q),
Gy: BigInt(2),

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { randomBytes } from '@noble/hashes/utils';
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { Field, mod, pow2 } from './abstract/modular.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import type { Hex, PrivKey } from './abstract/utils.js';
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
@@ -43,7 +43,6 @@ function sqrtMod(y: bigint): bigint {
}
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
type Fp = bigint;
export const secp256k1 = createCurve(
{
@@ -245,7 +244,7 @@ const isoMap = htf.isogenyMap(
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
);
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from '../esm/_shortw_utils.js';
import { sha224, sha256 } from '@noble/hashes/sha256';
import { Fp } from '../esm/abstract/modular.js';
import { Field as Fp } from '../esm/abstract/modular.js';
// NIST secp192r1 aka P192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { utf8ToBytes } from '@noble/hashes/utils';
import { Fp, validateField } from '../esm/abstract/modular.js';
import { Field as Fp, validateField } from '../esm/abstract/modular.js';
import { poseidon } from '../esm/abstract/poseidon.js';
import * as u from '../esm/abstract/utils.js';

View File

@@ -553,6 +553,7 @@ for (const name in CURVES) {
});
}
describe(name, () => {
if (['bn254', 'pallas', 'vesta'].includes(name)) return;
// Generic complex things (getPublicKey/sign/verify/getSharedSecret)
should('.getPublicKey() type check', () => {
throws(() => C.getPublicKey(0), '0');

View File

@@ -8,7 +8,8 @@ import './ed25519.test.js';
import './secp256k1.test.js';
import './secp256k1-schnorr.test.js';
import './jubjub.test.js';
import './bls12-381.test.js';
import './hash-to-curve.test.js';
import './poseidon.test.js';
import './bls12-381.test.js';
should.run();

View File

@@ -132,7 +132,9 @@ describe('Stark', () => {
// Official vectors: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt
should('poseidonperm_x5_255_3', () => {
const Fp = mod.Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001'));
const Fp = mod.Field(
BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')
);
const mds = [
[
@@ -179,7 +181,7 @@ should('poseidonperm_x5_255_3', () => {
});
should('poseidonperm_x5_255_5', () => {
const Fp = mod.Fp(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n);
const Fp = mod.Field(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n);
const t = 5;
const mds = [
@@ -250,7 +252,7 @@ should('poseidonperm_x5_255_5', () => {
});
should('poseidonperm_x5_254_3', () => {
const Fp = mod.Fp(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const Fp = mod.Field(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const t = 3;
const mds = [
@@ -297,7 +299,7 @@ should('poseidonperm_x5_254_3', () => {
});
should('poseidonperm_x5_254_5', () => {
const Fp = mod.Fp(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const Fp = mod.Field(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const t = 5;
const mds = [

View File

@@ -7,8 +7,8 @@
"outDir": ".",
"target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM
"module": "es6",
"moduleResolution": "node16",
"module": "commonjs",
"moduleResolution": "node",
"noUnusedLocals": true,
"baseUrl": ".",
},
@@ -17,4 +17,4 @@
"node_modules",
"*.d.ts"
],
}
}