14 Commits
0.7.0 ... 0.7.1

Author SHA1 Message Date
Paul Miller
586e2ad5fb Release 0.7.1. 2023-02-16 00:20:37 +01:00
Paul Miller
ed81707bdc readme 2023-02-16 00:12:23 +01:00
Paul Miller
6d56b2d78e readme 2023-02-16 00:08:18 +01:00
Paul Miller
8397241a8f bls, stark: adjust methods 2023-02-16 00:03:20 +01:00
Paul Miller
001d0cc24a weierstrass: rename method, adjust comments 2023-02-16 00:03:10 +01:00
Paul Miller
ce9d165657 readme hash-to-scalar 2023-02-15 23:46:43 +01:00
Paul Miller
2902b0299a readme 2023-02-15 23:38:26 +01:00
Paul Miller
e1cb8549e8 weierstrass, montgomery, secp: add comments 2023-02-15 23:26:56 +01:00
Paul Miller
26ebb5dcce x25519, x448: change param from a24 to a. Change Gu to bigint 2023-02-15 23:07:52 +01:00
Paul Miller
8b2863aeac Fix benchmark 2023-02-15 22:50:32 +01:00
Paul Miller
b1f50d9364 hash-to-curve: bls examples 2023-02-15 00:08:38 +01:00
Paul Miller
b81d74d3cb readme 2023-02-15 00:06:39 +01:00
Paul Miller
d5fe537159 hash-to-curve readme 2023-02-15 00:03:18 +01:00
Paul Miller
cde1d5c488 Fix tests 2023-02-14 23:51:11 +01:00
30 changed files with 235 additions and 181 deletions

166
README.md
View File

