42 Commits
0.7.0 ... 0.8.0

Author SHA1 Message Date
Paul Miller
49a659b248 Release 0.8.0. 2023-03-03 05:12:36 +04:00
Paul Miller
9d0a2e25dc readme: esm-only 2023-03-03 05:11:21 +04:00
Paul Miller
7c461af2b2 test: remove common.js support 2023-03-03 05:09:50 +04:00
Paul Miller
4a8f447c8d package.json, tsconfig: remove common.js support. Pure ESM now 2023-03-03 05:09:36 +04:00
Paul Miller
4b2d31ce7f stark: more methods 2023-02-28 23:18:06 +04:00
Paul Miller
16115f27a6 readme update 2023-02-28 14:04:15 +04:00
Paul Miller
0e0d0f530d benchmark: add tonneli-shanks sqrt 2023-02-28 02:59:28 +04:00
Paul Miller
fa5105aef2 ecdsa: remove scalar blinding. CSPRNG dep not good: cryptofuzz, other envs will fail 2023-02-28 01:48:06 +04:00
Paul Miller
11f1626ecc modular: Add comment. Add benchmark 2023-02-27 22:41:24 +04:00
Paul Miller
53ff287bf7 Schnorr: remove getExtendedPublicKey 2023-02-27 20:29:47 +04:00
Paul Miller
214c9aa553 secp256k1: Fix schnorrGetExtPubKey y coordinate 2023-02-27 20:20:13 +04:00
Paul Miller
ec2c3e1248 Add test for ristretto equality testing 2023-02-27 19:33:41 +04:00
Paul Miller
e64a9d654c Fix ristretto255 equals 2023-02-27 19:07:45 +04:00
Paul Miller
088edd0fbb h2c: move params validation. add experimental hash_to_ristretto255 2023-02-27 15:07:24 +01:00
Paul Miller
3e90930e9d Fix types 2023-02-26 19:10:50 +01:00
Paul Miller
b8b2e91f74 Release 0.7.3. 2023-02-26 19:05:53 +01:00
Paul Miller
9ee694ae23 docs updates 2023-02-26 19:05:40 +01:00
Paul Miller
6bc4b35cf4 hash-to-curve: speed-up os2ip, change code a bit 2023-02-26 18:55:30 +01:00
Paul Miller
0163b63532 Release 0.7.2. 2023-02-25 10:13:45 +01:00
Paul Miller
7e825520f1 README 2023-02-25 10:05:48 +01:00
Paul Miller
d739297b2c Move p192, p224 from main pkg to tests for now. Reason: not popular 2023-02-25 10:00:24 +01:00
Paul Miller
285aa6375d stark: refactor 2023-02-20 16:50:29 +01:00
Paul Miller
8c77331ef2 add hash-to-curve benchmark 2023-02-20 16:33:05 +01:00
Paul Miller
669641e0a3 README wording 2023-02-16 17:54:17 +01:00
Paul Miller
68dd57ed31 Cryptofuzz 2023-02-16 17:49:48 +01:00
Paul Miller
a9fdd6df9f readme: typo 2023-02-16 12:33:32 +01:00
Paul Miller
d485d8b0e6 Fix prettier 2023-02-16 12:32:32 +01:00
Paul Miller
0fdd763dc7 montgomery: add randomPrivateKey. Add ecdh benchmark. 2023-02-16 12:32:18 +01:00
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
42 changed files with 702 additions and 509 deletions

228
README.md
View File

@@ -5,25 +5,25 @@ Audited & minimal JS implementation of elliptic curve cryptography.
- **noble** family, zero dependencies - **noble** family, zero dependencies
- Short Weierstrass, Edwards, Montgomery curves - Short Weierstrass, Edwards, Montgomery curves
- ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement - ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
- #⃣ [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/) - #⃣ [hash to curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
for encoding or hashing an arbitrary string to an elliptic curve point for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash - 🧜‍♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines - 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
- 🔍 Unique tests ensure correctness. Wycheproof vectors included - 🔍 Unique tests ensure correctness with Wycheproof vectors and [cryptofuzz](https://github.com/guidovranken/cryptofuzz) differential fuzzing
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app - 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
Package consists of two parts: Package consists of two parts:
1. [Abstract](#abstract-api), zero-dependency EC algorithms 1. [Abstract](#abstract-api), zero-dependency EC algorithms
2. [Implementations](#implementations), utilizing one dependency `@noble/hashes`, providing ready-to-use: 2. [Implementations](#implementations), utilizing one dependency `@noble/hashes`, providing ready-to-use:
- NIST curves secp192r1/P192, secp224r1/P224, secp256r1/P256, secp384r1/P384, secp521r1/P521 - NIST curves secp256r1/P256, secp384r1/P384, secp521r1/P521
- SECG curve secp256k1 - SECG curve secp256k1
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff - ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 [RFC7748](https://www.rfc-editor.org/rfc/rfc7748) / [RFC8032](https://www.rfc-editor.org/rfc/rfc8032) / [ZIP215](https://zips.z.cash/zip-0215) stuff
- pairing-friendly curves bls12-381, bn254 - pairing-friendly curves bls12-381, bn254
Check out [Upgrading](#upgrading) if you've previously used single-feature noble packages Check out [Upgrading](#upgrading) if you've previously used single-feature noble packages
([secp256k1](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519)). ([secp256k1](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519)).
See [In the wild](#in-the-wild) for real-world software that uses curves. See [Resources](#resources) for articles and real-world software that uses curves.
### This library belongs to _noble_ crypto ### This library belongs to _noble_ crypto
@@ -45,7 +45,7 @@ Use NPM for browser / node.js:
> npm install @noble/curves > npm install @noble/curves
For [Deno](https://deno.land), use it with npm specifier. In browser, you could also include the single file from For [Deno](https://deno.land), use it with [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers). In browser, you could also include the single file from
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases). [GitHub's releases page](https://github.com/paulmillr/noble-curves/releases).
The library is tree-shaking-friendly and does not expose root entry point as `import * from '@noble/curves'`. The library is tree-shaking-friendly and does not expose root entry point as `import * from '@noble/curves'`.
@@ -56,7 +56,7 @@ Instead, you need to import specific primitives. This is done to ensure small si
Each curve can be used in the following way: Each curve can be used in the following way:
```ts ```ts
import { secp256k1 } from '@noble/curves/secp256k1'; // ECMAScript Modules (ESM) and Common.js import { secp256k1 } from '@noble/curves/secp256k1'; // ECMAScript Modules (ESM)
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno // import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
const priv = secp256k1.utils.randomPrivateKey(); const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv); const pub = secp256k1.getPublicKey(priv);
@@ -64,12 +64,8 @@ const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv); const sig = secp256k1.sign(msg, priv);
secp256k1.verify(sig, msg, pub) === true; secp256k1.verify(sig, msg, pub) === true;
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236' const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings
// Follows hash-to-curve specification to encode arbitrary hashes to EC points
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
hashToCurve('0102abcd');
``` ```
All curves: All curves:
@@ -125,7 +121,7 @@ import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
import { x25519 } from '@noble/curves/ed25519'; import { x25519 } from '@noble/curves/ed25519';
const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4'; const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c'; const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv); x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
// hash-to-curve // hash-to-curve
@@ -180,9 +176,9 @@ const signatures3 = privateKeys.map((p, i) => bls.sign(messages[i], p));
const aggSignature3 = bls.aggregateSignatures(signatures3); const aggSignature3 = bls.aggregateSignatures(signatures3);
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys); const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 }); console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// bls.pairing(PointG1, PointG2) // pairings
// Pairings // hash-to-curve examples can be seen below
// bls.pairing(PointG1, PointG2)
``` ```
## Abstract API ## Abstract API
@@ -190,7 +186,7 @@ console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
Abstract API allows to define custom curves. All arithmetics is done with JS bigints over finite fields, Abstract API allows to define custom curves. All arithmetics is done with JS bigints over finite fields,
which is defined from `modular` sub-module. For scalar multiplication, we use [precomputed tables with w-ary non-adjacent form (wNAF)](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/). which is defined from `modular` sub-module. For scalar multiplication, we use [precomputed tables with w-ary non-adjacent form (wNAF)](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/).
Precomputes are enabled for weierstrass and edwards BASE points of a curve. You could precompute any Precomputes are enabled for weierstrass and edwards BASE points of a curve. You could precompute any
other point (e.g. for ECDH) using `utils.precompute()` method. other point (e.g. for ECDH) using `utils.precompute()` method: check out examples.
There are following zero-dependency algorithms: There are following zero-dependency algorithms:
@@ -216,7 +212,7 @@ For this you will need `hmac` & `hash`, which in our implementations is provided
If you're using different hashing library, make sure to wrap it in the following interface: If you're using different hashing library, make sure to wrap it in the following interface:
```ts ```ts
export type CHash = { type CHash = {
(message: Uint8Array): Uint8Array; (message: Uint8Array): Uint8Array;
blockLen: number; blockLen: number;
outputLen: number; outputLen: number;
@@ -235,7 +231,7 @@ export type CHash = {
```ts ```ts
// T is usually bigint, but can be something else like complex numbers in BLS curves // T is usually bigint, but can be something else like complex numbers in BLS curves
export interface ProjPointType<T> extends Group<ProjPointType<T>> { interface ProjPointType<T> extends Group<ProjPointType<T>> {
readonly px: T; readonly px: T;
readonly py: T; readonly py: T;
readonly pz: T; readonly pz: T;
@@ -251,7 +247,7 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
toHex(isCompressed?: boolean): string; toHex(isCompressed?: boolean): string;
} }
// Static methods for 3d XYZ points // Static methods for 3d XYZ points
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> { interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
new (x: T, y: T, z: T): ProjPointType<T>; new (x: T, y: T, z: T): ProjPointType<T>;
fromAffine(p: AffinePoint<T>): ProjPointType<T>; fromAffine(p: AffinePoint<T>): ProjPointType<T>;
fromHex(hex: Hex): ProjPointType<T>; fromHex(hex: Hex): ProjPointType<T>;
@@ -262,7 +258,7 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
**ECDSA signatures** are represented by `Signature` instances and can be described by the interface: **ECDSA signatures** are represented by `Signature` instances and can be described by the interface:
```ts ```ts
export interface SignatureType { interface SignatureType {
readonly r: bigint; readonly r: bigint;
readonly s: bigint; readonly s: bigint;
readonly recovery?: number; readonly recovery?: number;
@@ -274,9 +270,14 @@ export interface SignatureType {
toCompactRawBytes(): Uint8Array; toCompactRawBytes(): Uint8Array;
toCompactHex(): string; toCompactHex(): string;
// DER-encoded // DER-encoded
toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERRawBytes(): Uint8Array;
toDERHex(isCompressed?: boolean): string; toDERHex(): string;
} }
type SignatureConstructor = {
new (r: bigint, s: bigint): SignatureType;
fromCompact(hex: Hex): SignatureType;
fromDER(hex: Hex): SignatureType;
};
``` ```
Example implementing [secq256k1](https://personaelabs.org/posts/spartan-ecdsa) (NOT secp256k1) Example implementing [secq256k1](https://personaelabs.org/posts/spartan-ecdsa) (NOT secp256k1)
@@ -307,24 +308,30 @@ secq256k1.getPublicKey(priv); // Convert private key to public.
const sig = secq256k1.sign(msg, priv); // Sign msg with private key. const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
secq256k1.verify(sig, msg, priv); // Verify if sig is correct. secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
const point = secq256k1.Point.BASE; // Elliptic curve Point class and BASE point static var. const Point = secq256k1.ProjectivePoint;
const point = Point.BASE; // Elliptic curve Point class and BASE point static var.
point.add(point).equals(point.double()); // add(), equals(), double() methods point.add(point).equals(point.double()); // add(), equals(), double() methods
point.subtract(point).equals(secq256k1.Point.ZERO); // subtract() method, ZERO static var point.subtract(point).equals(Point.ZERO); // subtract() method, ZERO static var
point.negate(); // Flips point over x/y coordinate. point.negate(); // Flips point over x/y coordinate.
point.multiply(31415n); // Multiplication of Point by scalar. point.multiply(31415n); // Multiplication of Point by scalar.
point.assertValidity(); // Checks for being on-curve point.assertValidity(); // Checks for being on-curve
point.toAffine(); // Converts to 2d affine xy coordinates point.toAffine(); // Converts to 2d affine xy coordinates
secq256k1.CURVE.n; secq256k1.CURVE.n;
secq256k1.CURVE.Fp.mod(); secq256k1.CURVE.Fp.mod();
secq256k1.CURVE.hash(); secq256k1.CURVE.hash();
// precomputes
const fast = secq256k1.utils.precompute(8, Point.fromHex(someonesPubKey));
fast.multiply(privKey); // much faster ECDH now
``` ```
`weierstrass()` returns `CurveFn`: `weierstrass()` returns `CurveFn`:
```ts ```ts
export type CurveFn = { type SignOpts = { lowS?: boolean; prehash?: boolean; extraEntropy: boolean | Uint8Array };
type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
@@ -338,8 +345,10 @@ export type CurveFn = {
ProjectivePoint: ProjectivePointConstructor; ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
isValidPrivateKey(privateKey: PrivKey): boolean; normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(key: PrivKey): boolean;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
}; };
}; };
``` ```
@@ -362,7 +371,7 @@ For EdDSA signatures, `hash` param required. `adjustScalarBytes` which instructs
7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions 7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions
```ts ```ts
export interface ExtPointType extends Group<ExtPointType> { interface ExtPointType extends Group<ExtPointType> {
readonly ex: bigint; readonly ex: bigint;
readonly ey: bigint; readonly ey: bigint;
readonly ez: bigint; readonly ez: bigint;
@@ -376,7 +385,7 @@ export interface ExtPointType extends Group<ExtPointType> {
toAffine(iz?: bigint): AffinePoint<bigint>; toAffine(iz?: bigint): AffinePoint<bigint>;
} }
// Static methods of Extended Point with coordinates in X, Y, Z, T // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtPointConstructor extends GroupConstructor<ExtPointType> { interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: AffinePoint<bigint>): ExtPointType; fromAffine(p: AffinePoint<bigint>): ExtPointType;
fromHex(hex: Hex): ExtPointType; fromHex(hex: Hex): ExtPointType;
@@ -388,13 +397,14 @@ Example implementing edwards25519:
```ts ```ts
import { twistedEdwards } from '@noble/curves/abstract/edwards'; import { twistedEdwards } from '@noble/curves/abstract/edwards';
import { div } from '@noble/curves/abstract/modular'; import { Field, div } from '@noble/curves/abstract/modular';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({ const ed25519 = twistedEdwards({
a: -1n, a: -1n,
d: div(-121665n, 121666n, 2n ** 255n - 19n), // -121665n/121666n d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
P: 2n ** 255n - 19n, Fp,
n: 2n ** 252n + 27742317777372353535851937790883648493n, n: 2n ** 252n + 27742317777372353535851937790883648493n,
h: 8n, h: 8n,
Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n, Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
@@ -414,13 +424,12 @@ const ed25519 = twistedEdwards({
`twistedEdwards()` returns `CurveFn` of following type: `twistedEdwards()` returns `CurveFn` of following type:
```ts ```ts
export type CurveFn = { type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex, context?: Hex) => Uint8Array;
verify: (sig: SigType, message: Hex, publicKey: PubKey, context?: Hex) => boolean; verify: (sig: SigType, message: Hex, publicKey: Hex, context?: Hex) => boolean;
ExtendedPoint: ExtendedPointConstructor; ExtendedPoint: ExtPointConstructor;
Signature: SignatureConstructor;
utils: { utils: {
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => { getExtendedPublicKey: (key: PrivKey) => {
@@ -438,22 +447,18 @@ export type CurveFn = {
The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet. The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet.
You must specify curve field, `a24` special variable, `montgomeryBits`, `nByteLength`, and coordinate `u` of generator point. You must specify curve params `Fp`, `a`, `Gu` coordinate of u, `montgomeryBits` and `nByteLength`.
```typescript ```typescript
import { montgomery } from '@noble/curves/abstract/montgomery'; import { montgomery } from '@noble/curves/abstract/montgomery';
const x25519 = montgomery({ const x25519 = montgomery({
P: 2n ** 255n - 19n, Fp: Field(2n ** 255n - 19n),
a24: 121665n, // TODO: change to a a: 486662n,
Gu: 9n,
montgomeryBits: 255, montgomeryBits: 255,
nByteLength: 32, nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000', // Optional param
// Optional params
powPminus2: (x: bigint): bigint => {
return mod.pow(x, P - 2, P);
},
adjustScalarBytes(bytes) { adjustScalarBytes(bytes) {
bytes[0] &= 248; bytes[0] &= 248;
bytes[31] &= 127; bytes[31] &= 127;
@@ -465,40 +470,58 @@ const x25519 = montgomery({
### abstract/hash-to-curve: Hashing strings to curve points ### abstract/hash-to-curve: Hashing strings to 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). The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v16](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16).
Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security:
```ts
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
import { randomBytes } from '@noble/hashes/utils';
hashToCurve('0102abcd');
console.log(hashToCurve(randomBytes()));
console.log(encodeToCurve(randomBytes()));
import { bls12_381 } from '@noble/curves/bls12-381';
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
```
If you need low-level methods from spec:
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits. `expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
```ts ```ts
function expand_message_xmd(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash): Uint8Array; function expand_message_xmd(
function expand_message_xof(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash): Uint8Array; msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: CHash
): Uint8Array;
function expand_message_xof(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
k: number,
H: CHash
): Uint8Array;
``` ```
`hash_to_field(msg, count, options)` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3) `hash_to_field(msg, count, options)` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F. 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 - `msg` a byte string containing the message to hash
_ `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}` - `count` the number of elements of F to output
_ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements. - `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`.
- `p` is field prime, m=field extension (1 for prime fields)
- `k` is security target in bits (e.g. 128).
- `expand` should be `xmd` for SHA2, SHA3, BLAKE; `xof` for SHAKE, BLAKE-XOF
- `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
- Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
```ts ```ts
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][]; function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
type htfOpts = {
DST: string; // a domain separation tag defined in section 2.2.5
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
p: bigint;
// m: the extension degree of F, m >= 1
// where F is a finite field of characteristic p and order q = p^m
m: number;
k: number; // the target security level for the suite in bits defined in section 5.1
expand?: 'xmd' | 'xof'; // option to use a message that has already been processed by expand_message_xmd
// Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash;
};
``` ```
### abstract/poseidon: Poseidon hash ### abstract/poseidon: Poseidon hash
@@ -532,10 +555,6 @@ and others with it.
### abstract/modular: Modular arithmetics utilities ### abstract/modular: Modular arithmetics utilities
The module also contains useful `hashToPrivateScalar` method which allows to create
scalars (e.g. private keys) with the modulo bias being neglible. It follows
FIPS 186 B.4.1. Requires at least 40 bytes of input for 32-byte private key.
```ts ```ts
import * as mod from '@noble/curves/abstract/modular'; import * as mod from '@noble/curves/abstract/modular';
const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19 const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19
@@ -548,7 +567,29 @@ fp.sqrt(21n); // square root
mod.mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10 mod.mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10
mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
mod.hashToPrivateScalar(sha512_of_something, secp256r1.n); ```
#### Creating private keys from hashes
Suppose you have `sha256(something)` (e.g. from HMAC) and you want to make a private key from it.
Even though p256 or secp256k1 may have 32-byte private keys,
and sha256 output is also 32-byte, you can't just use it and reduce it modulo `CURVE.n`.
Doing so will make the result key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/).
To avoid the bias, we implement FIPS 186 B.4.1, which allows to take arbitrary
byte array and produce valid scalars / private keys with bias being neglible.
Use [hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points) if you need
hashing to **public keys**; the function in the module instead operates on **private keys**.
```ts
import { p256 } from '@noble/curves/p256';
import { sha256 } from '@noble/hashes/sha256';
import { hkdf } from '@noble/hashes/hkdf';
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
``` ```
### abstract/utils: General utilities ### abstract/utils: General utilities
@@ -561,8 +602,8 @@ utils.hexToBytes('deadbeef');
utils.hexToNumber(); utils.hexToNumber();
utils.bytesToNumberBE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef])); utils.bytesToNumberBE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.bytesToNumberLE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef])); utils.bytesToNumberLE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.numberToBytesBE(123n); utils.numberToBytesBE(123n, 32);
utils.numberToBytesLE(123n); utils.numberToBytesLE(123n, 64);
utils.numberToHexUnpadded(123n); utils.numberToHexUnpadded(123n);
utils.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef])); utils.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef]));
utils.nLength(255n); utils.nLength(255n);
@@ -571,7 +612,7 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
## Security ## Security
The library had no prior security audit. The library had no prior security audit. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz): you can run the fuzzer by yourself to check it.
[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. [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.
@@ -635,6 +676,14 @@ pedersen x 884 ops/sec @ 1ms/op
poseidon x 8,598 ops/sec @ 116μs/op poseidon x 8,598 ops/sec @ 116μs/op
verify x 528 ops/sec @ 1ms/op verify x 528 ops/sec @ 1ms/op
ecdh
├─x25519 x 1,337 ops/sec @ 747μs/op
├─secp256k1 x 461 ops/sec @ 2ms/op
├─P256 x 441 ops/sec @ 2ms/op
├─P384 x 179 ops/sec @ 5ms/op
├─P521 x 93 ops/sec @ 10ms/op
└─x448 x 496 ops/sec @ 2ms/op
bls12-381 bls12-381
init x 32 ops/sec @ 30ms/op init x 32 ops/sec @ 30ms/op
getPublicKey 1-bit x 858 ops/sec @ 1ms/op getPublicKey 1-bit x 858 ops/sec @ 1ms/op
@@ -648,17 +697,32 @@ aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
aggregateSignatures/8 x 45 ops/sec @ 22ms/op aggregateSignatures/8 x 45 ops/sec @ 22ms/op
aggregateSignatures/32 x 11 ops/sec @ 84ms/op aggregateSignatures/32 x 11 ops/sec @ 84ms/op
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
hash-to-curve
hash_to_field x 850,340 ops/sec @ 1μs/op
hashToCurve
├─secp256k1 x 1,850 ops/sec @ 540μs/op
├─P256 x 3,352 ops/sec @ 298μs/op
├─P384 x 1,367 ops/sec @ 731μs/op
├─P521 x 691 ops/sec @ 1ms/op
├─ed25519 x 2,492 ops/sec @ 401μs/op
└─ed448 x 1,045 ops/sec @ 956μs/op
``` ```
## In the wild ## Resources
Elliptic curve calculator: [paulmillr.com/ecc](https://paulmillr.com/ecc). Article about some of library's features: [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
Demo: Elliptic curve calculator [paulmillr.com/ecc](https://paulmillr.com/ecc).
Projects using the library:
- secp256k1 - secp256k1
- [btc-signer](https://github.com/paulmillr/micro-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer) - [btc-signer](https://github.com/paulmillr/scure-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer)
- ed25519 - ed25519
- [sol-signer](https://github.com/paulmillr/micro-sol-signer) - [sol-signer](https://github.com/paulmillr/micro-sol-signer)
- BLS12-381 - BLS12-381
- Check out `bls12-381.ts` for articles about the curve
- Threshold sigs demo [genthresh.com](https://genthresh.com) - Threshold sigs demo [genthresh.com](https://genthresh.com)
- BBS signatures [github.com/Wind4Greg/BBS-Draft-Checks](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html) - BBS signatures [github.com/Wind4Greg/BBS-Draft-Checks](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)

View File

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

View File

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

View File

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

19
benchmark/ecdh.js Normal file
View File

@@ -0,0 +1,19 @@
import { run, mark, compare, utils } from 'micro-bmark';
import { generateData } from './_shared.js';
import { secp256k1 } from '../secp256k1.js';
import { P256 } from '../p256.js';
import { P384 } from '../p384.js';
import { P521 } from '../p521.js';
import { x25519 } from '../ed25519.js';
import { x448 } from '../ed448.js';
run(async () => {
const curves = { x25519, secp256k1, P256, P384, P521, x448 };
const fns = {};
for (let [k, c] of Object.entries(curves)) {
const pubB = c.getPublicKey(c.utils.randomPrivateKey());
const privA = c.utils.randomPrivateKey();
fns[k] = () => c.getSharedSecret(privA, pubB);
}
await compare('ecdh', 1000, fns);
});

View File

@@ -0,0 +1,29 @@
import { run, mark, utils } from 'micro-bmark';
import { hash_to_field } from '../abstract/hash-to-curve.js';
import { hashToPrivateScalar } from '../abstract/modular.js';
import { randomBytes } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256';
// import { generateData } from './_shared.js';
import { hashToCurve as secp256k1 } from '../secp256k1.js';
import { hashToCurve as P256 } from '../p256.js';
import { hashToCurve as P384 } from '../p384.js';
import { hashToCurve as P521 } from '../p521.js';
import { hashToCurve as ed25519 } from '../ed25519.js';
import { hashToCurve as ed448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
run(async () => {
const rand = randomBytes(40);
await mark('hashToPrivateScalar', 1000000, () => hashToPrivateScalar(rand, N));
// - p, the characteristic of F
// - m, the extension degree of F, m >= 1
// - L = ceil((ceil(log2(p)) + k) / 8), where k is the security of suite (e.g. 128)
await mark('hash_to_field', 1000000, () =>
hash_to_field(rand, 1, { DST: 'secp256k1', hash: sha256, p: N, m: 1, k: 128 })
);
const msg = utf8ToBytes('message');
for (let [title, fn] of Object.entries({ secp256k1, P256, P384, P521, ed25519, ed448 })) {
await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
}
});

13
benchmark/modular.js Normal file
View File

@@ -0,0 +1,13 @@
import { run, mark } from 'micro-bmark';
import { secp256k1 } from '../secp256k1.js';
import { Fp } from '../abstract/modular.js';
run(async () => {
console.log(`\x1b[36mmodular, secp256k1 field\x1b[0m`);
const { Fp: secpFp } = secp256k1.CURVE;
await mark('invert a', 300000, () => secpFp.inv(2n ** 232n - 5910n));
await mark('invert b', 300000, () => secpFp.inv(2n ** 231n - 5910n));
await mark('sqrt p = 3 mod 4', 15000, () => secpFp.sqrt(2n ** 231n - 5910n));
const FpStark = Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001'));
await mark('sqrt tonneli-shanks', 500, () => FpStark.sqrt(2n ** 231n - 5909n))
});

View File

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

View File

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

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "0.8.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "0.8.0",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",

View File

@@ -1,10 +1,9 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "0.8.0",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"abstract", "abstract",
"esm",
"src", "src",
"*.js", "*.js",
"*.js.map", "*.js.map",
@@ -12,8 +11,9 @@
"*.d.ts.map" "*.d.ts.map"
], ],
"scripts": { "scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js", "bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node stark.js; node bls.js",
"build": "tsc && tsc -p tsconfig.esm.json", "build": "tsc",
"build:clean": "rm *.{js,d.ts,js.map} esm/*.{js,js.map} 2> /dev/null",
"build:release": "rollup -c rollup.config.js", "build:release": "rollup -c rollup.config.js",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'", "lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'", "format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
@@ -40,130 +40,98 @@
"typescript": "4.7.3" "typescript": "4.7.3"
}, },
"main": "index.js", "main": "index.js",
"type": "module",
"exports": { "exports": {
".": { ".": {
"types": "./index.d.ts", "types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js" "default": "./index.js"
}, },
"./abstract/edwards": { "./abstract/edwards": {
"types": "./abstract/edwards.d.ts", "types": "./abstract/edwards.d.ts",
"import": "./esm/abstract/edwards.js",
"default": "./abstract/edwards.js" "default": "./abstract/edwards.js"
}, },
"./abstract/modular": { "./abstract/modular": {
"types": "./abstract/modular.d.ts", "types": "./abstract/modular.d.ts",
"import": "./esm/abstract/modular.js",
"default": "./abstract/modular.js" "default": "./abstract/modular.js"
}, },
"./abstract/montgomery": { "./abstract/montgomery": {
"types": "./abstract/montgomery.d.ts", "types": "./abstract/montgomery.d.ts",
"import": "./esm/abstract/montgomery.js",
"default": "./abstract/montgomery.js" "default": "./abstract/montgomery.js"
}, },
"./abstract/weierstrass": { "./abstract/weierstrass": {
"types": "./abstract/weierstrass.d.ts", "types": "./abstract/weierstrass.d.ts",
"import": "./esm/abstract/weierstrass.js",
"default": "./abstract/weierstrass.js" "default": "./abstract/weierstrass.js"
}, },
"./abstract/bls": { "./abstract/bls": {
"types": "./abstract/bls.d.ts", "types": "./abstract/bls.d.ts",
"import": "./esm/abstract/bls.js",
"default": "./abstract/bls.js" "default": "./abstract/bls.js"
}, },
"./abstract/hash-to-curve": { "./abstract/hash-to-curve": {
"types": "./abstract/hash-to-curve.d.ts", "types": "./abstract/hash-to-curve.d.ts",
"import": "./esm/abstract/hash-to-curve.js",
"default": "./abstract/hash-to-curve.js" "default": "./abstract/hash-to-curve.js"
}, },
"./abstract/curve": { "./abstract/curve": {
"types": "./abstract/curve.d.ts", "types": "./abstract/curve.d.ts",
"import": "./esm/abstract/curve.js",
"default": "./abstract/curve.js" "default": "./abstract/curve.js"
}, },
"./abstract/utils": { "./abstract/utils": {
"types": "./abstract/utils.d.ts", "types": "./abstract/utils.d.ts",
"import": "./esm/abstract/utils.js",
"default": "./abstract/utils.js" "default": "./abstract/utils.js"
}, },
"./abstract/poseidon": { "./abstract/poseidon": {
"types": "./abstract/poseidon.d.ts", "types": "./abstract/poseidon.d.ts",
"import": "./esm/abstract/poseidon.js",
"default": "./abstract/poseidon.js" "default": "./abstract/poseidon.js"
}, },
"./_shortw_utils": { "./_shortw_utils": {
"types": "./_shortw_utils.d.ts", "types": "./_shortw_utils.d.ts",
"import": "./esm/_shortw_utils.js",
"default": "./_shortw_utils.js" "default": "./_shortw_utils.js"
}, },
"./bls12-381": { "./bls12-381": {
"types": "./bls12-381.d.ts", "types": "./bls12-381.d.ts",
"import": "./esm/bls12-381.js",
"default": "./bls12-381.js" "default": "./bls12-381.js"
}, },
"./bn": { "./bn": {
"types": "./bn.d.ts", "types": "./bn.d.ts",
"import": "./esm/bn.js",
"default": "./bn.js" "default": "./bn.js"
}, },
"./ed25519": { "./ed25519": {
"types": "./ed25519.d.ts", "types": "./ed25519.d.ts",
"import": "./esm/ed25519.js",
"default": "./ed25519.js" "default": "./ed25519.js"
}, },
"./ed448": { "./ed448": {
"types": "./ed448.d.ts", "types": "./ed448.d.ts",
"import": "./esm/ed448.js",
"default": "./ed448.js" "default": "./ed448.js"
}, },
"./index": { "./index": {
"types": "./index.d.ts", "types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js" "default": "./index.js"
}, },
"./jubjub": { "./jubjub": {
"types": "./jubjub.d.ts", "types": "./jubjub.d.ts",
"import": "./esm/jubjub.js",
"default": "./jubjub.js" "default": "./jubjub.js"
}, },
"./p192": {
"types": "./p192.d.ts",
"import": "./esm/p192.js",
"default": "./p192.js"
},
"./p224": {
"types": "./p224.d.ts",
"import": "./esm/p224.js",
"default": "./p224.js"
},
"./p256": { "./p256": {
"types": "./p256.d.ts", "types": "./p256.d.ts",
"import": "./esm/p256.js",
"default": "./p256.js" "default": "./p256.js"
}, },
"./p384": { "./p384": {
"types": "./p384.d.ts", "types": "./p384.d.ts",
"import": "./esm/p384.js",
"default": "./p384.js" "default": "./p384.js"
}, },
"./p521": { "./p521": {
"types": "./p521.d.ts", "types": "./p521.d.ts",
"import": "./esm/p521.js",
"default": "./p521.js" "default": "./p521.js"
}, },
"./pasta": { "./pasta": {
"types": "./pasta.d.ts", "types": "./pasta.d.ts",
"import": "./esm/pasta.js",
"default": "./pasta.js" "default": "./pasta.js"
}, },
"./secp256k1": { "./secp256k1": {
"types": "./secp256k1.d.ts", "types": "./secp256k1.d.ts",
"import": "./esm/secp256k1.js",
"default": "./secp256k1.js" "default": "./secp256k1.js"
}, },
"./stark": { "./stark": {
"types": "./stark.d.ts", "types": "./stark.d.ts",
"import": "./esm/stark.js",
"default": "./stark.js" "default": "./stark.js"
} }
}, },

View File

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

View File

@@ -1,33 +1,36 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import type { Group, GroupConstructor, AffinePoint } from './curve.js'; import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import { mod, Field } from './modular.js'; import { mod, Field } from './modular.js';
import { CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js'; import { bytesToNumberBE, CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
/**
* * `DST` is a domain separation tag, defined in section 2.2.5
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
* * `m` is extension degree (1 for prime fields)
* * `k` is the target security target in bits (e.g. 128), from section 5.1
* * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
* * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
*/
type UnicodeOrBytes = string | Uint8Array;
export type Opts = { export type Opts = {
DST: string; // DST: a domain separation tag, defined in section 2.2.5 DST: UnicodeOrBytes;
encodeDST: string; p: bigint;
p: bigint; // characteristic of F, where F is a finite field of characteristic p and order q = p^m m: number;
m: number; // extension degree of F, m >= 1 k: number;
k: number; // k: the target security level for the suite in bits, defined in section 5.1 expand?: 'xmd' | 'xof';
expand?: 'xmd' | 'xof'; // use a message that has already been processed by expand_message_xmd
// Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash; hash: CHash;
}; };
// Octet Stream to Integer (bytesToNumberBE) function validateDST(dst: UnicodeOrBytes): Uint8Array {
function os2ip(bytes: Uint8Array): bigint { if (dst instanceof Uint8Array) return dst;
let result = 0n; if (typeof dst === 'string') return utf8ToBytes(dst);
for (let i = 0; i < bytes.length; i++) { throw new Error('DST must be Uint8Array or string');
result <<= 8n;
result += BigInt(bytes[i]);
}
return result;
} }
// Integer to Octet Stream // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
const os2ip = bytesToNumberBE;
// Integer to Octet Stream (numberToBytesBE)
function i2osp(value: number, length: number): Uint8Array { function i2osp(value: number, length: number): Uint8Array {
if (value < 0 || value >= 1 << (8 * length)) { if (value < 0 || value >= 1 << (8 * length)) {
throw new Error(`bad I2OSP call: value=${value} length=${length}`); throw new Error(`bad I2OSP call: value=${value} length=${length}`);
@@ -68,13 +71,12 @@ export function expand_message_xmd(
isNum(lenInBytes); isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST)); if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen; const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes); const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length'); if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = concatBytes(DST, i2osp(DST.length, 1)); const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
const Z_pad = i2osp(0, r_in_bytes); const Z_pad = i2osp(0, r_in_bytes);
const l_i_b_str = i2osp(lenInBytes, 2); const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
const b = new Array<Uint8Array>(ell); const b = new Array<Uint8Array>(ell);
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime)); const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime)); b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
@@ -120,30 +122,40 @@ export function expand_message_xof(
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3 * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
* @param msg a byte string containing the message to hash * @param msg a byte string containing the message to hash
* @param count the number of elements of F to output * @param count the number of elements of F to output
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}` * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
* @returns [u_0, ..., u_(count - 1)], a list of field elements. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/ */
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] { export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
validateObject(options, {
DST: 'string',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
hash: 'hash',
});
const { p, k, m, hash, expand, DST: _DST } = options; const { p, k, m, hash, expand, DST: _DST } = options;
isBytes(msg); isBytes(msg);
isNum(count); isNum(count);
if (typeof _DST !== 'string') throw new Error('DST must be valid'); const DST = validateDST(_DST);
const log2p = p.toString(2).length; const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L; const len_in_bytes = count * m * L;
const DST = utf8ToBytes(_DST); let prb; // pseudo_random_bytes
const pseudo_random_bytes = if (expand === 'xmd') {
expand === 'xmd' prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
? expand_message_xmd(msg, DST, len_in_bytes, hash) } else if (expand === 'xof') {
: expand === 'xof' prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
? expand_message_xof(msg, DST, len_in_bytes, k, hash) } else if (expand === undefined) {
: msg; prb = msg;
} else {
throw new Error('expand must be "xmd", "xof" or undefined');
}
const u = new Array(count); const u = new Array(count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const e = new Array(m); const e = new Array(m);
for (let j = 0; j < m; j++) { for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * m); const elm_offset = L * (j + i * m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); const tv = prb.subarray(elm_offset, elm_offset + L);
e[j] = mod(os2ip(tv), p); e[j] = mod(os2ip(tv), p);
} }
u[i] = e; u[i] = e;
@@ -179,27 +191,17 @@ export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
// Separated from initialization opts, so users won't accidentally change per-curve parameters // Separated from initialization opts, so users won't accidentally change per-curve parameters
// (changing DST is ok!) // (changing DST is ok!)
export type htfBasicOpts = { DST: string }; export type htfBasicOpts = { DST: UnicodeOrBytes };
export function createHasher<T>( export function createHasher<T>(
Point: H2CPointConstructor<T>, Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>, mapToCurve: MapToCurve<T>,
def: Opts def: Opts & { encodeDST?: UnicodeOrBytes }
) { ) {
validateObject(def, { if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
DST: 'string',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
hash: 'hash',
});
if (def.expand !== 'xmd' && def.expand !== 'xof' && def.expand !== undefined)
throw new Error('Invalid htf/expand');
if (typeof mapToCurve !== 'function')
throw new Error('hashToCurve: mapToCurve() has not been defined');
return { return {
// Encodes byte string to elliptic curve // Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) { hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts); const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const u0 = Point.fromAffine(mapToCurve(u[0])); const u0 = Point.fromAffine(mapToCurve(u[0]));

View File

@@ -56,6 +56,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
} }
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/ // Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
let a = mod(number, modulo); let a = mod(number, modulo);
let b = modulo; let b = modulo;
// prettier-ignore // prettier-ignore

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ import {
ensureBytes, ensureBytes,
} from './abstract/utils.js'; } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
import { AffinePoint } from './abstract/curve.js';
/** /**
* ed25519 Twisted Edwards curve with following addons: * ed25519 Twisted Edwards curve with following addons:
@@ -138,10 +139,10 @@ export const ed25519ph = twistedEdwards({
export const x25519 = montgomery({ export const x25519 = montgomery({
P: ED25519_P, P: ED25519_P,
a24: BigInt('121665'), a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits montgomeryBits: 255, // n is 253 bits
nByteLength: 32, nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(9),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ED25519_P; const P = ED25519_P;
// x^(p-2) aka x^(2^255-21) // x^(p-2) aka x^(2^255-21)
@@ -149,6 +150,7 @@ export const x25519 = montgomery({
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P); return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
}, },
adjustScalarBytes, adjustScalarBytes,
randomBytes,
}); });
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator) // Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
@@ -308,6 +310,11 @@ export class RistrettoPoint {
// Private property to discourage combining ExtendedPoint + RistrettoPoint // Private property to discourage combining ExtendedPoint + RistrettoPoint
// Always use Ristretto encoding/decoding instead. // Always use Ristretto encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {} constructor(private readonly ep: ExtendedPoint) {}
static fromAffine(ap: AffinePoint<bigint>) {
return new RistrettoPoint(ed25519.ExtendedPoint.fromAffine(ap));
}
/** /**
* Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`. * Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`.
* The hash-to-group operation applies Elligator twice and adds the results. * The hash-to-group operation applies Elligator twice and adds the results.
@@ -400,7 +407,7 @@ export class RistrettoPoint {
equals(other: RistrettoPoint): boolean { equals(other: RistrettoPoint): boolean {
assertRstPoint(other); assertRstPoint(other);
const { ex: X1, ey: Y1 } = this.ep; const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = this.ep; const { ex: X2, ey: Y2 } = other.ep;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
const one = mod(X1 * Y2) === mod(Y1 * X2); const one = mod(X1 * Y2) === mod(Y1 * X2);
@@ -426,3 +433,13 @@ export class RistrettoPoint {
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar)); return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
} }
} }
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
// Appendix B. Hashing to ristretto255
export const hash_to_ristretto255 = (msg: Uint8Array, options: htf.htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = htf.expand_message_xmd(msg, DST, 64, sha512);
const P = RistrettoPoint.hashToCurve(uniform_bytes);
return P;
};

View File

@@ -122,11 +122,11 @@ export const ed448 = twistedEdwards(ED448_DEF);
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 }); export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
export const x448 = montgomery({ export const x448 = montgomery({
a24: BigInt(39081), a: BigInt(156326),
montgomeryBits: 448, montgomeryBits: 448,
nByteLength: 57, nByteLength: 57,
P: ed448P, P: ed448P,
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(5),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ed448P; const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x); const Pminus3div4 = ed448_pow_Pminus3div4(x);
@@ -134,6 +134,7 @@ export const x448 = montgomery({
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2 return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
}, },
adjustScalarBytes, adjustScalarBytes,
randomBytes,
// The 4-isogeny maps between the Montgomery curve and this Edwards // The 4-isogeny maps between the Montgomery curve and this Edwards
// curve are: // curve are:
// (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3) // (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)

View File

@@ -1,25 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256';
import { Fp } from './abstract/modular.js';
// NIST secp192r1 aka P192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
export const P192 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
Fp: Fp(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
// Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
// Base point (x, y) aka generator point
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
h: BigInt(1),
lowS: false,
} as const,
sha256
);
export const secp192r1 = P192;

View File

@@ -1,25 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha224 } from '@noble/hashes/sha256';
import { Fp } from './abstract/modular.js';
// NIST secp224r1 aka P224
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-224
export const P224 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations;
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
// Base point (x, y) aka generator point
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
h: BigInt(1),
lowS: false,
} as const,
sha224
);
export const secp224r1 = P224;

View File

@@ -107,6 +107,7 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1); const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const numTo32b = (n: bigint) => numberToBytesBE(n, 32); const numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modP = (x: bigint) => mod(x, secp256k1P); const modP = (x: bigint) => mod(x, secp256k1P);
@@ -114,12 +115,18 @@ const modN = (x: bigint) => mod(x, secp256k1N);
const Point = secp256k1.ProjectivePoint; const Point = secp256k1.ProjectivePoint;
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) => const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
Point.BASE.multiplyAndAddUnsafe(Q, a, b); Point.BASE.multiplyAndAddUnsafe(Q, a, b);
// Calculate point, scalar and bytes
function schnorrGetExtPubKey(priv: PrivKey) { function schnorrGetExtPubKey(priv: PrivKey) {
const d = secp256k1.utils.normPrivateKeyToScalar(priv); let 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 let p = Point.fromPrivateKey(d_); // P = d'⋅G; 0 < d' < n check is done inside
const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d' const scalar = p.hasEvenY() ? d_ : modN(-d_);
return { point, scalar, bytes: pointToBytes(point) }; return { scalar: scalar, bytes: pointToBytes(p) };
} }
/**
* lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.
* @returns valid point checked for being on-curve
*/
function lift_x(x: bigint): PointType<bigint> { function lift_x(x: bigint): PointType<bigint> {
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const xx = modP(x * x); const xx = modP(x * x);
@@ -130,6 +137,9 @@ function lift_x(x: bigint): PointType<bigint> {
p.assertValidity(); p.assertValidity();
return p; return p;
} }
/**
* Create tagged hash, convert it to bigint, reduce modulo-n.
*/
function challenge(...args: Uint8Array[]): bigint { function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args))); return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
} }
@@ -157,10 +167,10 @@ function schnorrSign(
const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m) 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 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. 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 { 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. const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n). const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
sig.set(numTo32b(R.px), 0); sig.set(rx, 0);
sig.set(numTo32b(modN(k + e * d)), 32); sig.set(numTo32b(modN(k + e * d)), 32);
// If Verify(bytes(P), m, sig) (see below) returns failure, abort // If Verify(bytes(P), m, sig) (see below) returns failure, abort
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced'); if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
@@ -169,6 +179,7 @@ function schnorrSign(
/** /**
* Verifies Schnorr signature. * Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const sig = ensureBytes('signature', signature, 64); const sig = ensureBytes('signature', signature, 64);
@@ -195,7 +206,6 @@ export const schnorr = {
verify: schnorrVerify, verify: schnorrVerify,
utils: { utils: {
randomPrivateKey: secp256k1.utils.randomPrivateKey, randomPrivateKey: secp256k1.utils.randomPrivateKey,
getExtendedPublicKey: schnorrGetExtPubKey,
lift_x, lift_x,
pointToBytes, pointToBytes,
numberToBytesBE, numberToBytesBE,

View File

@@ -1,164 +1,117 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { keccak_256 } from '@noble/hashes/sha3'; import { keccak_256 } from '@noble/hashes/sha3';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass, ProjPointType } from './abstract/weierstrass.js';
import * as cutils from './abstract/utils.js';
import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { getHash } from './_shortw_utils.js';
import * as poseidon from './abstract/poseidon.js';
import { utf8ToBytes } from '@noble/hashes/utils'; import { utf8ToBytes } from '@noble/hashes/utils';
import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { poseidon } from './abstract/poseidon.js';
import { weierstrass, ProjPointType, SignatureType } from './abstract/weierstrass.js';
import * as u from './abstract/utils.js';
import type { Hex } from './abstract/utils.js';
import { getHash } from './_shortw_utils.js';
type ProjectivePoint = ProjPointType<bigint>;
// Stark-friendly elliptic curve // Stark-friendly elliptic curve
// https://docs.starkware.co/starkex/stark-curve.html // https://docs.starkware.co/starkex/stark-curve.html
const CURVE_N = BigInt( type ProjectivePoint = ProjPointType<bigint>;
const CURVE_ORDER = BigInt(
'3618502788666131213697322783095070105526743751716087489154079457884512865583' '3618502788666131213697322783095070105526743751716087489154079457884512865583'
); );
const nBitLength = 252; const nBitLength = 252;
// Copy-pasted from weierstrass.ts
function bits2int(bytes: Uint8Array): bigint { function bits2int(bytes: Uint8Array): bigint {
while (bytes[0] === 0) bytes = bytes.subarray(1); // strip leading 0s
// Copy-pasted from weierstrass.ts
const delta = bytes.length * 8 - nBitLength; const delta = bytes.length * 8 - nBitLength;
const num = cutils.bytesToNumberBE(bytes); const num = u.bytesToNumberBE(bytes);
return delta > 0 ? num >> BigInt(delta) : num; return delta > 0 ? num >> BigInt(delta) : num;
} }
function bits2int_modN(bytes: Uint8Array): bigint { function hex0xToBytes(hex: string): Uint8Array {
return mod(bits2int(bytes), CURVE_N); if (typeof hex === 'string') {
hex = strip0x(hex); // allow 0x prefix
if (hex.length & 1) hex = '0' + hex; // allow unpadded hex
}
return u.hexToBytes(hex);
} }
export const starkCurve = weierstrass({ const curve = weierstrass({
// Params: a, b a: BigInt(1), // Params: a, b
a: BigInt(1),
b: BigInt('3141592653589793238462643383279502884197169399375105820974944592307816406665'), b: BigInt('3141592653589793238462643383279502884197169399375105820974944592307816406665'),
// Field over which we'll do calculations; 2n**251n + 17n * 2n**192n + 1n // Field over which we'll do calculations; 2n**251n + 17n * 2n**192n + 1n
// There is no efficient sqrt for field (P%4==1) // There is no efficient sqrt for field (P%4==1)
Fp: Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001')), Fp: Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001')),
// Curve order, total count of valid points in the field. n: CURVE_ORDER, // Curve order, total count of valid points in the field.
n: CURVE_N, nBitLength, // len(bin(N).replace('0b',''))
nBitLength: nBitLength, // len(bin(N).replace('0b',''))
// Base point (x, y) aka generator point // Base point (x, y) aka generator point
Gx: BigInt('874739451078007766457464989774322083649278607533249481151382481072868806602'), Gx: BigInt('874739451078007766457464989774322083649278607533249481151382481072868806602'),
Gy: BigInt('152666792071518830868575557812948353041420400780739481342941381225525861407'), Gy: BigInt('152666792071518830868575557812948353041420400780739481342941381225525861407'),
h: BigInt(1), h: BigInt(1), // cofactor
// Default options lowS: false, // Allow high-s signatures
lowS: false,
...getHash(sha256), ...getHash(sha256),
// Custom truncation routines for stark curve // Custom truncation routines for stark curve
bits2int: (bytes: Uint8Array): bigint => { bits2int,
while (bytes[0] === 0) bytes = bytes.subarray(1);
return bits2int(bytes);
},
bits2int_modN: (bytes: Uint8Array): bigint => { bits2int_modN: (bytes: Uint8Array): bigint => {
let hashS = cutils.bytesToNumberBE(bytes).toString(16); // 2102820b232636d200cb21f1d330f20d096cae09d1bf3edb1cc333ddee11318 =>
if (hashS.length === 63) { // 2102820b232636d200cb21f1d330f20d096cae09d1bf3edb1cc333ddee113180
hashS += '0'; const hex = u.bytesToNumberBE(bytes).toString(16); // toHex unpadded
bytes = hexToBytes0x(hashS); if (hex.length === 63) bytes = hex0xToBytes(hex + '0'); // append trailing 0
} return mod(bits2int(bytes), CURVE_ORDER);
// Truncate zero bytes on left (compat with elliptic)
while (bytes[0] === 0) bytes = bytes.subarray(1);
return bits2int_modN(bytes);
}, },
}); });
export const _starkCurve = curve;
// Custom Starknet type conversion functions that can handle 0x and unpadded hex function ensureBytes(hex: Hex): Uint8Array {
function hexToBytes0x(hex: string): Uint8Array { return u.ensureBytes('', typeof hex === 'string' ? hex0xToBytes(hex) : hex);
if (typeof hex !== 'string') {
throw new Error('hexToBytes: expected string, got ' + typeof hex);
}
hex = strip0x(hex);
if (hex.length & 1) hex = '0' + hex; // padding
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
array[i] = byte;
}
return array;
}
function hexToNumber0x(hex: string): bigint {
if (typeof hex !== 'string') {
throw new Error('hexToNumber: expected string, got ' + typeof hex);
}
// Big Endian
// TODO: strip vs no strip?
return BigInt(`0x${strip0x(hex)}`);
}
function bytesToNumber0x(bytes: Uint8Array): bigint {
return hexToNumber0x(cutils.bytesToHex(bytes));
}
function ensureBytes0x(hex: Hex): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes0x(hex);
} }
function normalizePrivateKey(privKey: Hex) { function normPrivKey(privKey: Hex): string {
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0'); return u.bytesToHex(ensureBytes(privKey)).padStart(64, '0');
} }
function getPublicKey0x(privKey: Hex, isCompressed = false) { export function getPublicKey(privKey: Hex, isCompressed = false): Uint8Array {
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed); return curve.getPublicKey(normPrivKey(privKey), isCompressed);
} }
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) { export function getSharedSecret(privKeyA: Hex, pubKeyB: Hex): Uint8Array {
return starkCurve.getSharedSecret(normalizePrivateKey(privKeyA), pubKeyB); return curve.getSharedSecret(normPrivKey(privKeyA), pubKeyB);
}
export function sign(msgHash: Hex, privKey: Hex, opts?: any): SignatureType {
return curve.sign(ensureBytes(msgHash), normPrivKey(privKey), opts);
}
export function verify(signature: SignatureType | Hex, msgHash: Hex, pubKey: Hex) {
const sig = signature instanceof Signature ? signature : ensureBytes(signature);
return curve.verify(sig, ensureBytes(msgHash), ensureBytes(pubKey));
} }
function sign0x(msgHash: Hex, privKey: Hex, opts?: any) { const { CURVE, ProjectivePoint, Signature, utils } = curve;
if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0'); export { CURVE, ProjectivePoint, Signature, utils };
return starkCurve.sign(ensureBytes0x(msgHash), normalizePrivateKey(privKey), opts);
function extractX(bytes: Uint8Array): string {
const hex = u.bytesToHex(bytes.subarray(1));
const stripped = hex.replace(/^0+/gm, ''); // strip leading 0s
return `0x${stripped}`;
} }
function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) { function strip0x(hex: string) {
const sig = signature instanceof Signature ? signature : ensureBytes0x(signature); return hex.replace(/^0x/i, '');
return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey)); }
} function numberTo0x16(num: bigint) {
// can't use utils.numberToHexUnpadded: adds leading 0 for even byte length
const { CURVE, ProjectivePoint, Signature } = starkCurve; return `0x${num.toString(16)}`;
export const utils = starkCurve.utils;
export {
CURVE,
Signature,
ProjectivePoint,
getPublicKey0x as getPublicKey,
getSharedSecret0x as getSharedSecret,
sign0x as sign,
verify0x as verify,
};
const stripLeadingZeros = (s: string) => s.replace(/^0+/gm, '');
export const bytesToHexEth = (uint8a: Uint8Array): string =>
`0x${stripLeadingZeros(cutils.bytesToHex(uint8a))}`;
export const strip0x = (hex: string) => hex.replace(/^0x/i, '');
export const numberToHexEth = (num: bigint | number) => `0x${num.toString(16)}`;
// We accept hex strings besides Uint8Array for simplicity
type Hex = Uint8Array | string;
// 1. seed generation
function hashKeyWithIndex(key: Uint8Array, index: number) {
let indexHex = cutils.numberToHexUnpadded(index);
if (indexHex.length & 1) indexHex = '0' + indexHex;
return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex)));
} }
// seed generation
export function grindKey(seed: Hex) { export function grindKey(seed: Hex) {
const _seed = ensureBytes0x(seed); const _seed = ensureBytes(seed);
const sha256mask = 2n ** 256n; const sha256mask = 2n ** 256n;
const limit = sha256mask - mod(sha256mask, CURVE_ORDER);
const limit = sha256mask - mod(sha256mask, CURVE_N);
for (let i = 0; ; i++) { for (let i = 0; ; i++) {
const key = hashKeyWithIndex(_seed, i); const key = sha256Num(u.concatBytes(_seed, u.numberToVarBytesBE(BigInt(i))));
// key should be in [0, limit) if (key < limit) return mod(key, CURVE_ORDER).toString(16); // key should be in [0, limit)
if (key < limit) return mod(key, CURVE_N).toString(16); if (i === 100000) throw new Error('grindKey is broken: tried 100k vals'); // prevent dos
} }
} }
export function getStarkKey(privateKey: Hex) { export function getStarkKey(privateKey: Hex): string {
return bytesToHexEth(getPublicKey0x(privateKey, true).slice(1)); return extractX(getPublicKey(privateKey, true));
} }
export function ethSigToPrivate(signature: string) { export function ethSigToPrivate(signature: string): string {
signature = strip0x(signature.replace(/^0x/, '')); signature = strip0x(signature);
if (signature.length !== 130) throw new Error('Wrong ethereum signature'); if (signature.length !== 130) throw new Error('Wrong ethereum signature');
return grindKey(signature.substring(0, 64)); return grindKey(signature.substring(0, 64));
} }
@@ -170,15 +123,15 @@ export function getAccountPath(
application: string, application: string,
ethereumAddress: string, ethereumAddress: string,
index: number index: number
) { ): string {
const layerNum = int31(sha256Num(layer)); const layerNum = int31(sha256Num(layer));
const applicationNum = int31(sha256Num(application)); const applicationNum = int31(sha256Num(application));
const eth = hexToNumber0x(ethereumAddress); const eth = u.hexToNumber(strip0x(ethereumAddress));
return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`; return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`;
} }
// https://docs.starkware.co/starkex/pedersen-hash-function.html // https://docs.starkware.co/starkex/pedersen-hash-function.html
const PEDERSEN_POINTS_AFFINE = [ const PEDERSEN_POINTS = [
new ProjectivePoint( new ProjectivePoint(
2089986280348253421170679821480865132823066470938446095505822317253594081284n, 2089986280348253421170679821480865132823066470938446095505822317253594081284n,
1713931329540660377023406109199410414810705867260802078187082345529207694986n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n,
@@ -205,8 +158,6 @@ const PEDERSEN_POINTS_AFFINE = [
1n 1n
), ),
]; ];
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE;
function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] { function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] {
const out: ProjectivePoint[] = []; const out: ProjectivePoint[] = [];
@@ -230,14 +181,16 @@ const PEDERSEN_POINTS2 = pedersenPrecompute(PEDERSEN_POINTS[3], PEDERSEN_POINTS[
type PedersenArg = Hex | bigint | number; type PedersenArg = Hex | bigint | number;
function pedersenArg(arg: PedersenArg): bigint { function pedersenArg(arg: PedersenArg): bigint {
let value: bigint; let value: bigint;
if (typeof arg === 'bigint') value = arg; if (typeof arg === 'bigint') {
else if (typeof arg === 'number') { value = arg;
} else if (typeof arg === 'number') {
if (!Number.isSafeInteger(arg)) throw new Error(`Invalid pedersenArg: ${arg}`); if (!Number.isSafeInteger(arg)) throw new Error(`Invalid pedersenArg: ${arg}`);
value = BigInt(arg); value = BigInt(arg);
} else value = bytesToNumber0x(ensureBytes0x(arg)); } else {
// [0..Fp) value = u.bytesToNumberBE(ensureBytes(arg));
if (!(0n <= value && value < starkCurve.CURVE.Fp.ORDER)) }
throw new Error(`PedersenArg should be 0 <= value < CURVE.P: ${value}`); if (!(0n <= value && value < curve.CURVE.Fp.ORDER))
throw new Error(`PedersenArg should be 0 <= value < CURVE.P: ${value}`); // [0..Fp)
return value; return value;
} }
@@ -245,7 +198,7 @@ function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: P
let x = pedersenArg(value); let x = pedersenArg(value);
for (let j = 0; j < 252; j++) { for (let j = 0; j < 252; j++) {
const pt = constants[j]; const pt = constants[j];
if (pt.px === point.px) throw new Error('Same point'); if (pt.equals(point)) throw new Error('Same point');
if ((x & 1n) !== 0n) point = point.add(pt); if ((x & 1n) !== 0n) point = point.add(pt);
x >>= 1n; x >>= 1n;
} }
@@ -253,17 +206,17 @@ function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: P
} }
// shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3 // shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3
export function pedersen(x: PedersenArg, y: PedersenArg) { export function pedersen(x: PedersenArg, y: PedersenArg): string {
let point: ProjectivePoint = PEDERSEN_POINTS[0]; let point: ProjectivePoint = PEDERSEN_POINTS[0];
point = pedersenSingle(point, x, PEDERSEN_POINTS1); point = pedersenSingle(point, x, PEDERSEN_POINTS1);
point = pedersenSingle(point, y, PEDERSEN_POINTS2); point = pedersenSingle(point, y, PEDERSEN_POINTS2);
return bytesToHexEth(point.toRawBytes(true).slice(1)); return extractX(point.toRawBytes(true));
} }
export function hashChain(data: PedersenArg[], fn = pedersen) { export function hashChain(data: PedersenArg[], fn = pedersen) {
if (!Array.isArray(data) || data.length < 1) if (!Array.isArray(data) || data.length < 1)
throw new Error('data should be array of at least 1 element'); throw new Error('data should be array of at least 1 element');
if (data.length === 1) return numberToHexEth(pedersenArg(data[0])); if (data.length === 1) return numberTo0x16(pedersenArg(data[0]));
return Array.from(data) return Array.from(data)
.reverse() .reverse()
.reduce((acc, i) => fn(i, acc)); .reduce((acc, i) => fn(i, acc));
@@ -272,9 +225,9 @@ export function hashChain(data: PedersenArg[], fn = pedersen) {
export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) => export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) =>
[0, ...data, data.length].reduce((x, y) => fn(x, y)); [0, ...data, data.length].reduce((x, y) => fn(x, y));
const MASK_250 = cutils.bitMask(250); const MASK_250 = u.bitMask(250);
export const keccak = (data: Uint8Array): bigint => bytesToNumber0x(keccak_256(data)) & MASK_250; export const keccak = (data: Uint8Array): bigint => u.bytesToNumberBE(keccak_256(data)) & MASK_250;
const sha256Num = (data: Uint8Array | string): bigint => cutils.bytesToNumberBE(sha256(data)); const sha256Num = (data: Uint8Array | string): bigint => u.bytesToNumberBE(sha256(data));
// Poseidon hash // Poseidon hash
export const Fp253 = Fp( export const Fp253 = Fp(
@@ -318,7 +271,13 @@ export type PoseidonOpts = {
roundsPartial: number; roundsPartial: number;
}; };
export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) { export type PoseidonFn = ReturnType<typeof poseidon> & {
m: number;
rate: number;
capacity: number;
};
export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]): PoseidonFn {
validateField(opts.Fp); validateField(opts.Fp);
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity)) if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
throw new Error(`Wrong poseidon opts: ${opts}`); throw new Error(`Wrong poseidon opts: ${opts}`);
@@ -330,7 +289,7 @@ export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) {
for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j)); for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
roundConstants.push(row); roundConstants.push(row);
} }
return poseidon.poseidon({ const res: Partial<PoseidonFn> = poseidon({
...opts, ...opts,
t: m, t: m,
sboxPower: 3, sboxPower: 3,
@@ -338,6 +297,10 @@ export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) {
mds, mds,
roundConstants, roundConstants,
}); });
res.m = m;
res.rate = opts.rate;
res.capacity = opts.capacity;
return res as PoseidonFn;
} }
export function poseidonCreate(opts: PoseidonOpts, mdsAttempt = 0) { export function poseidonCreate(opts: PoseidonOpts, mdsAttempt = 0) {
@@ -351,6 +314,28 @@ export const poseidonSmall = poseidonBasic(
MDS_SMALL MDS_SMALL
); );
export function poseidonHash(x: bigint, y: bigint, fn = poseidonSmall) { export function poseidonHash(x: bigint, y: bigint, fn = poseidonSmall): bigint {
return fn([x, y, 2n])[0]; return fn([x, y, 2n])[0];
} }
export function poseidonHashFunc(x: Uint8Array, y: Uint8Array, fn = poseidonSmall): Uint8Array {
return u.numberToVarBytesBE(poseidonHash(u.bytesToNumberBE(x), u.bytesToNumberBE(y), fn));
}
export function poseidonHashSingle(x: bigint, fn = poseidonSmall): bigint {
return fn([x, 0n, 1n])[0];
}
export function poseidonHashMany(values: bigint[], fn = poseidonSmall): bigint {
const { m, rate } = fn;
if (!Array.isArray(values)) throw new Error('bigint array expected in values');
const padded = Array.from(values); // copy
padded.push(1n);
while (padded.length % rate !== 0) padded.push(0n);
let state: bigint[] = new Array(m).fill(0n);
for (let i = 0; i < padded.length; i += rate) {
for (let j = 0; j < rate; j++) state[j] += padded[i + j];
state = fn(state);
}
return state[0];
}

View File

@@ -0,0 +1,44 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from '../_shortw_utils.js';
import { sha224, sha256 } from '@noble/hashes/sha256';
import { Fp } from '../abstract/modular.js';
// NIST secp192r1 aka P192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
export const P192 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
Fp: Fp(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
// Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
// Base point (x, y) aka generator point
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
h: BigInt(1),
lowS: false,
},
sha256
);
export const secp192r1 = P192;
export const P224 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations;
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
// Base point (x, y) aka generator point
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
h: BigInt(1),
lowS: false,
},
sha224
);
export const secp224r1 = P224;

View File

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

View File

@@ -2,10 +2,10 @@ import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { wNAF } from '../lib/esm/abstract/curve.js'; import { wNAF } from '../abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../lib/esm/abstract/utils.js'; import { bytesToHex, utf8ToBytes } from '../abstract/utils.js';
import { hash_to_field } from '../lib/esm/abstract/hash-to-curve.js'; import { hash_to_field } from '../abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../lib/esm/bls12-381.js'; import { bls12_381 as bls } from '../bls12-381.js';
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' }; import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' }; import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
@@ -857,7 +857,7 @@ describe('bls12-381/basic', () => {
const options = { const options = {
p: bls.CURVE.r, p: bls.CURVE.r,
m: 1, m: 1,
expand: false, expand: undefined,
}; };
for (let vector of SCALAR_VECTORS) { for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector; const [okmAscii, expectedHex] = vector;

View File

@@ -2,9 +2,9 @@ import { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { deepStrictEqual, strictEqual, throws } from 'assert'; import { deepStrictEqual, strictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import { bytesToNumberLE, numberToBytesLE } from '../abstract/utils.js';
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../lib/esm/ed25519.js'; import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../ed25519.js';
// const ed = ed25519; // const ed = ed25519;
const hex = bytesToHex; const hex = bytesToHex;
@@ -97,7 +97,7 @@ should('X25519 base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE; const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE; const { Fp } = ed25519ph.CURVE;
const u = Fp.create((y + 1n) * Fp.inv(1n - y)); const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); deepStrictEqual(numberToBytesLE(u, 32), x25519.GuBytes);
}); });
describe('RFC7748', () => { describe('RFC7748', () => {
@@ -128,7 +128,7 @@ describe('RFC7748', () => {
for (let i = 0; i < rfc7748Iter.length; i++) { for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i]; const { scalar, iters } = rfc7748Iter[i];
should(`scalarMult iteration (${i})`, () => { should(`scalarMult iteration (${i})`, () => {
let k = x25519.Gu; let k = x25519.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k]; for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar); deepStrictEqual(hex(k), scalar);
}); });
@@ -281,10 +281,23 @@ describe('ristretto255', () => {
deepStrictEqual(point.toHex(), encodedHashToPoints[i]); deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
} }
}); });
should('have proper equality testing', () => {
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const bytes255ToNumberLE = (bytes) =>
ed25519ctx.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
const priv = new Uint8Array([
198, 101, 65, 165, 93, 120, 37, 238, 16, 133, 10, 35, 253, 243, 161, 246, 229, 135, 12, 137,
202, 114, 222, 139, 146, 123, 4, 125, 152, 173, 1, 7,
]);
const pub = RistrettoPoint.BASE.multiply(bytes255ToNumberLE(priv));
deepStrictEqual(pub.equals(RistrettoPoint.ZERO), false);
});
}); });
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
import { assert } from 'console';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run(); should.run();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,11 @@
import { describe, should } from 'micro-should';
import './basic.test.js'; import './basic.test.js';
import './stark.test.js'; import './stark.test.js';
import './property.test.js'; import './property.test.js';
import './poseidon.test.js'; import './poseidon.test.js';
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -1,6 +1,7 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../stark.js';
import { bytesToHex as hex } from '@noble/hashes/utils';
import * as fs from 'fs'; import * as fs from 'fs';
function parseTest(path) { function parseTest(path) {
@@ -107,6 +108,96 @@ should('Poseidon examples', () => {
]); ]);
}); });
should('Poseidon 2', () => {
// Cross-test with cairo-lang 0.11
deepStrictEqual(
starknet.poseidonHash(1n, 1n),
315729444126170353286530004158376771769107830460625027134495740547491428733n
);
deepStrictEqual(
starknet.poseidonHash(123n, 123n),
3149184350054566761517315875549307360045573205732410509163060794402900549639n
);
deepStrictEqual(
starknet.poseidonHash(1231231231231231231231231312312n, 1231231231231231231231231312312n),
2544250291965936388474000136445328679708604225006461780180655815882994563864n
);
// poseidonHashSingle
deepStrictEqual(
starknet.poseidonHashSingle(1n),
3085182978037364507644541379307921604860861694664657935759708330416374536741n
);
deepStrictEqual(
starknet.poseidonHashSingle(123n),
2751345659320901472675327541550911744303539407817894466726181731796247467344n
);
deepStrictEqual(
starknet.poseidonHashSingle(1231231231231231231231231312312n),
3083085683696942145160394401206391098729120397175152900096470498748103599322n
);
// poseidonHashMany
throws(() => starknet.poseidonHash(new Uint8Array([1, 2, 3])));
deepStrictEqual(
starknet.poseidonHashMany([1n]),
154809849725474173771833689306955346864791482278938452209165301614543497938n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n]),
1557996165160500454210437319447297236715335099509187222888255133199463084263n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n]),
976552833909388839716191681593200982850734838655927116322079791360264131378n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n, 6n, 7n]),
1426681430756292883765769449684978541173830451959857824597431064948702170774n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n, 6n]),
3578895185591466904832617962452140411216018208734547126302182794057260630783n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n]),
2047942584693618630610564708884241243670450597197937863619828684896211911953n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n]),
717812721730784692894550948559585317289413466140233907962980309405694367376n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n]),
2926122208425648133778911655767364584769133265503722614793281770361723147648n
);
deepStrictEqual(
starknet.poseidonHashMany([
154809849725474173771833689306955346864791482278938452209165301614543497938n,
1557996165160500454210437319447297236715335099509187222888255133199463084263n,
976552833909388839716191681593200982850734838655927116322079791360264131378n,
1426681430756292883765769449684978541173830451959857824597431064948702170774n,
3578895185591466904832617962452140411216018208734547126302182794057260630783n,
]),
1019392520709073131437410341528874594624843119359955302374885123884546721410n
);
// poseidon_hash_func
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array([1, 2]), new Uint8Array([3, 4]))),
'01f87cbb9c58139605384d0f0df49b446600af020aa9dac92301d45c96d78c0a'
);
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array(32).fill(255), new Uint8Array(32).fill(255))),
'05fd546b5ee3bcbbcbb733ed90bfc33033169d6765ac37bba71794a11cbb51a6'
);
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array(64).fill(255), new Uint8Array(64).fill(255))),
'07dba6b4d94b3e32697afe0825d6dac2dccafd439f7806a9575693c93735596b'
);
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array(256).fill(255), new Uint8Array(256).fill(255))),
'02f048581901865201dad701a5653d946b961748ec770fc11139aa7c06a9432a'
);
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

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

View File

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

View File

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

View File

@@ -3,12 +3,12 @@
"strict": true, "strict": true,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"sourceMap": true,
"outDir": ".", "outDir": ".",
"target": "es2020", "target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM "lib": ["es2020"], // Set explicitly to remove DOM
"sourceMap": true, "module": "es6",
"module": "commonjs", "moduleResolution": "node16",
"moduleResolution": "node",
"noUnusedLocals": true, "noUnusedLocals": true,
"baseUrl": ".", "baseUrl": ".",
}, },