30 Commits
0.6.4 ... 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
Paul Miller
3486bbf6b8 Release 0.7.0. 2023-02-14 23:45:53 +01:00
Paul Miller
0d7a8296c5 gitignore update 2023-02-14 23:45:39 +01:00
Paul Miller
0f1e7a5a43 Move output from lib to root. React Native does not support pkg.json#exports 2023-02-14 23:43:28 +01:00
Paul Miller
3da48cf899 bump bmark 2023-02-14 23:24:11 +01:00
Paul Miller
4ec46dd65d Remove scure-base from top-level dep 2023-02-14 18:00:11 +01:00
Paul Miller
7073f63c6b drbg: move from weierstrass to utils 2023-02-14 17:54:57 +01:00
Paul Miller
80966cbd03 hash-to-curve: more type checks. Rename method to createHasher 2023-02-14 17:39:56 +01:00
Paul Miller
98ea15dca4 edwards: improve hex errors 2023-02-14 17:35:19 +01:00
Paul Miller
e1910e85ea mod, utils, weierstrass, secp: improve hex errors. secp: improve verify() logic and schnorr 2023-02-14 17:34:31 +01:00
Paul Miller
4d311d7294 Emit source maps 2023-02-14 17:23:51 +01:00
Paul Miller
c36d90cae6 bump lockfile, add comment to shortw 2023-02-13 23:55:58 +01:00
Paul Miller
af5aa8424f readme: supply chain attacks 2023-02-13 23:32:49 +01:00
Paul Miller
67b99652fc BLS: add docs 2023-02-12 22:25:36 +01:00
Paul Miller
c8d292976b README 2023-02-12 22:25:22 +01:00
Paul Miller
daffaa2339 README: more docs 2023-02-12 21:37:27 +01:00
Paul Miller
a462fc5779 readme updates 2023-02-12 11:30:55 +01:00
44 changed files with 1081 additions and 841 deletions

14
.gitignore vendored
View File

@@ -1,7 +1,13 @@
build/
node_modules/
coverage/
/lib/**/*.js
/lib/**/*.ts
/lib/**/*.d.ts.map
/curve-definitions/lib
/*.js
/*.ts
/*.js.map
/*.d.ts.map
/esm/*.js
/esm/*.ts
/esm/*.js.map
/esm/*.d.ts.map
/esm/abstract
/abstract/

794
README.md
View File

@@ -1,36 +1,35 @@
# noble-curves
Minimal, auditable 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
- [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
for encoding or hashing an arbitrary string to a point on an elliptic curve
- [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
- # [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
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. Wycheproof vectors included
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
Package consists of two parts:
1. `abstract/` directory specifies zero-dependency EC algorithms
2. root directory utilizes one dependency `@noble/hashes` and provides ready-to-use:
1. [Abstract](#abstract-api), zero-dependency EC algorithms
2. [Implementations](#implementations), utilizing one dependency `@noble/hashes`, providing ready-to-use:
- NIST curves secp192r1/P192, secp224r1/P224, secp256r1/P256, secp384r1/P384, secp521r1/P521
- SECG curve secp256k1
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
- pairing-friendly curves bls12-381, bn254
- ed25519/curve25519/x25519/ristretto, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
Curves incorporate work from previous noble packages
([secp256k1](https://github.com/paulmillr/noble-secp256k1),
[ed25519](https://github.com/paulmillr/noble-ed25519)),
which had security audits and were developed from 2019 to 2022.
Check out [Upgrading](#upgrading) section if you've used them before.
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](#resouces) for articles and real-world software that uses curves.
### This library belongs to _noble_ crypto
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
- Protection against supply chain attacks
- No dependencies, protection against supply chain attacks
- Easily auditable TypeScript/JS code
- Supported in all major browsers and stable node.js versions
- All releases are signed with PGP keys
@@ -42,15 +41,41 @@ Check out [Upgrading](#upgrading) section if you've used them before.
## Usage
Use NPM in node.js / browser, or include single file from
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases):
Use NPM for browser / node.js:
> npm install @noble/curves
The library does not have an entry point. It allows you to select specific primitives and drop everything else. If you only want to use secp256k1, just use the library with rollup or other bundlers. This is done to make your bundles tiny. All 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
[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.
### Implementations
Each curve can be used in the following way:
```ts
import { secp256k1 } from '@noble/curves/secp256k1';
import { secp256k1 } from '@noble/curves/secp256k1'; // ECMAScript Modules (ESM) and Common.js
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv);
const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv);
secp256k1.verify(sig, msg, pub) === true;
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
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
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
hashToCurve('0102abcd');
```
All curves:
```typescript
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { p256 } from '@noble/curves/p256';
@@ -63,63 +88,214 @@ import { bn254 } from '@noble/curves/bn';
import { jubjub } from '@noble/curves/jubjub';
```
Every curve can be used in the following way:
Weierstrass curves feature recovering public keys from signatures and ECDH key agreement:
```ts
import { secp256k1 } from '@noble/curves/secp256k1'; // Common.js and ECMAScript Modules (ESM)
const key = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(key);
const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, key);
// weierstrass curves should use extraEntropy: https://moderncrypto.org/mail-archive/curves/2017/000925.html
const sigImprovedSecurity = secp256k1.sign(msg, key, { extraEntropy: true });
secp256k1.verify(sig, msg, pub) === true;
// secp, p*, pasta curves allow pub recovery
sig.recoverPublicKey(msg) === pub;
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
sig.recoverPublicKey(msg) === pub; // public key recovery
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(key, someonesPub);
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH (elliptic curve diffie-hellman)
```
To define a custom curve, check out docs below.
secp256k1 has schnorr signature implementation which follows
[BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki):
## API
```ts
import { schnorr } from '@noble/curves/secp256k1';
const priv = schnorr.utils.randomPrivateKey();
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);
```
- [Overview](#overview)
ed25519 module has ed25519ctx / ed25519ph variants,
x25519 ECDH and [ristretto255](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
It follows [ZIP215](https://zips.z.cash/zip-0215) and [can be used in consensus-critical applications](https://hdevalence.ca/blog/2020-10-04-its-25519am):
```ts
import { ed25519 } from '@noble/curves/ed25519';
// Variants from RFC8032: with context, prehashed
import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
// ECDH using curve25519 aka x25519
import { x25519 } from '@noble/curves/ed25519';
const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
// hash-to-curve
import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519';
import { RistrettoPoint } from '@noble/curves/ed25519';
const rp = RistrettoPoint.fromHex(
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
);
RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso coffee');
// also has add(), equals(), multiply(), toRawBytes() methods
```
ed448 module is basically the same:
```ts
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448';
```
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.
```ts
import { bls12_381 as bls } from '@noble/curves/bls12-381';
const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
const message = '64726e3da8';
const publicKey = bls.getPublicKey(privateKey);
const signature = bls.sign(message, privateKey);
const isValid = bls.verify(signature, message, publicKey);
console.log({ publicKey, signature, isValid });
// Sign 1 msg with 3 keys
const privateKeys = [
'18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
'16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5',
];
const messages = ['d2', '0d98', '05caf3'];
const publicKeys = privateKeys.map(bls.getPublicKey);
const signatures2 = privateKeys.map((p) => bls.sign(message, p));
const aggPubKey2 = bls.aggregatePublicKeys(publicKeys);
const aggSignature2 = bls.aggregateSignatures(signatures2);
const isValid2 = bls.verify(aggSignature2, message, aggPubKey2);
console.log({ signatures2, aggSignature2, isValid2 });
// Sign 3 msgs with 3 keys
const signatures3 = privateKeys.map((p, i) => bls.sign(messages[i], p));
const aggSignature3 = bls.aggregateSignatures(signatures3);
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// Pairings
// bls.pairing(PointG1, PointG2)
// Also, check out hash-to-curve examples below.
```
## 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.
There are following zero-dependency algorithms:
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- [abstract/modular](#abstractmodular)
- [abstract/utils](#abstractutils)
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
- [abstract/utils: General utilities](#abstractutils-general-utilities)
### Overview
There are following zero-dependency abstract algorithms:
### abstract/weierstrass: Short Weierstrass curve
```ts
import { bls } from '@noble/curves/abstract/bls';
import { twistedEdwards } from '@noble/curves/abstract/edwards';
import { montgomery } from '@noble/curves/abstract/montgomery';
import { weierstrass } from '@noble/curves/abstract/weierstrass';
import * as mod from '@noble/curves/abstract/modular';
import * as utils from '@noble/curves/abstract/utils';
```
They allow to define a new curve in a few lines of code:
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:
```ts
import { Field } from '@noble/curves/abstract/modular';
import { weierstrass } from '@noble/curves/abstract/weierstrass';
import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256';
import { concatBytes, randomBytes } from '@noble/hashes/utils';
type CHash = {
(message: Uint8Array): Uint8Array;
blockLen: number;
outputLen: number;
create(): any;
};
```
// secq (NOT secp) 256k1: cycle of secp256k1 with Fp/N flipped.
// https://zcash.github.io/halo2/background/curves.html#cycles-of-curves
// https://personaelabs.org/posts/spartan-ecdsa
**Weierstrass points:**
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()`
5. Have `assertValidity()` which checks for being on-curve
6. Have `toAffine()` and `x` / `y` getters which convert to 2d xy affine coordinates
```ts
// 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;
readonly py: T;
readonly pz: T;
multiply(scalar: bigint): ProjPointType<T>;
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
toAffine(iz?: T): AffinePoint<T>;
isTorsionFree(): boolean;
clearCofactor(): ProjPointType<T>;
assertValidity(): void;
hasEvenY(): boolean;
toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string;
}
// Static methods for 3d XYZ points
interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
new (x: T, y: T, z: T): ProjPointType<T>;
fromAffine(p: AffinePoint<T>): ProjPointType<T>;
fromHex(hex: Hex): ProjPointType<T>;
fromPrivateKey(privateKey: PrivKey): ProjPointType<T>;
}
```
**ECDSA signatures** are represented by `Signature` instances and can be described by the interface:
```ts
interface SignatureType {
readonly r: bigint;
readonly s: bigint;
readonly recovery?: number;
assertValidity(): void;
addRecoveryBit(recovery: number): SignatureType;
hasHighS(): boolean;
normalizeS(): SignatureType;
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
toCompactRawBytes(): Uint8Array;
toCompactHex(): string;
// DER-encoded
toDERRawBytes(): Uint8Array;
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)
[cycle](https://zcash.github.io/halo2/background/curves.html#cycles-of-curves) of secp256k1 with Fp/N flipped.
```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),
@@ -130,50 +306,110 @@ const secq256k1 = weierstrass({
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.
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
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.subtract(point).equals(Point.ZERO); // subtract() method, ZERO static var
point.negate(); // Flips point over x/y coordinate.
point.multiply(31415n); // Multiplication of Point by scalar.
point.assertValidity(); // Checks for being on-curve
point.toAffine(); // Converts to 2d affine xy coordinates
secq256k1.CURVE.n;
secq256k1.CURVE.Fp.mod();
secq256k1.CURVE.hash();
// precomputes
const fast = secq256k1.utils.precompute(8, Point.fromHex(someonesPubKey));
fast.multiply(privKey); // much faster ECDH now
```
- To initialize new curve, you must specify its variables, order (number of points on curve), field prime (over which the modular division would be done)
- All curves expose same generic interface:
- `getPublicKey()`, `sign()`, `verify()` functions
- `Point` conforming to `Group` interface with add/multiply/double/negate/add/equals methods
- `CURVE` object with curve variables like `Gx`, `Gy`, `Fp` (field), `n` (order)
- `utils` object with `randomPrivateKey()`, `mod()`, `invert()` methods (`mod CURVE.P`)
- All arithmetics is done with JS bigints over finite fields, which is defined from `modular` sub-module
- Many features require hashing, which is not provided. `@noble/hashes` can be used for this purpose.
Any other library must conform to the CHash interface:
```ts
export type CHash = {
(message: Uint8Array): Uint8Array;
blockLen: number;
outputLen: number;
create(): any;
`weierstrass()` returns `CurveFn`:
```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>;
};
```
- w-ary non-adjacent form (wNAF) method with constant-time adjustments is used for point multiplication.
It is possible to enable precomputes for edwards & weierstrass curves.
Precomputes are calculated once (takes ~20-40ms), after that most `G` base point multiplications:
for example, `getPublicKey()`, `sign()` and similar methods - would be much faster.
Use `curve.utils.precompute()` to adjust precomputation window size
- You could use optional special params to tune performance:
- `Fp({sqrt})` square root calculation, used for point decompression
- `endo` endomorphism options for Koblitz curves
};
```
### abstract/edwards: Twisted Edwards curve
Twisted Edwards curve's formula is: ax² + y² = 1 + dx²y².
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.
- You must specify curve params `a`, `d`, field `Fp`, order `n`, cofactor `h` and coordinates `Gx`, `Gy` of generator point
- For EdDSA signatures, params `hash` is also required. `adjustScalarBytes` which instructs how to change private scalars could be specified
For EdDSA signatures, `hash` param required. `adjustScalarBytes` which instructs how to change private scalars could be specified.
```typescript
**Edwards points:**
1. Exported as `ExtendedPoint`
2. Represented in extended coordinates: (x, y, z, t) ∋ (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 `ExtendedPoint.fromHex` and `ExtendedPoint#toRawBytes()`
5. Have `assertValidity()` which checks for being on-curve
6. Have `toAffine()` and `x` / `y` getters which convert to 2d xy affine coordinates
7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions
```ts
interface ExtPointType extends Group<ExtPointType> {
readonly ex: bigint;
readonly ey: bigint;
readonly ez: bigint;
readonly et: bigint;
assertValidity(): void;
multiply(scalar: bigint): ExtPointType;
multiplyUnsafe(scalar: bigint): ExtPointType;
isSmallOrder(): boolean;
isTorsionFree(): boolean;
clearCofactor(): ExtPointType;
toAffine(iz?: bigint): AffinePoint<bigint>;
}
// Static methods of Extended Point with coordinates in X, Y, Z, T
interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: AffinePoint<bigint>): ExtPointType;
fromHex(hex: Hex): ExtPointType;
fromPrivateKey(privateKey: Hex): ExtPointType;
}
```
Example implementing edwards25519:
```ts
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';
const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({
a: -1n,
d: div(-121665n, 121666n, 2n ** 255n - 19n), // -121665n/121666n
P: 2n ** 255n - 19n,
d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
Fp,
n: 2n ** 252n + 27742317777372353535851937790883648493n,
h: 8n,
Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
@@ -181,31 +417,24 @@ const ed25519 = twistedEdwards({
hash: sha512,
randomBytes,
adjustScalarBytes(bytes) {
// optional in general, mandatory in ed25519
// optional; but mandatory in ed25519
bytes[0] &= 248;
bytes[31] &= 127;
bytes[31] |= 64;
return bytes;
},
} as const);
const key = ed25519.utils.randomPrivateKey();
const pub = ed25519.getPublicKey(key);
const msg = new TextEncoder().encode('hello world'); // strings not accepted, must be Uint8Array
const sig = ed25519.sign(msg, key);
ed25519.verify(sig, msg, pub) === true;
```
`twistedEdwards()` returns `CurveFn` of following type:
```ts
export type CurveFn = {
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
sign: (message: Hex, privateKey: Hex) => Uint8Array;
verify: (sig: SigType, message: Hex, publicKey: PubKey) => boolean;
Point: PointConstructor;
ExtendedPoint: ExtendedPointConstructor;
Signature: SignatureConstructor;
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) => {
@@ -221,26 +450,20 @@ export type CurveFn = {
### abstract/montgomery: Montgomery curve
For now the module only contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748.
The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet.
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
import { montgomery } from '@noble/curves/abstract/montgomery';
const x25519 = montgomery({
P: 2n ** 255n - 19n,
a24: 121665n, // TODO: change to a
Fp: Field(2n ** 255n - 19n),
a: 486662n,
Gu: 9n,
montgomeryBits: 255,
nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000',
// Optional params
powPminus2: (x: bigint): bigint => {
return mod.pow(x, P - 2, P);
},
// Optional param
adjustScalarBytes(bytes) {
bytes[0] &= 248;
bytes[31] &= 127;
@@ -250,145 +473,61 @@ const x25519 = montgomery({
});
```
### abstract/weierstrass: Short Weierstrass curve
Short Weierstrass curve's formula is: y² = x³ + ax + b. Uses deterministic ECDSA from RFC6979. You can also specify `extraEntropy` in `sign()`.
- You must specify curve params: `a`, `b`, field `Fp`, order `n`, cofactor `h` and coordinates `Gx`, `Gy` of generator point
- For ECDSA, you must specify `hash`, `hmac`. It is also possible to recover keys from signatures
- For ECDH, use `getSharedSecret(privKeyA, pubKeyB)`
- Optional params are `lowS` (default value) and `endo` (endomorphism)
```typescript
import { Fp } from '@noble/curves/abstract/modular';
import { weierstrass } from '@noble/curves/abstract/weierstrass'; // Short Weierstrass curve
import { sha256 } from '@noble/hashes/sha256';
import { hmac } from '@noble/hashes/hmac';
import { concatBytes, randomBytes } from '@noble/hashes/utils';
const secp256k1 = weierstrass({
a: 0n,
b: 7n,
Fp: Fp(2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n),
n: 2n ** 256n - 432420386565659656852420866394968145599n,
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
hash: sha256,
hmac: (k: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
randomBytes,
// Optional params
h: 1n, // Cofactor
lowS: true, // Allow only low-S signatures by default in sign() and verify()
endo: {
// Endomorphism options for Koblitz curve
// Beta param
beta: 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501een,
// Split scalar k into k1, k2
splitScalar: (k: bigint) => {
// return { k1neg: true, k1: 512n, k2neg: false, k2: 448n };
},
},
});
// Usage
const key = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(key);
const msg = randomBytes(32);
const sig = secp256k1.sign(msg, key);
secp256k1.verify(sig, msg, pub); // true
sig.recoverPublicKey(msg); // == pub
const someonesPubkey = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(key, someonesPubkey);
```
`weierstrass()` returns `CurveFn`:
```ts
export 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 }
) => boolean;
Point: PointConstructor;
ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor;
utils: {
isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array;
};
};
```
### abstract/hash-to-curve: Hashing strings to curve points
The module allows to hash arbitrary strings to elliptic curve points.
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).
- `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..
Every curve has exported `hashToCurve` and `encodeToCurve` methods:
```ts
function expand_message_xmd(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: CHash
): Uint8Array;
function expand_message_xof(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
k: number,
H: CHash
): Uint8Array;
```
```ts
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
import { randomBytes } from '@noble/hashes/utils';
console.log(hashToCurve(randomBytes()));
console.log(encodeToCurve(randomBytes()));
- `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}`
_ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
import { bls12_381 } from '@noble/curves/bls12-381';
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
```
```ts
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
type htfOpts = {
// DST: a domain separation tag
// defined in section 2.2.5
DST: string;
// 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: the target security level for the suite in bits
// defined in section 5.1
k: number;
// option to use a message that has already been processed by
// expand_message_xmd
expand?: 'xmd' | 'xof';
// 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;
};
```
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.
```ts
function expand_message_xmd(
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)
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}`
_ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
```ts
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
```
### abstract/poseidon: Poseidon hash
Implements [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash.
There are many poseidon instances with different constants. We don't provide them,
but we provide ability to specify them manually. For actual usage, check out
stark curve source code.
There are many poseidon variants with different constants.
We don't provide them: you should construct them manually.
The only variant provided resides in `stark` module: inspect it for proper usage.
```ts
import { poseidon } from '@noble/curves/abstract/poseidon';
@@ -406,27 +545,54 @@ type PoseidonOpts = {
const instance = poseidon(opts: PoseidonOpts);
```
### abstract/modular
### abstract/bls
Modular arithmetics utilities.
The module abstracts BLS (Barreto-Lynn-Scott) primitives. In theory you should be able to write BLS12-377, BLS24,
and others with it.
```typescript
import { Fp, mod, invert, div, invertBatch, sqrt } from '@noble/curves/abstract/modular';
const fp = Fp(2n ** 255n - 19n); // Finite field over 2^255-19
fp.mul(591n, 932n);
fp.pow(481n, 11024858120n);
### abstract/modular: Modular arithmetics utilities
```ts
import * as mod from '@noble/curves/abstract/modular';
const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19
fp.mul(591n, 932n); // multiplication
fp.pow(481n, 11024858120n); // exponentiation
fp.div(5n, 17n); // division: 5/17 mod 2^255-19 == 5 * invert(17)
fp.sqrt(21n); // square root
// Generic non-FP utils are also available
mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10
invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
div(5n, 17n, 10n); // 5/17 mod 10 == 5 * invert(17) mod 10; division
invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
sqrt(21n, 73n); // √21 mod 73; square root
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.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
```
### abstract/utils
#### Creating private keys from hashes
```typescript
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
```ts
import * as utils from '@noble/curves/abstract/utils';
utils.bytesToHex(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
@@ -434,12 +600,11 @@ utils.hexToBytes('deadbeef');
utils.hexToNumber();
utils.bytesToNumberBE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.bytesToNumberLE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.numberToBytesBE(123n);
utils.numberToBytesLE(123n);
utils.numberToBytesBE(123n, 32);
utils.numberToBytesLE(123n, 64);
utils.numberToHexUnpadded(123n);
utils.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef]));
utils.nLength(255n);
utils.hashToPrivateScalar(sha512_of_something, secp256r1.n);
utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
```
@@ -447,80 +612,103 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
The library had no prior security audit.
[Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to achieve in a scripting language. Which means _any other JS library can't have constant-timeness_. Even statically typed Rust, a language without GC, [makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
[Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: we are using non-CT bigints. However, _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to achieve in a scripting language. Which means _any other JS library can't have constant-timeness_. Even statically typed Rust, a language without GC, [makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every `npm install`. Our goal is to minimize this attack vector.
We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every `npm install`. Our goal is to minimize this attack vector. As for devDependencies used by the library:
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing) are developed by us
and follow the same practices such as: minimal library size, auditability, signed releases
- prettier (linter), fast-check (property-based testing),
typescript versions are locked and rarely updated. Every update is checked with `npm-diff`.
The packages are big, which makes it hard to audit their source code thoroughly and fully.
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
## Speed
Benchmark results on Apple M2 with node v18.10:
Benchmark results on Apple M2 with node v19:
```
secp256k1
init x 57 ops/sec @ 17ms/op
getPublicKey x 4,946 ops/sec @ 202μs/op
sign x 3,914 ops/sec @ 255μs/op
verify x 682 ops/sec @ 1ms/op
getSharedSecret x 427 ops/sec @ 2ms/op
recoverPublicKey x 683 ops/sec @ 1ms/op
schnorr.sign x 539 ops/sec @ 1ms/op
schnorr.verify x 716 ops/sec @ 1ms/op
init x 58 ops/sec @ 17ms/op
getPublicKey x 5,640 ops/sec @ 177μs/op
sign x 3,909 ops/sec @ 255μs/op
verify x 780 ops/sec @ 1ms/op
getSharedSecret x 465 ops/sec @ 2ms/op
recoverPublicKey x 740 ops/sec @ 1ms/op
schnorr.sign x 597 ops/sec @ 1ms/op
schnorr.verify x 775 ops/sec @ 1ms/op
P256
init x 30 ops/sec @ 32ms/op
getPublicKey x 5,008 ops/sec @ 199μs/op
sign x 3,970 ops/sec @ 251μs/op
verify x 515 ops/sec @ 1ms/op
init x 31 ops/sec @ 31ms/op
getPublicKey x 5,607 ops/sec @ 178μs/op
sign x 3,930 ops/sec @ 254μs/op
verify x 540 ops/sec @ 1ms/op
P384
init x 14 ops/sec @ 66ms/op
getPublicKey x 2,434 ops/sec @ 410μs/op
sign x 1,942 ops/sec @ 514μs/op
verify x 206 ops/sec @ 4ms/op
init x 15 ops/sec @ 63ms/op
getPublicKey x 2,622 ops/sec @ 381μs/op
sign x 1,913 ops/sec @ 522μs/op
verify x 222 ops/sec @ 4ms/op
P521
init x 7 ops/sec @ 126ms/op
getPublicKey x 1,282 ops/sec @ 779μs/op
sign x 1,077 ops/sec @ 928μs/op
verify x 110 ops/sec @ 9ms/op
init x 8 ops/sec @ 119ms/op
getPublicKey x 1,371 ops/sec @ 729μs/op
sign x 1,090 ops/sec @ 917μs/op
verify x 118 ops/sec @ 8ms/op
ed25519
init x 37 ops/sec @ 26ms/op
getPublicKey x 8,147 ops/sec @ 122μs/op
sign x 3,979 ops/sec @ 251μs/op
verify x 848 ops/sec @ 1ms/op
init x 47 ops/sec @ 20ms/op
getPublicKey x 9,414 ops/sec @ 106μs/op
sign x 4,516 ops/sec @ 221μs/op
verify x 912 ops/sec @ 1ms/op
ed448
init x 17 ops/sec @ 58ms/op
getPublicKey x 3,083 ops/sec @ 324μs/op
sign x 1,473 ops/sec @ 678μs/op
verify x 323 ops/sec @ 3ms/op
bls12-381
init x 30 ops/sec @ 33ms/op
getPublicKey x 788 ops/sec @ 1ms/op
sign x 45 ops/sec @ 21ms/op
verify x 32 ops/sec @ 30ms/op
pairing x 88 ops/sec @ 11ms/op
init x 17 ops/sec @ 56ms/op
getPublicKey x 3,363 ops/sec @ 297μs/op
sign x 1,615 ops/sec @ 619μs/op
verify x 319 ops/sec @ 3ms/op
stark
init x 31 ops/sec @ 31ms/op
pedersen
├─old x 84 ops/sec @ 11ms/op
└─noble x 802 ops/sec @ 1ms/op
poseidon x 7,466 ops/sec @ 133μs/op
verify
├─old x 300 ops/sec @ 3ms/op
└─noble x 474 ops/sec @ 2ms/op
init x 35 ops/sec @ 28ms/op
pedersen x 884 ops/sec @ 1ms/op
poseidon x 8,598 ops/sec @ 11s/op
verify x 528 ops/sec @ 1ms/op
bls12-381
init x 32 ops/sec @ 30ms/op
getPublicKey 1-bit x 858 ops/sec @ 1ms/op
getPublicKey x 858 ops/sec @ 1ms/op
sign x 49 ops/sec @ 20ms/op
verify x 34 ops/sec @ 28ms/op
pairing x 94 ops/sec @ 10ms/op
aggregatePublicKeys/8 x 116 ops/sec @ 8ms/op
aggregatePublicKeys/32 x 31 ops/sec @ 31ms/op
aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
```
## Resources
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
- [btc-signer](https://github.com/paulmillr/micro-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer)
- ed25519
- [sol-signer](https://github.com/paulmillr/micro-sol-signer)
- BLS12-381
- Check out `bls12-381.ts` for articles about the curve
- 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)
## Upgrading
If you're coming from single-curve noble packages, the following changes need to be kept in mind:
If you're coming from single-feature noble packages, the following changes need to be kept in mind:
- 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.js` now.
- 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 1.7:

View File

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

View File

@@ -1,6 +1,6 @@
import { readFileSync } from 'fs';
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')
.trim()

View File

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

View File

@@ -1,5 +1,5 @@
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';
run(async () => {

View File

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

13
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@noble/curves",
"version": "0.6.2",
"version": "0.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "0.6.2",
"version": "0.7.0",
"funding": [
{
"type": "individual",
@@ -18,12 +18,11 @@
"@noble/hashes": "1.2.0"
},
"devDependencies": {
"@scure/base": "~1.1.1",
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"fast-check": "3.0.0",
"micro-bmark": "0.3.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.3",
"typescript": "4.7.3"
@@ -120,9 +119,9 @@
}
},
"node_modules/micro-bmark": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.3.0.tgz",
"integrity": "sha512-rYu+AtUq8lC3zPCoxkOOtwhgJoMpCDGe0/BXUCkj6+H9f/U/TunH/n/qkN98yh04dCCtDV8Aj9uYO3+DKxYrcw==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.3.1.tgz",
"integrity": "sha512-bNaKObD4yPAAPrpEqp5jO6LJ2sEFgLoFSmRjEY809mJ62+2AehI/K3+RlVpN3Oo92RHpgC2RQhj6b1Tb4dmo+w==",
"dev": true
},
"node_modules/micro-should": {

View File

@@ -1,9 +1,15 @@
{
"name": "@noble/curves",
"version": "0.6.4",
"version": "0.7.1",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [
"lib"
"abstract",
"esm",
"src",
"*.js",
"*.js.map",
"*.d.ts",
"*.d.ts.map"
],
"scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js",
@@ -24,12 +30,11 @@
"@noble/hashes": "1.2.0"
},
"devDependencies": {
"@scure/base": "~1.1.1",
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"fast-check": "3.0.0",
"micro-bmark": "0.3.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.3",
"typescript": "4.7.3"
@@ -37,129 +42,129 @@
"main": "index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/esm/index.js",
"default": "./lib/index.js"
"types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js"
},
"./abstract/edwards": {
"types": "./lib/abstract/edwards.d.ts",
"import": "./lib/esm/abstract/edwards.js",
"default": "./lib/abstract/edwards.js"
"types": "./abstract/edwards.d.ts",
"import": "./esm/abstract/edwards.js",
"default": "./abstract/edwards.js"
},
"./abstract/modular": {
"types": "./lib/abstract/modular.d.ts",
"import": "./lib/esm/abstract/modular.js",
"default": "./lib/abstract/modular.js"
"types": "./abstract/modular.d.ts",
"import": "./esm/abstract/modular.js",
"default": "./abstract/modular.js"
},
"./abstract/montgomery": {
"types": "./lib/abstract/montgomery.d.ts",
"import": "./lib/esm/abstract/montgomery.js",
"default": "./lib/abstract/montgomery.js"
"types": "./abstract/montgomery.d.ts",
"import": "./esm/abstract/montgomery.js",
"default": "./abstract/montgomery.js"
},
"./abstract/weierstrass": {
"types": "./lib/abstract/weierstrass.d.ts",
"import": "./lib/esm/abstract/weierstrass.js",
"default": "./lib/abstract/weierstrass.js"
"types": "./abstract/weierstrass.d.ts",
"import": "./esm/abstract/weierstrass.js",
"default": "./abstract/weierstrass.js"
},
"./abstract/bls": {
"types": "./lib/abstract/bls.d.ts",
"import": "./lib/esm/abstract/bls.js",
"default": "./lib/abstract/bls.js"
"types": "./abstract/bls.d.ts",
"import": "./esm/abstract/bls.js",
"default": "./abstract/bls.js"
},
"./abstract/hash-to-curve": {
"types": "./lib/abstract/hash-to-curve.d.ts",
"import": "./lib/esm/abstract/hash-to-curve.js",
"default": "./lib/abstract/hash-to-curve.js"
"types": "./abstract/hash-to-curve.d.ts",
"import": "./esm/abstract/hash-to-curve.js",
"default": "./abstract/hash-to-curve.js"
},
"./abstract/curve": {
"types": "./lib/abstract/curve.d.ts",
"import": "./lib/esm/abstract/curve.js",
"default": "./lib/abstract/curve.js"
"types": "./abstract/curve.d.ts",
"import": "./esm/abstract/curve.js",
"default": "./abstract/curve.js"
},
"./abstract/utils": {
"types": "./lib/abstract/utils.d.ts",
"import": "./lib/esm/abstract/utils.js",
"default": "./lib/abstract/utils.js"
"types": "./abstract/utils.d.ts",
"import": "./esm/abstract/utils.js",
"default": "./abstract/utils.js"
},
"./abstract/poseidon": {
"types": "./lib/abstract/poseidon.d.ts",
"import": "./lib/esm/abstract/poseidon.js",
"default": "./lib/abstract/poseidon.js"
"types": "./abstract/poseidon.d.ts",
"import": "./esm/abstract/poseidon.js",
"default": "./abstract/poseidon.js"
},
"./_shortw_utils": {
"types": "./lib/_shortw_utils.d.ts",
"import": "./lib/esm/_shortw_utils.js",
"default": "./lib/_shortw_utils.js"
"types": "./_shortw_utils.d.ts",
"import": "./esm/_shortw_utils.js",
"default": "./_shortw_utils.js"
},
"./bls12-381": {
"types": "./lib/bls12-381.d.ts",
"import": "./lib/esm/bls12-381.js",
"default": "./lib/bls12-381.js"
"types": "./bls12-381.d.ts",
"import": "./esm/bls12-381.js",
"default": "./bls12-381.js"
},
"./bn": {
"types": "./lib/bn.d.ts",
"import": "./lib/esm/bn.js",
"default": "./lib/bn.js"
"types": "./bn.d.ts",
"import": "./esm/bn.js",
"default": "./bn.js"
},
"./ed25519": {
"types": "./lib/ed25519.d.ts",
"import": "./lib/esm/ed25519.js",
"default": "./lib/ed25519.js"
"types": "./ed25519.d.ts",
"import": "./esm/ed25519.js",
"default": "./ed25519.js"
},
"./ed448": {
"types": "./lib/ed448.d.ts",
"import": "./lib/esm/ed448.js",
"default": "./lib/ed448.js"
"types": "./ed448.d.ts",
"import": "./esm/ed448.js",
"default": "./ed448.js"
},
"./index": {
"types": "./lib/index.d.ts",
"import": "./lib/esm/index.js",
"default": "./lib/index.js"
"types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js"
},
"./jubjub": {
"types": "./lib/jubjub.d.ts",
"import": "./lib/esm/jubjub.js",
"default": "./lib/jubjub.js"
"types": "./jubjub.d.ts",
"import": "./esm/jubjub.js",
"default": "./jubjub.js"
},
"./p192": {
"types": "./lib/p192.d.ts",
"import": "./lib/esm/p192.js",
"default": "./lib/p192.js"
"types": "./p192.d.ts",
"import": "./esm/p192.js",
"default": "./p192.js"
},
"./p224": {
"types": "./lib/p224.d.ts",
"import": "./lib/esm/p224.js",
"default": "./lib/p224.js"
"types": "./p224.d.ts",
"import": "./esm/p224.js",
"default": "./p224.js"
},
"./p256": {
"types": "./lib/p256.d.ts",
"import": "./lib/esm/p256.js",
"default": "./lib/p256.js"
"types": "./p256.d.ts",
"import": "./esm/p256.js",
"default": "./p256.js"
},
"./p384": {
"types": "./lib/p384.d.ts",
"import": "./lib/esm/p384.js",
"default": "./lib/p384.js"
"types": "./p384.d.ts",
"import": "./esm/p384.js",
"default": "./p384.js"
},
"./p521": {
"types": "./lib/p521.d.ts",
"import": "./lib/esm/p521.js",
"default": "./lib/p521.js"
"types": "./p521.d.ts",
"import": "./esm/p521.js",
"default": "./p521.js"
},
"./pasta": {
"types": "./lib/pasta.d.ts",
"import": "./lib/esm/pasta.js",
"default": "./lib/pasta.js"
"types": "./pasta.d.ts",
"import": "./esm/pasta.js",
"default": "./pasta.js"
},
"./secp256k1": {
"types": "./lib/secp256k1.d.ts",
"import": "./lib/esm/secp256k1.js",
"default": "./lib/secp256k1.js"
"types": "./secp256k1.d.ts",
"import": "./esm/secp256k1.js",
"default": "./secp256k1.js"
},
"./stark": {
"types": "./lib/stark.d.ts",
"import": "./lib/esm/stark.js",
"default": "./lib/stark.js"
"types": "./stark.d.ts",
"import": "./esm/stark.js",
"default": "./stark.js"
}
},
"keywords": [

View File

@@ -4,6 +4,7 @@ import { concatBytes, randomBytes } from '@noble/hashes/utils';
import { weierstrass, CurveType } from './abstract/weierstrass.js';
import { CHash } from './abstract/utils.js';
// connects noble-curves to noble-hashes
export function getHash(hash: CHash) {
return {
hash,

View File

@@ -13,7 +13,7 @@
*/
import { AffinePoint } from './curve.js';
import { Field, hashToPrivateScalar } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, hexToBytes, bytesToHex } from './utils.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js';
import {
CurvePointsType,
@@ -67,16 +67,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
Fp2: Field<Fp2>;
Fp6: Field<Fp6>;
Fp12: Field<Fp12>;
G1: CurvePointsRes<Fp>;
G2: CurvePointsRes<Fp2>;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
// prettier-ignore
hashToCurve: {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
},
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: {
@@ -102,16 +97,14 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean;
utils: {
stringToBytes: typeof htf.stringToBytes;
hashToField: typeof htf.hash_to_field;
expandMessageXMD: typeof htf.expand_message_xmd;
randomPrivateKey: () => Uint8Array;
};
};
export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options
// Fields looks pretty specific for curve, so for now we need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const BLS_X_LEN = bitLen(CURVE.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
@@ -180,31 +173,20 @@ export function bls<Fp2, Fp6, Fp12>(
}
const utils = {
hexToBytes: hexToBytes,
bytesToHex: bytesToHex,
stringToBytes: htf.stringToBytes,
// TODO: do we need to export it here?
hashToField: (
msg: Uint8Array,
count: number,
options: Partial<typeof CURVE.htfDefaults> = {}
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
randomPrivateKey: (): Uint8Array => {
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r));
},
};
// Point on G1 curve: (x, y)
const G1 = weierstrassPoints({
n: Fr.ORDER,
...CURVE.G1,
});
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G1.htfDefaults,
});
const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 });
const G1 = Object.assign(
G1_,
htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G1.htfDefaults,
})
);
// Sparse multiplication against precomputed coefficients
// TODO: replace with weakmap?
@@ -223,15 +205,14 @@ export function bls<Fp2, Fp6, Fp12>(
// }
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
const G2 = weierstrassPoints({
n: Fr.ORDER,
...CURVE.G2,
});
const C = G2.ProjectivePoint as htf.H2CPointConstructor<Fp2>; // TODO: fix
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G2.htfDefaults,
});
const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
const G2 = Object.assign(
G2_,
htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G2.htfDefaults,
})
);
const { Signature } = CURVE.G2;
@@ -260,7 +241,7 @@ export function bls<Fp2, Fp6, Fp12>(
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.ProjectivePoint
? point
: (G2HashToCurve.hashToCurve(point, htfOpts) as G2);
: (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
}
// Multiplies generator by private key.
@@ -276,7 +257,7 @@ export function bls<Fp2, Fp6, Fp12>(
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.encode(sigPoint);
}
@@ -383,7 +364,6 @@ export function bls<Fp2, Fp6, Fp12>(
Signature,
millerLoop,
calcPairingPrecomputes,
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
pairing,
getPublicKey,
sign,

View File

@@ -344,7 +344,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
static fromHex(hex: Hex, strict = true): Point {
const { d, a } = CURVE;
const len = Fp.BYTES;
hex = ensureBytes(hex, len); // copy hex to a new array
hex = ensureBytes('pointHex', hex, len); // copy hex to a new array
const normed = hex.slice(); // copy again, we'll manipulate it
const lastByte = hex[len - 1]; // select last byte
normed[len - 1] = lastByte & ~0x80; // clear last bit
@@ -392,18 +392,14 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
function modN_LE(hash: Uint8Array): bigint {
return modN(ut.bytesToNumberLE(hash));
}
function isHex(item: Hex, err: string) {
if (typeof item !== 'string' && !(item instanceof Uint8Array))
throw new Error(`${err} must be hex string or Uint8Array`);
}
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
function getExtendedPublicKey(key: Hex) {
isHex(key, 'private key');
const len = nByteLength;
key = ensureBytes('private key', key, len);
// Hash private key with curve's hash function to produce uniformingly random input
// Check byte lengths: ensure(64, h(ensure(32, key)))
const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len);
const hashed = ensureBytes('hashed private key', cHash(key), 2 * len);
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
const scalar = modN_LE(head); // The actual private scalar
@@ -420,13 +416,12 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// int('LE', SHA512(dom2(F, C) || msgs)) mod N
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
const msg = ut.concatBytes(...msgs);
return modN_LE(cHash(domain(msg, ensureBytes(context), !!preHash)));
return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!preHash)));
}
/** Signs message with privateKey. RFC8032 5.1.6 */
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
isHex(msg, 'message');
msg = ensureBytes(msg);
msg = ensureBytes('message', msg);
if (preHash) msg = preHash(msg); // for ed25519ph etc.
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
@@ -435,15 +430,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const s = modN(r + k * scalar); // S = (r + k * s) mod L
assertGE0(s); // 0 <= s < l
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
return ensureBytes(res, nByteLength * 2); // 64-byte signature
return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
}
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
isHex(sig, 'sig');
isHex(msg, 'message');
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
sig = ensureBytes(sig, 2 * len); // An extended group equation is checked.
msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
msg = ensureBytes('message', msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
if (preHash) msg = preHash(msg); // for ed25519ph, etc
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P

View File

@@ -1,7 +1,7 @@
/*! 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 { CHash, Hex, concatBytes, ensureBytes, validateObject } from './utils.js';
import { CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
export type Opts = {
DST: string; // DST: a domain separation tag, defined in section 2.2.5
@@ -17,18 +17,6 @@ export type Opts = {
hash: CHash;
};
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
declare const TextDecoder: any;
export function stringToBytes(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
// Octet Stream to Integer (bytesToNumberBE)
function os2ip(bytes: Uint8Array): bigint {
let result = 0n;
@@ -60,6 +48,13 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
return arr;
}
function isBytes(item: unknown): void {
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected');
}
function isNum(item: unknown): void {
if (!Number.isSafeInteger(item)) throw new Error('number expected');
}
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
export function expand_message_xmd(
@@ -68,8 +63,11 @@ export function expand_message_xmd(
lenInBytes: number,
H: CHash
): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen;
const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes);
@@ -95,11 +93,14 @@ export function expand_message_xof(
k: number,
H: CHash
): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8);
DST = H.create({ dkLen }).update(stringToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
}
if (lenInBytes > 65535 || DST.length > 255)
throw new Error('expand_message_xof: invalid lenInBytes');
@@ -123,25 +124,27 @@ export function expand_message_xof(
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
// if options is provided but incomplete, fill any missing fields with the
// value in hftDefaults (ie hash to G2).
const log2p = options.p.toString(2).length;
const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * options.m * L;
const DST = stringToBytes(options.DST);
let pseudo_random_bytes = msg;
if (options.expand === 'xmd') {
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
} else if (options.expand === 'xof') {
pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash);
}
const { p, k, m, hash, expand, DST: _DST } = options;
isBytes(msg);
isNum(count);
if (typeof _DST !== 'string') throw new Error('DST must be valid');
const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L;
const DST = utf8ToBytes(_DST);
const pseudo_random_bytes =
expand === 'xmd'
? expand_message_xmd(msg, DST, len_in_bytes, hash)
: expand === 'xof'
? expand_message_xof(msg, DST, len_in_bytes, k, hash)
: msg;
const u = new Array(count);
for (let i = 0; i < count; i++) {
const e = new Array(options.m);
for (let j = 0; j < options.m; j++) {
const elm_offset = L * (j + i * options.m);
const e = new Array(m);
for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
e[j] = mod(os2ip(tv), options.p);
e[j] = mod(os2ip(tv), p);
}
u[i] = e;
}
@@ -178,7 +181,7 @@ export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
// (changing DST is ok!)
export type htfBasicOpts = { DST: string };
export function hashToCurve<T>(
export function createHasher<T>(
Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>,
def: Opts
@@ -197,21 +200,17 @@ export function hashToCurve<T>(
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
hashToCurve(msg: Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0]))
.add(Point.fromAffine(mapToCurve(u[1])))
.clearCofactor();
const u0 = Point.fromAffine(mapToCurve(u[0]));
const u1 = Point.fromAffine(mapToCurve(u[1]));
const P = u0.add(u1).clearCofactor();
P.assertValidity();
return P;
},
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
P.assertValidity();

View File

@@ -407,7 +407,7 @@ export function hashToPrivateScalar(
groupOrder: bigint,
isLE = false
): bigint {
hash = ensureBytes(hash);
hash = ensureBytes('privateHash', hash);
const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8;
if (minLen < 24 || hashLen < minLen || hashLen > 1024)

View File

@@ -11,25 +11,25 @@ export type CurveType = {
nByteLength: number;
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
a24: bigint; // Related to d, but cannot be derived from it
a: bigint;
montgomeryBits: number;
powPminus2?: (x: bigint) => bigint;
xyToU?: (x: bigint, y: bigint) => bigint;
Gu: string;
Gu: bigint;
};
export type CurveFn = {
scalarMult: (scalar: Hex, u: Hex) => Uint8Array;
scalarMultBase: (scalar: Hex) => Uint8Array;
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array;
getPublicKey: (privateKey: Hex) => Uint8Array;
Gu: string;
GuBytes: Uint8Array;
};
function validateOpts(curve: CurveType) {
validateObject(
curve,
{
a24: 'bigint',
a: 'bigint',
},
{
montgomeryBits: 'isSafeInteger',
@@ -37,7 +37,7 @@ function validateOpts(curve: CurveType) {
adjustScalarBytes: 'function',
domain: 'function',
powPminus2: 'function',
Gu: 'string',
Gu: 'bigint',
}
);
// Set defaults
@@ -49,7 +49,7 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef);
const { P } = CURVE;
const modP = (a: bigint) => mod(a, P);
const modP = (n: bigint) => mod(n, P);
const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength;
@@ -73,12 +73,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
return [x_2, x_3];
}
// Accepts 0 as well
function assertFieldElement(n: bigint): bigint {
if (typeof n === 'bigint' && _0n <= n && n < P) return n;
throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
}
// 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
@@ -90,8 +93,6 @@ export function montgomery(curveDef: CurveType): CurveFn {
// Section 5: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime.
const k = assertFieldElement(scalar);
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = CURVE.a24;
const x_1 = u;
let x_2 = _1n;
let z_2 = _0n;
@@ -149,13 +150,13 @@ export function montgomery(curveDef: CurveType): CurveFn {
// MUST mask the most significant bit in the final byte.
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
const u = ensureBytes(uEnc, montgomeryBytes);
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
return bytesToNumberLE(u);
}
function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes(n);
const bytes = ensureBytes('scalar', n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
return bytesToNumberLE(adjustScalarBytes(bytes));
@@ -170,8 +171,9 @@ export function montgomery(curveDef: CurveType): CurveFn {
return encodeUCoordinate(pu);
}
// Computes public key from private. By doing scalar multiplication of base point.
const GuBytes = encodeUCoordinate(CURVE.Gu);
function scalarMultBase(scalar: Hex): Uint8Array {
return scalarMult(scalar, CURVE.Gu);
return scalarMult(scalar, GuBytes);
}
return {
@@ -179,6 +181,6 @@ export function montgomery(curveDef: CurveType): CurveFn {
scalarMultBase,
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
Gu: CURVE.Gu,
GuBytes: GuBytes,
};
}

View File

@@ -33,14 +33,14 @@ export function numberToHexUnpadded(num: number | bigint): string {
}
export function hexToNumber(hex: string): bigint {
if (typeof hex !== 'string') throw new Error('string expected, got ' + typeof hex);
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
// Big Endian
return BigInt(hex === '' ? '0' : `0x${hex}`);
}
// Caching slows it down 2-3x
export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') throw new Error('string expected, got ' + typeof hex);
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
@@ -68,13 +68,25 @@ export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, le
// Returns variable number bytes (minimal bigint encoding?)
export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex);
if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
throw new Error(`Expected ${expectedLength} bytes`);
return bytes;
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
let res: Uint8Array;
if (typeof hex === 'string') {
try {
res = hexToBytes(hex);
} catch (e) {
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
}
} else if (u8a(hex)) {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
res = Uint8Array.from(hex);
} else {
throw new Error(`${title} must be hex string or Uint8Array`);
}
const len = res.length;
if (typeof expectedLength === 'number' && len !== expectedLength)
throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
return res;
}
// Copies several Uint8Arrays into one.
@@ -96,6 +108,16 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
return true;
}
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
export function utf8ToBytes(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
// Bit operations
// Amount of bits inside bigint (Same as n.toString(2).length)
@@ -114,6 +136,70 @@ export const bitSet = (n: bigint, pos: number, value: boolean) =>
// Not using ** operator with bigints for old engines.
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
// DRBG
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
type Pred<T> = (v: Uint8Array) => T | undefined;
/**
* Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
* @returns function that will call DRBG until 2nd arg returns something meaningful
* @example
* const drbg = createHmacDRBG<Key>(32, 32, hmac);
* drbg(seed, bytesToKey); // bytesToKey must return Key or undefined
*/
export function createHmacDrbg<T>(
hashLen: number,
qByteLen: number,
hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array
): (seed: Uint8Array, predicate: Pred<T>) => T {
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
let i = 0; // Iterations counter, will throw when over 1000
const reset = () => {
v.fill(1);
k.fill(0);
i = 0;
};
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
const reseed = (seed = u8n()) => {
// HMAC-DRBG reseed() function. Steps D-G
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
v = h(); // v = hmac(k || v)
if (seed.length === 0) return;
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
v = h(); // v = hmac(k || v)
};
const gen = () => {
// HMAC-DRBG generate() function
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
let len = 0;
const out: Uint8Array[] = [];
while (len < qByteLen) {
v = h();
const sl = v.slice();
out.push(sl);
len += v.length;
}
return concatBytes(...out);
};
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset();
reseed(seed); // Steps D-G
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(gen()))) reseed();
reset();
return res;
};
return genUntil;
}
// Validating curves and fields
const validatorFns = {
bigint: (val: any) => typeof val === 'bigint',
function: (val: any) => typeof val === 'function',

View File

@@ -59,9 +59,6 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
readonly py: T;
readonly pz: T;
multiply(scalar: bigint): ProjPointType<T>;
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
_setWindowSize(windowSize: number): void;
toAffine(iz?: T): AffinePoint<T>;
isTorsionFree(): boolean;
clearCofactor(): ProjPointType<T>;
@@ -69,6 +66,10 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
hasEvenY(): boolean;
toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string;
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
_setWindowSize(windowSize: number): void;
}
// Static methods for 3d XYZ points
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
@@ -121,7 +122,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
export type CurvePointsRes<T> = {
ProjectivePoint: ProjConstructor<T>;
normalizePrivateKey: (key: PrivKey) => bigint;
normPrivateKeyToScalar: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T;
isWithinCurveOrder: (num: bigint) => boolean;
};
@@ -202,8 +203,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n');
}
// Validates if priv key is valid and converts it to bigint.
// Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey.
function normalizePrivateKey(key: PrivKey): bigint {
// Supports options allowedPrivateKeyLengths and wrapPrivateKey.
function normPrivateKeyToScalar(key: PrivKey): bigint {
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
if (lengths && typeof key !== 'bigint') {
if (key instanceof Uint8Array) key = ut.bytesToHex(key);
@@ -213,7 +214,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
}
let num: bigint;
try {
num = typeof key === 'bigint' ? key : ut.bytesToNumberBE(ensureBytes(key, nByteLength));
num =
typeof key === 'bigint'
? key
: ut.bytesToNumberBE(ensureBytes('private key', key, nByteLength));
} catch (error) {
throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`);
}
@@ -276,14 +280,14 @@ 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(hex)));
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes('pointHex', hex)));
P.assertValidity();
return P;
}
// Multiplies generator point by privateKey.
static fromPrivateKey(privateKey: PrivKey) {
return Point.BASE.multiply(normalizePrivateKey(privateKey));
return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
}
// We calculate precomputes for elliptic curve point multiplication
@@ -484,8 +488,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* Constant time multiplication.
* Uses wNAF method. Windowed method may be 10% faster,
* 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 affinePoint optional point ot save cached precompute windows on it
* @returns New point
*/
multiply(scalar: bigint): Point {
@@ -513,6 +518,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/**
* 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
*/
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
@@ -568,7 +575,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return {
ProjectivePoint: Point as ProjConstructor<T>,
normalizePrivateKey,
normPrivateKeyToScalar,
weierstrassEquation,
isWithinCurveOrder,
};
@@ -638,65 +645,11 @@ export type CurveFn = {
utils: {
normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
};
};
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
// Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
type Pred<T> = (v: Uint8Array) => T | undefined;
function hmacDrbg<T>(
hashLen: number,
qByteLen: number,
hmacFn: HmacFnSync
): (seed: Uint8Array, predicate: Pred<T>) => T {
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
let i = 0; // Iterations counter, will throw when over 1000
const reset = () => {
v.fill(1);
k.fill(0);
i = 0;
};
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
const reseed = (seed = u8n()) => {
// HMAC-DRBG reseed() function. Steps D-G
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
v = h(); // v = hmac(k || v)
if (seed.length === 0) return;
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
v = h(); // v = hmac(k || v)
};
const gen = () => {
// HMAC-DRBG generate() function
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
let len = 0;
const out: Uint8Array[] = [];
while (len < qByteLen) {
v = h();
const sl = v.slice();
out.push(sl);
len += v.length;
}
return ut.concatBytes(...out);
};
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset();
reseed(seed); // Steps D-G
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(gen()))) reseed();
reset();
return res;
};
return genUntil;
}
export function weierstrass(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const CURVE_ORDER = CURVE.n;
@@ -716,7 +669,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const {
ProjectivePoint: Point,
normalizePrivateKey,
normPrivateKeyToScalar,
weierstrassEquation,
isWithinCurveOrder,
} = weierstrassPoints({
@@ -726,7 +679,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const x = Fp.toBytes(a.x);
const cat = ut.concatBytes;
if (isCompressed) {
// TODO: hasEvenY
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
} else {
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y));
@@ -782,24 +734,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// pair (bytes of r, bytes of s)
static fromCompact(hex: Hex) {
const gl = CURVE.nByteLength;
hex = ensureBytes(hex, gl * 2);
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl));
const l = CURVE.nByteLength;
hex = ensureBytes('compactSignature', hex, l * 2);
return new Signature(slcNum(hex, 0, l), slcNum(hex, l, 2 * l));
}
// DER encoded ECDSA signature
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
static fromDER(hex: Hex) {
if (typeof hex !== 'string' && !(hex instanceof Uint8Array))
throw new Error(`Signature.fromDER: Expected string or Uint8Array`);
const { r, s } = DER.toSig(ensureBytes(hex));
const { r, s } = DER.toSig(ensureBytes('DER', hex));
return new Signature(r, s);
}
assertValidity(): void {
// can use assertGE here
if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < n');
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < n');
if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < CURVE.n');
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n');
}
addRecoveryBit(recovery: number) {
@@ -807,11 +757,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6
const { r, s, recovery: rec } = this;
const h = bits2int_modN(ensureBytes(msgHash)); // Truncate hash
const h = bits2int_modN(ensureBytes('msgHash', msgHash)); // Truncate hash
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
const radj = rec === 2 || rec === 3 ? r + N : r;
const radj = rec === 2 || rec === 3 ? r + CURVE.n : r;
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
const prefix = (rec & 1) === 0 ? '02' : '03';
const R = Point.fromHex(prefix + numToNByteStr(radj));
@@ -853,37 +802,35 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const utils = {
isValidPrivateKey(privateKey: PrivKey) {
try {
normalizePrivateKey(privateKey);
normPrivateKeyToScalar(privateKey);
return true;
} catch (error) {
return false;
}
},
normPrivateKeyToScalar: normalizePrivateKey,
/**
* 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),
normPrivateKeyToScalar: normPrivateKeyToScalar,
/**
* Produces cryptographically secure private key from random of size (nBitLength+64)
* as per FIPS 186 B.4.1 with modulo bias being neglible.
*/
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.
* 2. Precomputes point multiplication table. Is done by default on first `getPublicKey()` call.
* 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
* Creates precompute table for an arbitrary EC point. Makes point "cached".
* Allows to massively speed-up `point.multiply(scalar)`.
* @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 {
point._setWindowSize(windowSize);
point.multiply(BigInt(3));
point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here
return point;
},
};
@@ -914,7 +861,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
/**
* ECDH (Elliptic Curve Diffie Hellman).
* 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 publicB different public key
* @param isCompressed whether to return compact (default), or full key
@@ -924,7 +872,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (isProbPub(privateA)) throw new Error('first arg must be private key');
if (!isProbPub(publicB)) throw new Error('second arg must be public key');
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.
@@ -936,8 +884,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
function (bytes: Uint8Array): bigint {
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
// for some cases, since bytes.length * 8 is not actual bitLength.
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
return delta > 0 ? num >> BigInt(delta) : num;
};
const bits2int_modN =
@@ -947,10 +895,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
};
// NOTE: pads output with zero as per spec
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 {
if (typeof num !== 'bigint') throw new Error('bigint expected');
if (!(_0n <= num && num < ORDER_MASK))
// n in [0..ORDER_MASK-1]
throw new Error(`bigint expected < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField!
return ut.numberToBytesBE(num, CURVE.nByteLength);
@@ -962,26 +912,25 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521.
// Also it can be bigger for P224 + SHA256
function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) {
const { hash, randomBytes } = CURVE;
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
if (['recovered', 'canonical'].some((k) => k in opts))
// Ban legacy options
throw new Error('sign() legacy options not supported');
const { hash, randomBytes } = CURVE;
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
if (prehash) msgHash = hash(ensureBytes(msgHash));
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash
msgHash = ensureBytes('msgHash', msgHash);
if (prehash) msgHash = ensureBytes('prehashed msgHash', hash(msgHash));
// We can't later call bits2octets, since nested bits2int is broken for curves
// with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
// const bits2octets = (bits) => int2octets(bits2int_modN(bits))
const h1int = bits2int_modN(ensureBytes(msgHash));
const d = normalizePrivateKey(privateKey); // validate private key, convert to bigint
const h1int = bits2int_modN(msgHash);
const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint
const seedArgs = [int2octets(d), int2octets(h1int)];
// extraEntropy. RFC6979 3.6: additional k' (optional).
if (ent != null) {
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
// Either pass as-is, or generate random bytes. Then validate for being ui8a of size BYTES
seedArgs.push(ensureBytes(ent === true ? randomBytes(Fp.BYTES) : ent, Fp.BYTES));
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
}
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
@@ -1030,8 +979,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
*/
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
const genUntil = hmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
return genUntil(seed, k2sig); // Steps B, C, D, E, F, G
const drbg = ut.createHmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
return drbg(seed, k2sig); // Steps B, C, D, E, F, G
}
// Enable precomputes. Slows down first publicKey computation by 20ms.
@@ -1057,30 +1006,38 @@ export function weierstrass(curveDef: CurveType): CurveFn {
publicKey: Hex,
opts = defaultVerOpts
): boolean {
let P: ProjPointType<bigint>;
const sg = signature;
msgHash = ensureBytes('msgHash', msgHash);
publicKey = ensureBytes('publicKey', publicKey);
if ('strict' in opts) throw new Error('options.strict was renamed to lowS');
const { lowS, prehash } = opts;
let _sig: Signature | undefined = undefined;
if (publicKey instanceof Point) throw new Error('publicKey must be hex');
let P: ProjPointType<bigint>;
try {
if (signature && typeof signature === 'object' && !(signature instanceof Uint8Array)) {
const { r, s } = signature;
_sig = new Signature(r, s); // assertValidity() is executed on creation
} else {
if (typeof sg === 'string' || sg instanceof Uint8Array) {
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
// Since DER can also be 2*nByteLength bytes, we check for it first.
try {
_sig = Signature.fromDER(signature as Hex);
_sig = Signature.fromDER(sg);
} catch (derError) {
if (!(derError instanceof DER.Err)) throw derError;
_sig = Signature.fromCompact(signature as Hex);
_sig = Signature.fromCompact(sg);
}
} else if (typeof sg === 'object' && typeof sg.r === 'bigint' && typeof sg.s === 'bigint') {
const { r, s } = sg;
_sig = new Signature(r, s);
} else {
throw new Error('PARSE');
}
msgHash = ensureBytes(msgHash);
P = Point.fromHex(publicKey);
} catch (error) {
if ((error as Error).message === 'PARSE')
throw new Error(`signature must be Signature instance, Uint8Array or hex string`);
return false;
}
if (opts.lowS && _sig.hasHighS()) return false;
if (opts.prehash) msgHash = CURVE.hash(msgHash);
if (lowS && _sig.hasHighS()) return false;
if (prehash) msgHash = CURVE.hash(msgHash);
const { r, s } = _sig;
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
const is = invN(s); // s^-1

View File

@@ -1,9 +1,43 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
// bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
// - Construct zk-SNARKs at the 128-bit security
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
// Differences from @noble/bls12-381 1.4:
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and
// verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
//
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
// [pairing-curves-10](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-10),
// [bls-sigs-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12).
//
// ### Summary
// 1. BLS Relies on Bilinear Pairing (expensive)
// 2. Private Keys: 32 bytes
// 3. Public Keys: 48 bytes: 381 bit affine x coordinate, encoded into 48 big-endian bytes.
// 4. Signatures: 96 bytes: two 381 bit integers (affine x coordinate), encoded into two 48 big-endian byte arrays.
// - The signature is a point on the G2 subgroup, which is defined over a finite field
// with elements twice as big as the G1 curve (G2 is over Fp2 rather than Fp. Fp2 is analogous to the complex numbers).
// 5. The 12 stands for the Embedding degree.
//
// ### Formulas
// - `P = pk x G` - public keys
// - `S = pk x H(m)` - signing
// - `e(P, H(m)) == e(G, S)` - verification using pairings
// - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
// Filecoin uses little endian byte arrays for private keys -
// so ensure to reverse byte order if you'll use it with FIL.
//
// ### Resources
// - [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
// - [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
// - Pairing over bls12-381:
// [part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
// [part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
// [part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
// - [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
//
// ### Differences from @noble/bls12-381 1.4
// - PointG1 -> G1.Point
// - PointG2 -> G2.Point
// - PointG2.fromSignature -> Signature.decode
@@ -910,7 +944,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
// m = 2 (or 1 for G1 see section 8.8.1)
// k = 128
const htfDefaults = {
const htfDefaults = Object.freeze({
// DST: a domain separation tag
// defined in section 2.2.5
// Use utils.getDSTLabel(), utils.setDSTLabel(value)
@@ -932,7 +966,7 @@ const htfDefaults = {
// 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
hash: sha256,
} as const;
} as const);
// Encoding utils
// Point on G1 curve: (x, y)
@@ -1186,7 +1220,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
Signature: {
// TODO: Optimize, it's very slow because of sqrt.
decode(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes(hex);
hex = ensureBytes('signatureHex', hex);
const P = Fp.ORDER;
const half = hex.length / 2;
if (half !== 48 && half !== 96)
@@ -1213,7 +1247,6 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.neg(y);
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
// console.log('Signature.decode', point);
point.assertValidity();
return point;
},

View File

@@ -5,12 +5,12 @@ 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 {
ensureBytes,
equalBytes,
bytesToHex,
bytesToNumberLE,
numberToBytesLE,
Hex,
ensureBytes,
} from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js';
@@ -138,10 +138,10 @@ export const ed25519ph = twistedEdwards({
export const x25519 = montgomery({
P: ED25519_P,
a24: BigInt('121665'),
a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits
nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000',
Gu: BigInt(9),
powPminus2: (x: bigint): bigint => {
const P = ED25519_P;
// x^(p-2) aka x^(2^255-21)
@@ -223,7 +223,7 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
const inv = Fp.invertBatch([xd, yd]); // batch division
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
}
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
const { hashToCurve, encodeToCurve } = htf.createHasher(
ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{
@@ -316,7 +316,7 @@ export class RistrettoPoint {
* @param hex 64-bit output of a hash function
*/
static hashToCurve(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 64);
hex = ensureBytes('ristrettoHash', hex, 64);
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
const R1 = calcElligatorRistrettoMap(r1);
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
@@ -330,7 +330,7 @@ export class RistrettoPoint {
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
*/
static fromHex(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 32);
hex = ensureBytes('ristrettoHex', hex, 32);
const { a, d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER;
const mod = ed25519.CURVE.Fp.create;

View File

@@ -122,11 +122,11 @@ export const ed448 = twistedEdwards(ED448_DEF);
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
export const x448 = montgomery({
a24: BigInt(39081),
a: BigInt(156326),
montgomeryBits: 448,
nByteLength: 57,
P: ed448P,
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
Gu: BigInt(5),
powPminus2: (x: bigint): bigint => {
const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x);
@@ -225,7 +225,7 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
}
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
const { hashToCurve, encodeToCurve } = htf.createHasher(
ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{

View File

@@ -37,7 +37,7 @@ export const P256 = createCurve(
);
export const secp256r1 = P256;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]),
{

View File

@@ -41,7 +41,7 @@ export const P384 = createCurve({
);
export const secp384r1 = P384;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
const { hashToCurve, encodeToCurve } = htf.createHasher(
secp384r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]),
{

View File

@@ -41,7 +41,7 @@ export const P521 = createCurve({
} as const, sha512);
export const secp521r1 = P521;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
const { hashToCurve, encodeToCurve } = htf.createHasher(
secp521r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]),
{

View File

@@ -1,26 +1,12 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { createCurve } from './_shortw_utils.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import {
ensureBytes,
concatBytes,
Hex,
bytesToNumberBE as bytesToInt,
PrivKey,
numberToBytesBE,
} from './abstract/utils.js';
import { randomBytes } from '@noble/hashes/utils';
import { Fp as 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';
import * as htf from './abstract/hash-to-curve.js';
/**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* Should always be used for Projective's double-and-add multiplication.
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
import { createCurve } from './_shortw_utils.js';
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
@@ -61,23 +47,22 @@ type Fp = bigint;
export const secp256k1 = createCurve(
{
// Params: a, b
// Seem to be rigid https://bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
a: BigInt(0),
b: BigInt(7),
// Field over which we'll do calculations;
// 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
Fp,
// Curve order, total count of valid points in the field
n: secp256k1N,
a: BigInt(0), // equation params: a, b
b: BigInt(7), // Seem to be rigid: bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
Fp, // Field's prime: 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
n: secp256k1N, // Curve order, total count of valid points in the field
// Base point (x, y) aka generator point
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
h: BigInt(1),
// Alllow only low-S signatures by default in sign() and verify()
lowS: true,
h: BigInt(1), // Cofactor
lowS: true, // Allow only low-S signatures by default in sign() and verify()
/**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* Explanation: https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
endo: {
// Params taken from https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
splitScalar: (k: bigint) => {
const n = secp256k1N;
@@ -105,19 +90,11 @@ export const secp256k1 = createCurve(
sha256
);
// Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340.
// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
const _0n = BigInt(0);
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
const TAGS = {
challenge: 'BIP0340/challenge',
aux: 'BIP0340/aux',
nonce: 'BIP0340/nonce',
} as const;
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
@@ -130,51 +107,64 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
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 numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modP = (x: bigint) => mod(x, secp256k1P);
const modN = (x: bigint) => mod(x, secp256k1N);
const Point = secp256k1.ProjectivePoint;
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
Point.BASE.multiplyAndAddUnsafe(Q, a, b);
const hex32ToInt = (key: Hex) => bytesToInt(ensureBytes(key, 32));
// Calculate point, scalar and bytes
function schnorrGetExtPubKey(priv: PrivKey) {
let d = typeof priv === 'bigint' ? priv : hex32ToInt(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 scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d'
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> {
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p.
const xx = modP(x * x);
const c = modP(xx * x + BigInt(7)); // Let c = x³ + 7 mod p.
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and
if (y % 2n !== 0n) y = modP(-y); // Return the unique point P such that x(P) = x and
const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
p.assertValidity();
return p;
}
/**
* Create tagged hash, convert it to bigint, reduce modulo-n.
*/
function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToInt(taggedHash(TAGS.challenge, ...args)));
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
}
// Schnorr's pubkey is just `x` of Point (BIP340)
/**
* Schnorr public key is just `x` coordinate of Point as per BIP340.
*/
function schnorrGetPublicKey(privateKey: Hex): Uint8Array {
return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
}
// Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
// auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous
/**
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
*/
function schnorrSign(
message: Hex,
privateKey: PrivKey,
auxRand: Hex = randomBytes(32)
): Uint8Array {
if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`);
const m = ensureBytes(message); // checks for isWithinCurveOrder
const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey);
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
const t = numTo32b(d ^ bytesToInt(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const k_ = modN(bytesToInt(rand)); // Let k' = int(rand) mod n
const m = ensureBytes('message', message);
const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey); // checks for isWithinCurveOrder
const a = ensureBytes('auxRand', auxRand, 32); // Auxiliary random data a: a 32-byte array
const t = numTo32b(d ^ bytesToNumberBE(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const { point: R, bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G.
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
@@ -187,18 +177,20 @@ function schnorrSign(
}
/**
* Verifies Schnorr signature synchronously.
* Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const sig = ensureBytes('signature', signature, 64);
const m = ensureBytes('message', message);
const pub = ensureBytes('publicKey', publicKey, 32);
try {
const P = lift_x(hex32ToInt(publicKey)); // P = lift_x(int(pk)); fail if that fails
const sig = ensureBytes(signature, 64);
const r = bytesToInt(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
const P = lift_x(bytesToNumberBE(pub)); // P = lift_x(int(pk)); fail if that fails
const r = bytesToNumberBE(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
if (!fe(r)) return false;
const s = bytesToInt(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
const s = bytesToNumberBE(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
if (!ge(s)) return false;
const m = ensureBytes(message);
const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n
const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m))%n
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
@@ -212,11 +204,12 @@ export const schnorr = {
sign: schnorrSign,
verify: schnorrVerify,
utils: {
randomPrivateKey: secp256k1.utils.randomPrivateKey,
getExtendedPublicKey: schnorrGetExtPubKey,
lift_x,
pointToBytes,
numberToBytesBE,
bytesToNumberBE: bytesToInt,
bytesToNumberBE,
taggedHash,
mod,
},
@@ -259,7 +252,7 @@ const mapSWU = mapToCurveSimpleSWU(Fp, {
B: BigInt('1771'),
Z: Fp.create(BigInt('-11')),
});
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
export const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256k1.ProjectivePoint,
(scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
@@ -275,4 +268,3 @@ const { hashToCurve, encodeToCurve } = htf.hashToCurve(
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };

View File

@@ -94,19 +94,19 @@ function ensureBytes0x(hex: Hex): Uint8Array {
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');
}
function getPublicKey0x(privKey: Hex, isCompressed = false) {
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed);
return starkCurve.getPublicKey(normPrivKey(privKey), isCompressed);
}
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) {
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) {
const sig = signature instanceof Signature ? signature : ensureBytes0x(signature);

View File

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

View File

@@ -1,18 +1,14 @@
import { bls12_381 } from '../lib/esm/bls12-381.js';
import { describe, should } from 'micro-should';
import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import { sha512 } from '@noble/hashes/sha512';
import * as fc from 'fast-check';
import { readFileSync } from 'fs';
import { describe, should } from 'micro-should';
import { wNAF } from '../esm/abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../esm/abstract/utils.js';
import { hash_to_field } from '../esm/abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../esm/bls12-381.js';
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 { wNAF } from '../lib/esm/abstract/curve.js';
const bls = bls12_381;
const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
@@ -28,7 +24,10 @@ const SCALAR_VECTORS = readFileSync('./test/bls12-381/bls12-381-scalar-test-vect
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
fc.configureGlobal({ numRuns: NUM_RUNS });
// @ts-ignore
const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const CURVE_ORDER = bls.CURVE.r;
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
@@ -851,7 +850,7 @@ describe('bls12-381/basic', () => {
for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector;
const sig = bls.sign(msg, priv);
deepStrictEqual(bls.utils.bytesToHex(sig), expected);
deepStrictEqual(bytesToHex(sig), expected);
}
});
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
@@ -863,8 +862,8 @@ describe('bls12-381/basic', () => {
for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector;
const expected = BigInt('0x' + expectedHex);
const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0)));
const scalars = bls.utils.hashToField(okm, 1, options);
const okm = utf8ToBytes(okmAscii);
const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options));
deepStrictEqual(scalars[0][0], expected);
}
});
@@ -973,25 +972,25 @@ describe('hash-to-curve', () => {
// Point G1
const VECTORS_G1 = [
{
msg: bls.utils.stringToBytes(''),
msg: utf8ToBytes(''),
expected:
'0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' +
'1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae',
},
{
msg: bls.utils.stringToBytes('abc'),
msg: utf8ToBytes('abc'),
expected:
'061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' +
'0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
msg: utf8ToBytes('abcdef0123456789'),
expected:
'0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' +
'177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57',
},
{
msg: bls.utils.stringToBytes(
msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
@@ -1002,7 +1001,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_G1.length; i++) {
const t = VECTORS_G1[i];
should(`hashToCurve/G1 Killic (${i})`, () => {
const p = bls.hashToCurve.G1.hashToCurve(t.msg, {
const p = bls.G1.hashToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
});
deepStrictEqual(p.toHex(false), t.expected);
@@ -1011,25 +1010,25 @@ describe('hash-to-curve', () => {
const VECTORS_ENCODE_G1 = [
{
msg: bls.utils.stringToBytes(''),
msg: utf8ToBytes(''),
expected:
'1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' +
'0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5',
},
{
msg: bls.utils.stringToBytes('abc'),
msg: utf8ToBytes('abc'),
expected:
'179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' +
'0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
msg: utf8ToBytes('abcdef0123456789'),
expected:
'15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' +
'0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788',
},
{
msg: bls.utils.stringToBytes(
msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
@@ -1040,7 +1039,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
const t = VECTORS_ENCODE_G1[i];
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, {
const p = bls.G1.encodeToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
});
deepStrictEqual(p.toHex(false), t.expected);
@@ -1049,7 +1048,7 @@ describe('hash-to-curve', () => {
// Point G2
const VECTORS_G2 = [
{
msg: bls.utils.stringToBytes(''),
msg: utf8ToBytes(''),
expected:
'0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' +
'0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' +
@@ -1057,7 +1056,7 @@ describe('hash-to-curve', () => {
'0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83',
},
{
msg: bls.utils.stringToBytes('abc'),
msg: utf8ToBytes('abc'),
expected:
'03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' +
'1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' +
@@ -1065,7 +1064,7 @@ describe('hash-to-curve', () => {
'0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
msg: utf8ToBytes('abcdef0123456789'),
expected:
'195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' +
'17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' +
@@ -1073,7 +1072,7 @@ describe('hash-to-curve', () => {
'174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e',
},
{
msg: bls.utils.stringToBytes(
msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
@@ -1086,7 +1085,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_G2.length; i++) {
const t = VECTORS_G2[i];
should(`hashToCurve/G2 Killic (${i})`, () => {
const p = bls.hashToCurve.G2.hashToCurve(t.msg, {
const p = bls.G2.hashToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
});
deepStrictEqual(p.toHex(false), t.expected);
@@ -1095,7 +1094,7 @@ describe('hash-to-curve', () => {
const VECTORS_ENCODE_G2 = [
{
msg: bls.utils.stringToBytes(''),
msg: utf8ToBytes(''),
expected:
'0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' +
'027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' +
@@ -1103,7 +1102,7 @@ describe('hash-to-curve', () => {
'053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7',
},
{
msg: bls.utils.stringToBytes('abc'),
msg: utf8ToBytes('abc'),
expected:
'18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' +
'09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' +
@@ -1111,7 +1110,7 @@ describe('hash-to-curve', () => {
'02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811',
},
{
msg: bls.utils.stringToBytes('abcdef0123456789'),
msg: utf8ToBytes('abcdef0123456789'),
expected:
'19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' +
'149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' +
@@ -1119,7 +1118,7 @@ describe('hash-to-curve', () => {
'04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551',
},
{
msg: bls.utils.stringToBytes(
msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
),
expected:
@@ -1132,7 +1131,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
const t = VECTORS_ENCODE_G2[i];
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, {
const p = bls.G2.encodeToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
});
deepStrictEqual(p.toHex(false), t.expected);
@@ -1265,7 +1264,7 @@ describe('bls12-381 deterministic', () => {
should('Killic based/Pairing', () => {
const t = bls.pairing(G1Point.BASE, G2Point.BASE);
deepStrictEqual(
bls.utils.bytesToHex(Fp12.toBytes(t)),
bytesToHex(Fp12.toBytes(t)),
killicHex([
'0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631',
'04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef',
@@ -1287,7 +1286,7 @@ describe('bls12-381 deterministic', () => {
let p2 = G2Point.BASE;
for (let v of pairingVectors) {
deepStrictEqual(
bls.utils.bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))),
bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))),
// Reverse order
v.match(/.{96}/g).reverse().join('')
);

View File

@@ -2,9 +2,9 @@ import { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { deepStrictEqual, strictEqual, throws } from 'assert';
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 { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../lib/esm/ed25519.js';
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../esm/ed25519.js';
// const ed = ed25519;
const hex = bytesToHex;
@@ -97,7 +97,7 @@ should('X25519 base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE;
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', () => {
@@ -128,7 +128,7 @@ describe('RFC7748', () => {
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[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];
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 { describe, should } from 'micro-should';
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 { 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 x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
@@ -509,7 +509,7 @@ describe('ed448', () => {
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[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];
deepStrictEqual(hex(k), scalar);
});
@@ -664,7 +664,7 @@ describe('ed448', () => {
// const invX = Fp.invert(x * x); // x²
const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)
// const u = Fp.create(y * y * invX);
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu);
deepStrictEqual(numberToBytesLE(u, 56), x448.GuBytes);
});
});

View File

@@ -5,18 +5,15 @@ import { bytesToHex } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3';
import * as secp256r1 from '../lib/esm/p256.js';
import * as secp384r1 from '../lib/esm/p384.js';
import * as secp521r1 from '../lib/esm/p521.js';
import * as ed25519 from '../lib/esm/ed25519.js';
import * as ed448 from '../lib/esm/ed448.js';
import * as secp256k1 from '../lib/esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js';
import {
stringToBytes,
expand_message_xmd,
expand_message_xof,
} from '../lib/esm/abstract/hash-to-curve.js';
import * as secp256r1 from '../esm/p256.js';
import * as secp384r1 from '../esm/p384.js';
import * as secp521r1 from '../esm/p521.js';
import * as ed25519 from '../esm/ed25519.js';
import * as ed448 from '../esm/ed448.js';
import * as secp256k1 from '../esm/secp256k1.js';
import { bls12_381 } from '../esm/bls12-381.js';
import { expand_message_xmd, expand_message_xof } from '../esm/abstract/hash-to-curve.js';
import { utf8ToBytes } from '../esm/abstract/utils.js';
// 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_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
@@ -56,9 +53,9 @@ function testExpandXMD(hash, vectors) {
const t = vectors.tests[i];
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
const p = expand_message_xmd(
stringToBytes(t.msg),
stringToBytes(vectors.DST),
t.len_in_bytes,
utf8ToBytes(t.msg),
utf8ToBytes(vectors.DST),
Number.parseInt(t.len_in_bytes),
hash
);
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
@@ -79,9 +76,9 @@ function testExpandXOF(hash, vectors) {
const t = vectors.tests[i];
should(`${i}`, () => {
const p = expand_message_xof(
stringToBytes(t.msg),
stringToBytes(vectors.DST),
+t.len_in_bytes,
utf8ToBytes(t.msg),
utf8ToBytes(vectors.DST),
Number.parseInt(t.len_in_bytes),
vectors.k,
hash
);
@@ -112,7 +109,7 @@ function testCurve(curve, ro, nu) {
const t = ro.vectors[i];
should(`(${i})`, () => {
const p = curve
.hashToCurve(stringToBytes(t.msg), {
.hashToCurve(utf8ToBytes(t.msg), {
DST: ro.dst,
})
.toAffine();
@@ -126,7 +123,7 @@ function testCurve(curve, ro, nu) {
const t = nu.vectors[i];
should(`(${i})`, () => {
const p = curve
.encodeToCurve(stringToBytes(t.msg), {
.encodeToCurve(utf8ToBytes(t.msg), {
DST: nu.dst,
})
.toAffine();
@@ -140,8 +137,8 @@ function testCurve(curve, ro, nu) {
testCurve(secp256r1, p256_ro, p256_nu);
testCurve(secp384r1, p384_ro, p384_nu);
testCurve(secp521r1, p521_ro, p521_nu);
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu);
testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu);
testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
testCurve(ed25519, ed25519_ro, ed25519_nu);
testCurve(ed448, ed448_ro, ed448_nu);

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 { deepStrictEqual, throws } from 'assert';
const Point = jubjub.ExtendedPoint;

View File

@@ -1,12 +1,12 @@
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import { secp192r1, P192 } from '../lib/esm/p192.js';
import { secp224r1, P224 } from '../lib/esm/p224.js';
import { secp256r1, P256 } from '../lib/esm/p256.js';
import { secp384r1, P384 } from '../lib/esm/p384.js';
import { secp521r1, P521 } from '../lib/esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js';
import { hexToBytes, bytesToHex } from '../lib/esm/abstract/utils.js';
import { secp192r1, P192 } from '../esm/p192.js';
import { secp224r1, P224 } from '../esm/p224.js';
import { secp256r1, P256 } from '../esm/p256.js';
import { secp384r1, P384 } from '../esm/p384.js';
import { secp521r1, P521 } from '../esm/p521.js';
import { secp256k1 } from '../esm/secp256k1.js';
import { hexToBytes, bytesToHex } from '../esm/abstract/utils.js';
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 rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };

View File

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

View File

@@ -2,7 +2,7 @@ import { deepStrictEqual, throws } from 'assert';
import { readFileSync } from 'fs';
import { should, describe } from 'micro-should';
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');
describe('schnorr.sign()', () => {

View File

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

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert';
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 * as bip32 from '@scure/bip32';
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 bench from 'micro-bmark';
const { run, mark } = bench; // or bench.mark

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert';
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';
function parseTest(path) {

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert';
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';
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);

View File

@@ -1,15 +1,15 @@
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import { hex, utf8 } from '@scure/base';
import { utf8ToBytes } from '@noble/hashes/utils';
import * as bip32 from '@scure/bip32';
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 precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
describe('starknet', () => {
should('custom keccak', () => {
const value = starknet.keccak(utf8.decode('hello'));
const value = starknet.keccak(utf8ToBytes('hello'));
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
deepStrictEqual(value < 2n ** 250n, true);
});

View File

@@ -1,11 +1,12 @@
{
"compilerOptions": {
"strict": true,
"outDir": "lib/esm",
"outDir": "esm",
"target": "es2020",
"module": "es6",
"moduleResolution": "node16",
"noUnusedLocals": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": [ "src/crypto" ]

View File

@@ -2,9 +2,11 @@
"compilerOptions": {
"strict": true,
"declaration": true,
"outDir": "lib",
"declarationMap": true,
"outDir": ".",
"target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM
"sourceMap": true,
"module": "commonjs",
"moduleResolution": "node",
"noUnusedLocals": true,