@@ -23,7 +23,7 @@ Package consists of two parts:
Check out [Upgrading](#upgrading) if you've previously used single-feature noble packages 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)). ([secp256k1](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519)).
See [In the wild](#in-the-wild) for real-world software that uses curves. See [Resources](#resouces) for articles and real-world software that uses curves.
### This library belongs to _noble_ crypto ### This library belongs to _noble_ crypto
@@ -45,7 +45,7 @@ Use NPM for browser / node.js:
> npm install @noble/curves > npm install @noble/curves
For [Deno](https://deno.land), use it with npm specifier. 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). [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'`. The library is tree-shaking-friendly and does not expose root entry point as `import * from '@noble/curves'`.
@@ -64,7 +64,7 @@ const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv); const sig = secp256k1.sign(msg, priv);
secp256k1.verify(sig, msg, pub) === true; secp256k1.verify(sig, msg, pub) === true;
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236' const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings
// Follows hash-to-curve specification to encode arbitrary hashes to EC points // Follows hash-to-curve specification to encode arbitrary hashes to EC points
@@ -125,7 +125,7 @@ import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
import { x25519 } from '@noble/curves/ed25519'; import { x25519 } from '@noble/curves/ed25519';
const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4'; const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c'; const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv); x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
// hash-to-curve // hash-to-curve
@@ -183,6 +183,7 @@ console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// Pairings // Pairings
// bls.pairing(PointG1, PointG2) // bls.pairing(PointG1, PointG2)
// Also, check out hash-to-curve examples below.
``` ```
## Abstract API ## Abstract API
@@ -190,7 +191,7 @@ console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
Abstract API allows to define custom curves. All arithmetics is done with JS bigints over finite fields, 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/). 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 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. other point (e.g. for ECDH) using `utils.precompute()` method: check out examples.
There are following zero-dependency algorithms: There are following zero-dependency algorithms:
@@ -216,7 +217,7 @@ For this you will need `hmac` & `hash`, which in our implementations is provided
If you're using different hashing library, make sure to wrap it in the following interface: If you're using different hashing library, make sure to wrap it in the following interface:
```ts ```ts
export type CHash = { type CHash = {
(message: Uint8Array): Uint8Array; (message: Uint8Array): Uint8Array;
blockLen: number; blockLen: number;
outputLen: number; outputLen: number;
@@ -235,7 +236,7 @@ export type CHash = {
```ts ```ts
// T is usually bigint, but can be something else like complex numbers in BLS curves // T is usually bigint, but can be something else like complex numbers in BLS curves
export interface ProjPointType<T> extends Group<ProjPointType<T>> { interface ProjPointType<T> extends Group<ProjPointType<T>> {
readonly px: T; readonly px: T;
readonly py: T; readonly py: T;
readonly pz: T; readonly pz: T;
@@ -251,7 +252,7 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
toHex(isCompressed?: boolean): string; toHex(isCompressed?: boolean): string;
} }
// Static methods for 3d XYZ points // Static methods for 3d XYZ points
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> { interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
new (x: T, y: T, z: T): ProjPointType<T>; new (x: T, y: T, z: T): ProjPointType<T>;
fromAffine(p: AffinePoint<T>): ProjPointType<T>; fromAffine(p: AffinePoint<T>): ProjPointType<T>;
fromHex(hex: Hex): ProjPointType<T>; fromHex(hex: Hex): ProjPointType<T>;
@@ -262,7 +263,7 @@ export 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 ```ts
export interface SignatureType { interface SignatureType {
readonly r: bigint; readonly r: bigint;
readonly s: bigint; readonly s: bigint;
readonly recovery?: number; readonly recovery?: number;
@@ -274,9 +275,14 @@ export interface SignatureType {
toCompactRawBytes(): Uint8Array; toCompactRawBytes(): Uint8Array;
toCompactHex(): string; toCompactHex(): string;
// DER-encoded // DER-encoded
toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERRawBytes(): Uint8Array;
toDERHex(isCompressed?: boolean): string; toDERHex(): string;
} }
type SignatureConstructor = {
new (r: bigint, s: bigint): SignatureType;
fromCompact(hex: Hex): SignatureType;
fromDER(hex: Hex): SignatureType;
};
``` ```
Example implementing [secq256k1](https://personaelabs.org/posts/spartan-ecdsa) (NOT secp256k1) Example implementing [secq256k1](https://personaelabs.org/posts/spartan-ecdsa) (NOT secp256k1)
@@ -307,24 +313,30 @@ secq256k1.getPublicKey(priv); // Convert private key to public.
const sig = secq256k1.sign(msg, priv); // Sign msg with private key. const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
secq256k1.verify(sig, msg, priv); // Verify if sig is correct. secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
const point = secq256k1.Point.BASE; // Elliptic curve Point class and BASE point static var. const Point = secq256k1.ProjectivePoint;
const point = Point.BASE; // Elliptic curve Point class and BASE point static var.
point.add(point).equals(point.double()); // add(), equals(), double() methods point.add(point).equals(point.double()); // add(), equals(), double() methods
point.subtract(point).equals(secq256k1.Point.ZERO); // subtract() method, ZERO static var point.subtract(point).equals(Point.ZERO); // subtract() method, ZERO static var
point.negate(); // Flips point over x/y coordinate. point.negate(); // Flips point over x/y coordinate.
point.multiply(31415n); // Multiplication of Point by scalar. point.multiply(31415n); // Multiplication of Point by scalar.
point.assertValidity(); // Checks for being on-curve point.assertValidity(); // Checks for being on-curve
point.toAffine(); // Converts to 2d affine xy coordinates point.toAffine(); // Converts to 2d affine xy coordinates
secq256k1.CURVE.n; secq256k1.CURVE.n;
secq256k1.CURVE.Fp.mod(); secq256k1.CURVE.Fp.mod();
secq256k1.CURVE.hash(); secq256k1.CURVE.hash();
// precomputes
const fast = secq256k1.utils.precompute(8, Point.fromHex(someonesPubKey));
fast.multiply(privKey); // much faster ECDH now
``` ```
`weierstrass()` returns `CurveFn`: `weierstrass()` returns `CurveFn`:
```ts ```ts
export type CurveFn = { type SignOpts = { lowS?: boolean; prehash?: boolean; extraEntropy: boolean | Uint8Array };
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
@@ -338,8 +350,10 @@ export type CurveFn = {
ProjectivePoint: ProjectivePointConstructor; ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
isValidPrivateKey(privateKey: PrivKey): boolean; normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(key: PrivKey): boolean;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
}; };
}; };
``` ```
@@ -362,7 +376,7 @@ For EdDSA signatures, `hash` param required. `adjustScalarBytes` which instructs
7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions 7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions
```ts ```ts
export interface ExtPointType extends Group<ExtPointType> { interface ExtPointType extends Group<ExtPointType> {
readonly ex: bigint; readonly ex: bigint;
readonly ey: bigint; readonly ey: bigint;
readonly ez: bigint; readonly ez: bigint;
@@ -376,7 +390,7 @@ export interface ExtPointType extends Group<ExtPointType> {
toAffine(iz?: bigint): AffinePoint<bigint>; toAffine(iz?: bigint): AffinePoint<bigint>;
} }
// Static methods of Extended Point with coordinates in X, Y, Z, T // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtPointConstructor extends GroupConstructor<ExtPointType> { interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: AffinePoint<bigint>): ExtPointType; fromAffine(p: AffinePoint<bigint>): ExtPointType;
fromHex(hex: Hex): ExtPointType; fromHex(hex: Hex): ExtPointType;
@@ -388,13 +402,14 @@ Example implementing edwards25519:
```ts ```ts
import { twistedEdwards } from '@noble/curves/abstract/edwards'; import { twistedEdwards } from '@noble/curves/abstract/edwards';
import { div } from '@noble/curves/abstract/modular'; import { Field, div } from '@noble/curves/abstract/modular';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({ const ed25519 = twistedEdwards({
a: -1n, a: -1n,
d: div(-121665n, 121666n, 2n ** 255n - 19n), // -121665n/121666n d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
P: 2n ** 255n - 19n, Fp,
n: 2n ** 252n + 27742317777372353535851937790883648493n, n: 2n ** 252n + 27742317777372353535851937790883648493n,
h: 8n, h: 8n,
Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n, Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
@@ -414,13 +429,12 @@ const ed25519 = twistedEdwards({
`twistedEdwards()` returns `CurveFn` of following type: `twistedEdwards()` returns `CurveFn` of following type:
```ts ```ts
export type CurveFn = { type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex, context?: Hex) => Uint8Array;
verify: (sig: SigType, message: Hex, publicKey: PubKey, context?: Hex) => boolean; verify: (sig: SigType, message: Hex, publicKey: Hex, context?: Hex) => boolean;
ExtendedPoint: ExtendedPointConstructor; ExtendedPoint: ExtPointConstructor;
Signature: SignatureConstructor;
utils: { utils: {
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => { getExtendedPublicKey: (key: PrivKey) => {
@@ -438,22 +452,18 @@ export type CurveFn = {
The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet. 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 field, `a24` special variable, `montgomeryBits`, `nByteLength`, and coordinate `u` of generator point. You must specify curve params `Fp`, `a`, `Gu` coordinate of u, `montgomeryBits` and `nByteLength`.
```typescript ```typescript
import { montgomery } from '@noble/curves/abstract/montgomery'; import { montgomery } from '@noble/curves/abstract/montgomery';
const x25519 = montgomery({ const x25519 = montgomery({
P: 2n ** 255n - 19n, Fp: Field(2n ** 255n - 19n),
a24: 121665n, // TODO: change to a a: 486662n,
Gu: 9n,
montgomeryBits: 255, montgomeryBits: 255,
nByteLength: 32, nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000', // Optional param
// Optional params
powPminus2: (x: bigint): bigint => {
return mod.pow(x, P - 2, P);
},
adjustScalarBytes(bytes) { adjustScalarBytes(bytes) {
bytes[0] &= 248; bytes[0] &= 248;
bytes[31] &= 127; bytes[31] &= 127;
@@ -467,11 +477,37 @@ const x25519 = montgomery({
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v11](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11). The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v11](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11).
Every curve has exported `hashToCurve` and `encodeToCurve` methods:
```ts
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
import { randomBytes } from '@noble/hashes/utils';
console.log(hashToCurve(randomBytes()));
console.log(encodeToCurve(randomBytes()));
import { bls12_381 } from '@noble/curves/bls12-381';
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. `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.
```ts ```ts
function expand_message_xmd(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash): Uint8Array; function expand_message_xmd(
function expand_message_xof(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash): Uint8Array; msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: CHash
): Uint8Array;
function expand_message_xof(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
k: number,
H: CHash
): 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)
@@ -483,22 +519,6 @@ _ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
```ts ```ts
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][]; function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
type htfOpts = {
DST: string; // a domain separation tag defined in section 2.2.5
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
p: bigint;
// m: the extension degree of F, m >= 1
// where F is a finite field of characteristic p and order q = p^m
m: number;
k: number; // the target security level for the suite in bits defined in section 5.1
expand?: 'xmd' | 'xof'; // option to use a message that has already been processed by expand_message_xmd
// Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash;
};
``` ```
### abstract/poseidon: Poseidon hash ### abstract/poseidon: Poseidon hash
@@ -532,10 +552,6 @@ and others with it.
### abstract/modular: Modular arithmetics utilities ### abstract/modular: Modular arithmetics utilities
The module also contains useful `hashToPrivateScalar` method which allows to create
scalars (e.g. private keys) with the modulo bias being neglible. It follows
FIPS 186 B.4.1. Requires at least 40 bytes of input for 32-byte private key.
```ts ```ts
import * as mod from '@noble/curves/abstract/modular'; import * as mod from '@noble/curves/abstract/modular';
const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19 const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19
@@ -548,9 +564,32 @@ fp.sqrt(21n); // square root
mod.mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10 mod.mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10
mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
mod.hashToPrivateScalar(sha512_of_something, secp256r1.n);
``` ```
#### 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`.
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 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**.
```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 validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
```
### abstract/utils: General utilities ### abstract/utils: General utilities
```ts ```ts
@@ -561,8 +600,8 @@ utils.hexToBytes('deadbeef');
utils.hexToNumber(); utils.hexToNumber();
utils.bytesToNumberBE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef])); utils.bytesToNumberBE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.bytesToNumberLE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef])); utils.bytesToNumberLE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.numberToBytesBE(123n); utils.numberToBytesBE(123n, 32);
utils.numberToBytesLE(123n); utils.numberToBytesLE(123n, 64);
utils.numberToHexUnpadded(123n); utils.numberToHexUnpadded(123n);
utils.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef])); utils.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef]));
utils.nLength(255n); utils.nLength(255n);
@@ -650,15 +689,16 @@ aggregateSignatures/32 x 11 ops/sec @ 84ms/op
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
``` ```
## In the wild ## Resources
Elliptic curve calculator: [paulmillr.com/ecc](https://paulmillr.com/ecc). Article about some of library's features: [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/). Elliptic curve calculator: [paulmillr.com/ecc](https://paulmillr.com/ecc)
- secp256k1 - secp256k1
- [btc-signer](https://github.com/paulmillr/micro-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer) - [btc-signer](https://github.com/paulmillr/micro-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer)
- ed25519 - ed25519
- [sol-signer](https://github.com/paulmillr/micro-sol-signer) - [sol-signer](https://github.com/paulmillr/micro-sol-signer)
- BLS12-381 - BLS12-381
- Check out `bls12-381.ts` for articles about the curve
- Threshold sigs demo [genthresh.com](https://genthresh.com) - 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) - 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)

View File

@@ -4,8 +4,8 @@
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| >=0.5.0 | :white_check_mark: | | >=1.0.0 | :white_check_mark: |
| <0.5.0 | :x: | | <1.0.0 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View File

@@ -1,6 +1,6 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { mark, run } from 'micro-bmark'; import { mark, run } from 'micro-bmark';
import { bls12_381 as bls } from '../lib/bls12-381.js'; import { bls12_381 as bls } from '../bls12-381.js';
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8') const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim() .trim()

View File

@@ -1,10 +1,10 @@
import { run, mark, utils } from 'micro-bmark'; import { run, mark, utils } from 'micro-bmark';
import { generateData } from './_shared.js'; import { generateData } from './_shared.js';
import { P256 } from '../lib/p256.js'; import { P256 } from '../p256.js';
import { P384 } from '../lib/p384.js'; import { P384 } from '../p384.js';
import { P521 } from '../lib/p521.js'; import { P521 } from '../p521.js';
import { ed25519 } from '../lib/ed25519.js'; import { ed25519 } from '../ed25519.js';
import { ed448 } from '../lib/ed448.js'; import { ed448 } from '../ed448.js';
run(async () => { run(async () => {
const RAM = false const RAM = false

View File

@@ -1,5 +1,5 @@
import { run, mark, utils } from 'micro-bmark'; import { run, mark, utils } from 'micro-bmark';
import { secp256k1, schnorr } from '../lib/secp256k1.js'; import { secp256k1, schnorr } from '../secp256k1.js';
import { generateData } from './_shared.js'; import { generateData } from './_shared.js';
run(async () => { run(async () => {

View File

@@ -1,6 +1,6 @@
import { run, mark, compare, utils } from 'micro-bmark'; import { run, mark, compare, utils } from 'micro-bmark';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils'; import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as stark from '../lib/stark.js'; import * as stark from '../stark.js';
run(async () => { run(async () => {
const RAM = false; const RAM = false;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "0.7.1",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"abstract", "abstract",

View File

@@ -257,7 +257,7 @@ export function bls<Fp2, Fp6, Fp12>(
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 { function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message, htfOpts); const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity(); msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G2.ProjectivePoint) return sigPoint; if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.encode(sigPoint); return Signature.encode(sigPoint);
} }

View File

@@ -11,25 +11,25 @@ export type CurveType = {
nByteLength: number; nByteLength: number;
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
a24: bigint; // Related to d, but cannot be derived from it a: bigint;
montgomeryBits: number; montgomeryBits: number;
powPminus2?: (x: bigint) => bigint; powPminus2?: (x: bigint) => bigint;
xyToU?: (x: bigint, y: bigint) => bigint; xyToU?: (x: bigint, y: bigint) => bigint;
Gu: string; Gu: bigint;
}; };
export type CurveFn = { export type CurveFn = {
scalarMult: (scalar: Hex, u: Hex) => Uint8Array; scalarMult: (scalar: Hex, u: Hex) => Uint8Array;
scalarMultBase: (scalar: Hex) => Uint8Array; scalarMultBase: (scalar: Hex) => Uint8Array;
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array; getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array;
getPublicKey: (privateKey: Hex) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
Gu: string; GuBytes: Uint8Array;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
validateObject( validateObject(
curve, curve,
{ {
a24: 'bigint', a: 'bigint',
}, },
{ {
montgomeryBits: 'isSafeInteger', montgomeryBits: 'isSafeInteger',
@@ -37,7 +37,7 @@ function validateOpts(curve: CurveType) {
adjustScalarBytes: 'function', adjustScalarBytes: 'function',
domain: 'function', domain: 'function',
powPminus2: 'function', powPminus2: 'function',
Gu: 'string', Gu: 'bigint',
} }
); );
// Set defaults // Set defaults
@@ -49,7 +49,7 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn { export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef); const CURVE = validateOpts(curveDef);
const { P } = CURVE; const { P } = CURVE;
const modP = (a: bigint) => mod(a, P); const modP = (n: bigint) => mod(n, P);
const montgomeryBits = CURVE.montgomeryBits; const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8); const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength; const fieldLen = CURVE.nByteLength;
@@ -73,12 +73,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
return [x_2, x_3]; return [x_2, x_3];
} }
// Accepts 0 as well
function assertFieldElement(n: bigint): bigint { function assertFieldElement(n: bigint): bigint {
if (typeof n === 'bigint' && _0n <= n && n < P) return n; if (typeof n === 'bigint' && _0n <= n && n < P) return n;
throw new Error('Expected valid scalar 0 < scalar < CURVE.P'); throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
} }
// x25519 from 4 // x25519 from 4
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = (CURVE.a - BigInt(2)) / BigInt(4);
/** /**
* *
* @param pointU u coordinate (x) on Montgomery Curve 25519 * @param pointU u coordinate (x) on Montgomery Curve 25519
@@ -90,8 +93,6 @@ export function montgomery(curveDef: CurveType): CurveFn {
// Section 5: Implementations MUST accept non-canonical values and process them as // Section 5: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime. // if they had been reduced modulo the field prime.
const k = assertFieldElement(scalar); const k = assertFieldElement(scalar);
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = CURVE.a24;
const x_1 = u; const x_1 = u;
let x_2 = _1n; let x_2 = _1n;
let z_2 = _0n; let z_2 = _0n;
@@ -170,8 +171,9 @@ export function montgomery(curveDef: CurveType): CurveFn {
return encodeUCoordinate(pu); return encodeUCoordinate(pu);
} }
// Computes public key from private. By doing scalar multiplication of base point. // Computes public key from private. By doing scalar multiplication of base point.
const GuBytes = encodeUCoordinate(CURVE.Gu);
function scalarMultBase(scalar: Hex): Uint8Array { function scalarMultBase(scalar: Hex): Uint8Array {
return scalarMult(scalar, CURVE.Gu); return scalarMult(scalar, GuBytes);
} }
return { return {
@@ -179,6 +181,6 @@ export function montgomery(curveDef: CurveType): CurveFn {
scalarMultBase, scalarMultBase,
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey), getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey), getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
Gu: CURVE.Gu, GuBytes: GuBytes,
}; };
} }

View File

@@ -122,7 +122,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
export type CurvePointsRes<T> = { export type CurvePointsRes<T> = {
ProjectivePoint: ProjConstructor<T>; ProjectivePoint: ProjConstructor<T>;
normalizePrivateKey: (key: PrivKey) => bigint; normPrivateKeyToScalar: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T; weierstrassEquation: (x: T) => T;
isWithinCurveOrder: (num: bigint) => boolean; isWithinCurveOrder: (num: bigint) => boolean;
}; };
@@ -203,8 +203,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n'); if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n');
} }
// Validates if priv key is valid and converts it to bigint. // Validates if priv key is valid and converts it to bigint.
// Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey. // Supports options allowedPrivateKeyLengths and wrapPrivateKey.
function normalizePrivateKey(key: PrivKey): bigint { function normPrivateKeyToScalar(key: PrivKey): bigint {
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE; const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
if (lengths && typeof key !== 'bigint') { if (lengths && typeof key !== 'bigint') {
if (key instanceof Uint8Array) key = ut.bytesToHex(key); if (key instanceof Uint8Array) key = ut.bytesToHex(key);
@@ -287,7 +287,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Multiplies generator point by privateKey. // Multiplies generator point by privateKey.
static fromPrivateKey(privateKey: PrivKey) { static fromPrivateKey(privateKey: PrivKey) {
return Point.BASE.multiply(normalizePrivateKey(privateKey)); return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
} }
// We calculate precomputes for elliptic curve point multiplication // We calculate precomputes for elliptic curve point multiplication
@@ -488,8 +488,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* Constant time multiplication. * Constant time multiplication.
* Uses wNAF method. Windowed method may be 10% faster, * Uses wNAF method. Windowed method may be 10% faster,
* but takes 2x longer to generate and consumes 2x memory. * but takes 2x longer to generate and consumes 2x memory.
* Uses precomputes when available.
* Uses endomorphism for Koblitz curves.
* @param scalar by which the point would be multiplied * @param scalar by which the point would be multiplied
* @param affinePoint optional point ot save cached precompute windows on it
* @returns New point * @returns New point
*/ */
multiply(scalar: bigint): Point { multiply(scalar: bigint): Point {
@@ -517,6 +518,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/** /**
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. * Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly.
* Not using Strauss-Shamir trick: precomputation tables are faster.
* The trick could be useful if both P and Q are not G (not in our case).
* @returns non-zero affine point * @returns non-zero affine point
*/ */
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined { multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
@@ -572,7 +575,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return { return {
ProjectivePoint: Point as ProjConstructor<T>, ProjectivePoint: Point as ProjConstructor<T>,
normalizePrivateKey, normPrivateKeyToScalar,
weierstrassEquation, weierstrassEquation,
isWithinCurveOrder, isWithinCurveOrder,
}; };
@@ -642,7 +645,6 @@ export type CurveFn = {
utils: { utils: {
normPrivateKeyToScalar: (key: PrivKey) => bigint; normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(privateKey: PrivKey): boolean; isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>; precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
}; };
@@ -667,7 +669,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const { const {
ProjectivePoint: Point, ProjectivePoint: Point,
normalizePrivateKey, normPrivateKeyToScalar,
weierstrassEquation, weierstrassEquation,
isWithinCurveOrder, isWithinCurveOrder,
} = weierstrassPoints({ } = weierstrassPoints({
@@ -677,7 +679,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const x = Fp.toBytes(a.x); const x = Fp.toBytes(a.x);
const cat = ut.concatBytes; const cat = ut.concatBytes;
if (isCompressed) { if (isCompressed) {
// TODO: hasEvenY
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
} else { } else {
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y));
@@ -801,37 +802,35 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const utils = { const utils = {
isValidPrivateKey(privateKey: PrivKey) { isValidPrivateKey(privateKey: PrivKey) {
try { try {
normalizePrivateKey(privateKey); normPrivateKeyToScalar(privateKey);
return true; return true;
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
normPrivateKeyToScalar: normalizePrivateKey, normPrivateKeyToScalar: normPrivateKeyToScalar,
/**
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
*/
hashToPrivateKey: (hash: Hex): Uint8Array =>
ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength),
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Produces cryptographically secure private key from random of size (nBitLength+64)
* as per FIPS 186 B.4.1 with modulo bias being neglible. * as per FIPS 186 B.4.1 with modulo bias being neglible.
*/ */
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(Fp.BYTES + 8)), randomPrivateKey: (): Uint8Array => {
const rand = CURVE.randomBytes(Fp.BYTES + 8);
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
return ut.numberToBytesBE(num, CURVE.nByteLength);
},
/** /**
* 1. Returns cached point which you can use to pass to `getSharedSecret` or `#multiply` by it. * Creates precompute table for an arbitrary EC point. Makes point "cached".
* 2. Precomputes point multiplication table. Is done by default on first `getPublicKey()` call. * Allows to massively speed-up `point.multiply(scalar)`.
* If you want your first getPublicKey to take 0.16ms instead of 20ms, make sure to call
* utils.precompute() somewhere without arguments first.
* @param windowSize 2, 4, 8, 16
* @returns cached point * @returns cached point
* @example
* const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey));
* fast.multiply(privKey); // much faster ECDH now
*/ */
precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE { precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
point._setWindowSize(windowSize); point._setWindowSize(windowSize);
point.multiply(BigInt(3)); point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here
return point; return point;
}, },
}; };
@@ -862,7 +861,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
/** /**
* ECDH (Elliptic Curve Diffie Hellman). * ECDH (Elliptic Curve Diffie Hellman).
* Computes shared public key from private key and public key. * Computes shared public key from private key and public key.
* Checks: 1) private key validity 2) shared key is on-curve * Checks: 1) private key validity 2) shared key is on-curve.
* Does NOT hash the result.
* @param privateA private key * @param privateA private key
* @param publicB different public key * @param publicB different public key
* @param isCompressed whether to return compact (default), or full key * @param isCompressed whether to return compact (default), or full key
@@ -872,7 +872,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (isProbPub(privateA)) throw new Error('first arg must be private key'); if (isProbPub(privateA)) throw new Error('first arg must be private key');
if (!isProbPub(publicB)) throw new Error('second arg must be public key'); if (!isProbPub(publicB)) throw new Error('second arg must be public key');
const b = Point.fromHex(publicB); // check for being on-curve const b = Point.fromHex(publicB); // check for being on-curve
return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed); return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed);
} }
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
@@ -895,10 +895,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
// NOTE: pads output with zero as per spec // NOTE: pads output with zero as per spec
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); const ORDER_MASK = ut.bitMask(CURVE.nBitLength);
/**
* Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`.
*/
function int2octets(num: bigint): Uint8Array { function int2octets(num: bigint): Uint8Array {
if (typeof num !== 'bigint') throw new Error('bigint expected'); if (typeof num !== 'bigint') throw new Error('bigint expected');
if (!(_0n <= num && num < ORDER_MASK)) if (!(_0n <= num && num < ORDER_MASK))
// n in [0..ORDER_MASK-1]
throw new Error(`bigint expected < 2^${CURVE.nBitLength}`); throw new Error(`bigint expected < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField! // works with order, can have different size than numToField!
return ut.numberToBytesBE(num, CURVE.nByteLength); return ut.numberToBytesBE(num, CURVE.nByteLength);
@@ -922,7 +924,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call. // with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
// const bits2octets = (bits) => int2octets(bits2int_modN(bits)) // const bits2octets = (bits) => int2octets(bits2int_modN(bits))
const h1int = bits2int_modN(msgHash); const h1int = bits2int_modN(msgHash);
const d = normalizePrivateKey(privateKey); // validate private key, convert to bigint const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint
const seedArgs = [int2octets(d), int2octets(h1int)]; const seedArgs = [int2octets(d), int2octets(h1int)];
// extraEntropy. RFC6979 3.6: additional k' (optional). // extraEntropy. RFC6979 3.6: additional k' (optional).
if (ent != null) { if (ent != null) {

View File

@@ -138,10 +138,10 @@ export const ed25519ph = twistedEdwards({
export const x25519 = montgomery({ export const x25519 = montgomery({
P: ED25519_P, P: ED25519_P,
a24: BigInt('121665'), a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits montgomeryBits: 255, // n is 253 bits
nByteLength: 32, nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(9),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ED25519_P; const P = ED25519_P;
// x^(p-2) aka x^(2^255-21) // x^(p-2) aka x^(2^255-21)

View File

@@ -122,11 +122,11 @@ export const ed448 = twistedEdwards(ED448_DEF);
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 }); export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
export const x448 = montgomery({ export const x448 = montgomery({
a24: BigInt(39081), a: BigInt(156326),
montgomeryBits: 448, montgomeryBits: 448,
nByteLength: 57, nByteLength: 57,
P: ed448P, P: ed448P,
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(5),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ed448P; const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x); const Pminus3div4 = ed448_pow_Pminus3div4(x);

View File

@@ -107,6 +107,7 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1); const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const numTo32b = (n: bigint) => numberToBytesBE(n, 32); const numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modP = (x: bigint) => mod(x, secp256k1P); const modP = (x: bigint) => mod(x, secp256k1P);
@@ -114,12 +115,17 @@ const modN = (x: bigint) => mod(x, secp256k1N);
const Point = secp256k1.ProjectivePoint; const Point = secp256k1.ProjectivePoint;
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) => const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
Point.BASE.multiplyAndAddUnsafe(Q, a, b); Point.BASE.multiplyAndAddUnsafe(Q, a, b);
// Calculate point, scalar and bytes
function schnorrGetExtPubKey(priv: PrivKey) { function schnorrGetExtPubKey(priv: PrivKey) {
const d = secp256k1.utils.normPrivateKeyToScalar(priv); const d = secp256k1.utils.normPrivateKeyToScalar(priv); // same method executed in fromPrivateKey
const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside
const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d' const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d'
return { point, scalar, bytes: pointToBytes(point) }; return { point, scalar, bytes: pointToBytes(point) };
} }
/**
* lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.
* @returns valid point checked for being on-curve
*/
function lift_x(x: bigint): PointType<bigint> { function lift_x(x: bigint): PointType<bigint> {
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const xx = modP(x * x); const xx = modP(x * x);
@@ -130,6 +136,9 @@ function lift_x(x: bigint): PointType<bigint> {
p.assertValidity(); p.assertValidity();
return p; return p;
} }
/**
* Create tagged hash, convert it to bigint, reduce modulo-n.
*/
function challenge(...args: Uint8Array[]): bigint { function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args))); return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
} }
@@ -169,6 +178,7 @@ function schnorrSign(
/** /**
* Verifies Schnorr signature. * Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const sig = ensureBytes('signature', signature, 64); const sig = ensureBytes('signature', signature, 64);

View File

@@ -94,19 +94,19 @@ function ensureBytes0x(hex: Hex): Uint8Array {
return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes0x(hex); return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes0x(hex);
} }
function normalizePrivateKey(privKey: Hex) { function normPrivKey(privKey: Hex) {
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0'); return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0');
} }
function getPublicKey0x(privKey: Hex, isCompressed = false) { function getPublicKey0x(privKey: Hex, isCompressed = false) {
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed); return starkCurve.getPublicKey(normPrivKey(privKey), isCompressed);
} }
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) { function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) {
return starkCurve.getSharedSecret(normalizePrivateKey(privKeyA), pubKeyB); return starkCurve.getSharedSecret(normPrivKey(privKeyA), pubKeyB);
} }
function sign0x(msgHash: Hex, privKey: Hex, opts?: any) { function sign0x(msgHash: Hex, privKey: Hex, opts?: any) {
if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0'); if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0');
return starkCurve.sign(ensureBytes0x(msgHash), normalizePrivateKey(privKey), opts); return starkCurve.sign(ensureBytes0x(msgHash), normPrivKey(privKey), opts);
} }
function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) { function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) {
const sig = signature instanceof Signature ? signature : ensureBytes0x(signature); const sig = signature instanceof Signature ? signature : ensureBytes0x(signature);

View File

@@ -1,22 +1,22 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import * as mod from '../lib/esm/abstract/modular.js'; import * as mod from '../esm/abstract/modular.js';
import { bytesToHex as toHex } from '../lib/esm/abstract/utils.js'; import { bytesToHex as toHex } from '../esm/abstract/utils.js';
// Generic tests for all curves in package // Generic tests for all curves in package
import { secp192r1 } from '../lib/esm/p192.js'; import { secp192r1 } from '../esm/p192.js';
import { secp224r1 } from '../lib/esm/p224.js'; import { secp224r1 } from '../esm/p224.js';
import { secp256r1 } from '../lib/esm/p256.js'; import { secp256r1 } from '../esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.js'; import { secp384r1 } from '../esm/p384.js';
import { secp521r1 } from '../lib/esm/p521.js'; import { secp521r1 } from '../esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { secp256k1 } from '../esm/secp256k1.js';
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../lib/esm/ed25519.js'; import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../esm/ed25519.js';
import { ed448, ed448ph } from '../lib/esm/ed448.js'; import { ed448, ed448ph } from '../esm/ed448.js';
import { starkCurve } from '../lib/esm/stark.js'; import { starkCurve } from '../esm/stark.js';
import { pallas, vesta } from '../lib/esm/pasta.js'; import { pallas, vesta } from '../esm/pasta.js';
import { bn254 } from '../lib/esm/bn.js'; import { bn254 } from '../esm/bn.js';
import { jubjub } from '../lib/esm/jubjub.js'; import { jubjub } from '../esm/jubjub.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../esm/bls12-381.js';
// Fields tests // Fields tests
const FIELDS = { const FIELDS = {

View File

@@ -2,10 +2,10 @@ import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { wNAF } from '../lib/esm/abstract/curve.js'; import { wNAF } from '../esm/abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../lib/esm/abstract/utils.js'; import { bytesToHex, utf8ToBytes } from '../esm/abstract/utils.js';
import { hash_to_field } from '../lib/esm/abstract/hash-to-curve.js'; import { hash_to_field } from '../esm/abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../lib/esm/bls12-381.js'; import { bls12_381 as bls } from '../esm/bls12-381.js';
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' }; import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' }; import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };

View File

@@ -2,9 +2,9 @@ import { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { deepStrictEqual, strictEqual, throws } from 'assert'; import { deepStrictEqual, strictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import { numberToBytesLE } from '../esm/abstract/utils.js';
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../lib/esm/ed25519.js'; import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../esm/ed25519.js';
// const ed = ed25519; // const ed = ed25519;
const hex = bytesToHex; const hex = bytesToHex;
@@ -97,7 +97,7 @@ should('X25519 base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE; const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE; const { Fp } = ed25519ph.CURVE;
const u = Fp.create((y + 1n) * Fp.inv(1n - y)); const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); deepStrictEqual(numberToBytesLE(u, 32), x25519.GuBytes);
}); });
describe('RFC7748', () => { describe('RFC7748', () => {
@@ -128,7 +128,7 @@ describe('RFC7748', () => {
for (let i = 0; i < rfc7748Iter.length; i++) { for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i]; const { scalar, iters } = rfc7748Iter[i];
should(`scalarMult iteration (${i})`, () => { should(`scalarMult iteration (${i})`, () => {
let k = x25519.Gu; let k = x25519.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k]; for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar); deepStrictEqual(hex(k), scalar);
}); });

View File

@@ -1 +1 @@
export { ed25519, ED25519_TORSION_SUBGROUP } from '../lib/esm/ed25519.js'; export { ed25519, ED25519_TORSION_SUBGROUP } from '../esm/ed25519.js';

View File

@@ -1,9 +1,9 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js'; import { ed448, ed448ph, x448 } from '../esm/ed448.js';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import { numberToBytesLE } from '../esm/abstract/utils.js';
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' }; import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' }; import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
@@ -509,7 +509,7 @@ describe('ed448', () => {
for (let i = 0; i < rfc7748Iter.length; i++) { for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i]; const { scalar, iters } = rfc7748Iter[i];
should(`RFC7748: scalarMult iteration (${i})`, () => { should(`RFC7748: scalarMult iteration (${i})`, () => {
let k = x448.Gu; let k = x448.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k]; for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar); deepStrictEqual(hex(k), scalar);
}); });
@@ -664,7 +664,7 @@ describe('ed448', () => {
// const invX = Fp.invert(x * x); // x² // const invX = Fp.invert(x * x); // x²
const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)
// const u = Fp.create(y * y * invX); // const u = Fp.create(y * y * invX);
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); deepStrictEqual(numberToBytesLE(u, 56), x448.GuBytes);
}); });
}); });

View File

@@ -5,15 +5,15 @@ import { bytesToHex } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3'; import { shake128, shake256 } from '@noble/hashes/sha3';
import * as secp256r1 from '../lib/esm/p256.js'; import * as secp256r1 from '../esm/p256.js';
import * as secp384r1 from '../lib/esm/p384.js'; import * as secp384r1 from '../esm/p384.js';
import * as secp521r1 from '../lib/esm/p521.js'; import * as secp521r1 from '../esm/p521.js';
import * as ed25519 from '../lib/esm/ed25519.js'; import * as ed25519 from '../esm/ed25519.js';
import * as ed448 from '../lib/esm/ed448.js'; import * as ed448 from '../esm/ed448.js';
import * as secp256k1 from '../lib/esm/secp256k1.js'; import * as secp256k1 from '../esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../esm/bls12-381.js';
import { expand_message_xmd, expand_message_xof } from '../lib/esm/abstract/hash-to-curve.js'; import { expand_message_xmd, expand_message_xof } from '../esm/abstract/hash-to-curve.js';
import { utf8ToBytes } from '../lib/esm/abstract/utils.js'; import { utf8ToBytes } from '../esm/abstract/utils.js';
// XMD // XMD
import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' }; import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' };
import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' }; import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };

View File

@@ -1,4 +1,4 @@
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js'; import { jubjub, findGroupHash } from '../esm/jubjub.js';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
const Point = jubjub.ExtendedPoint; const Point = jubjub.ExtendedPoint;

View File

@@ -1,12 +1,12 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { secp192r1, P192 } from '../lib/esm/p192.js'; import { secp192r1, P192 } from '../esm/p192.js';
import { secp224r1, P224 } from '../lib/esm/p224.js'; import { secp224r1, P224 } from '../esm/p224.js';
import { secp256r1, P256 } from '../lib/esm/p256.js'; import { secp256r1, P256 } from '../esm/p256.js';
import { secp384r1, P384 } from '../lib/esm/p384.js'; import { secp384r1, P384 } from '../esm/p384.js';
import { secp521r1, P521 } from '../lib/esm/p521.js'; import { secp521r1, P521 } from '../esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { secp256k1 } from '../esm/secp256k1.js';
import { hexToBytes, bytesToHex } from '../lib/esm/abstract/utils.js'; import { hexToBytes, bytesToHex } from '../esm/abstract/utils.js';
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' }; import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' }; import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' }; import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };

View File

@@ -1,8 +1,8 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import * as poseidon from '../lib/esm/abstract/poseidon.js'; import * as poseidon from '../esm/abstract/poseidon.js';
import * as stark from '../lib/esm/stark.js'; import * as stark from '../esm/stark.js';
import * as mod from '../lib/esm/abstract/modular.js'; import * as mod from '../esm/abstract/modular.js';
import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' }; import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' };
const { st1, st2, st3, st4 } = pvectors; const { st1, st2, st3, st4 } = pvectors;

View File

@@ -2,7 +2,7 @@ import { deepStrictEqual, throws } from 'assert';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import { bytesToHex as hex } from '@noble/hashes/utils'; import { bytesToHex as hex } from '@noble/hashes/utils';
import { schnorr } from '../lib/esm/secp256k1.js'; import { schnorr } from '../esm/secp256k1.js';
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8'); const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
describe('schnorr.sign()', () => { describe('schnorr.sign()', () => {

View File

@@ -1,8 +1,8 @@
// @ts-ignore // @ts-ignore
export { secp256k1 as secp } from '../lib/esm/secp256k1.js'; export { secp256k1 as secp } from '../esm/secp256k1.js';
import { secp256k1 as _secp } from '../lib/esm/secp256k1.js'; import { secp256k1 as _secp } from '../esm/secp256k1.js';
export { bytesToNumberBE, numberToBytesBE } from '../lib/esm/abstract/utils.js'; export { bytesToNumberBE, numberToBytesBE } from '../esm/abstract/utils.js';
export { mod } from '../lib/esm/abstract/modular.js'; export { mod } from '../esm/abstract/modular.js';
export const sigFromDER = (der) => { export const sigFromDER = (der) => {
return _secp.Signature.fromDER(der); return _secp.Signature.fromDER(der);
}; };

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../esm/stark.js';
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' }; import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
import * as bip32 from '@scure/bip32'; import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39'; import * as bip39 from '@scure/bip39';

View File

@@ -1,4 +1,4 @@
import * as microStark from '../../../lib/esm/stark.js'; import * as microStark from '../../../esm/stark.js';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils'; import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as bench from 'micro-bmark'; import * as bench from 'micro-bmark';
const { run, mark } = bench; // or bench.mark const { run, mark } = bench; // or bench.mark

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../esm/stark.js';
import * as fs from 'fs'; import * as fs from 'fs';
function parseTest(path) { function parseTest(path) {

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../esm/stark.js';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n); const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);

View File

@@ -1,9 +1,9 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { utf8ToBytes } from '@noble/hashes/utils.js'; import { utf8ToBytes } from '@noble/hashes/utils';
import * as bip32 from '@scure/bip32'; import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39'; import * as bip39 from '@scure/bip39';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../esm/stark.js';
import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' }; import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' }; import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };